I hope there is no flaw in the logic.
Step 1: caller creates offer
Step 2: caller sets localDescription
Ste
Your code is correct. This is a long-standing bug in Chrome with negotiationneeded
.
I've instrumented it in a fiddle (right-click and open in TWO adjacent windows, then click call in one).
In Firefox, it works. The offerer negotiates once because you add two tracks (video/audio) at once:
negotiating in stable
onmessage answer
and, on the answerer side, the tracks you add outside of 'stable'
state are added to the answer:
onmessage offer
adding audio track
adding video track
But in Chrome, it's broken, firing negotiationneeded
twice on the offerer, once per track added:
negotiating in stable
negotiating in stable
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
and firing negotiationneeded
twice on the answerer side, which isn't even in 'stable'
state:
onmessage offer
adding audio track
adding video track
negotiating in have-remote-offer
negotiating in have-remote-offer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
These extra events cause the havoc of reciprocal state errors seen on both ends here.
To be specific, Chrome violates two parts of the spec here:
"Queue a task" to fire this event. "queueing prevents negotiationneeded from firing prematurely, in the common situation where multiple modifications to connection are being made at once."
If connection's signaling state is not "stable"
, abort these steps [to fire the event].
Working around both Chrome bugs requires (using async
/await
for brevity):
let negotiating = false;
pc.onnegotiationneeded = async e => {
try {
if (negotiating || pc.signalingState != "stable") return;
negotiating = true;
/* Your async/await-using code goes here */
} finally {
negotiating = false;
}
}