如何屏幕录制滚动的 n 秒 window

How to screen record a rolling n-second window

我知道 MediaRecorder API and how to record screen/audio/video, and then download those recordings. I'm also aware of npm modules such as react-media-recorder 利用 API。

我想录制滚动的 n 秒 window 屏幕录像,以允许用户创建剪辑,然后能够共享这些剪辑。我无法录制整个会话,因为我不知道它们会持续多久,这意味着我不知道录音可能有多大(我假设录音在内存中的容量是有限的。)

有什么简单的方法可以使用 MediaRecorder 来记录滚动 window(即始终在内存中记录最后 30 秒)?

我花了很长时间来尝试完成这项工作。不幸的是,唯一对我有用的解决方案是制作 30 个录音机。

这个问题的天真的解决方案是调用recorder.start(1000)以一秒为间隔记录数据,然后在dataavailable事件上维护一个循环缓冲区。这个问题是 MediaRecorder 支持的编码数量非常非常有限。 None 这些编码允许从一开始就丢弃数据包,因为它们包含重要的元数据。通过更好地理解协议,我确信在某种程度上可以使该策略发挥作用。然而,简单地将数据包连接在一起(当一些数据包丢失时)并不能创建一个有效的文件。

我的另一次尝试同时使用了两个 MediaRecorder 对象。其中一个记录second-long个起始包,另一个记录常规数据包。拍摄剪辑时,这会将来自第一个记录器的起始数据包与来自第二个记录器的数据包组合在一起。但是,这通常会导致录音损坏。

这个解决方案并不理想,但确实有效:想法是保留 30 个 MediaRecorder 个对象,每个对象偏移一秒。为了演示,剪辑的时长为 5 秒,而不是 30 秒:

<canvas></canvas><button>Clip!</button>

<style>
   canvas, video, button {
      display: block;
   }
</style>

<!-- draw to the canvas to create a stream for testing -->
<script>
   const canvas = document.querySelector('canvas');
   const ctx = canvas.getContext('2d');
   // fill background with white
   ctx.fillStyle = 'white';
   ctx.fillRect(0, 0, canvas.width, canvas.height);

   // randomly draw stuff
   setInterval(() => {
      const x = Math.floor(Math.random() * canvas.width);
      const y = Math.floor(Math.random() * canvas.height);
      const radius = Math.floor(Math.random() * 30);
      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.stroke();
   }, 100);
</script>

<!-- actual recording -->
<script>
   // five second clips
   const LENGTH = 5;

   const codec = 'video/webm;codecs=vp8,opus'
   const stream = canvas.captureStream();

   // circular buffer of recorders
   let head = 0;
   const recorders = new Array(LENGTH)
      .fill()
      .map(() => new MediaRecorder(stream, { mimeType: codec }));

   // start them all
   recorders.forEach((recorder) => recorder.start());

   let data = undefined;
   recorders.forEach((r) => r.addEventListener('dataavailable', (e) => {
      data = e.data;
   }));

   setInterval(() => {
      recorders[head].stop();
      recorders[head].start();

      head = (head + 1) % LENGTH;
   }, 1000);

   // download the data
   const download = () => {
      if (data === undefined) return;
      const url = URL.createObjectURL(data);

      // download the url
      const a = document.createElement('a');
      a.download = 'test.webm';
      a.href = url;
      a.click();
      URL.revokeObjectURL(url);
   };

   // Whosebug doesn't allow downloads
   // we show the clip instead
   const show = () => {
      if (data === undefined) return;
      const url = URL.createObjectURL(data);

      // display url in new video element
      const v = document.createElement('video');
      v.src = url;
      v.controls = true;
      document.body.appendChild(v);
   };

   document.querySelector('button').addEventListener('click', show);
</script>