2

我是 WebRTC 的新手。我正在尝试在两个对等方之间建立一个简单的数据通道,没有音频也没有视频;只是文本数据。最后,它将成为一个游戏,其中 2-7 个对等点将连接到将成为游戏大师的对等点。

经过数小时的谷歌搜索和阅读 html5rocks、MDN 和其他堆栈帖子,我尝试了很多东西,但我仍然无法让它工作。

当我在两个不同的 Firefox 选项卡上打开页面时,一切正常。我可以看到其中一个标签发送“你好,世界!” 并且另一个发送“它有效!”。DataChannel 建立良好,两个选项卡都获得了各自对等方的消息。

但是,在 Chrome 上运行它时,它不起作用。在我的一个测试中,在我能够发送任何东西之前,DataChannel 神秘地关闭了,而在另一个测试中,似乎根本没有调用 RTCPeerConnection.ondatachannel 事件(更多细节在下面)。如果我尝试让 Firefox 与 Chrome 通信,无论顺序如何,我都会收到关于 setRemoteDescription 失败的不同神秘错误。

当然,在这些情况下,我都不会在 web/JavaScript 控制台中收到任何错误消息;这太容易了。

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

基本场景非常简单:页面上每 15 秒自动刷新一个当前连接用户列表。通过单击用户名,您可以连接到他,发送“Hello, world!” 当他回答“它有效!”时 同时;暂时就这么吃。简单的聊天文本框当然是下一个合乎逻辑的步骤,一旦我能够建立基本的通信。

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

  1. 通过信令 WebSocket,A 向 B 发送消息,表示他想呼叫他
  2. B 用 WebRTC 报价回复 A
  3. A 获得报价并回复 WebRTC 答案。
  4. 数据通道已建立
  5. 当B端DataChannel打开时,他发送“Hello, world!” 到 A
  6. 当A端的DataChannel打开时,他发送“It works!” 给乙;这可能以相反的顺序发生

    • 无论使用什么浏览器,我应该修改什么才能使其正常工作?(当然我知道它目前仅适用于 Firefox 和 Chrome)
    • 额外的可选问题,为什么我会得到多个 ICE 候选人,尤其是在成功建立连接之后?

我想我应该拥有最新的 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 时的输出,失败:

飞狐来电者:

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

铬调用:

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

火狐调用:

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=>{ pc.setLocalDescription(desc, _=>say("setLocalDescription 成功"), fail=>say("setLocalDescription failed: " + fail)); say("发送offer到" + o. from); ws.send({type: 'RTCSignal', to: o.from, answer: true, sdp: desc}); }, fail=>say("createOffer failed: "+fail), sdpOptions); pc.channel = createDataChannel(pc, 'chat');

此修改不会更改 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,因此我必须创建一个报价。从那里开始,我有义务通过信令通道将它发送给另一个对等方似乎是合乎逻辑的,我有义务调用 setRemoteDescription 然后需要回答。

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

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

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

非常感谢您的帮助。

编辑:天哪!似乎我所有的代码行都被折叠成一个大行了。非常抱歉,没想到。在小评论中告诉我下次如何解决这个问题。谢谢你。

4

1 回答 1

5

删除这个:

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

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

当您使用它时,也将其删除:

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

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

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

但是对于数据通道来说是不必要的。如果它仍然不起作用,请告诉我。

我也强烈推荐adapter.js,官方的WebRTC polyfill,它可以让你使用带有promise 等的最新规范,像这样。它不仅仅是一个图书馆,它的目标是最终消失。

于 2016-05-20T02:05:54.677 回答