WebRTC DataChannel:在Firefox中工作但不是Chrome

时间:2016-05-19 20:59:36

标签: javascript webrtc rtcdatachannel

我在WebRTC中相当新。我试图在两个对等体之间建立一个简单的数据通道,没有音频和视频;只是文字数据。 最后,它将成为一个游戏,2-7个同伴将连接到将成为游戏大师的同伴。

经过几个小时的谷歌搜索和阅读html5rocks,MDN和其他堆栈帖子,我尝试了很多东西,但我仍然没有设法让它发挥作用。

当我在两个不同的Firefox标签页面上打开页面时,一切正常。我可以看到其中一个标签发送了" Hello,world!"并且另一个发送"它有效!"。 DataChannel已经建立完好,两个标签都可以获得各自的同行消息。

但是,在Chrome上运行它时,它无法正常工作。在我的一个测试中,DataChannel在我能够发送任何内容之前神秘地关闭,而在另一个测试中,RTCPeerConnection.ondatachannel事件似乎根本没有被调用(更多细节进一步向下)。 如果我尝试让Firefox与Chrome通信,无论订单如何,我都会收到有关setRemoteDescription失败的不同神秘错误。

当然,在这些情况下,我都没有在Web / JavaScript控制台中收到任何错误消息;这本来太容易了。

我的问题不在信号传递过程中,至少我不这么认为。普通的WebSocket用于与非常简单的Node.js服务器通信。 我宁愿避免使用像PeerJS这样的库。首先,因为我们通过手动更好地学习它,其次是因为我想使用信令Node.js服务器来做除信令之外的其他事情。这在节点方面本身不是问题,但它在浏览器端(因为我不会在100 + KB缩小/混淆的源代码的海洋中找到一点雨滴)

基本方案非常简单:当前连接的用户列表会在页面上每15秒自动刷新一次。通过点击用户名,您就可以与他联系,发送" Hello,world!"当他回答"它有效!"同时;那个时候吃的。 一旦我能够建立基本通信,简单的聊天文本框当然是下一个合乎逻辑的步骤。

更具体地说,如果我是用户A并点击用户B,则应该发生以下情况:

  1. 通过信令WebSocket,A向B发送消息,表明他想给他打电话
  2. B使用WebRTC优惠回复A
  3. A获取要约并回复WebRTC答案。
  4. 已建立DataChannel
  5. 当B侧的DataChannel打开时,他发送" Hello,world!"到A
  6. 当A侧的DataChannel打开时,他发送"它有效!"到B;这可能以相反的顺序发生

    • 无论使用哪种浏览器,我应该修改哪些内容才能使其正常工作? (当然我知道它目前仅适用于Firefox和Chrome)
    • Bonus可选问题,为什么我会获得多个ICE候选人,特别是在连接成功建立之后?
  7. 我想我应该拥有最新的Firefox和Crhome:resp。 45和49,在Windows 7 64位上。

    以下是我的JavaScript代码;那么输出对应于几个场景,最后我通过阅读其他帖子和教程得到了一些。

    function log (s) {
    $('#log')[0].insertAdjacentHTML('beforeEnd', s+'<br />');
    }
    
    function callUser (e) {
    var uname = this.href.substring(1+this.href.indexOf('#'));
    ws.send({ type: 'RTCCall', to: uname });
    log('Calling ' + uname + '...');
    e.preventDefault();
    return false;
    }
    
    function updateUserList (o) {
    var div = $('#userlist')[0];
    div.innerHTML='';
    div.append('p', o.userlist.length + ' connected users');
    for (var i=0, n=o.userlist.length; i<n; i++) {
    var uname = o.userlist[i];
    var a = div.append('a', {href: '#'+uname }, uname);
    div.append('br');
    a.onclick = callUser;
    }}
    
    function createRTCPeerConnection (to) {
    log("Creating RTCPeerConnection...");
    var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
    var pc = new RTCPeerConnection(pcConfig, pcOptions);
    pc.onicecandidate = e=>{ if (e&&e.candidate) { ws.send({ type: 'RTCSignal', to: to, candidate: e.candidate }); log('ICE candidate received'); }};
    pc.onconnectionstatechange = e=>log("Connection state change: " +pc.connectionState);
    pc.onnegotiationneeded = e=>{ console.log("Negotiation needed: ", e); log("Negotiation needed: " +e); };
    pc.onicecandidateerror = e=>log("ICE candidate error: " +e);
    pc.oniceconnectionstatechange = e=>log("ICE connection state change: " +pc.iceConnectionState);
    pc.onicegatheringstatechange = e=>log("ICE gathering state change: " +pc.iceGatheringState);
    pc.onsignalingstatechange = e=>log("Signaling state change: " +pc.signalingState);
    pc.onaddstream = e=>{ console.log(e); log('Add stream'); };
    pc.ondatachannel = e=>{ 
    log("Received data channel " + e.channel.label);
    pc.channel=e.channel;
    pc.channel.onopen = e=>{ log("Data channel opened"); pc.channel.send("It works!"); };
    pc.channel.onmessage = e=>log("Message from " + to + ": " + e.data);
    pc.channel.onerror = e=>log("Data channel error: " +e);
    pc.channel.onclose = e=>log("Data channel closed: " +e);
    };
    log("RTCPeerConnection created");
    return pc;
    }
    
    function createDataChannel (pc, name) {
    log("Creating DataChannel " + name + "...");
    pc.channel=pc.createDataChannel(name, { ordered: false });
    pc.channel.onopen = _=>{ pc.channel.send("Hello, world!"); log("Data channel opened"); };
    pc.channel.onmessage = e=>log("Message from " + pc.from + ": " + e.data);
    pc.channel.onerror = e=>log("Data channel error: " +e);
    pc.channel.onclose = e=>log("Data channel closed: " +e);
    log("DataChannel " + name + " created");
    return pc.channel;
    }
    
    var ws = new WSClient('ws://localhost:3003/');
    var pc, 
    pcConfig = {iceServers:[{url:'stun:stun.l.google.com:19302'}]},
    pcOptions = { optional:  [
    {DtlsSrtpKeyAgreement: true}, {RtpDataChannels: true }] 
    },
    sdpOptions = {mandatory:  { OfferToReceiveAudio: true,  OfferToReceiveVideo: false } };
    
    log('Initializing...');
    ws.on('connect', _=>log('Connected to web socket'));
    ws.on('disconnect', _=>log('Disconnected from web socket'));
    ws.on('userlist', o=>updateUserList(o));
    ws.connect() .then(_=>{ ws.send({type:'userlist'}); setInterval(_=>ws.send({ type: 'userlist' }), 15000); });
    
    ws.on('RTCCall', o=>{
    log(o.from + " is calling !");
    if (!pc) pc = createRTCPeerConnection(o.from);
    pc.from = o.from;
    pc.channel = createDataChannel(pc, 'chat');
    pc.createOffer(desc=>{ 
    pc.setLocalDescription(desc, _=>log("setLocalDescription succeeded"), fail=>log("setLocalDescription failed: " + fail));
    log("Sending offer to " + o.from); 
    ws.send({type: 'RTCSignal', to: o.from, answer: true, sdp: desc}); }, 
    fail=>log("createOffer failed: "+fail), sdpOptions);
    });//RTCCall
    
    ws.on('RTCSignal', o=>{
    log("Received signal from " + o.from + ": " + (o.sdp?"sdp":"") + (o.candidate?"ICE":""));
    if (!pc) pc = createRTCPeerConnection(o.from);
    pc.from = o.from;
    if (o.sdp)  pc.setRemoteDescription(new RTCSessionDescription(o.sdp), _=>log("setRemoteDescription succeeded"), fail=>log("setRemoteDescription failed: " +fail));
    else  if (o.candidate) pc.addIceCandidate(new RTCIceCandidate(o.candidate));
    if (o.answer) pc.createAnswer(desc=>{ 
    pc.setLocalDescription(desc, _=>log("setLocalDescription succeeded"), fail=>log("setLocalDescription failed: " + fail));
    log("Sending answer to " + o.from); 
    ws.send({type: 'RTCSignal', to: o.from, sdp: desc}); 
    }, 
    fail=>log("createAnswer failed: "+fail), sdpOptions);
    });
    

    这是firefox连接到firefox时的输出,完美无缺:

    呼叫者:

    Initializing...
    Connected to web socket
    Calling user132...
    Received signal from user132: sdp
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Signaling state change: have-remote-offer
    setRemoteDescription succeeded
    Received signal from user132: ICE
    Received signal from user132: ICE
    Received signal from user132: ICE
    Sending answer to user132
    Signaling state change: stable
    setLocalDescription succeeded
    Received signal from user132: ICE
    ICE connection state change: checking
    ICE connection state change: connected
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE candidate received
    Received data channel chat
    Received signal from user132: ICE
    ICE candidate received
    Data channel opened
    Message from user132: Hello, world!
    

    称为:

    Initializing...
    Connected to web socket
    user133 is calling !
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Creating DataChannel chat...
    Negotiation needed: [object Event]
    DataChannel chat created
    Sending offer to user133
    Signaling state change: have-local-offer
    setLocalDescription succeeded
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE candidate received
    Received signal from user133: sdp
    Signaling state change: stable
    setRemoteDescription succeeded
    ICE connection state change: checking
    ICE connection state change: connected
    ICE candidate received
    Data channel opened
    Received signal from user133: ICE
    Received signal from user133: ICE
    Received signal from user133: ICE
    Received signal from user133: ICE
    Received signal from user133: ICE
    Message from user133: It works!
    

    以下是Chrome连接到Chrome时的输出,失败了:

    呼叫者:

    Initializing...
    Connected to web socket
    Calling user134...
    Received signal from user134: sdp
    Creating RTCPeerConnection...
    RTCPeerConnection created
    setRemoteDescription succeeded
    Signaling state change: have-remote-offer
    Sending answer to user134
    setLocalDescription succeeded
    Signaling state change: stable
    Received signal from user134: ICE
    ICE connection state change: checking
    ICE candidate received
    Received signal from user134: ICE
    ICE candidate received
    ICE connection state change: connected
    

    调用:

    Initializing...
    Connected to web socket
    user135 is calling !
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Creating DataChannel chat...
    DataChannel chat created
    Negotiation needed: [object Event]
    Sending offer to user135
    Signaling state change: have-local-offer
    setLocalDescription succeeded
    Received signal from user135: sdp
    Data channel closed: [object Event]
    setRemoteDescription succeeded
    Signaling state change: stable
    ICE connection state change: checking
    ICE candidate received
    Received signal from user135: ICE
    ICE candidate received
    Received signal from user135: ICE
    ICE connection state change: connected
    ICE connection state change: completed
    

    以下是Firefox连接到Chrome时的输出,但失败了:

    Fiefox来电者:

    Initializing...
    Connected to web socket
    Calling user136...
    Received signal from user136: sdp
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Signaling state change: have-remote-offer
    setRemoteDescription succeeded
    Received signal from user136: ICE
    Received signal from user136: ICE
    Received signal from user136: ICE
    Received signal from user136: ICE
    Sending answer to user136
    Signaling state change: stable
    setLocalDescription succeeded
    ICE connection state change: failed
    Received signal from user136: ICE
    Received signal from user136: ICE
    Received signal from user136: ICE
    Received signal from user136: ICE
    

    Chrome称:

    Initializing...
    Connected to web socket
    user137 is calling !
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Creating DataChannel chat...
    DataChannel chat created
    Negotiation needed: [object Event]
    Sending offer to user137
    setLocalDescription succeeded
    Signaling state change: have-local-offer
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE candidate received
    Received signal from user137: sdp
    setRemoteDescription failed: OperationError: Failed to parse SessionDescription. 
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE candidate received
    

    这是Firefox相反连接到Chrome时的输出,也是失败的: Chrome来电者:

    Initializing...
    Connected to web socket
    Calling user138...
    Received signal from user138: sdp
    Creating RTCPeerConnection...
    RTCPeerConnection created
    setRemoteDescription failed: OperationError: Failed to set remote offer sdp: Session error code: ERROR_CONTENT. Session error description: Failed to set
    remote data description send parameters..
    Signaling state change: have-remote-offer
    Sending answer to user138
    setLocalDescription failed: OperationError: Failed to set local sdp: Session error code: ERROR_CONTENT. Session error description: Failed to set remote
    data description send parameters..
    Received signal from user138: ICE
    Received signal from user138: ICE
    Received signal from user138: ICE
    Received signal from user138: ICE
    

    Firefox调用:

    Initializing...
    Connected to web socket
    user139 is calling !
    Creating RTCPeerConnection...
    RTCPeerConnection created
    Creating DataChannel chat...
    Negotiation needed: [object Event]
    DataChannel chat created
    Sending offer to user139
    Signaling state change: have-local-offer
    setLocalDescription succeeded
    Received signal from user139: sdp
    Signaling state change: stable
    setRemoteDescription succeeded
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE candidate received
    ICE connection state change: failed
    

    现在,有几个人:

    1. 我已经多次读过应该在发送商品之前创建DataChannel。因此,我尝试按如下方式修改我的代码,以确保它是这样的:

      pc.createOffer(DESC =&GT; { pc.setLocalDescription(desc,_ =&gt;说(&#34; setLocalDescription成功&#34;),fail =&gt;说(&#34; setLocalDescription失败:&#34; +失败)); 说(&#34;向&#34; + o.from发送报价); ws.send({type:&#39; RTCSignal&#39;,to:o.from,answer:true,sdp:desc}); }, fail =&gt;说(&#34; createOffer失败:&#34; +失败),sdpOptions); pc.channel = createDataChannel(pc,&#39; chat&#39;);

    2. 此修改不会为firefox更改任何内容。它继续和以前一样工作。 对于Chrome,它仍然无法正常工作;但输出是不同的。以前,在我调用setRemoteDescription之前,我能够发送任何内容之前就已经神秘地关闭了DataChannel。 但是,在这种情况下,我没有得到任何消息,DataChannel保持连接状态。这是输出:

      呼叫者:

      Initializing...
      Connected to web socket
      Calling user142...
      Received signal from user142: sdp
      Creating RTCPeerConnection...
      RTCPeerConnection created
      setRemoteDescription succeeded
      Signaling state change: have-remote-offer
      Sending answer to user142
      Signaling state change: stable
      setLocalDescription succeeded
      ICE candidate received
      Received signal from user142: ICE
      ICE connection state change: checking
      ICE candidate received
      Received signal from user142: ICE
      ICE connection state change: connected
      

      调用:

      Initializing...
      Connected to web socket
      user143 is calling !
      Creating RTCPeerConnection...
      RTCPeerConnection created
      Creating DataChannel chat...
      DataChannel chat created
      Negotiation needed: [object Event]
      Sending offer to user143
      setLocalDescription succeeded
      Signaling state change: have-local-offer
      Received signal from user143: sdp
      Signaling state change: stable
      setRemoteDescription succeeded
      ICE connection state change: checking
      Received signal from user143: ICE
      ICE candidate received
      ICE candidate received
      Received signal from user143: ICE
      ICE connection state change: connected
      ICE connection state change: completed
      

      无论如何,似乎在两种情况中都不会调用事件RTCPeerConnection.ondatachannel。我觉得如果我的处理程序从未被调用过,或者如果连接尚未建立,我就不能很清楚。

      我还尝试在另一个时刻创建DataChannel但没有成功。例如,在双方都调用了setRemoteDescription之后。 在这种情况下,Firefox拒绝创建一个报价,因为我既没有请求音频/视频,也没有跟踪(我不知道它是什么),也没有DataChannel(它还没有创造了)。 所以到目前为止我的结论是在发送报价之前创建频道是正确的方法;至少是唯一可以与Firefox一起使用的。

      我还读了很多次,鉴于我没有要求音频/视频,我没有义务发送报价和答案。但如果我从代码中挤出来,似乎什么都没发生。没有ICE服务器交换等等...... 在其他地方,我读到在调用setLocalDescription之前没有启动ICE服务器的东西。所以我必须调用setLocalDescription,因此我必须创建一个offer。从那里看来,我有义务通过信令通道将其发送给另一个对等方,我有义务调用setRemoteDescription,然后需要回答。

      我使用sdpOptions = {mandatory:{OfferToReceiveAudio:true,OfferToReceiveVideo:false}};``在我的代码中,虽然我不打算发送音频/视频流。 我已经搜索了很多,然后才注意到如果我将它们都设置为false,那么Chrome就永远不会启动它的ICE服务器,因此无法进行任何P2P连接。

      这一个:{DtlsSrtpKeyAgreement: true}, {RtpDataChannels: true }] 我从教程中复制它而不知道它的作用。无论如何,完全删除它,或者将一个或另一个设置为false并不会改变我的结果。

      感谢您阅读这么长的帖子。我希望你知道如何解决这个问题。 请告诉我应该做些什么,或者至少给我一些可能的线索。

      非常感谢你的帮助。

      编辑:天啊!似乎我的所有代码行都在一个大行中折叠在一起。对不起,我没有预料到。请稍后通过一些评论告诉我如何解决这个问题。谢谢。

1 个答案:

答案 0 :(得分:5)

删除它:

pcOptions = { optional:  [
{DtlsSrtpKeyAgreement: true}, {RtpDataChannels: true }] 
},

这是旧的非标准Chrome内容,在Firefox中什么都不做,但会导致蝙蝠在Chrome中飞出来。数据通道不会在规范中运行rtp,也不依赖于srtp。

当你在这里时,也要删除它:

sdpOptions = {mandatory: {OfferToReceiveAudio: true,  OfferToReceiveVideo: false}};

格式已更改为(请注意小写的“o”):

sdpOptions = { offerToReceiveAudio: true,  offerToReceiveVideo: false};

但这对于数据渠道来说是不必要的。如果它仍然无效,请告诉我。

我还强烈推荐官方WebRTC polyfill adapter.js,它允许您使用最新的规范和承诺等like this。它不仅仅是一个图书馆,它最终会消失。