如何使用 MediaRecorder 将 canvas 录制到与另一个视频完全相同的持续时间?

How can I record a canvas with MediaRecorder to the exact same duration as another video?

我有一个持续时间为 9200 毫秒的视频,还有一个 canvas 显示用户的网络摄像头视频。我的目标是在播放原始视频时录制网络摄像头视频,以创建与 MediaRecorder 具有完全相同持续时间的输出 blob,但似乎总是获得长度更长的视频(通常约为 9400 毫秒)。

我发现,如果我在输出视频中采用持续时间的差异并向前跳过这个量,它基本上会与原始视频同步,但我希望不必使用此 hack。知道这一点后,我假设差异是因为 HTML5 视频的 play() 函数是异步的,但即使在 play() 承诺之后在 .then() 中调用 recorder.start() 仍然会导致持续时间更长的输出 blob。

start() MediaRecorderplay() 原始视频之后,当我看到原始视频时在 requestAnimationFrame 循环中调用 stop()结束了。将 MediaRecorder.start() 更改为仅在检查原始视频正在播放后才在 requestAnimationFrame 循环中开始也会导致更长的输出 blob。

输出较长的原因可能是什么?从文档中看,MediaRecorder 的开始或停止功能似乎不是异步的,那么有什么方法可以保证 HTML5 视频和 MediaRecorder 的准确开始时间?

是的 start()stop() 是异步的,这就是我们触发 onstartonstop 事件的原因:

const stream = makeEmptyStream();
const rec = new MediaRecorder(stream);
rec.onstart = (evt) => { console.log( "took %sms to start", performance.now() - begin ); }; 
const begin = performance.now();
rec.start();

setTimeout( () => {
  rec.onstop = (evt) => { console.log( "took %sms to stop", performance.now() - begin ); }; 
  const begin = performance.now();
  rec.stop();
}, 1000 );

function makeEmptyStream() {
  const canvas = document.createElement('canvas');
  canvas.getContext('2d').fillRect(0,0,1,1);
  return canvas.captureStream();
}

因此,您可以在视频准备好播放后尝试暂停,然后等待录像机启动,然后再开始播放视频。
但是,鉴于 HTMLMediaElement 和 MediaRecorder 中的所有内容都是异步的,因此无法获得完美的 1 对 1 关系...

const vid = document.querySelector('video');
onclick = (evt) => {
onclick = null;
vid.play().then( () => {

  // pause immediately the main video
  vid.pause();
  // we may have advanced of a few µs already, so go back to beginning
  vid.currentTime = 0;
  // only when we're back to beginning
  vid.onseeked = (evt) => {

    console.log( 'recording will begin shortly, please wait until the end of the video' ); 

    console.log( 'original %ss', vid.duration );
    
    const stream = vid.captureStream ? vid.captureStream() : vid.mozCaptureStream();

    const chunks = [];
    const rec = new MediaRecorder( stream );

    rec.ondataavailable = (evt) => {
      chunks.push( evt.data );
    };
    rec.onstop = (evt) => {
      logVideoDuration( new Blob( chunks ), "recorded %ss" );
    };
    vid.onended = (evt) => {
      rec.stop();
    };
    // wait until the recorder is ready before playing the video again
    rec.onstart = (evt) => {
      vid.play();
    };

    rec.start();

  };
  
  } );

  function logVideoDuration( blob, name ) {
    const el = document.createElement('video');
    el.src = URL.createObjectURL( blob );
    el.play().then( () => {
      el.pause();
      el.onseeked = (evt) => console.log( name, el.duration );
      el.currentTime = 10e25;
    } );
  }
  
};
video { pointer-events: none; width: 100% }
click to start<br>
<video src="https://upload.wikimedia.org/wikipedia/commons/a/a4/BBH_gravitational_lensing_of_gw150914.webm" controls crossorigin></video>

另请注意,您的媒体声明的持续时间、录制媒体的计算持续时间及其实际持续时间可能存在一些差异。事实上,这些持续时间通常只是一个硬编码在文件元数据中的值,但是考虑到 MediaRecorder API 的工作方式,很难在那里做到这一点,例如 Chrome 将生成没有持续时间,玩家将尝试根据他们可以在媒体中寻找的最后一点来估计该持续时间。