问题描述
我正在用这个拔头发.大约一个月前,我能够使用 SignalR 的好人提供的一些示例代码,制作一个概念验证 WebRTC 演示.该演示位于这里,其来源是这里,它会做它应该做的事情.
I'm pulling my hair out with this one. A month or so ago, I was able to put together a proof-of-concept WebRTC demo, using some sample code from the good folks at SignalR. The demo is located here, the source for it is here, and it does what it's supposed to do.
但是当我把那个代码移到我们的实际应用程序中时,我一直无法让它工作.当然,代码必须进行重大更改 - 不同的后端,不同的框架和支持代码,支持多个同时连接,诸如此类 - 但核心逻辑非常相似.但我无法让它工作.
But when I took that code and moved it into our actual application, I haven't been able to get it to work. Of course the code had to be changed significantly - different backends, different set of frameworks and supporting code, supporting multiple simultaneous connections, that sort of thing - but the core logic is very similar. But I can't get it to work.
我在这里整理了一个示例应用程序来演示问题:
I've put together a sample app here that demonstrates the problem:
https://bitbucket.org/smithkl42/signalr.webrtc
核心 WebRTC 逻辑都在这个 TypeScript 文件中:
The core WebRTC logic is all in this TypeScript file:
它有几百行长,所以我不想在这里张贴它,但你可以通过点击上面的链接看到它.
It's several hundred lines long, so I won't bother posting it here, but you can see it by clicking on the link above.
当它运行时,它会产生如下输出:
When it runs, it produces output like this:
12:17:58.531 WebRTCController.call():准备完成后调用 7d9e0d39-5047-4afe-86e5-e6e01b9f5955
12:17:58.533 WebRTCController.prepareForCall():准备呼叫:localSessionId='39d2df53-6854-415a-8748-b5230eda2eb1';remoteSessionId='7d9e0d39-5047-4afe-86e5-e6e01b9f5955'
12:17:58.533 WebRTCController.prepareForCall(): Preparing for call: localSessionId='39d2df53-6854-415a-8748-b5230eda2eb1'; remoteSessionId='7d9e0d39-5047-4afe-86e5-e6e01b9f5955'
12:18:0.139 Object.():用户已授予媒体设备访问权限,因此继续准备呼叫
12:18:0.139 Object.(): The user has granted media device access, so proceeding to prepare for call
12:18:0.141 Connection.createPeerConnection():创建对等连接;使用 stunServer stun:stun1.l.google.com:19302
12:18:0.141 Connection.createPeerConnection(): Creating peer connection; using stunServer stun:stun1.l.google.com:19302
12:18:0.144 ():准备工作完成.创建和发送 JSEP 报价.Util.js:21
12:18:0.144 (): Preparations finished. Creating and sending JSEP offer. Util.js:21
12:18:0.272 Connection.handleIceCandidate():STUN 服务器已找到 ICE 候选对象 (event.type='icecandidate').
12:18:0.272 Connection.handleIceCandidate(): STUN server has found an ICE candidate (event.type='icecandidate').
12:18:0.282 Connection.handleIceCandidate():STUN 服务器已找到 ICE 候选对象 (event.type='icecandidate').
12:18:0.282 Connection.handleIceCandidate(): STUN server has found an ICE candidate (event.type='icecandidate').
(更像)
12:18:0.655 WebRTCController.handleJsepAnswer():处理来自 7d9e0d39-5047-4afe-86e5-e6e01b9f5955 的 JsepAnswer
12:18:0.655 WebRTCController.handleJsepAnswer(): Handling JsepAnswer from 7d9e0d39-5047-4afe-86e5-e6e01b9f5955
12:18:0.694 Object.(): 发送 ICE 候选到远程机器:{"sdpMLineIndex":0,"sdpMid":"audio","candidate":"a=candidate:2999745851 1 udp 2113937151 192.16.56.1 62978 典型主机代 0\r\n"}
12:18:0.694 Object.(): Sending ICE candidate to the remote machine: {"sdpMLineIndex":0,"sdpMid":"audio","candidate":"a=candidate:2999745851 1 udp 2113937151 192.168.56.1 62978 typ host generation 0\r\n"}
12:18:0.706 Object.(): 将 ICE 候选发送到远程机器:{"sdpMLineIndex":0,"sdpMid":"audio","candidate":"a=candidate:2999745851 2 udp 2113937151 192.16.56.1 62978 典型主机代 0\r\n"}
12:18:0.706 Object.(): Sending ICE candidate to the remote machine: {"sdpMLineIndex":0,"sdpMid":"audio","candidate":"a=candidate:2999745851 2 udp 2113937151 192.168.56.1 62978 typ host generation 0\r\n"}
(更像)
但是它永远不会连接,即来自另一端的视频永远不会开始播放.在信令层,我可以通过日志和单步执行代码判断第一个浏览器正在发送 JSEP 报价;第二个浏览器接收它,存储它并发回一个适当的 JSEP 答案;并且第一台机器正在存储该答案.然后每个 peerConnection 找到 ICE 候选并将它们发送到远程机器;并且每个 peerConnection 正在接收并显然正在尝试那些 ICE 候选者;并且 peerConnections 甚至引发 onaddstream
事件.但视频从未开始播放.
But then it never connects, i.e., the video from the other side never starts playing. At the signaling layer, I can tell by the logs and by stepping through the code that the first browser is sending a JSEP offer; the second browser is receiving it, storing it and sending back an appropriate JSEP answer; and the first machine is storing that answer. Each peerConnection is then finding the ICE candidates and sending them to the remote machine; and each peerConnection is receiving and apparently trying those ICE candidates; and the peerConnections are even raising the onaddstream
event. But the video never starts playing.
peerConnection 对象的状态一直是这样的:
The state of the peerConnection object all the way through looks like this:
(iceGatheringState=new; iceState=starting; readyState=active)
令人沮丧的是,每隔一段时间,也许 20 次中就有一次,它确实起作用,即,两个视频都会出现.所以我没有做错一切.这听起来像是某种时间问题 - 但我无法弄清楚它是什么.据我所知,WebRTC 对象(特别是 RTCPeerConnection)中没有太多内容可以告诉您出了什么问题.
The frustrating bit is that every so often, maybe one time out of 20, it does work, i.e., both videos show up. So I'm not doing everything wrong. It sounds like a timing issue of some sort - but I can't figure out what it is. And so far as I can tell, there's not much in the WebRTC objects (specifically RTCPeerConnection) to tell you what's going wrong.
我不想让其他人帮我排除故障,但是……好吧,我的选择已经不多了.有没有其他人看到我做的明显错误的事情?
I hate to ask anybody else to do my troubleshooting for me, but... well, I'm running out of options. Does anybody else see anything I'm doing obviously wrong?
更新 2012-12-19:我正在取得一些进展.我意识到我正在同步调用 peerConnection.setLocalDescription()
,即没有指定回调.所以现在我有一些看起来像这样的代码行:
Update 2012-12-19: I'm making some progress. I realized I was calling peerConnection.setLocalDescription()
synchronously, i.e., without specifying callbacks. So now I've got some lines of code that look like this:
// Answer the call by sending a JsepAnswer message.
connection.peerConnection.createAnswer(
answer => {
connection.peerConnection.setLocalDescription(answer, () => {
var signalState: mData.SignalState = {
FromSessionId: connection.localSessionId,
ToSessionId: connection.remoteSessionId,
Message: JSON.stringify(answer)
};
me.roomHub.server.jsepAnswer(signalState);
mUtil.log("Sent JSEP answer: " + signalState.Message);
connection.readyForIceCandidates.resolve();
},
error => {
mUtil.error("Error setting local description from created answer: " + error + "; answer=" + JSON.stringify(answer));
});
},
error => {
mUtil.error("Error creating answer: " + error);
}, me.mediaConstraints);
并且 setLocalDescription()
错误回调显示此错误:
And the setLocalDescription()
error callback is showing this error:
16:14:42.439 WebRTCController.handleJsepOffer(): Error setting local description from created answer: SetLocalDescription failed.;answer={"sdp":"v=0\r\no=- 439659381 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 音频视频\r\na=msid-语义:WMS u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLhjf\r\nm=audio 1 RTP/SAVPF 103 104 111 0 8 107 106 105 = 13 126\r.0cp0rt.n.0.0\r\na=ice-ufrag:vOKflTJ56gV0R9i0\r\na=ice-pwd:9nuXPMDvQ2mZATFCQyEzPRQz\r\na=sendrecv\r\na=mid:audio\r\na=rtcp-mux\r\na=crypto:1 AES_CM_128_HMAC_SHA1_80 内联:m9q9pmLgLuFnfFC09KXKW5p8TjsKk+VdqX0OWv77\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32100tp\usrt/32100rtp:PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:107 CN/48000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:126 phone-event/8000\r\na=ssrc:548068416 cname:IXg8QRisWrd7+7f8\r\na=ssrc:548068416 msid:u9fhVrZj0lsh6bjw16=ssrc:548068416 mslabel:u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLhjf\r\na=ssrc:548068416 标签:u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLh01AV1R\r\na=ssrc:548068416117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=ice-ufrag:vOKflTJ56gV0R9i0\r\na=ice-pwd:9nuXPMDvQ2mZATFCQyEzPRQsendrer\na\r\na=mid:video\r\na=rtcp-mux\r\na=crypto:1 AES_CM_128_HMAC_SHA1_80 内联:m9q9pmLgLuFnfFC09KXKW5p8TjsKk+VdqX0OWv77\r\na=rtpmap:100101VP8/90000 \ r \ NA = rtpmap:117 ulpfec/90000 \ r \ NA = SSRC:1460425980 CNAME:IXg8QRisWrd7 + 7f8 \ r \ NA = SSRC:1460425980 MSID:u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLhjf V0 \ r \ NA = SSRC:1460425980 mslabel:u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLhjf \r\na=ssrc:1460425980 标签:u9fhVrWeLLweqb5ubLkw61Ijsh6BM6vZLhjfv0\r\n","type":"answer"}
现在我只需要弄清楚为什么直接来自 createAnswer()
方法的特定 SDP 会失败.
Now I just need to figure out why that particular SDP - which comes straight from the createAnswer()
method - is failing.
2012-12-20 更新:我在此处创建了该问题的在线演示:http://srdemo.alanta.com/.我还打开了 Chrome 调试日志记录,结果我看到了一堆看起来像这样的错误:
Update 2012-12-20: I've created an online demonstration of the problem here: http://srdemo.alanta.com/. I've also turned on Chrome debug logging, with the result that I see a bunch of errors that look like this:
[6584:7308:1220/091356:ERROR:rtc_peer_connection_handler.cc(84)] 本机会话描述为空.[6584:7308:1220/091356:ERROR:rtc_peer_connection_handler.cc(84)] 本机会话描述为空.[6584:7308:1220/091356:ERROR:rtc_peer_connection_handler.cc(84)] 本机会话描述为空.[6584:7308:1220/091356:ERROR:rtc_peer_connection_handler.cc(84)] 本机会话描述为空.[6584:7308:1220/091356:ERROR:rtc_peer_connection_handler.cc(84)] 本机会话描述为空.
不确定他们与我的问题有什么关系,但我会继续调查.
Not sure what relationship they have to my problem, but I'm continuing to look into it.
*Edit 2012-12-20:我已经设法(我认为)缩小了问题的范围.请参阅此问题了解更多详细信息.
*Edit 2012-12-20: I've managed (I think) to narrow the problem down. See this question for more precise details.
推荐答案
想通了.原来 SignalR 1.0 RC1 中有一个错误,它将字符串中的任何+"更改为空格.所以 SDP 中的行看起来像这样:
Figured it out. Turns out that SignalR 1.0 RC1 has a bug in it that changes any "+" in a string into a space. So lines in the SDP that looked like this:
a=ice-pwd:qZFVvgfnSso1b8UV1SUDd2+z
被改成这样:
a=ice-pwd:qZFVvgfnSso1b8UV1SUDd2 z
但是因为并不是每个 SDP 在关键线上都有一个+",所以有时它会起作用.一切都解释了.
But because not every SDP had a "+" in it on a critical line, sometimes it would work. Everything explained.
该错误已报告给在 SignalR 上工作的好人(请参阅 https://github.com/SignalR/SignalR/issues/1194),同时,围绕相关字符串的简单 encodeURIComponent()
和 decodeURIComponent()
修复了它.
The bug has been reported to the good folks working on SignalR (see https://github.com/SignalR/SignalR/issues/1194), and in the meantime, a simple encodeURIComponent()
and decodeURIComponent()
around the strings in question fixed it.
这篇关于WebRTC 代码故障排除的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!