WebRTC video chat with Ajax instead of WebSocket: Possible? WebRTC video chat with Ajax instead of WebSocket: Possible? ajax ajax

WebRTC video chat with Ajax instead of WebSocket: Possible?


My first advice about your app. working/not working sporadically is to look at current online working implementations. There are plenty of WebRTC demos on the Internets.

AJAX

About AJAX: why wouldn't it work? I am currently working on just the same thing as you and it works fine everytime (I cannot disclose the source for the moment). Clients are constantly polling the server at regular intervals and they can send SDP descriptions/ICE candidates to a specific other client that way. The server acts as a simple bridge (this is the basis of signalling).

Be it WebSocket, AJAX or IPoAC, as long as you transfer to the other client everything he needs (and at the right moment, more on this later), it should work. I even made a demo where you manually copy/paste the SDP description and ICE candidates using text areas and click on buttons to move forward in the signalling process and that, of course, worked fine too.

ICE candidates

Now: yes, you need ICE candidates. Look at a sample SDP offer chunk I just generated using createOffer on Chromium 27:

v=0o=- 3866099361 2 IN IP4 127.0.0.1s=-t=0 0a=group:BUNDLE audio videoa=msid-semantic: WMS 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126c=IN IP4 0.0.0.0a=rtcp:1 IN IP4 0.0.0.0a=ice-ufrag:l8Qu31Vu4VG5YApSa=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhea=ice-options:google-icea=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-levela=sendrecva=mid:audioa=rtcp-muxa=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbla=rtpmap:111 opus/48000/2a=fmtp:111 minptime=10a=rtpmap:103 ISAC/16000a=rtpmap:104 ISAC/32000a=rtpmap:0 PCMU/8000a=rtpmap:8 PCMA/8000a=rtpmap:107 CN/48000a=rtpmap:106 CN/32000a=rtpmap:105 CN/16000a=rtpmap:13 CN/8000a=rtpmap:126 telephone-event/8000a=maxptime:60a=ssrc:1976175890 cname:/+lKYsttecoiyiu5a=ssrc:1976175890 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0a=ssrc:1976175890 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a=ssrc:1976175890 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0m=video 1 RTP/SAVPF 100 116 117c=IN IP4 0.0.0.0a=rtcp:1 IN IP4 0.0.0.0a=ice-ufrag:l8Qu31Vu4VG5YApSa=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhea=ice-options:google-icea=extmap:2 urn:ietf:params:rtp-hdrext:toffseta=sendrecva=mid:videoa=rtcp-muxa=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbla=rtpmap:100 VP8/90000a=rtcp-fb:100 ccm fira=rtcp-fb:100 nack a=rtpmap:116 red/90000a=rtpmap:117 ulpfec/90000a=ssrc:3452335690 cname:/+lKYsttecoiyiu5a=ssrc:3452335690 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0a=ssrc:3452335690 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a=ssrc:3452335690 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0

Do you see anything that could help another client connect to my machine? I don't think so. The purpose of all this ICE mechanism is to gather connection candidates (local ones like 192.168.1.15, "public" ones (the public IP assigned by your ISP) using STUN if you're behind any NAT that's not symmetrical, or TURN ones for symmetrical NATs).

Upon receiving those ICE candidates, the other peer will order them using some predefined metrics for prioritizing and then issue connection tests to find a good candidate. So please share them: the other peer needs them (and you need its too).

So here are some of my ICE candidates:

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

Now those are concrete (albeit only local, because I didn't configure the RTC peer connection with any STUN URL) ways for another peer to connect to my machine.

WebRTC signalling tips

There are a few interesting tips on the bottom of this page. I honnestly could not tell you right now why you should follow those or why they exist in the first place, but I did follow them and had no signalling problems. Here they are:

  1. For answerer: NEVER add ICE candidates until that peer generates/creates answer SDP
  2. Stop adding ICE candidates when remote stream starts flowing
  3. Don't create peer connection for answerer until you get offer SDP

You can manage all that on the client side by having some WebRTC finite-state machine. Please see the referenced page to understand what he means by remote stream starts flowing.

Try to share ICE candidates and add them at the opposite side and at least follow tips #1 and #3 and your application should work again.

Signalling communication

You asked how to transfer an ICE candidate from a peer to another one in the event of ICE candidates being important to share (which they are). To share things using AJAX, whatever those things are, you can use mailboxes. I believe you're already doing this by placing what is due for a client in the database.

Whenever a peer needs to send something to another one, send it ASAP (using AJAX). On the server side, place this "mail" in the destination client's mailbox. When a peer (periodically) polls the server for new mails, give it all its new mails.

When an SDP offer is created, ICE candidates are quickly generated. All those ICE candidates and the SDP description will probably get into the destination mailbox within a few milliseconds. There are good chances that the destination peer will poll everything neede at once. Even if an ICE candidate arrives late, its next polling will get it.


This doesn't really answer your questions, but for a signalling server you might want to take a look at Socket.io (on Node). I wrote a codelab explaining how to set this up:bitbucket.org/webrtc/codelab. It's very straightforward -- the complete example is here: the signalling server code is about 50 lines.

SimpleWebRTC runs the Signalmaster server, which uses Socket.io.

(Robert Nyman wrote a good blog post explaining this.)

Another option is to use XHR with the Google Channel API, as per the apprtc.appspot.com example: code here.


Any way of sending messages should work equivalently. It might help to keep in mind that are only about 4 basic messages you want to exchange:

  1. notification that a peer has joined (or left)
  2. an offer message ==> SetRemoteDescription with it, then make an answer & send it
  3. an answer message ===> SetRemoteDescription with it
  4. an ice candidate sent from the other peer ==> call addIceCandidate with it

The ice candidate thing is the strange part. Also, the candidate object contains funny characters, so when you send it, URI encode it. In Coffeescript, mine looks something like:

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

eepp's answer is good, but it contains some advice I don't think is correct. Specifically, these 3 tips I believe are all incorrect:

  • For answerer: NEVER add ICE candidates until that peer generates/creates answer SDP
  • Stop adding ICE candidates when remote stream starts flowing
  • Don't create peer connection for answerer until you get offer SDP

Here is a sequence of events I have working today (Feb 2014) in Chrome. This is for a simplified case where peer 1 will stream video to peer 2.

  1. Set up some way for the peers to exchange messages. (The variance in how people accomplish this is what makes different WebRTC code samples so incommensurable, sadly. But mentally, and in your code organization, try to separate this logic out from the rest.)
  2. On each side, set up message handlers for the important signalling messages. You can set them up and leave them up. There are 4 core messages to handle & send:
    • other peer joined
    • an ice candidate sent from the other side ==> call addIceCandidate with it
    • an offer message ==> SetRemoteDescription with it, then make an answer & send it
    • an answer message ===> SetRemoteDescription with it
  3. On each side, create a new peerconnection object and attach event handlers to it for important events: onicecandidate, onremovestream, onaddstream, etc.
    • ice candidate ===> send it to other side
    • stream added ===> attach it to a video element so you can see it
  4. When both peers are present and all the handlers are in place, peer 1 gets a trigger message of some kind to start video capture (using the getUserMedia call)
  5. Once getUserMedia succeeds, we have a stream. Call addStream on the peer 1's peer connection object.
  6. Then -- and only then -- peer 1 makes an offer
  7. Due to the handlers we set up in step 2, peer 2 gets this and sends an answer
  8. Concurrently with this (and somewhat obscurely), the peer connection object starts producing ice candidates. They get sent back and forth between the two peers and handled (steps 2 & 3 above)
  9. Streaming starts by itself, opaquely, as a result of 2 conditions:
    • offer/answer exchange
    • ice candidates received, exchanged, and added

I haven't found a way to add video after step 9. When I want to change something, I go back to step 3.