HTML5画布内容流的高效方式是什么?

5
我正在尝试使用websockets和nodejs实时流式传输html5 canvas的内容。这个html5 canvas的内容只是一个视频。
到目前为止,我已经完成了以下工作:
我将canvas转换为blob,然后获取blob URL并使用websockets将该URL发送到我的nodejs服务器。
我可以通过以下方式获取blob URL:
canvas.toBlob(function(blob) {
   url = window.URL.createObjectURL(blob);
});

blob URL是每个视频帧(确切地说,每秒20帧)生成的URL,看起来像这样:

blob:null/e3e8888e-98da-41aa-a3c0-8fe3f44frt53

我通过WebSockets从服务器获取blob URL,以便将其绘制到另一个画布上,以供其他用户查看。
我搜索了如何从blob URL绘制到画布上,但找不到接近我尝试做的事情。
所以我的问题是:
1.这是我尝试实现的正确方法吗?任何优缺点都会很感激。
2.是否有其他更有效的方法来完成这项工作,或者我正在走正确的道路?
提前感谢您的回答。
编辑:
我应该提到在这个项目中我不能使用WebRTC,我必须用我所拥有的一切来完成它。
为了让大家更容易理解我目前的进展,这是我如何使用WebSockets在画布中显示上述blob URL的方式:
websocket.onopen = function(event) {

        websocket.onmessage = function(evt) {
            var val = evt.data;
            console.log("new data "+val);
            var canvas2 = document.querySelector('.canvMotion2');
            var ctx2 = canvas2.getContext('2d');
            var img = new Image();     

            img.onload = function(){
                ctx2.drawImage(img, 0, 0)
            }
            img.src = val;
        };

        // Listen for socket closes
        websocket.onclose = function(event) {

        };

        websocket.onerror = function(evt) {

        };

};

问题在于当我在FireFox中运行该代码时,画布始终为空/空白,但我在控制台中看到了blob URL,这让我觉得我所做的事情是错误的。
而在Google Chrome中,我遇到了“不允许加载本地资源:blob:”错误。
第二次编辑:
目前我所处的位置是这样的。
第一个选项:
我尝试通过WebSockets发送整个blob(s)并成功地完成了这一点。然而,由于某种奇怪的原因,我无法在客户端上读取它!
当我查看我的nodejs服务器的控制台时,我可以看到每个我发送到服务器的blob都会出现类似于以下的内容:
<buffer fd67676 hdsjuhsd8 sjhjs....

第二个选项:

所以上面的选项失败了,我想到了另一种方法,即将每个画布帧转换为base64(jpeg),并通过websocket将其发送到服务器上,然后在客户端上显示/绘制这些base64图像到画布上。

我每秒向服务器发送24个帧。

这个方法行得通。但是,在客户端显示这些base64图像的画布非常慢,就像每秒只绘制1个画面。这是我现在遇到的问题。

第三个选项:

我还尝试过在没有画布的情况下使用视频。因此,我使用WebRTC将视频流作为单个Blob获取。但我不确定如何使用它并将其发送到客户端,以便人们可以看到它。

重要提示:我正在开发的系统不是点对点连接,只是我试图实现的单向流式传输。


这是WebRTC的完美应用案例。请看这个例子:https://webrtc.github.io/samples/src/content/capture/canvas-pc/ - Rob
@Robert,很抱歉我不能在我的项目中使用WebRTC。 - william wolly
@williamwolly 在我们得到与您的情况无关的答案之前,最好先提出一个完整的问题并包含您的限制条件。这样做是值得的。 - Adrian
@Adriani6 修改了我的问题。 - william wolly
@PatrickRoberts,你所说的直接访问画布上的图纸是什么意思?这是一个正在画布上显示的实时流视频。 - william wolly
@williamwolly 哦,我没注意到那个。我想象的是一个共享的绘图画布之类的东西。 - Patrick Roberts
1个回答

6

使用WebRTC流式传输画布内容最自然的方式

原贴作者明确表示无法使用这种方式,可能是因为:

  1. 浏览器支持度还不够好。
  2. 需要运行MediaServer(至少需要ICE+STUN/TURN,如果想要向多个对等方流式传输,则可能需要使用网关)。

但是如果您有能力支付相关费用,只需执行以下操作即可从canvas元素获取MediaStream:

const canvas_stream = canvas.captureStream(minimumFrameRate);

然后你只需要将它添加到你的RTCPeerConnection中:

pc.addTrack(stream.getVideoTracks()[0], stream);

以下示例仅将MediaStream显示到<video>元素中。

let x = 0;
const ctx = canvas.getContext('2d');
draw();
startStream();

function startStream() {
  // grab our MediaStream
  const stream = canvas.captureStream(30);
  // feed the <video>
  vid.srcObject = stream;
  vid.play();
}
function draw() {
  x = (x + 1) % (canvas.width + 50);
  ctx.fillStyle = 'white';
  ctx.fillRect(0,0,canvas.width,canvas.height);
  ctx.fillStyle = 'red';
  ctx.beginPath();
  ctx.arc(x - 25, 75, 25, 0, Math.PI*2);
  ctx.fill();
  requestAnimationFrame(draw);
}
video,canvas{border:1px solid}
<canvas id="canvas">75</canvas>
<video id="vid" controls></video>

最有效的流式传输实时画布绘制的方法是:流式传输绘图操作。
再次提醒,OP表示他们不想使用此解决方案,但对许多读者可能有所帮助:
不要发送画布的结果,而是将绘图命令发送给您的同行,然后在其端执行这些命令。
但这种方法也有自己的注意事项:
- 您将不得不编写自己的编码器/解码器来传递命令。 - 有些情况可能难以共享(例如,外部媒体必须以相同的方式在所有同行之间共享和预加载,最糟糕的情况是绘制另一个画布,其中您还必须共享其自己的绘图过程)。 - 您可能希望避免在所有同行上执行密集的图像处理(例如ImageData操作)。
因此,第三种明显性能较差的方法就是像OP尝试做的那样:
定期上传帧。
我不会详细介绍这个方法,但请记住,您正在发送独立的图像文件,因此比编码为视频要多出很多数据。
相反,我将重点介绍为什么OP的代码无法正常工作? 首先,可能需要简单地了解一下Blob是什么(在canvas.toBlob(callback)的回调函数中提供的内容)。
Blob是一个特殊的JavaScript对象,表示二进制数据,通常存储在浏览器的内存中,或者至少存储在用户的磁盘上,可以被浏览器访问。
这些二进制数据不直接可用于JavaScript。为了能够访问它们,我们需要读取该Blob(通过FileReader或Response对象),或者创建一个BlobURI,它是一个虚假的URI,允许大多数API指向二进制数据,就像它存储在真实服务器上一样,即使二进制数据仍然只在浏览器分配的内存中。
但是,这个BlobURI只是一个虚假的、临时的、受限制的域路径到浏览器内存,不能共享给任何其他跨域文档、应用程序,甚至更少的计算机。
所有这些都是为了说明应该直接发送Blobs,而不是BlobURIs。
您只需在使用者端创建BlobURIs,以便他们可以从Blob的二进制数据中加载这些图像,这些数据现在位于他们的分配的内存中。
发射器端:
canvas.toBlob(blob=>ws.send(blob));

消费者端:

ws.onmessage = function(evt) {
  const blob = evt.data;
  const url = URL.createObjectURL(blob);
  img.src = url;
};

但实际上,为了更好地回答OP的问题并提供最终解决方案,可能在这种情况下最好的方法是:

分享绘制在画布上的视频流。


我已经编辑了我的问题,并添加了一些更多的细节和我的进展。 - william wolly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接