WebRTC 数据通道连接建立但消息不来

WebRTC Data Channel connection established but messages do not come

我在接收两个对等方之间的 WebRTC 数据通道消息时遇到问题。

下面是一个 MVP,其中两个对等点在同一个页面上,并且信令通道被替换为普通 JavaScript 对象交换。

我没有选择使用 trickle ICE,我知道不使用它的缺点,我认为它不会以任何方式干扰我下面的 MVP。

代码的行为完全符合预期,所有日志消息都通过了。唯一缺少的是用于数据通道消息处理的那些,就好像消息从未从任何一个点传到另一个点一样。这是尝试发送 Test 的事件处理程序。 send 方法调用失败并出现空引用错误,我无法追踪其根本原因。

我关注了Untangling the WebRTC Flow to get where I am with this MVP. You can see it live here so you don't have to run it yourself.

class Peer {
    constructor(name) {
        this.name = name;
    }
    offer() {
        const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
        peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
        peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
        peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
        peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
        peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
        peerConnection.ondatachannel = event => {
        const dataChannel = event.channel;
        dataChannel.onopen = event => {
            console.debug(this.name, "onopen");
            dataChannel.send("TEST");
        };
        dataChannel.onclose = event => console.debug(this.name, "onclose");
        dataChannel.onerror = event => console.debug(this.name, "onerror");
        dataChannel.onmessage = event => console.debug(this.name, "onmessage");
        console.debug(this.name, "ondatachannelO");
        this.dataChannel = dataChannel;
        };

        return new Promise((resolve, reject) => {
        peerConnection.onicecandidate = event => {
            if (!event.candidate) {
            peerConnection.createOffer()
                .then(offer => {
                console.debug(this.name, "created an offer with candidates.");
                this.peerConnection = peerConnection;
                resolve(peerConnection.localDescription);
                })
                .catch(reject);
            }
        };
        peerConnection.createDataChannel("datachannel");
        peerConnection.createOffer()
            .then(offer => {
            console.debug(this.name, "created an offer without candidates.");
            peerConnection.setLocalDescription(offer)
                .then(() => {
                console.debug(this.name, "set local description. Collecting candidates…");
                })
                .catch(reject);
            })
            .catch(reject);
        });
    }
    answer(offer) {
        const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
        peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
        peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
        peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
        peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
        peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
        peerConnection.ondatachannel = event => {
        const dataChannel = event.channel;
        dataChannel.onopen = event => {
            console.debug(this.name, "onopen");
            dataChannel.send("TEST");
        };
        dataChannel.onclose = event => console.debug(this.name, "onclose");
        dataChannel.onerror = event => console.debug(this.name, "onerror");
        dataChannel.onmessage = event => console.debug(this.name, "onmessage");
        console.debug(this.name, "ondatachannelA");
        this.dataChannel = dataChannel;
        };
        return new Promise((resolve, reject) => {
        peerConnection.onicecandidate = event => {
            if (!event.candidate) {
            peerConnection.createAnswer()
                .then(answer => {
                console.debug(this.name, "created an answer with candidates.");
                resolve(peerConnection.localDescription);
                })
                .catch(reject);
            }
        };
        peerConnection.setRemoteDescription(offer)
            .then(() => {
            console.debug(this.name, "set remote description.");
            peerConnection.createAnswer()
                .then(answer => {
                console.debug(this.name, "created an answer without candidates.");
                peerConnection.setLocalDescription(answer)
                    .then(() => {
                    console.debug(this.name, "set local description.");
                    })
                    .catch(reject);
                })
                .catch(reject);
            })
            .catch(reject);
        });
    }
    sealTheDeal(proffer) {
        return new Promise((resolve, reject) => {
        this.peerConnection.setRemoteDescription(proffer)
            .then(() => {
            console.debug(this.name, "set remote description.");
            resolve();
            })
            .catch(console.e);
        });
    }
    send() {
        this.dataChannel.send("TEST");
    }
    }
    function flow() {
    const peerA = new Peer("Alice");
    const peerB = new Peer("Bob");
    peerA.offer()
        .then(offer => {
        console.debug("Signal transfering offer from Alice to Bob.");
        peerB.answer(offer)
            .then(proffer => {
            console.debug("Signal transfering proffer from Bob to Alice.");
            peerA.sealTheDeal(proffer)
                .then(() => {
                peerB.offer()
                    .then(offer => {
                    console.debug("Signal transfering offer from Bob to Alice.");
                    peerA.answer(offer)
                        .then(proffer => {
                        console.debug("Signal transfering proffer from Alice to Bob.");
                        peerB.sealTheDeal(proffer)
                            .then(() => {
                            console.debug("HYPE");
                            peerA.send("From Alice to Bob.");
                            peerB.send("From Bob to Alice.");
                            })
                            .catch(console.error);
                        })
                        .catch(console.error);
                    })
                    .catch(console.error);
                })
                .catch(console.error);
            })
            .catch(console.error);
        })
        .catch(console.error);
        window.peerA = peerA;
        window.peerB = peerB;
    }
    flow();

peerConnection.ondatachannel 仅在应答者上触发。

offerer获取数据通道如下:

this.dataChannel = peerConnection.createDataChannel("datachannel");

其他评论:

  • webkitRTCPeerConnection 仅适用于 Chrome/Opera。应该填充还是使用 adapter.js.
  • 厄运金字塔不需要承诺。
  • 避免 promise constructor anti-pattern.

示例:

var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();

pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);

pc1.onnegotiationneeded = e =>
  pc1.createOffer().then(d => pc1.setLocalDescription(d))
  .then(() => pc2.setRemoteDescription(pc1.localDescription))
  .then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
  .then(() => pc1.setRemoteDescription(pc2.localDescription))
  .catch(e => console.log(e));

var dc1, dc2;
pc2.ondatachannel = e => {
  dc2 = e.channel;
  dc2.onopen = () => console.log("Chat!");
  dc2.onmessage = e => console.log("> " + e.data);
};

dc1 = pc1.createDataChannel("chat");
dc1.onopen = () => (chat.disabled = false, chat.select());

chat.onkeypress = e => {
  if (e.keyCode != 13) return;
  dc1.send(chat.value);
  chat.value = "";
};
Chat: <input id="chat" disabled></input>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>