title: How to use Blob URL, MediaSource or other methods to play concatenated Blobs of media fragments?
body: <p>Am attempting to implement, for lack of a different description, an offline media context.</p>
  
  <p>The concept is to create 1 second <code>Blob</code>s of recorded media, with the ability to </p>
  
  <ol>
  <li>Play the 1 second <code>Blobs</code> independently at an <code>HTMLMediaElement</code></li>
  <li>Play the full media resource from concatenated <code>Blob</code>s</li>
  </ol>
  
  <p>The issue is that once the <code>Blob</code>s are concatenated the media resource does not play at <code>HTMLMedia</code> element using either a <code>Blob URL</code> or <code>MediaSource</code>.</p>
  
  <p>The created <code>Blob URL</code> only plays 1 second of the concatenated <code>Blob</code>'s. <code>MediaSource</code> throws two exceptions</p>
  
  <pre><code>DOMException: Failed to execute 'addSourceBuffer' on 'MediaSource': The MediaSource's readyState is not 'open'
  </code></pre>
  
  <p>and</p>
  
  <pre><code>DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
  </code></pre>
  
  <p>How to properly encode the concatenated <code>Blob</code>s or otherwise implement a workaround to play the media fragments as a single re-constituted media resource?</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
  
  &lt;head&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
    &lt;script&gt;
      const src = "https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4";
      fetch(src)
        .then(response =&gt; response.blob())
        .then(blob =&gt; {
          const blobURL = URL.createObjectURL(blob);
          const chunks = [];
          const mimeCodec = "vdeo/webm; codecs=opus";
          let duration;
          let media = document.createElement("video");
          media.onloadedmetadata = () =&gt; {
            media.onloadedmetadata = null;
            duration = Math.ceil(media.duration);
            let arr = Array.from({
              length: duration
            }, (_, index) =&gt; index);
            // record each second of media
            arr.reduce((p, index) =&gt;
                p.then(() =&gt;
                  new Promise(resolve =&gt; {
                    let recorder;
                    let video = document.createElement("video");
                    video.onpause = e =&gt; {
                      video.onpause = null;
                      console.log(e);
                      recorder.stop();
                    }
  
                    video.oncanplay = () =&gt; {
                      video.oncanplay = null;
                      video.play();
  
                      let stream = video.captureStream();
  
                      recorder = new MediaRecorder(stream);
  
                      recorder.start();
  
                      recorder.ondataavailable = e =&gt; {
                        console.log("data event", recorder.state, e.data);
                        chunks.push(e.data);
                      }
  
                      recorder.onstop = e =&gt; {
                        resolve();
                      }
  
                    }
                    video.src = `${blobURL}#t=${index},${index+1}`;
                  })
                ), Promise.resolve())
              .then(() =&gt; {
                console.log(chunks);
                let video = document.createElement("video");
                video.controls = true;
                document.body.appendChild(video);
                let select = document.createElement("select");
                document.body.appendChild(select);
                let option = new Option("select a segment");
                select.appendChild(option);
                for (let chunk of chunks) {
                  let index = chunks.indexOf(chunk);
                  let option = new Option(`Play ${index}-${index + 1} seconds of media`, index);
                  select.appendChild(option)
                }
                let fullMedia = new Blob(chunks, {
                  type: mimeCodec
                });
  
                let opt = new Option("Play full media", "Play full media");
                select.appendChild(opt);
                select.onchange = () =&gt; {
                  if (select.value !== "Play full media") {
                    video.src = URL.createObjectURL(chunks[select.value])
                  } else {
  
                    const mediaSource = new MediaSource();
                    video.src = URL.createObjectURL(mediaSource);
                    mediaSource.addEventListener("sourceopen", sourceOpen);
  
                    function sourceOpen(event) {
                      // if the media type is supported by `mediaSource`
                      // fetch resource, begin stream read, 
                      // append stream to `sourceBuffer`
                      if (MediaSource.isTypeSupported(mimeCodec)) {
                        var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
                        // set `sourceBuffer` `.mode` to `"sequence"`
                        sourceBuffer.mode = "segments";
  
                        fetch(URL.createObjectURL(fullMedia))
                          // return `ReadableStream` of `response`
                          .then(response =&gt; response.body.getReader())
                          .then(reader =&gt; {
  
                            const processStream = (data) =&gt; {
                                if (data.done) {
                                  return;
                                }
                                // append chunk of stream to `sourceBuffer`
                                sourceBuffer.appendBuffer(data.value);
                              }
                              // at `sourceBuffer` `updateend` call `reader.read()`,
                              // to read next chunk of stream, append chunk to 
                              // `sourceBuffer`
                            sourceBuffer.addEventListener("updateend", function() {
                              reader.read().then(processStream);
                            });
                            // start processing stream
                            reader.read().then(processStream);
                            // do stuff `reader` is closed, 
                            // read of stream is complete
                            return reader.closed.then(() =&gt; {
                              // signal end of stream to `mediaSource`
                              mediaSource.endOfStream();
                              return mediaSource.readyState;
                            })
                          })
                          // do stuff when `reader.closed`, `mediaSource` stream ended
                          .then(msg =&gt; console.log(msg))
                          .catch(err =&gt; console.log(err))
                      }
                      // if `mimeCodec` is not supported by `MediaSource`  
                      else {
                        alert(mimeCodec + " not supported");
                      }
                    };
  
                  }
  
                }
  
              })
          }
          media.src = blobURL;
        })
    &lt;/script&gt;
  &lt;/body&gt;
  
  &lt;/html&gt;
  </code></pre>
  
  <p>using <code>Blob URL</code> at <code>else</code> statement at <code>select</code> <code>change</code> event, which only plays first second of media resource</p>
  
  <pre><code>video.src = URL.createObjectURL(fullMedia);
  </code></pre>
  
  <p>plnkr <a href="http://plnkr.co/edit/dNznvxe504JX7RWY658T?p=preview" rel="noreferrer">http://plnkr.co/edit/dNznvxe504JX7RWY658T?p=preview</a> version 1 <code>Blob URL</code>, version 2 <code>MediaSource</code></p>