WebRTC onicecandidate: Am getting ICE candidates with sdpMid=audio only but not for video

(It sounds from comments that you're caching ICE candidates. Don't do that. I also suspect timing issues may be behind loss of some candidates.)

The whole point of Trickle ICE is to trickle candidates, that is, send candidates as soon as they become available.

With WebRTC, your app is responsible for signaling between peers, which is time-sensitive. So:

  1. Send pc.localDescription no later than in the setLocalDescription success callback.
  2. Expect pc.onicecandidate to start firing immediately after that callback. Send them.

This is true on both sides (for offer and answer). What you want to see on the wire is:

offer, candidate, candidate, candidate

and the other way:

answer, candidate, candidate, candidate

What not to do:

  • Don't cache ICE candidates.
  • Don't wait until you have an answer back, that just wastes time.
  • Don't delay calling setRemoteDescription when an offer comes in on the receiving end for any reason, or it wont be ready to receive candidates.

Update 2:

Your sdp says a=recvonly and not a=sendrecv, which means the receiver is resigned to receive only, without send anything in return. One of two things can cause this:

  1. Caller set createOffer options like offerToReceiveVideo:false and/or offerToReceiveAudio:false.
  2. Receiver did not called pc.addStream in time for (before) pc.setLocalDescription.

The second can happen if there's a race between getUserMedia and receiving an offer.

Update 3:

If all else fails, compare to working code. I've shared a cross-tab demo before in other answers, but it only sent video, didn't receive any.

Here's a modified version of that demo that only receives video from the remote camera instead. As usual, open it in two tabs in the same browser.

Note that in Firefox, after you hit Call, you have to physically focus the other tab before it allows access to the camera.


What you are dealing with is called bundeling. Offerer and answerer agree to bundle all ICE transports into a single ICE transport. Therefore you only get a single ICE candidate for the first m-section.

The offerer is still giving you all these ICE candidates for the video m-section, because it does not know if the answerer is going to agree to use bundle.

As AlexD points out in his answer you can influence this behavior on the offerer side via the "bundlePolicy". "maxBundle" as policy will for example result in the offerer assuming the answerer is going to understand bundleling and therefore only create ICE candidates for a single transport.

But as long as the offerer is offering bundle the answerer is going to use it if it supports it.


I was able to solve this by setting:

rtcConfiguration.bundlePolicy = "max-compat"

See: http://w3c.github.io/webrtc-pc/#dom-rtcbundlepolicy-max-compat