-1

Given the code

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <video width="320" height="280"></video>
  <script>
      const video = document.querySelector("video");
      const src = "https://mirrors.creativecommons.org/movingimages/webm/ScienceCommonsJesseDylan_240p.webm#t=0,10";
      let recorder;
      video.oncanplay = () => {
        video.play(); 
        const mediaStream = video.captureStream();
        recorder = new MediaRecorder(mediaStream);
        recorder.ondataavailable = event => 
          console.log(URL.createObjectURL(event.data));
        
        recorder.start();
      }

      video.onpause = () => recorder && recorder.stop();

      video.src = src;
  </script>
</body>

</html>

Chromium browser captures the playback of <video> element using .captureStream(), MediaRecorder() and logs Blob URL of resulting Blob at dataavailable event of MediaRecorder instance.

At Firefox browser which currently implements .mozCaptureStream() an error is thrown recorder.start() call SecurityError: The operation is insecure. and at recorder.stop() call InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable.

The errors are apparently due to Security Considerations of Media Capture from DOM Elements.

How can we avoid the errors described above and achieve same result which Chromium and Chrome browsers implement at Firefox browser?

guest271314
  • 1
  • 15
  • 104
  • 177
  • That actually sounds like a chrome bug! The video's `crossOrigin` should be set to `"anonymous"` for your code to not throw (note that FF indeed [has a bug about it](https://bugzilla.mozilla.org/show_bug.cgi?id=1341016), but at least, they're **too** secure). Also note that you can't expect `mozCaptureStream` to work the same way as `captureStream`, that's why it's still prefixed/ – Kaiido Sep 18 '17 at 05:22
  • @Kaiido No error is thrown at Chromium or Chrome. Composed workaround while continuing to look into solution for concatenating recordings created using `MediaRecorder`. If `crossOrigin` attribute also allows recording of media playback without error, can you post an Answer describing the solution? – guest271314 Sep 18 '17 at 05:27
  • As said in the FF bug I linked to, FF doesn't allow MediaElement served with the correct CORS headers, so as long as it is not fixed, it won't work as a workaround. But what I am saying is that this is a security bug in chrome (I'm letting them now right away about it.) They should not let captureStream be called on a mediaElement served without CORS headers. This goes against all the other CORS security measures (e.g in webAudio or canvas APIs) So you should anyway have the `cross-origin` attribute set on your mediaElement. – Kaiido Sep 18 '17 at 05:46
  • @Kaiido The [specification](https://w3c.github.io/mediacapture-fromelement/#security-considerations) is not entirely clear about what exactly should occur for cross-origin requests. Are you also lodging an [issue](https://github.com/w3c/mediacapture-fromelement/issues) at the specification? Can you share link to Chromium bug that you intend to or have filed? – guest271314 Sep 18 '17 at 05:54
  • What is not clear in the specs? It clearly states that it *"MUST be protected from access by the document origin."* Also, how would [this](https://jsfiddle.net/w77z6w90/) not be a bug? And no I can't link to the bug since it is a security one (except if you're a google dev?) – Kaiido Sep 18 '17 at 05:57
  • @Kaiido "protected" is very broad. Also are either a `Blob` or an `ArrayBuffer` a cross origin resource? – guest271314 Sep 18 '17 at 06:04
  • No *"protected"* in this context is very clear. It means that no API should be able to extract the content of the media. Be it in `WebAudioAPI.createMediaElementSource`, in HTMLCanvas `toDataURL` or `toBlob`or 2d `getImageData` *(well the marking should be done in 2d's `drawImage` and webgl's `texImage2D`)* or in MediaRecorder's `start` method. And maybe in others I forget/don't know. All these methods must check that the source is clean and not *protected*. If you really want to write to w3c, then you could propose to include MediaRecorder in there, but their list is obviously not exhaustive. – Kaiido Sep 18 '17 at 06:11
  • And here FF does correctly throws on MediaRecorder's `start` method: https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder-methods – Kaiido Sep 18 '17 at 06:13
  • @Kaiido _"It means that no API should be able to extract the content of the media."_ The [specification](https://www.w3.org/TR/mediacapture-fromelement/#security-considerations) does not contain that language, rather states _"How this protection manifests will differ, depending on how the content is accessed."_, which again, is not exactingly clear. Note, the resource at OP is served with `access-control-allow-origin:*` header. Why would you expect for the media content to not be accessible at `MediaRecorder`? – guest271314 Sep 18 '17 at 06:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154643/discussion-between-kaiido-and-guest271314). – Kaiido Sep 18 '17 at 06:19

1 Answers1

0

You can use XMLHttpRequest() or fetch() to get Blob representation of media resource, create a Blob URL from Blob using URL.createObjectURL()

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <video width="320" height="280"></video>
  <script>
    (async() => {
      const video = document.querySelector("video");
      const src = "https://mirrors.creativecommons.org/movingimages/webm/ScienceCommonsJesseDylan_240p.webm#t=0,10";
      const url = new URL(src);
      let recorder;
      video.oncanplay = () => {
        // note, audio is not output here
        video.play();
        const mediaStream = video.mozCaptureStream();
        recorder = new MediaRecorder(mediaStream);
        recorder.ondataavailable = event =>
          // audio is output at resulting `Blob URL`         
          console.log(URL.createObjectURL(event.data));

        recorder.start();
      }

      video.onpause = () => recorder && recorder.stop();

      const blob = await fetch(url).then(response => response.blob());
      video.src = URL.createObjectURL(blob) + url.hash;

    })();
  </script>
</body>

</html>

alternatively, we can record the media playback of MediaSource() set as .src of HTMLMediaElement by appending an ArrayBuffer representation of media resource to the SourceBuffer of MediaSource instance

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <video width="320" height="280"></video>
  <script>
  (async() => {
  // SecurityError: The operation is insecure.
  //  InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
  const video = document.querySelector("video");
  const src = "https://mirrors.creativecommons.org/movingimages/webm/ScienceCommonsJesseDylan_240p.webm#t=0,10";
  const url = new URL(src);
  const mediaSource = new MediaSource();
  const mimeCodec = "video/webm;codecs=opus";
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.onsourceopen = async() => {
    const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
    const mediaBuffer = await fetch(url).then(response => response.arrayBuffer());
    sourceBuffer.appendBuffer(mediaBuffer);
  }
  let recorder;
  video.oncanplay = () => {
    // note, audio is not output here
    video.play(); 
    const mediaStream = video.mozCaptureStream();
    recorder = new MediaRecorder(mediaStream);
    recorder.ondataavailable = event => 
      // audio is output at resulting `Blob URL` 
      console.log(URL.createObjectURL(event.data)); 
      
    recorder.start();
  }

  video.ontimeupdate = () => {
    if (Math.floor(video.currentTime) === Number(url.hash.split(",").pop())) {
      video.ontimeupdate = null;
      video.pause();
      mediaSource.endOfStream();
    }
  }

  video.onpause = () => recorder && recorder.stop();

})();

</script>

</body>
</html>

We could additionally use a helper function to use the same code at each Chromium, Chrome browser and Firefox browser, for example, see Answer at captureStream() on dynamically created video element.

guest271314
  • 1
  • 15
  • 104
  • 177