我在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,则应该发生以下情况:
当A侧的DataChannel打开时,他发送"它有效!"到B;这可能以相反的顺序发生
我想我应该拥有最新的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
现在,有几个人:
我已经多次读过应该在发送商品之前创建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;);
此修改不会为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并不会改变我的结果。
感谢您阅读这么长的帖子。我希望你知道如何解决这个问题。 请告诉我应该做些什么,或者至少给我一些可能的线索。
非常感谢你的帮助。
编辑:天啊!似乎我的所有代码行都在一个大行中折叠在一起。对不起,我没有预料到。请稍后通过一些评论告诉我如何解决这个问题。谢谢。答案 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。它不仅仅是一个图书馆,它最终会消失。