问题描述
我正在尝试使用 WebRTC 数据通道将从画布捕获的图像发送到我的 NodeJS 后端服务器。那是我试图使我的服务器成为对等方。但是由于某种原因,我无法建立连接。
客户端
async function initChannel()
{
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
const response = await fetch("/connect",{
headers: {
'Content-Type': 'application/json',},method: 'post',body: JSON.stringify({ sdp: offer,id: Math.random() })
}).then((res) => res.json());
peer.setRemoteDescription(response.sdp);
const imageChannel = peer.createDataChannel("imageChannel",{ ordered: false,maxPacketLifeTime: 100 });
peer.addEventListener("icecandidate",console.log);
peer.addEventListener("icegatheringstatechange",console.log);
// drawCanvas function draws images got from the server.
imageChannel.addEventListener("message",message => drawCanvas(remoteCanvasCtx,message.data,imageChannel));
// captureImage function captures and sends image to server using imageChannel.send()
imageChannel.addEventListener("open",() => captureImage(recordCanvasCtx,recordCanvas,imageChannel));
}
const peer = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.stunprotocol.org:3478" }] });
initChannel();
此处未调用 captureImage
和 drawCanvas
。
服务器端
import webrtc from "wrtc"; // The wrtc module ( npm i wrtc )
function handleChannel(channel)
{
console.log(channel.label); // This function is not being called.
}
app.use(express.static(resolve(__dirname,"public")))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }));
app.post("/connect",async ({ body },res) =>
{
console.log("Connecting to client...");
let answer,id = body.id;
const peer = new webrtc.RTCPeerConnection({ iceServers: [{ urls: "stun:stun.stunprotocol.org:3478" }] });
await peer.setRemoteDescription(new webrtc.RTCSessionDescription(body.sdp));
await peer.setLocalDescription(answer = await peer.createAnswer());
peer.addEventListener("datachannel",handleChannel)
return res.json({ sdp: answer });
});
app.listen(process.env.PORT || 2000);
此处发布请求处理得很好,但从未调用 handleChannel
。
当我运行它时,我没有收到任何错误,但是当我检查连接状态时,它永远显示“新”。我控制台记录了远程和本地描述,它们似乎都已设置。 我在这里做错了什么?
我对 WebRTC 很陌生,我什至不确定这是否是将图像(用户的网络摄像头馈送的帧)连续发送到服务器和从服务器返回的正确方法,如果有人能告诉我更好的方法,请告诉我。
还有一件事,我如何以低延迟通过数据通道发送图像 blob(从 canvas.toBlob()
获取)。
解决方法
我终于在我朋友的帮助下弄明白了。问题是我必须在调用 peer.createOffer()
之前创建 DataChannel。 peer.onnegotiationneeded
回调仅在创建频道后调用。通常,当您通过将流传递给 WebRTC 来创建媒体频道(音频或视频)时会发生这种情况,但在这里,由于我没有使用它们,因此我必须这样做。
客户端
const peer = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] });
const imageChannel = peer.createDataChannel("imageChannel");
imageChannel.onmessage = ({ data }) =>
{
// Do something with received data.
};
imageChannel.onopen = () => imageChannel.send(imageData);// Data channel opened,start sending data.
peer.onnegotiationneeded = initChannel
async function initChannel()
{
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
// Send offer and fetch answer from the server
const { sdp } = await fetch("/connect",{
headers: {
"Content-Type": "application/json",},method: "post",body: JSON.stringify({ sdp: peer.localDescription }),})
.then(res => res.json());
peer.setRemoteDescription(new RTCSessionDescription(sdp));
}
服务器
通过发布请求接收来自客户的报价。为其创建一个答案并作为响应发送。
app.post('/connect',async ({ body },res) =>
{
const peer = new webrtc.RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],});
console.log('Connecting to client...');
peer.ondatachannel = handleChannel;
await peer.setRemoteDescription(new webrtc.RTCSessionDescription(body.sdp));
await peer.setLocalDescription(await peer.createAnswer());
return res.json({ sdp: peer.localDescription });
});
处理数据通道的函数。
/**
* This function is called once a data channel is ready.
*
* @param {{ type: 'datachannel',channel: RTCDataChannel }} event
*/
function handleChannel({ channel })
{
channel.addEventListener("message",{data} =>
{
// Do something with data received from client.
});
// Can use the channel to send data to client.
channel.send("Hi from server");
}
所以这是发生的事情:
- 客户端创建一个数据通道。
- 一旦创建了数据通道,就会调用
onnegotiationneeded
回调。 - 客户端创建报价并将其发送到服务器(作为发布请求)。
- 服务器接收报价并创建答案。
- 服务器将答案发送回客户端(作为发布响应)。
- 客户端使用收到的答案完成初始化。
-
ondatachannel
回调在服务器和客户端上被调用。
我在这里使用 post request 来交换报价和回答,但如果您喜欢,使用 Web Socket 也应该很容易做到这一点。