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

Unable to establish WebRTC connection with Node JS server as a peer

我正在尝试使用 WebRTC 数据通道将从 canvas 捕获的图像发送到我的 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);

此处 post 请求处理良好,但从未调用 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));
}

服务器

收到客户通过 post 请求发送的报价。为其创建一个答案并作为回复发送。

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. 客户端创建报价并将其发送到服务器(作为 post 请求)。
  4. 服务器接收报价并创建答案。
  5. 服务器将答案发送回客户端(作为 post 响应)。
  6. 客户端使用收到的应答完成初始化。
  7. ondatachannel 在服务器和客户端上调用回调。

我在这里使用 post 请求来交换报价和​​答案,但如果您喜欢的话,使用 Web Socket 做同样的事情应该相当容易。