来自 captureStream() 的第一帧不发送

mar*_*cco 2 html javascript video canvas

我们正在开展一个项目,人们可以在聊天室中使用他们的网络摄像头,他们可以在那个时刻抓取某人摄像头的快照,在上面做一些注释,然后分享修改后的图片,就好像它是他们自己的网络摄像头(例如共享白板)。

将网络摄像头流捕获到可以编辑的画布元素中相对容易。在我们的页面上找到画布元素并在其上执行 .getContext('2d') ,使用一个开放的库向其添加编辑工具。从那个画布中抓取一个流是这样完成的:

var canvasToSend = document.querySelector('canvas');
var stream = canvasToSend.captureStream(60);
var room = osTwilioVideoWeb.getConnectedRoom();

var mytrack = null;
room.localParticipant.publishTrack(stream.getTracks()[0]).then((publication) => {
    mytrack = publication.track;
    var videoElement = mytrack.attach();
}); 
Run Code Online (Sandbox Code Playgroud)

这可以发布流,但是除非您在画布上绘制其他内容,否则不会发送第一帧。假设您画了 2 个圆圈,然后点击分享,流将开始,但不会显示在收件人一侧,除非您画一条线、另一个圆圈或任何东西。似乎需要更改帧才能发送数据。

我能够通过执行诸如 context.fill(); 之类的操作来使用开发人员工具强制执行此操作,但是当我尝试在发布函数之后添加它时,即使在 then() 中……也不走运。

关于如何强制这种“刷新”发生的任何想法?

Kai*_*ido 5

所以这似乎是预期的行为(因此会使我的 FF 越野车)。

从关于帧请求算法的规范

frameCaptureRequestedtrue并且画布被绘制时,从画布请求一个新的帧。

让我们把重点放在“画布上”。这意味着我们需要两个条件,虽然captureStream本身,或者它的 frameRate 参数 ellapsing 或像requestFrame这样的方法都会将frameCaptureRequested标志设置为 true,但我们仍然需要新的绘画......

规格甚至有一 说明

该算法导致捕获的轨道直到画布中的某些变化才开始。

如果在画布绘制后调用captureStream,Chrome 确实似乎会生成一个空的CanvasCaptureMediaStreamTrack

const ctx = document.createElement('canvas')
  .getContext('2d');
ctx.fillRect(0,0,20,20);
// let's request a stream from before it gets painted
// (in the same frame)
const stream1 = ctx.canvas.captureStream();
vid1.srcObject = stream1;
// now let's wait that a frame ellapsed
// (rAF fires before next painting, so we need 2 of them)
requestAnimationFrame(()=>
  requestAnimationFrame(()=> {
    const stream2 = ctx.canvas.captureStream();
    vid2.srcObject = stream1;
  })
);
Run Code Online (Sandbox Code Playgroud)
<p>stream initialised in the same frame as the drawings (i.e before paiting).</p>
<video id="vid1" controls autoplay></video>
<p>stream initialised after paiting.</p>
<video id="vid2" controls autoplay></video>
Run Code Online (Sandbox Code Playgroud)

因此,要解决此问题,您应该能够通过从与画布上的第一次绘图相同的操作请求流来获取带有帧的流stream1,如上例所示。

或者,您可以通过ctx.drawImage(ctx.canvas,0,0)在将 globalCompositeOperation 设置为“复制”以避免透明度问题后调用来重绘画布上下文(假设它是一个二维上下文)。

const ctx = document.createElement('canvas')
  .getContext('2d');
ctx.font = '15px sans-serif';
ctx.fillText('if forced to redraw it should work', 20, 20);
// produce a silent stream again
requestAnimationFrame(() =>
  requestAnimationFrame(() => {
    const stream = ctx.canvas.captureStream();
    forcePainting(stream);
    vid.srcObject = stream;
  })
);
// beware will work only for canvas intialised with a 2D context
function forcePainting(stream) {
  const ctx = (stream.getVideoTracks()[0].canvas ||
      stream.canvas) // FF has it wrong...
    .getContext('2d');
  const gCO = ctx.globalCompositeOperation;
  ctx.globalCompositeOperation = 'copy';
  ctx.drawImage(ctx.canvas, 0, 0);
  ctx.globalCompositeOperation = gCO;
}
Run Code Online (Sandbox Code Playgroud)
<video id="vid" controls autoplay></video>
Run Code Online (Sandbox Code Playgroud)