Search code examples
javascriptmedia-source

Difference between "update" and "updateend" events in Media Source Extensions


Could anyone explain the difference between the two SourceBuffer events and when to use one over the other? The W3C spec is confusing to me because it reads like the same thing ("completed" vs "ended"):

  1. update - The append or remove has successfully completed
  2. updateend - The append or remove has ended.

Testing with the following code below. Chrome fires the error event, Firefox does not:

const tests = {
  //  64K of invalid bytes
  'Invalid Bytes': new Int8Array(1024 * 64),

  // valid WebM Opus header bytes (from a random file)
  'Valid Header Bytes': new Uint8Array([26,69,223,163,1,0,0,0,0,0,0,31,66,134,129,1,66,247,129,1,66,242,129,4,66,243,129,8,66,130,132,119,101,98,109,66,135,129,4,66,133,129,2,24,83,128,103,1,0,0,0,0,0,217,18,17,77,155,116,64,29,77,187,139,83,171,132,21,73,169,102,83,172,129,223,77,187,140,83,171,132,22,84,174,107,83,172,130,1,48,236,1,0,0,0,0,0,0,179,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,73,169,102,1,0,0,0,0,0,0,69,42,215,177,131,15,66,64,77,128,141,76,97,118,102,53,54,46,52,48,46,49,48,49,87,65,141,76,97,118,102,53,54,46,52,48,46,49,48,49,115,164,144,52,234,234,126,227,12,45,154,239,221,105,21,212,101,42,213,68,137,136,64,184,70,0,0,0,0,0,22,84,174,107,1,0,0,0,0,0,0,98,174,1,0,0,0,0,0,0,89,215,129,1,115,197,129,1,156,129,0,34,181,156,131,117,110,100,134,134,65,95,79,80,85,83,86,170,131,99,46,160,86,187,132,4,196,180,0,131,129,2,225,1,0,0,0,0,0,0,17,159,129,2,181,136,64,231,112,0,0,0,0,0,98,100,129,16,99,162,147,79,112,117,115,72,101,97,100,1,2,56,1,128,187,0,0,0,0,0])
};

(async () => {
  for (let testName of Object.keys(tests)) {
    console.group(testName);
    await testSourceBuffer(tests[testName])
    console.groupEnd();
  };
  console.log('All tests done!');
})()

async function testSourceBuffer(byteArray) {
  return new Promise(resolve => {
    const audio = new Audio();
    const mediaSource = new MediaSource();

    // debug other MediaSource Events
    ['sourceended', 'sourceclose'].forEach(name => {
      mediaSource.addEventListener(name, e => {
        console.log('MediaSource', e.type);
        if (e.type === 'sourceended') {
          resolve();
        }
      })
    })

    mediaSource.onsourceopen = e => {
      console.log('MediaSource', e.type);
      URL.revokeObjectURL(audio.src);
      const sourceBuffer = mediaSource.addSourceBuffer('audio/webm; codecs=opus');

      // debug SoruceBuffer events
      ['abort', 'error', 'update', 'updateend', 'updatestart'].forEach(name => {
        sourceBuffer.addEventListener(name, e => {
          const color = e.type === 'error'? 'color: red' : ''
          console.log(`%cSourceBuffer ${name}`, color);
          if (e.type === 'updateend') {
            if (mediaSource.readyState === 'open') {
              mediaSource.endOfStream();
            }
          }
        })
      })

      sourceBuffer.appendBuffer(byteArray);
    }

    audio.src = URL.createObjectURL(mediaSource)
  });
}

Solution

  • The difference lies in the success of the completion.

    The update event is only fired when either the append or removal operations did succeed, while updateend is also fired when there has been an error while appending, or when the operation has been aborted.