Transport `connectionstatechange` event mismatch
ezioda004 opened this issue · comments
Bug Report
Hi,
From the documentation, transport.on(“connectionstatechange”, fn(connectionState)
, emits RTCPeerConnectionState
. However, mediasoup-client internally listens and emits RTCIceConnectionState
.
Your environment
- Operating system: MacOS
- Browser version: Chrome 108
- npm version: 7.24
- mediasoup version: 3.11.3
- mediasoup-client version: 3.6.57
Issue description
The issue with this comes during disconnection. Suppose, you turn off the internet, connectionstatechange
goes from connected
-> disconnect
and there's still a chance that the connection can go connected
again (there's a 10-second window for the next retry). If the retry is unsuccessful, RTCPeerConnectionState
emits failed
event whereas RTCIceConnectionState
is still in disconnect
state so transport never emits the failed
event.
For ice restarts, I'm waiting for this failed
state, rather than disconnect
state because of the auto retry.
Now, since mediasoup-client only emits RTCIceConnectionState
on transport, I have to internally listen to this private state via transport.handler._pc.connectionState
to find out if the actual connection state and then do an ice restart.
This is mediasoup-client code related to this:
// Listens to RTCIceConnectionState and not RTCPeerConnectionState
this._pc.addEventListener('iceconnectionstatechange', () => {
switch (this._pc.iceConnectionState) {
case 'checking':
this.emit('@connectionstatechange', 'connecting');
break;
case 'connected':
case 'completed':
this.emit('@connectionstatechange', 'connected');
break;
case 'failed':
this.emit('@connectionstatechange', 'failed');
break;
case 'disconnected':
this.emit('@connectionstatechange', 'disconnected');
break;
case 'closed':
this.emit('@connectionstatechange', 'closed');
break;
}
});
Wanted to know if this is intentional. If so, then the documentation should reflect the actually used state. Having the transport emit the RTCPeerConnectionState
would be useful for the above case mentioned instead of listening on the RTCPeerConnection
object itself.
At the time this code was written majority of browsers didn't implement connectionState but just iceConnectionState. Wondering if we should listen for and expose both events or just connectionState.
I think exposing both events makes sense, the developer can decide to use them accordingly. However, firefox still doesn't support connectionState.
Also, I'd love to contribute :D (if you decide to add this event).
That's exactly the problem: how to expose an event in a unified way if it don't exist in some browsers , and we cannot break backwards compatibility. Do you know which exact browsers and versions implement connectionState?
Here is the compatibility table:
For firefox, could we emit iceConnectionState
for connectionState
?
Basically for other browsers:
iceConnectionState
is emitted when iceconnectionstatechange
event occurs, mediasoup emits iceconnectionstatechange
connectionState
is emitted when connectionstatechange
event occurs, mediasoup emits connectionstatechange
Firefox:
iceConnectionState
is emitted when iceconnectionstatechange
event occurs, mediasoup emits iceconnectionstatechange
and connectionstatechange
.
The downside of this approach is mediasoup will be emitting two different events in firefox which are the same.
Also fixing events in other browsers will break backwards compatibility in applications? Right now mediasoup emits connectionstatechange
on iceconnectionstatechange
, but with the fix it'll emit connectionstatechange
on connectionstatechange
.
I'll think about this in next days. Thanks a lot.
Suppose, you turn off the internet,
connectionstatechange
goes fromconnected
->disconnect
and there's still a chance that the connection can goconnected
again (there's a 10-second window for the next retry). If the retry is unsuccessful,RTCPeerConnectionState
emitsfailed
event whereasRTCIceConnectionState
is still indisconnect
state so transport never emits thefailed
event.
SoI don't understand this. According to the spec, if the retry fails then both RTCIceConnectionState
and RTCPeerConnectionState
become "failed":
- https://www.w3.org/TR/webrtc/#rtciceconnectionstate-enum
- https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum
Description of failed
state in RTCIceConnectionState
:
The previous state doesn't apply and any RTCIceTransports are in the "failed" state.
Description of failed
state in RTCPeerConnectionState
:
The previous state doesn't apply and any RTCIceTransports are in the "failed" state or any RTCDtlsTransports are in the "failed" state.
This is: in case the ICE connection retry fails, the peerconnection emits iceconnectionstatechange
and its RTCIceConnectionState
becomes failed
, so Transport
in mediasoup-client emits on('connectionState, "failed")
and you can do ICE restart.
Am I missing something?
SoI don't understand this. According to the spec, if the retry fails then both
RTCIceConnectionState
andRTCPeerConnectionState
become "failed":
- https://www.w3.org/TR/webrtc/#rtciceconnectionstate-enum
- https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum
Description of
failed
state inRTCIceConnectionState
:The previous state doesn't apply and any RTCIceTransports are in the "failed" state.
Description of
failed
state inRTCPeerConnectionState
:The previous state doesn't apply and any RTCIceTransports are in the "failed" state or any RTCDtlsTransports are in the "failed" state.
This is: in case the ICE connection retry fails, the peerconnection emits
iceconnectionstatechange
and itsRTCIceConnectionState
becomesfailed
, soTransport
in mediasoup-client emitson('connectionState, "failed")
and you can do ICE restart.Am I missing something?
Okay, so it seems like an issue with Chrome.
Steps to reproduce this bug is simple:
- Create a transport.
- Create a producer.
- Verify the transport is connected and producer is producing track.
- Disconnect from the internet.
- After disconnecting, 10 secs later
iceconnectionstate
andconnectionstate
changes todisconnected
. AttachingRTCPeerConnection
details below:
- After 20 secs post disconnecting from the internet and 10 secs after
disconnected
state,iceconnectionstate
remainsdisconnected
whereasconnectionstate
changes tofailed
.
This is the timeline of the states from chrome://webrtc-internals
I tried this in safari as well, there the iceconnectionstate
and connectionstate
matches with disconnect
and failed
respectively:
Further digging down, in Chrome, it seems RTCIceTransport
and RTCDtlsTransport
are not in failed
state when connectionstate
is in failed
state.
So seems like the issue is with Chrome either having a bug or not following the spec?
This is: in case the ICE connection retry fails, the peerconnection emits iceconnectionstatechange and its RTCIceConnectionState becomes failed, so Transport in mediasoup-client emits on('connectionState, "failed") and you can do ICE restart
I have to handle iceRestarts in both the cases now (disconnected
and failed
), because failed
event doesnt get triggered in Chrome but does in Safari. Adding a snippet for someone who stumbles upon in the future:
this.producerTransport.on('connectionstatechange', (connectionState) => {
console.log('createSendTransport::connectionStateChange', connectionState, this.producerTransport);
switch (connectionState) {
case 'connected':
console.log('createSendTransport::connectionstatechange', 'connected');
break;
case 'disconnected':
setTimeout(() => {
if ((this.producerTransport.handler as any)._pc.connectionState as any === 'failed') {
console.log('connectionStateChange::failed', (this.producerTransport.handler as any)._pc);
this.transportRestartIce(this.producerTransport);
}
}, 15000);
break;
case 'failed':
this.transportRestartIce(this.producerTransport);
break;
}
});
Side note, found a discourse forum post with the same issue, they are also using chrome.
If this is a bug in Chrome (so in libwebrtc) please report the issue in their trackers.
Other than that, I'd really like to know if we should make modern handlers (i.e. Chrome74 which implements pc.peerConnectionState
) listen for peerconnectionstatechange
instead of iceconnectionstatechange
. AFAIU from above rationale that would basically fix the issue without introducing any regression and without breaking the API surface (no breaking changes), am I right?
Am I correct to assume you meant handlers that support peerconnectionstatechange
? All browsers apart from Firefox?
It should fix the above issue ideally without any breaking change, but at the same time, I also wonder, if some application is relying on this broken behavior in Chrome, cant comment much on that since we don't know when this bug got introduced or if it has been there since the inception.
But as of right now, listening to peerconnectionstatechange
instead of iceconnectionstatechange
will fix the issue in Chrome.
Am I correct to assume you meant handlers that support peerconnectionstatechange?
Yes, sorry.
All browsers apart from Firefox?
Old versions of other browser do not support it either, but anyway.
Done in 3134822 and released in 3.6.67. Thanks.
Thanks, I'll test it out!