How to avoid SecurityError: The operation is insecure. at Firefox when using .mozCaptureStream and MediaRecorder?

2.3k views Asked by At

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?

1

There are 1 answers

0
guest271314 On

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.