流式传输html5画布内容的高效方式



我正在尝试使用websocketsnodejs实时流式传输html5 canvas的内容。

html5画布的内容只是一个视频

到目前为止,我所做的是:

我将画布转换为blob,然后获得blob URL,并使用websockets将该URL发送到我的nodejs服务器。

我得到的blob URL如下:

canvas.toBlob(function(blob) {
url = window.URL.createObjectURL(blob);
});

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

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

然后,我通过websockets从服务器获取blob URL,这样我就可以使用它将其绘制到另一个画布上,供其他用户查看。

我确实搜索了如何从blob URL绘制到画布上,但我找不到任何接近我想要做的事情。

所以我的问题是:

  1. 这是我努力实现的目标的正确方式吗?任何赞成和反对意见将不胜感激。

  2. 还有其他更有效的方法吗?或者我是正确的路径

提前感谢。

编辑:

我应该提到的是,我不能在这个项目中使用WebRTC,我必须用我所拥有的来完成这一切。

为了让我现在所在的每个人都更容易,我尝试使用websocket:在画布中显示上面提到的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中,我得到了Not allowed to load local resource: blob:错误。

第二版:

这就是我现在的处境。

第一个选项

我尝试通过websocket发送整个blob,并成功地做到了。然而,由于某种奇怪的原因,我无法在客户端上读回来!

当我在nodejs服务器的控制台上查看时,我可以看到我发送到服务器的每个blob都有这样的内容:

<buffer fd67676 hdsjuhsd8 sjhjs....

第二个选项:

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

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

这起到了作用。但是,再次显示这些base64图像的客户端画布非常慢,并且每秒绘制1帧。这就是我目前面临的问题。

第三个选项:

我还试着使用一个没有画布的视频。因此,使用WebRTC,我将video Stream作为单个Blob。但我不完全确定如何使用它并将其发送到客户端,以便人们可以看到它。

重要信息:我正在使用的这个系统不是对等连接。这只是我试图实现的一种流媒体方式。

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

OP明确表示他们不能使用它,对许多人来说可能是这样,因为

  1. 浏览器支持仍然没有那么好
  2. 这意味着要运行一个MediaServer(至少是ICE+STUN/TURN,如果你想流式传输到多个对等点,可能还有一个网关)

但是,如果你能负担得起,那么你只需要就可以从画布元素中获得MediaStream

const canvas_stream = canvas.captureStream(minimumFrameRate);

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

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

下面的示例将仅向<video>元素显示MediaStream。

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只是一个假的、临时的、受域限制的浏览器内存路径,不能共享给任何其他跨域文档、应用程序,甚至更少的计算机。

所有这些都表明,应该直接发送到WebSocket的是Blob,而不是BlobURI。

您只需要在使用者一侧创建Blob URI,这样他们就可以从Blob的二进制数据加载这些图像,这些二进制数据现在位于其分配的内存中。

发射器侧:

canvas.toBlob(blob=>ws.send(blob));

消费者端:

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

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

共享绘制在画布上的视频流

最新更新