我一直在阅读this article作为一种信号解决方案。作者提到建立连接时使用RTCDataChannel发信号。

由于已经建立连接,为什么需要信令?

最佳答案

每一端最初声明要发送的音频和/或视频轨道,以便可以打开正确数量的端口,并可以确定适用于两个对等端的分辨率和格式。需要一个信令 channel 将生成的SDP提议/答案以及每个端口的trick流ICE候选发送到另一侧。

连接后,如果您不理会此设置-基本上从不向连接添加轨道,删除任何轨道或​​显着更改轨道属性-那么您将不再需要信令服务器。

但是,如果您确实更改了其中的任何一项,则需要重新协商,这听起来很像:在信令信道上进行另一轮回合,就像第一个回合一样。

添加轨道的原因可能是第二台摄像机,另一个视频源(可能来自其他参与者)或屏幕共享,诸如此类。

该文章是正确的,可以使用数据 channel 。这是a demo! (仅适用于Firefox。)

这篇文章错误地描述了所需的信令服务-如果您有另一种发现方式-该演示很la弱地证明了这一点。

最初的连接仅是聊天,但是任何一方都可以插入将视频添加到混合中。重新协商是通过数据 channel 完成的(因为没有信令服务器!)

使用 fiddle 的说明:

  • 没有服务器(因为它是 fiddle ),因此请按Offer按钮并复制报价。
  • 将报价粘贴到另一个标签或另一台计算机上相同提琴中的相同位置。
  • 按ENTER,然后复制您得到的答案并将其粘贴回第一个 fiddle 中。
  • 再次按ENTER(尚未addTrack!)
  • 您现在连接了两个数据 channel :一个用于聊天,另一个用于信令。
  • 现在,在任一端按addTrack,视频应在另一端显示。
  • 在另一个方向上按addTrack,您应该使视频双向播放。


  • var dc = null, sc = null, pc = new mozRTCPeerConnection(), live = false;
    pc.onaddstream = e => v2.mozSrcObject = e.stream;
    pc.ondatachannel = e => dc? scInit(sc = e.channel) : dcInit(dc = e.channel);
    v2.onloadedmetadata = e => { log("Face time!"); };
    
    function addTrack() {
      navigator.mediaDevices.getUserMedia({video:true, audio:true})
      .then(stream => pc.addStream(v1.mozSrcObject = stream));
    }
    
    pc.onnegotiationneeded = e => {
      pc.createOffer().then(d => pc.setLocalDescription(d)).then(() => {
        if (live) sc.send(JSON.stringify({ "sdp": pc.localDescription }));
      }).catch(failed);
    };
    
    function scInit() {
      sc.onmessage = e => {
        var msg = JSON.parse(e.data);
        if (msg.sdp) {
          var desc = new mozRTCSessionDescription(JSON.parse(e.data).sdp);
          if (desc.type == "offer") {
            pc.setRemoteDescription(desc).then(() => pc.createAnswer())
            .then(answer => pc.setLocalDescription(answer)).then(() => {
              sc.send(JSON.stringify({ "sdp": pc.localDescription }));
            }).catch(failed);
          } else {
            pc.setRemoteDescription(desc).catch(failed);
          }
        } else if (msg.candidate) {
          pc.addIceCandidate(new mozRTCIceCandidate(msg.candidate)).catch(failed);
        }
      };
    }
    
    function dcInit() {
      dc.onopen = () => { live = true; log("Chat!"); };
      dc.onmessage = e => log(e.data);
    }
    
    function createOffer() {
      button.disabled = true;
      dcInit(dc = pc.createDataChannel("chat"));
      scInit(sc = pc.createDataChannel("signaling"));
      pc.createOffer().then(d => pc.setLocalDescription(d)).catch(failed);
      pc.onicecandidate = e => {
        if (e.candidate) return;
        if (!live) {
          offer.value = pc.localDescription.sdp;
          offer.select();
          answer.placeholder = "Paste answer here";
        } else {
          sc.send(JSON.stringify({ "candidate": e.candidate }));
        }
      };
    };
    
    offer.onkeypress = e => {
      if (e.keyCode != 13 || pc.signalingState != "stable") return;
      button.disabled = offer.disabled = true;
      var obj = { type:"offer", sdp:offer.value };
      pc.setRemoteDescription(new mozRTCSessionDescription(obj))
      .then(() => pc.createAnswer()).then(d => pc.setLocalDescription(d))
      .catch(failed);
      pc.onicecandidate = e => {
        if (e.candidate) return;
        if (!live) {
          answer.focus();
          answer.value = pc.localDescription.sdp;
          answer.select();
        } else {
          sc.send(JSON.stringify({ "candidate": e.candidate }));
        }
      };
    };
    
    answer.onkeypress = e => {
      if (e.keyCode != 13 || pc.signalingState != "have-local-offer") return;
      answer.disabled = true;
      var obj = { type:"answer", sdp:answer.value };
      pc.setRemoteDescription(new mozRTCSessionDescription(obj)).catch(failed);
    };
    
    chat.onkeypress = e => {
      if (e.keyCode != 13) return;
      dc.send(chat.value);
      log(chat.value);
      chat.value = "";
    };
    
    var log = msg => div.innerHTML += "<p>" + msg + "</p>";
    var failed = e => log(e.name + ": " + e.message + " line " + e.lineNumber);
    <video id="v1" height="120" width="160" autoplay muted></video>
    <video id="v2" height="120" width="160" autoplay></video><br>
    <button id="button" onclick="createOffer()">Offer:</button>
    <textarea id="offer" placeholder="Paste offer here"></textarea><br>
    Answer: <textarea id="answer"></textarea><br>
    <button id="button" onclick="addTrack()">AddTrack</button>
    <div id="div"></div><br>
    Chat: <input id="chat"></input><br>

    09-26 07:18