WebRTC:无法使用 DataChannel 成功完成信令过程
WebRTC: Unable to successfully complete signalling process using DataChannel
我一直在建立 WebRTC 会话时遇到问题,我正在尝试尽可能简化问题。所以我写了一个简单的复制和粘贴示例,您只需将 offer/answer 粘贴到网络表单中,然后单击提交。
HTML+JS,全部在一个文件中,可以在这里找到:http://pastebin.com/Ktmb3mVf
我在本地网络上,因此我删除了 ICE 服务器初始化过程,使这个示例尽可能简单。
以下是我在示例中执行的步骤:
第 1 页
第 1 页(加载页面),输入频道名称(例如 test
)并单击 create
。
一个新的Host
对象被创建,new PeerConnection()
和createDataChannel
被调用。
createOffer
被调用,结果 offerSDP
被粘贴到 offer
文本区域。
第 2 页
从第 1 页复制 offerSDP
并粘贴到第 2 页的报价文本区域,单击 join
。
创建了新的 Guest
对象,PeerConnection
并设置了 ondatachannel
处理程序。
setRemoteDescription
为 Guest
对象调用,带有 offerSDP
数据。
调用 createAnswer
并将结果粘贴到 answer
文本框。
第 1 页
answerSDP
从第2页复制粘贴到第1页的answer
文本区域,点击submit answer
Host.setRemoteDescription
使用 answerSDP
数据调用。这将创建一个 SessionDescription
,然后使用结果数据调用 peer.setRemoteDescription
。
这些是示例中执行的步骤,但我似乎遗漏了一些关键的东西。使用 answerSDP
设置报价者的 remoteDescription 后,我尝试在 dataChannel
:
上发送测试消息
Chrome 40
"-- complete"
> host.dataChannel.send('hello world');
VM1387:2 Uncaught DOMException: Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'
火狐 35
"-- complete"
ICE failed, see about:webrtc for more details
> host.dataChannel.send('hello world');
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
我也有一个更复杂的演示操作,有一个 WebSocket 信令服务器,并列出了 ICE 候选人,但遇到了同样的错误。所以我希望这种简化可以帮助追踪问题。
同样,单文件代码link:http://pastebin.com/Ktmb3mVf
要使webRTC 客户端能够相互连接,您需要ICE。虽然您不需要进行此类测试的 STUN 和 TURN 是其中的一部分,但即使没有这些助手,您仍然需要使用 ICE 来告诉另一端 IP/port/protocol 连接到哪个。
有两种方法可以做到这一点:Google 的 "trickle ice",其中 SDP (answer/offer) 在没有任何 ICE 候选的情况下传递。然后将它们通过单独的信令层传输并在发现时添加。这加快了连接过程,因为 ICE 需要时间,并且可能不需要一些迟到的 ICE 候选对象。
经典的方法是等到所有的 ICE 候选者都被收集起来,然后生成已经包含这些的 SDP。
我已经修改了你的最新版本:http://pastebin.com/g2YVvrRd
您还需要等待 datachannel/connection 可用后才能使用它,因此我已将消息的发送移至频道的 onopen 事件。
对原代码的重大改动:
接口回调已从 Host.prototype.createOffer 和 Guest.prototype.createAnswer 中删除,我们将提供的回调函数附加到各自的对象以供以后使用。
self.cb = cb;
主机和来宾都为 PeerConnection 添加了一个 ICE 处理程序:
var self = this;
this.peer.onicecandidate = function (event) {
// This event is called for every discovered ICE candidate.
// If this was trickle ICE, you'd pass them on here.
// An event without an actual candidate signals the end of the
// ICE collection process, which is what we need for classic ICE.
if (!event.candidate) {
// We fetch the up to date description from the PeerConnection
// It now contains lines with the available ICE candidates
self.offer = self.peer.localDescription;
// Now we move on to the deferred callback function
self.cb(self.offer);
}
}
为客人self.offer变为self.answer
接口处理程序 $("#submitAnswer").click() 不再发送消息,而是在 setChannelEvents() 中定义的 onopen 事件中数据通道准备就绪时发送。
channel.onopen = function () {
console.log('** channel.onopen');
channel.send('hello world!');
};
我一直在建立 WebRTC 会话时遇到问题,我正在尝试尽可能简化问题。所以我写了一个简单的复制和粘贴示例,您只需将 offer/answer 粘贴到网络表单中,然后单击提交。
HTML+JS,全部在一个文件中,可以在这里找到:http://pastebin.com/Ktmb3mVf
我在本地网络上,因此我删除了 ICE 服务器初始化过程,使这个示例尽可能简单。
以下是我在示例中执行的步骤:
第 1 页
第 1 页(加载页面),输入频道名称(例如
test
)并单击create
。一个新的
Host
对象被创建,new PeerConnection()
和createDataChannel
被调用。createOffer
被调用,结果offerSDP
被粘贴到offer
文本区域。
第 2 页
从第 1 页复制
offerSDP
并粘贴到第 2 页的报价文本区域,单击join
。创建了新的
Guest
对象,PeerConnection
并设置了ondatachannel
处理程序。setRemoteDescription
为Guest
对象调用,带有offerSDP
数据。
调用 createAnswer
并将结果粘贴到answer
文本框。
第 1 页
answerSDP
从第2页复制粘贴到第1页的answer
文本区域,点击submit answer
Host.setRemoteDescription
使用answerSDP
数据调用。这将创建一个SessionDescription
,然后使用结果数据调用peer.setRemoteDescription
。
这些是示例中执行的步骤,但我似乎遗漏了一些关键的东西。使用 answerSDP
设置报价者的 remoteDescription 后,我尝试在 dataChannel
:
Chrome 40
"-- complete"
> host.dataChannel.send('hello world');
VM1387:2 Uncaught DOMException: Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'
火狐 35
"-- complete"
ICE failed, see about:webrtc for more details
> host.dataChannel.send('hello world');
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
我也有一个更复杂的演示操作,有一个 WebSocket 信令服务器,并列出了 ICE 候选人,但遇到了同样的错误。所以我希望这种简化可以帮助追踪问题。
同样,单文件代码link:http://pastebin.com/Ktmb3mVf
要使webRTC 客户端能够相互连接,您需要ICE。虽然您不需要进行此类测试的 STUN 和 TURN 是其中的一部分,但即使没有这些助手,您仍然需要使用 ICE 来告诉另一端 IP/port/protocol 连接到哪个。
有两种方法可以做到这一点:Google 的 "trickle ice",其中 SDP (answer/offer) 在没有任何 ICE 候选的情况下传递。然后将它们通过单独的信令层传输并在发现时添加。这加快了连接过程,因为 ICE 需要时间,并且可能不需要一些迟到的 ICE 候选对象。
经典的方法是等到所有的 ICE 候选者都被收集起来,然后生成已经包含这些的 SDP。
我已经修改了你的最新版本:http://pastebin.com/g2YVvrRd
您还需要等待 datachannel/connection 可用后才能使用它,因此我已将消息的发送移至频道的 onopen 事件。
对原代码的重大改动:
接口回调已从 Host.prototype.createOffer 和 Guest.prototype.createAnswer 中删除,我们将提供的回调函数附加到各自的对象以供以后使用。
self.cb = cb;
主机和来宾都为 PeerConnection 添加了一个 ICE 处理程序:
var self = this;
this.peer.onicecandidate = function (event) {
// This event is called for every discovered ICE candidate.
// If this was trickle ICE, you'd pass them on here.
// An event without an actual candidate signals the end of the
// ICE collection process, which is what we need for classic ICE.
if (!event.candidate) {
// We fetch the up to date description from the PeerConnection
// It now contains lines with the available ICE candidates
self.offer = self.peer.localDescription;
// Now we move on to the deferred callback function
self.cb(self.offer);
}
}
为客人self.offer变为self.answer
接口处理程序 $("#submitAnswer").click() 不再发送消息,而是在 setChannelEvents() 中定义的 onopen 事件中数据通道准备就绪时发送。
channel.onopen = function () {
console.log('** channel.onopen');
channel.send('hello world!');
};