Skip to content

Commit 100ac49

Browse files
authored
Allow for background timers to be overriden (#556)
* Allow for background timers to be overriden for platform specific implementations * Add changeset * Formatting * More timers converted and renamed to CriticalTimers
1 parent 0bc67ba commit 100ac49

File tree

7 files changed

+46
-21
lines changed

7 files changed

+46
-21
lines changed

.changeset/weak-dogs-act.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'livekit-client': patch
3+
---
4+
5+
Allow for internal background timer implementation to be overridden

src/api/SignalClient.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
UpdateTrackSettings,
3232
} from '../proto/livekit_rtc';
3333
import { ConnectionError, ConnectionErrorReason } from '../room/errors';
34+
import CriticalTimers from '../room/timers';
3435
import { getClientInfo, Mutex, sleep } from '../room/utils';
3536

3637
// internal options
@@ -583,7 +584,7 @@ export class SignalClient {
583584
log.warn('ping timeout duration not set');
584585
return;
585586
}
586-
this.pingTimeout = setTimeout(() => {
587+
this.pingTimeout = CriticalTimers.setTimeout(() => {
587588
log.warn(
588589
`ping timeout triggered. last pong received at: ${new Date(
589590
Date.now() - this.pingTimeoutDuration! * 1000,
@@ -597,7 +598,7 @@ export class SignalClient {
597598

598599
private clearPingTimeout() {
599600
if (this.pingTimeout) {
600-
clearTimeout(this.pingTimeout);
601+
CriticalTimers.clearTimeout(this.pingTimeout);
601602
}
602603
}
603604

@@ -609,7 +610,7 @@ export class SignalClient {
609610
return;
610611
}
611612
log.debug('start ping interval');
612-
this.pingInterval = setInterval(() => {
613+
this.pingInterval = CriticalTimers.setInterval(() => {
613614
this.sendPing();
614615
}, this.pingIntervalDuration * 1000);
615616
}
@@ -618,7 +619,7 @@ export class SignalClient {
618619
log.debug('clearing ping interval');
619620
this.clearPingTimeout();
620621
if (this.pingInterval) {
621-
clearInterval(this.pingInterval);
622+
CriticalTimers.clearInterval(this.pingInterval);
622623
}
623624
}
624625
}

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import RemoteTrackPublication from './room/track/RemoteTrackPublication';
1616
import RemoteVideoTrack from './room/track/RemoteVideoTrack';
1717
import type { ElementInfo } from './room/track/RemoteVideoTrack';
1818
import { TrackPublication } from './room/track/TrackPublication';
19+
import CriticalTimers from './room/timers';
1920
import {
2021
getEmptyAudioStreamTrack,
2122
getEmptyVideoStreamTrack,
@@ -71,4 +72,5 @@ export {
7172
ConnectionQuality,
7273
ElementInfo,
7374
DefaultReconnectPolicy,
75+
CriticalTimers,
7476
};

src/room/RTCEngine.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
import { EngineEvent } from './events';
3333
import PCTransport, { PCEvents } from './PCTransport';
3434
import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
35+
import CriticalTimers from './timers';
3536
import type LocalTrack from './track/LocalTrack';
3637
import type LocalVideoTrack from './track/LocalVideoTrack';
3738
import type { SimulcastTrackInfo } from './track/LocalVideoTrack';
@@ -710,10 +711,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
710711

711712
log.debug(`reconnecting in ${delay}ms`);
712713

713-
if (this.reconnectTimeout) {
714-
clearTimeout(this.reconnectTimeout);
715-
}
716-
this.reconnectTimeout = setTimeout(() => this.attemptReconnect(signalEvents), delay);
714+
this.clearReconnectTimeout();
715+
this.reconnectTimeout = CriticalTimers.setTimeout(
716+
() => this.attemptReconnect(signalEvents),
717+
delay,
718+
);
717719
};
718720

719721
private async attemptReconnect(signalEvents: boolean = false) {
@@ -740,11 +742,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
740742
} else {
741743
await this.resumeConnection(signalEvents);
742744
}
743-
this.reconnectAttempts = 0;
745+
this.clearPendingReconnect();
744746
this.fullReconnectOnNext = false;
745-
if (this.reconnectTimeout) {
746-
clearTimeout(this.reconnectTimeout);
747-
}
748747
} catch (e) {
749748
this.reconnectAttempts += 1;
750749
let reconnectRequired = false;
@@ -1046,19 +1045,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
10461045
}
10471046
}
10481047

1049-
private clearPendingReconnect() {
1048+
private clearReconnectTimeout() {
10501049
if (this.reconnectTimeout) {
1051-
clearTimeout(this.reconnectTimeout);
1050+
CriticalTimers.clearTimeout(this.reconnectTimeout);
10521051
}
1052+
}
1053+
1054+
private clearPendingReconnect() {
1055+
this.clearReconnectTimeout();
10531056
this.reconnectAttempts = 0;
10541057
}
10551058

10561059
private handleBrowserOnLine = () => {
10571060
// in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
10581061
if (this.client.isReconnecting) {
1059-
if (this.reconnectTimeout) {
1060-
clearTimeout(this.reconnectTimeout);
1061-
}
1062+
this.clearReconnectTimeout();
10621063
this.attemptReconnect(true);
10631064
}
10641065
};

src/room/Room.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import type Participant from './participant/Participant';
4444
import type { ConnectionQuality } from './participant/Participant';
4545
import RemoteParticipant from './participant/RemoteParticipant';
4646
import RTCEngine from './RTCEngine';
47+
import CriticalTimers from './timers';
4748
import LocalAudioTrack from './track/LocalAudioTrack';
4849
import LocalTrackPublication from './track/LocalTrackPublication';
4950
import LocalVideoTrack from './track/LocalVideoTrack';
@@ -364,15 +365,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
364365
}
365366

366367
// don't return until ICE connected
367-
const connectTimeout = setTimeout(() => {
368+
const connectTimeout = CriticalTimers.setTimeout(() => {
368369
// timeout
369370
this.recreateEngine();
370371
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
371372
reject(new ConnectionError('could not connect PeerConnection after timeout'));
372373
}, this.connOptions.peerConnectionTimeout);
373374
const abortHandler = () => {
374375
log.warn('closing engine');
375-
clearTimeout(connectTimeout);
376+
CriticalTimers.clearTimeout(connectTimeout);
376377
this.recreateEngine();
377378
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
378379
reject(new ConnectionError('room connection has been cancelled'));
@@ -383,7 +384,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
383384
this.abortController?.signal.addEventListener('abort', abortHandler);
384385

385386
this.engine.once(EngineEvent.Connected, () => {
386-
clearTimeout(connectTimeout);
387+
CriticalTimers.clearTimeout(connectTimeout);
387388
this.abortController?.signal.removeEventListener('abort', abortHandler);
388389
// also hook unload event
389390
if (isWeb()) {

src/room/timers.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Timers that can be overridden with platform specific implementations
3+
* that ensure that they are fired. These should be used when it is critical
4+
* that the timer fires on time.
5+
*/
6+
export default class CriticalTimers {
7+
static setTimeout = setTimeout;
8+
9+
static setInterval = setInterval;
10+
11+
static clearTimeout = clearTimeout;
12+
13+
static clearInterval = clearInterval;
14+
}

src/room/track/RemoteVideoTrack.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { debounce } from 'ts-debounce';
22
import log from '../../logger';
33
import { TrackEvent } from '../events';
44
import { computeBitrate, VideoReceiverStats } from '../stats';
5+
import CriticalTimers from '../timers';
56
import { getIntersectionObserver, getResizeObserver, ObservableMediaElement } from '../utils';
67
import RemoteTrack from './RemoteTrack';
78
import { attachToElement, detachTrack, Track } from './Track';
@@ -233,7 +234,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
233234

234235
if (!isVisible && Date.now() - lastVisibilityChange < REACTION_DELAY) {
235236
// delay hidden events
236-
setTimeout(() => {
237+
CriticalTimers.setTimeout(() => {
237238
this.updateVisibility();
238239
}, REACTION_DELAY);
239240
return;

0 commit comments

Comments
 (0)