Data channel mode (media: false) fails to establish a working connection. Both peers show "Connected" but the data channel doesn't open reliably, and sending messages fails with "Data channel may not be open."
Issue
Failed to execute 'setLocalDescription' on 'RTCPeerConnection':
Failed to set local offer sdp: The order of m-lines in subsequent
offer doesn't match order from previous offer/answer.
Reproduced in the SDK Explorer WebRTC demo (Data Channel tab) by joining the same room from two browser tabs. The error appears on one or both peers. Screen sharing in the Video Call tab works fine.
Environment: Chrome (latest), @agentuity/frontend current main, @agentuity/react useWebRTCCall hook with media: false + dataChannels: [{ label: 'chat', ordered: true }].
Additional Reproduction: Media + Data Channel
The same SDP error also triggers when combining media streams (audio/video) with a data channel in the same peer connection. Adding dataChannels: [{ label: 'media-state', ordered: true }] to a video call config (with media: true) reliably reproduces the m-line ordering error, since the data channel setup triggers renegotiation that collides with the media negotiation.
This confirms the issue is not specific to data-channel-only mode. It's a general lack of collision detection in onnegotiationneeded (~line 846) versus the guarded path in handleRemoteSDP (~line 924).
Potential Cause
Appears to be a race condition in the perfect negotiation implementation in WebRTCManager. Two code paths create offers independently:
- Explicit path (
createPeerSession, ~line 898): calls createOffer() then setLocalDescription(offer) when the offerer joins
- Implicit path (
onnegotiationneeded handler, ~line 846): calls setLocalDescription() without createOffer(), which auto-generates a new offer
When data channels are created, onnegotiationneeded fires and hits the implicit path while the explicit offer is still in flight. The auto-generated offer may have different m-line ordering than the first, which Chrome rejects per the WebRTC spec.
The onnegotiationneeded handler also appears to be missing the offer collision guard that handleRemoteSDP() already has (lines 923-927). So the offerer can generate a second offer while signalingState !== 'stable'.
Data-channel-only mode seems to hit this more reliably than media-enabled mode, likely because there are no audio/video tracks to stabilize the m-line structure.
Potential Fixes
- Add collision detection to the
onnegotiationneeded handler: check session.makingOffer || pc.signalingState !== 'stable' before proceeding, mirroring the logic already in handleRemoteSDP()?
- Use explicit
createOffer() in the onnegotiationneeded handler instead of bare setLocalDescription()?
- Make the answer path explicit too:
createAnswer() + setLocalDescription(answer) in handleRemoteSDP() instead of bare setLocalDescription()?
Data channel mode (
media: false) fails to establish a working connection. Both peers show "Connected" but the data channel doesn't open reliably, and sending messages fails with "Data channel may not be open."Issue
Reproduced in the SDK Explorer WebRTC demo (Data Channel tab) by joining the same room from two browser tabs. The error appears on one or both peers. Screen sharing in the Video Call tab works fine.
Environment: Chrome (latest),
@agentuity/frontendcurrent main,@agentuity/reactuseWebRTCCall hook withmedia: false+dataChannels: [{ label: 'chat', ordered: true }].Additional Reproduction: Media + Data Channel
The same SDP error also triggers when combining media streams (audio/video) with a data channel in the same peer connection. Adding
dataChannels: [{ label: 'media-state', ordered: true }]to a video call config (withmedia: true) reliably reproduces the m-line ordering error, since the data channel setup triggers renegotiation that collides with the media negotiation.This confirms the issue is not specific to data-channel-only mode. It's a general lack of collision detection in
onnegotiationneeded(~line 846) versus the guarded path inhandleRemoteSDP(~line 924).Potential Cause
Appears to be a race condition in the perfect negotiation implementation in
WebRTCManager. Two code paths create offers independently:createPeerSession, ~line 898): callscreateOffer()thensetLocalDescription(offer)when the offerer joinsonnegotiationneededhandler, ~line 846): callssetLocalDescription()withoutcreateOffer(), which auto-generates a new offerWhen data channels are created,
onnegotiationneededfires and hits the implicit path while the explicit offer is still in flight. The auto-generated offer may have different m-line ordering than the first, which Chrome rejects per the WebRTC spec.The
onnegotiationneededhandler also appears to be missing the offer collision guard thathandleRemoteSDP()already has (lines 923-927). So the offerer can generate a second offer whilesignalingState !== 'stable'.Data-channel-only mode seems to hit this more reliably than media-enabled mode, likely because there are no audio/video tracks to stabilize the m-line structure.
Potential Fixes
onnegotiationneededhandler: checksession.makingOffer || pc.signalingState !== 'stable'before proceeding, mirroring the logic already inhandleRemoteSDP()?createOffer()in theonnegotiationneededhandler instead of baresetLocalDescription()?createAnswer()+setLocalDescription(answer)inhandleRemoteSDP()instead of baresetLocalDescription()?