我正在尝试使用websockets
和nodejs
实时流式传输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绘制到画布上,但我找不到任何接近我想要做的事情。
所以我的问题是:
这是我努力实现的目标的正确方式吗?任何赞成和反对意见将不胜感激。
还有其他更有效的方法吗?或者我是正确的路径
提前感谢。
编辑:
我应该提到的是,我不能在这个项目中使用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明确表示他们不能使用它,对许多人来说可能是这样,因为
- 浏览器支持仍然没有那么好
- 这意味着要运行一个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的问题,最终的解决方案,在这种情况下可能是最好的,