无法与 Node JS 服务器建立 WebRTC 连接作为对等方

问题描述

我正在尝试使用 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();

此处未调用 captureImagedrawCanvas

服务器端

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");
}

所以这是发生的事情:

  1. 客户端创建一个数据通道。
  2. 一旦创建了数据通道,就会调用 onnegotiationneeded 回调。
  3. 客户端创建报价并将其发送到服务器(作为发布请求)。
  4. 服务器接收报价并创建答案。
  5. 服务器将答案发送回客户端(作为发布响应)。
  6. 客户端使用收到的答案完成初始化。
  7. ondatachannel 回调在服务器和客户端上被调用。

我在这里使用 post request 来交换报价和​​回答,但如果您喜欢,使用 Web Socket 也应该很容易做到这一点。