使用Ajax而不是WebSocket进行WebRTC视频聊天:可能吗?

时间:2013-06-18 05:39:14

标签: php ajax websocket webrtc

大约六个月前,我能够在PHP中成功编写自己的WebSocket服务器脚本。通过这些,我能够在我的本地主机上设置WebRTC视频聊天服务。我很高兴,直到我意识到为了部署它,我需要一个允许我访问套接字的Web服务器。

不幸的是,没有共享的Web托管允许套接字,并且所有提供套接字的Web服务器都很昂贵。虽然不是大规模的有效解决方案,但为了设置演示以向人们展示,我想将信令方法从WebSocket更改为Ajax,以便我可以展示我所做的WebRTC视频聊天服务。

为此,我过去几天一直试图编写代码,但是没有成功让WebRTC对等方捕获对方的视频。

目前,当一个客户端连接到脚本时,我正在使用Ajax向PHP脚本发送请求,该脚本检查数据库中是否还有其他活动用户。如果没有,则脚本会创建商品,并将商品放入数据库中。之后,客户端每秒轮询一个单独的PHP脚本,以检查连接到脚本的另一个客户端的答案。

之后,我再从另一个客户端连接到该脚本,该客户端查询相同的PHP脚本和DB,然后实现活动用户(第一个连接)已经发布了第二个客户端获取并设置的商品用于远程描述。然后第二个客户端创建一个答案,该答案放在DB中。

此时,第一个客户端(每秒轮询数据库)检测到答案存在,并将该答案设置为第一个客户端的远程描述。不幸的是,即使在成功完成所有这些操作之后,其他客户端的视频也不会弹出。

所以这里我很困惑并且有三个(多部分)问题:

1)我认为在两个客户端设置了他们的本地描述然后将该本地描述发送到另一个客户端和另一个接收描述的客户端集作为onaddstream事件应该触发的远程描述,从而允许我显示远程视频。但是,这种情况并没有发生。这在我使用WebSocket之前工作正常,但它与纯Ajax完全不兼容。有什么特别的东西我不见了吗? WebRTC规范在过去六个月中是否发生了根本变化?我曾尝试查看WebRTC规范,但我没有看到任何重大变化。

2)在因为没有使用Ajax工作而感到沮丧之后,我又回到了我的WebSocket版本并将其加载到我的本地主机上。自从上次使用它以来(我六个月前工作正常),我根本没有更改过代码,但是现在,当我尝试使用它时,有时它会起作用,有时它不起作用。有时我会收到与无法设置本地和/或远程描述相关的错误。怎么了?那些会导致这种情况发生变化的规格有变化吗?与此相关,即使我无法使用Ajax版本弹出远程视频,我也一直在向控制台发出很多东西,而且看起来像是Ajax版本,有时是本地和成功设置了两个客户端的远程描述,有时在尝试设置本地/远程描述时出于任何原因发生错误,即使我每次运行完全相同的脚本而没有任何更改。我正在使用最新版本的Chrome,我开始怀疑它是否存在错误或其他内容。

3)是否需要onicecandidate事件处理程序来建立连接?我的假设是,对等体可以通过简单的有效提供和回答建立连接,并且onicecandidate事件用于提供备用路由等,这可以导致更好的连接(但不是必需的)。我错了吗?如果需要onicecandidate信息,你如何建议我用Ajax作为信令方法处理这个?

我知道这是很多信息和很多问题,但任何人都可以提供的任何信息/洞察都会非常感激。在过去的几天里,我一直在敲我的桌子试图解决这个问题,没有任何意义。

3 个答案:

答案 0 :(得分:12)

我对你的应用的第一个建议。零星工作/不工作是看当前的在线工作实施。互联网上有很多WebRTC演示。

AJAX

关于AJAX:为什么它不起作用?我目前正在和你做同样的事情,而且每次都可以正常工作(我暂时无法透露消息来源)。客户端不断定期轮询服务器,他们可以通过这种方式将SDP描述/ ICE候选者发送给特定的其他客户端。服务器充当简单的桥(这是信令的基础)。

无论是WebSocket,AJAX还是IPoAC,只要您将所需的一切转移到其他客户端(并且在适当的时候,稍后会更多),它应该可以工作。我甚至做了一个演示,您可以使用文本区域手动复制/粘贴SDP描述和ICE候选,然后单击按钮在信号传输过程中向前移动,当然,也可以正常工作。

ICE候选人

现在:是的,你需要ICE候选人。查看我刚刚在Chromium 27上使用createOffer生成的示例 SDP商品块:

v=0
o=- 3866099361 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:l8Qu31Vu4VG5YApS
a=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhe
a=ice-options:google-ice
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbl
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:107 CN/48000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:1976175890 cname:/+lKYsttecoiyiu5
a=ssrc:1976175890 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0
a=ssrc:1976175890 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
a=ssrc:1976175890 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:l8Qu31Vu4VG5YApS
a=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhe
a=ice-options:google-ice
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=sendrecv
a=mid:video
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbl
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack 
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=ssrc:3452335690 cname:/+lKYsttecoiyiu5
a=ssrc:3452335690 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0
a=ssrc:3452335690 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
a=ssrc:3452335690 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0

您是否看到任何可以帮助其他客户端连接到我的计算机的内容?我不这么认为。所有这个ICE机制的目的是使用STUN收集连接候选者(如192.168.1.15,“公共”的本地IP(由您的ISP分配的公共IP),如果您在任何不对称的NAT后面,或者TURN)用于对称NAT的那些)。

收到那些ICE候选人后,另一个同行将使用一些预定义的指标对其进行排序以确定优先级,然后发出连接测试以找到一个好的候选人。所以请分享它们:另一个同伴需要它们(你也需要它)。

以下是我的一些ICE候选人:

a=candidate:303249700 1 udp 2113937151 192.168.50.238 43806 typ host generation 0
a=candidate:303249700 2 udp 2113937151 192.168.50.238 43806 typ host generation 0
a=candidate:1552991700 1 tcp 1509957375 192.168.50.238 35630 typ host generation 0

现在这些是具体的(尽管只是本地的,因为我没有使用任何STUN URL配置RTC对等连接)另一个对等方连接到我的机器。

WebRTC信令提示

this page底部有一些有趣的提示。我现在真的不能告诉你为什么你应该遵循这些或为什么它们首先存在,但我确实遵循它们并且没有信号问题。他们在这里:

  1. 对于回答者:永远不要添加ICE候选者,直到该对等体生成/创建回答SDP
  2. 当远程流开始流动时停止添加ICE候选
  3. 在获得商品SDP
  4. 之前,不要为回答者创建对等连接

    您可以通过使用一些WebRTC有限状态机来管理客户端上的所有内容。请参阅参考页面以了解远程流开始流动的含义

    尝试分享ICE候选人,并在对方添加,至少按照提示#1和#3,您的应用程序应该再次运作。

    信令通信

    如果ICE候选人很重要(他们是),你问如何将ICE候选人从同伴转移到另一个候选人。要使用AJAX分享内容,无论这些内容是什么,您都可以使用邮箱。我相信你已经通过在数据库中放置客户应有的东西来做到这一点。

    每当对等方需要向另一个用户发送内容时,请尽快发送(使用AJAX)。在服务器端,将此“邮件”放在目标客户端的邮箱中。当对等体(定期)轮询服务器以获取新邮件时,请将其全部新邮件提供给它。

    创建SDP优惠时,会快速生成ICE候选。所有那些ICE候选者和SDP描述可能会在几毫秒内进入目标邮箱。目标对等体很有可能立即轮询所有内容。即使ICE候选人迟到,它的下一轮投票也会得到它。

答案 1 :(得分:3)

这并不能真正回答您的问题,但对于信令服务器,您可能需要查看Socket.io(在节点上)。我写了一个codelab来解释如何设置它:bitbucket.org/webrtc/codelab。它非常简单 - 完整的示例是here:信令服务器代码大约有50行。

SimpleWebRTC运行Signalmaster服务器,该服务器使用Socket.io。

(罗伯特·尼曼写了一篇很好的blog post来解释这一点。)

另一个选择是根据apprtc.appspot.com示例使用XHR和Google Channel API:代码here

答案 2 :(得分:2)

任何发送消息的方式都应该等效。记住只需要交换的4条基本信息可能会有所帮助:

  1. 通知对方已加入(或离开)
  2. 优惠讯息==> SetRemoteDescription用它,然后回答&发送它
  3. 回答消息===> SetRemoteDescription随身携带
  4. 从其他同伴发送的冰候选人==>用它来呼叫addIceCandidate
  5. 冰候选物是奇怪的部分。此外,候选对象包含有趣的字符,因此当您发送它时,URI会对其进行编码。在Coffeescript中,我看起来像:

    peer_connection.onicecandidate = (e) ->
       send { 
              line_index: e.candidate.sdpMLineIndex
              candidate: encodeURIComponent(e.candidate.candidate) }
    

    eepp的答案很好,但它包含一些我认为不正确的建议。具体来说,我认为这三个提示都是不正确的:

      
        
    • 对于回答者:永远不要添加ICE候选人直到该同伴   生成/创建答案SDP
    •   
    • 远程时停止添加ICE候选者   流开始流动
    •   
    • 在回复之前不要为回答者创建对等连接   你得到提供SDP
    •   

    以下是我今天(2014年2月)在Chrome中发布的一系列事件。这是针对简化的情况,其中对等体1将视频流式传输到对等体2。

    1. 为对等方设置一些交换消息的方法。 (人们如何实现这一点的不同之处在于,不同的WebRTC代码示例如此难以理解,令人遗憾。但在精神上,在您的代码组织中,尝试将此逻辑与其他代码分开。)
    2. 在每一侧,为重要的信令消息设置消息处理程序。你可以设置它们并将它们保留下来。有4个核心消息要处理&发送:
      • 其他同行加入
      • 从另一方发送的冰候选人==>用它来呼叫addIceCandidate
      • 优惠讯息==> SetRemoteDescription用它,然后回答&发送它
      • 回答消息===> SetRemoteDescription随身携带
    3. 在每一侧,创建一个新的peerconnection对象,并为其执行重要事件的事件处理程序:onicecandidate,onremovestream,onaddstream等。
      • 冰候选人===>将其发送到另一方
      • stream added ===>将它附加到视频元素,以便您可以看到它
    4. 当两个对等方都存在并且所有处理程序都已到位时,对等方1会获得某种触发消息以启动视频捕获(使用getUserMedia调用)
    5. 一旦getUserMedia成功,我们就有了一个流。在对等方1的对等连接对象上调用addStream
    6. 然后 - 只有那时 - 同伴1提出要约
    7. 由于我们在步骤2中设置了处理程序,因此对等方2获取此处并发送答案
    8. 与此同时(并且有点模糊),对等连接对象开始产生冰候选者。它们在两个同伴之间来回传递并处理(上面的步骤2和3)
    9. 由于两个条件,流式传输本身是不透明的:
      • 提供/回答交流
      • 冰候选人收到,交换和添加
    10. 我还没有找到在第9步之后添加视频的方法。当我想要改变某些内容时,我会回到第3步。