Skip to content

Commit dd37283

Browse files
authored
Speed up network switch recovery (#1745)
1 parent 1546b49 commit dd37283

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

.changeset/late-squids-refuse.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"livekit-client": patch
3+
---
4+
5+
Speed up network switch recovery

src/room/RTCEngine.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import {
8484
sleep,
8585
supportsAddTrack,
8686
supportsTransceiver,
87+
toHttpUrl,
8788
} from './utils';
8889

8990
const lossyDataChannel = '_lossy';
@@ -204,6 +205,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
204205

205206
private midToTrackId: { [key: string]: string } = {};
206207

208+
/** used to indicate whether the browser is currently waiting to reconnect */
209+
private isWaitingForNetworkReconnect: boolean = false;
210+
207211
constructor(private options: InternalRoomOptions) {
208212
super();
209213
this.log = getLogger(options.loggerName ?? LoggerNames.Engine);
@@ -1619,23 +1623,62 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
16191623
this.reconnectAttempts = 0;
16201624
}
16211625

1622-
private handleBrowserOnLine = () => {
1623-
// in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
1624-
if (this.client.currentState === SignalConnectionState.RECONNECTING) {
1626+
private handleBrowserOnLine = async () => {
1627+
if (!this.url) {
1628+
return;
1629+
}
1630+
const hasNetworkConnection = await fetch(toHttpUrl(this.url!), { method: 'HEAD' })
1631+
.then((resp) => resp.ok)
1632+
.catch(() => false);
1633+
1634+
if (!hasNetworkConnection) {
1635+
return;
1636+
}
1637+
this.log.info('detected network reconnected');
1638+
1639+
if (
1640+
// in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
1641+
this.client.currentState === SignalConnectionState.RECONNECTING ||
1642+
// also if the browser went offline before and the engine still thinks it's in a connected state, treat it as a network interruption that we haven't noticed yet
1643+
(this.isWaitingForNetworkReconnect &&
1644+
this.client.currentState === SignalConnectionState.CONNECTED)
1645+
) {
16251646
this.clearReconnectTimeout();
16261647
this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
1648+
this.isWaitingForNetworkReconnect = false;
1649+
}
1650+
};
1651+
1652+
private handleBrowserOffline = async () => {
1653+
if (!this.url) {
1654+
return;
1655+
}
1656+
try {
1657+
await Promise.race([
1658+
fetch(toHttpUrl(this.url), { method: 'HEAD' }),
1659+
// if there's no internet connection the fetch rejects immediately, so we only use a short timeout here
1660+
sleep(4_000).then(() => Promise.reject()),
1661+
]);
1662+
} catch (e) {
1663+
// only set if the browser still thinks it's offline after the request failed
1664+
if (window.navigator.onLine === false) {
1665+
this.log.info('detected network interruption');
1666+
this.isWaitingForNetworkReconnect = true;
1667+
}
16271668
}
16281669
};
16291670

16301671
private registerOnLineListener() {
16311672
if (isWeb()) {
16321673
window.addEventListener('online', this.handleBrowserOnLine);
1674+
window.addEventListener('offline', this.handleBrowserOffline);
16331675
}
16341676
}
16351677

16361678
private deregisterOnLineListener() {
16371679
if (isWeb()) {
16381680
window.removeEventListener('online', this.handleBrowserOnLine);
1681+
window.removeEventListener('offline', this.handleBrowserOffline);
16391682
}
16401683
}
16411684

0 commit comments

Comments
 (0)