livekit-client 2.18.3 → 2.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.esm.mjs +703 -334
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +12 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +3 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +4 -3
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +12 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +12 -4
- package/dist/ts4.2/room/Room.d.ts +3 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -0
- package/dist/ts4.2/room/events.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -0
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +4 -3
- package/dist/ts4.2/room/track/LocalTrack.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +12 -1
- package/dist/ts4.2/room/types.d.ts +1 -1
- package/package.json +3 -3
- package/src/api/SignalClient.ts +4 -0
- package/src/room/PCTransport.ts +10 -8
- package/src/room/RTCEngine.ts +59 -28
- package/src/room/RegionUrlProvider.ts +7 -0
- package/src/room/Room.ts +93 -23
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +331 -16
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +92 -41
- package/src/room/events.ts +2 -0
- package/src/room/participant/LocalParticipant.ts +70 -5
- package/src/room/participant/RemoteParticipant.ts +14 -2
- package/src/room/token-source/TokenSource.test.ts +337 -0
- package/src/room/token-source/test-tokens.ts +28 -0
- package/src/room/token-source/utils.test.ts +12 -20
- package/src/room/track/LocalTrack.ts +15 -1
- package/src/room/track/LocalVideoTrack.ts +126 -2
- package/src/room/track/RemoteVideoTrack.ts +8 -2
- package/src/room/types.ts +2 -1
- package/src/utils/deferrable-map.ts +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ParticipantInfo } from '@livekit/protocol';
|
|
2
2
|
import type { SignalClient } from '../../api/SignalClient';
|
|
3
3
|
import { DeferrableMap } from '../../utils/deferrable-map';
|
|
4
|
-
import
|
|
4
|
+
import RemoteDataTrack from '../data-track/RemoteDataTrack';
|
|
5
|
+
import type IncomingDataTrackManager from '../data-track/incoming/IncomingDataTrackManager';
|
|
5
6
|
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
|
6
7
|
import { Track } from '../track/Track';
|
|
7
8
|
import type { AudioOutputOptions } from '../track/options';
|
|
@@ -24,13 +25,13 @@ export default class RemoteParticipant extends Participant {
|
|
|
24
25
|
private volumeMap;
|
|
25
26
|
private audioOutput?;
|
|
26
27
|
/** @internal */
|
|
27
|
-
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo, loggerOptions: LoggerOptions): RemoteParticipant;
|
|
28
|
+
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo, loggerOptions: LoggerOptions, manager: IncomingDataTrackManager): RemoteParticipant;
|
|
28
29
|
protected get logContext(): {
|
|
29
30
|
remoteParticipantID: string;
|
|
30
31
|
remoteParticipant: string;
|
|
31
32
|
};
|
|
32
33
|
/** @internal */
|
|
33
|
-
constructor(signalClient: SignalClient, sid: string, identity?: string, name?: string, metadata?: string, attributes?: Record<string, string>, loggerOptions?: LoggerOptions, kind?: ParticipantKind);
|
|
34
|
+
constructor(signalClient: SignalClient, sid: string, identity?: string, name?: string, metadata?: string, attributes?: Record<string, string>, loggerOptions?: LoggerOptions, kind?: ParticipantKind, remoteDataTracks?: Array<RemoteDataTrack>);
|
|
34
35
|
protected addTrackPublication(publication: RemoteTrackPublication): void;
|
|
35
36
|
getTrackPublication(source: Track.Source): RemoteTrackPublication | undefined;
|
|
36
37
|
getTrackPublicationByName(name: string): RemoteTrackPublication | undefined;
|
|
@@ -60,6 +60,13 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
|
|
|
60
60
|
unmute(): Promise<this>;
|
|
61
61
|
replaceTrack(track: MediaStreamTrack, options?: ReplaceTrackOptions): Promise<typeof this>;
|
|
62
62
|
replaceTrack(track: MediaStreamTrack, userProvidedTrack?: boolean): Promise<typeof this>;
|
|
63
|
+
/**
|
|
64
|
+
* Hook invoked after the MediaStreamTrack on the sender has been swapped
|
|
65
|
+
* (via replaceTrack, setProcessor, or stopProcessor). Fires outside the
|
|
66
|
+
* trackChangeLock so subclasses can do asynchronous work such as polling
|
|
67
|
+
* for new dimensions without blocking other track operations.
|
|
68
|
+
*/
|
|
69
|
+
protected onSenderTrackSwapped(): Promise<void>;
|
|
63
70
|
protected restart(constraints?: MediaTrackConstraints, isUnmuting?: boolean): Promise<this>;
|
|
64
71
|
protected setTrackMuted(muted: boolean): void;
|
|
65
72
|
protected get needsReAcquisition(): boolean;
|
|
@@ -4,7 +4,7 @@ import type { VideoSenderStats } from '../stats';
|
|
|
4
4
|
import type { LoggerOptions } from '../types';
|
|
5
5
|
import LocalTrack from './LocalTrack';
|
|
6
6
|
import { Track, VideoQuality } from './Track';
|
|
7
|
-
import type { VideoCaptureOptions, VideoCodec } from './options';
|
|
7
|
+
import type { TrackPublishOptions, VideoCaptureOptions, VideoCodec } from './options';
|
|
8
8
|
import type { TrackProcessor } from './processor/types';
|
|
9
9
|
export declare class SimulcastTrackInfo {
|
|
10
10
|
codec: VideoCodec;
|
|
@@ -23,6 +23,8 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
23
23
|
private degradationPreference;
|
|
24
24
|
private isCpuConstrained;
|
|
25
25
|
private optimizeForPerformance;
|
|
26
|
+
publishOptions?: TrackPublishOptions;
|
|
27
|
+
lastEncodedDimensions?: Track.Dimensions;
|
|
26
28
|
get sender(): RTCRtpSender | undefined;
|
|
27
29
|
set sender(sender: RTCRtpSender | undefined);
|
|
28
30
|
/**
|
|
@@ -43,6 +45,15 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
43
45
|
getSenderStats(): Promise<VideoSenderStats[]>;
|
|
44
46
|
setPublishingQuality(maxQuality: VideoQuality): void;
|
|
45
47
|
restartTrack(options?: VideoCaptureOptions): Promise<void>;
|
|
48
|
+
protected onSenderTrackSwapped(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Recomputes encoding parameters for this track's senders based on the current
|
|
51
|
+
* MediaStreamTrack dimensions and reapplies them via setParameters. This is a no-op
|
|
52
|
+
* if the track hasn't been published yet or if the track is in performance-optimized
|
|
53
|
+
* mode (which manages its own encodings).
|
|
54
|
+
*/
|
|
55
|
+
private refreshSenderEncodings;
|
|
56
|
+
private applyEncodingsToSender;
|
|
46
57
|
setProcessor(processor: TrackProcessor<Track.Kind.Video>, showProcessedStreamLocally?: boolean): Promise<void>;
|
|
47
58
|
setDegradationPreference(preference: RTCDegradationPreference): Promise<void>;
|
|
48
59
|
addSimulcastTrack(codec: VideoCodec, encodings?: RTCRtpEncodingParameters[]): SimulcastTrackInfo | undefined;
|
|
@@ -62,7 +62,7 @@ export type LiveKitReactNativeInfo = {
|
|
|
62
62
|
platform: 'ios' | 'android' | 'windows' | 'macos' | 'web' | 'native';
|
|
63
63
|
devicePixelRatio: number;
|
|
64
64
|
};
|
|
65
|
-
export type SimulationScenario = 'signal-reconnect' | 'speaker' | 'node-failure' | 'server-leave' | 'migration' | 'resume-reconnect' | 'force-tcp' | 'force-tls' | 'full-reconnect' | 'subscriber-bandwidth' | 'disconnect-signal-on-resume' | 'disconnect-signal-on-resume-no-messages' | 'leave-full-reconnect';
|
|
65
|
+
export type SimulationScenario = 'signal-reconnect' | 'speaker' | 'node-failure' | 'server-leave' | 'migration' | 'resume-reconnect' | 'force-tcp' | 'force-tls' | 'full-reconnect' | 'subscriber-bandwidth' | 'disconnect-signal-on-resume' | 'disconnect-signal-on-resume-no-messages' | 'leave-full-reconnect' | 'fail-on-v1-path';
|
|
66
66
|
export type LoggerOptions = {
|
|
67
67
|
loggerName?: string;
|
|
68
68
|
loggerContextCb?: () => Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livekit-client",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.5",
|
|
4
4
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
|
5
5
|
"main": "./dist/livekit-client.umd.js",
|
|
6
6
|
"unpkg": "./dist/livekit-client.umd.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"sdp-transform": "^2.15.0",
|
|
45
45
|
"tslib": "2.8.1",
|
|
46
46
|
"typed-emitter": "^2.1.0",
|
|
47
|
-
"webrtc-adapter": "
|
|
47
|
+
"webrtc-adapter": "9.0.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@types/dom-mediacapture-record": "^1"
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@eslint/js": "9.39.2",
|
|
58
58
|
"@livekit/changesets-changelog-github": "^0.0.4",
|
|
59
59
|
"@livekit/throws-transformer": "^0.1.3",
|
|
60
|
-
"@rollup/plugin-babel": "
|
|
60
|
+
"@rollup/plugin-babel": "7.0.0",
|
|
61
61
|
"@rollup/plugin-commonjs": "28.0.9",
|
|
62
62
|
"@rollup/plugin-json": "6.1.0",
|
|
63
63
|
"@rollup/plugin-node-resolve": "16.0.3",
|
package/src/api/SignalClient.ts
CHANGED
|
@@ -1081,6 +1081,10 @@ export class SignalClient {
|
|
|
1081
1081
|
|
|
1082
1082
|
switch (resp.status) {
|
|
1083
1083
|
case 404:
|
|
1084
|
+
const errorMsg = await resp.text();
|
|
1085
|
+
if (errorMsg.includes('requested room does not exist')) {
|
|
1086
|
+
return ConnectionError.notAllowed(errorMsg, resp.status);
|
|
1087
|
+
}
|
|
1084
1088
|
return ConnectionError.serviceNotFound(
|
|
1085
1089
|
'v1 RTC path not found. Consider upgrading your LiveKit server version',
|
|
1086
1090
|
'v0-rtc',
|
package/src/room/PCTransport.ts
CHANGED
|
@@ -165,7 +165,7 @@ export default class PCTransport extends EventEmitter {
|
|
|
165
165
|
this.remoteStereoMids = stereoMids;
|
|
166
166
|
this.remoteNackMids = nackMids;
|
|
167
167
|
} else if (sd.type === 'answer') {
|
|
168
|
-
if (this.pendingInitialOffer) {
|
|
168
|
+
if (this.pendingInitialOffer && this._pc) {
|
|
169
169
|
const initialOffer = this.pendingInitialOffer;
|
|
170
170
|
this.pendingInitialOffer = undefined;
|
|
171
171
|
const sdpParsed = parse(initialOffer.sdp ?? '');
|
|
@@ -523,6 +523,7 @@ export default class PCTransport extends EventEmitter {
|
|
|
523
523
|
if (!this._pc) {
|
|
524
524
|
return;
|
|
525
525
|
}
|
|
526
|
+
this.pendingInitialOffer = undefined;
|
|
526
527
|
this._pc.close();
|
|
527
528
|
this._pc.onconnectionstatechange = null;
|
|
528
529
|
this._pc.oniceconnectionstatechange = null;
|
|
@@ -539,8 +540,8 @@ export default class PCTransport extends EventEmitter {
|
|
|
539
540
|
};
|
|
540
541
|
|
|
541
542
|
private async setMungedSDP(sd: RTCSessionDescriptionInit, munged?: string, remote?: boolean) {
|
|
543
|
+
const originalSdp = sd.sdp;
|
|
542
544
|
if (munged) {
|
|
543
|
-
const originalSdp = sd.sdp;
|
|
544
545
|
sd.sdp = munged;
|
|
545
546
|
try {
|
|
546
547
|
this.log.debug(
|
|
@@ -557,7 +558,8 @@ export default class PCTransport extends EventEmitter {
|
|
|
557
558
|
this.log.warn(`not able to set ${sd.type}, falling back to unmodified sdp`, {
|
|
558
559
|
...this.logContext,
|
|
559
560
|
error: e,
|
|
560
|
-
|
|
561
|
+
mungedSdp: munged,
|
|
562
|
+
originalSdp,
|
|
561
563
|
});
|
|
562
564
|
sd.sdp = originalSdp;
|
|
563
565
|
}
|
|
@@ -565,9 +567,9 @@ export default class PCTransport extends EventEmitter {
|
|
|
565
567
|
|
|
566
568
|
try {
|
|
567
569
|
if (remote) {
|
|
568
|
-
await this.
|
|
570
|
+
await this._pc?.setRemoteDescription(sd);
|
|
569
571
|
} else {
|
|
570
|
-
await this.
|
|
572
|
+
await this._pc?.setLocalDescription(sd);
|
|
571
573
|
}
|
|
572
574
|
} catch (e) {
|
|
573
575
|
let msg = 'unknown error';
|
|
@@ -581,6 +583,9 @@ export default class PCTransport extends EventEmitter {
|
|
|
581
583
|
error: msg,
|
|
582
584
|
sdp: sd.sdp,
|
|
583
585
|
};
|
|
586
|
+
if (munged && munged !== originalSdp) {
|
|
587
|
+
fields.mungedSdp = munged;
|
|
588
|
+
}
|
|
584
589
|
if (!remote && this.pc.remoteDescription) {
|
|
585
590
|
fields.remoteSdp = this.pc.remoteDescription;
|
|
586
591
|
}
|
|
@@ -609,9 +614,6 @@ export default class PCTransport extends EventEmitter {
|
|
|
609
614
|
if (this.ddExtID === 0) {
|
|
610
615
|
let maxID = 0;
|
|
611
616
|
sdp.media.forEach((m) => {
|
|
612
|
-
if (m.type !== 'video') {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
617
|
m.ext?.forEach((ext) => {
|
|
616
618
|
if (ext.value > maxID) {
|
|
617
619
|
maxID = ext.value;
|
package/src/room/RTCEngine.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
PublishDataTrackResponse,
|
|
22
22
|
ReconnectReason,
|
|
23
23
|
type ReconnectResponse,
|
|
24
|
+
type RegionSettings,
|
|
24
25
|
RequestResponse,
|
|
25
26
|
Room as RoomModel,
|
|
26
27
|
RoomMovedResponse,
|
|
@@ -62,7 +63,6 @@ import { TTLMap } from '../utils/ttlmap';
|
|
|
62
63
|
import PCTransport, { PCEvents } from './PCTransport';
|
|
63
64
|
import { PCTransportManager, PCTransportState } from './PCTransportManager';
|
|
64
65
|
import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
|
|
65
|
-
import { DEFAULT_MAX_AGE_MS, type RegionUrlProvider } from './RegionUrlProvider';
|
|
66
66
|
import { DataTrackInfo } from './data-track/types';
|
|
67
67
|
import { roomConnectOptionDefaults } from './defaults';
|
|
68
68
|
import {
|
|
@@ -86,6 +86,7 @@ import type { TrackPublishOptions, VideoCodec } from './track/options';
|
|
|
86
86
|
import { getTrackPublicationInfo } from './track/utils';
|
|
87
87
|
import type { LoggerOptions } from './types';
|
|
88
88
|
import {
|
|
89
|
+
Future,
|
|
89
90
|
isCompressionStreamSupported,
|
|
90
91
|
isVideoCodec,
|
|
91
92
|
isVideoTrack,
|
|
@@ -220,7 +221,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
220
221
|
|
|
221
222
|
private shouldFailNext: boolean = false;
|
|
222
223
|
|
|
223
|
-
private
|
|
224
|
+
private shouldFailOnV1Path: boolean = false;
|
|
225
|
+
|
|
226
|
+
private regionStrategy?: RegionStrategy;
|
|
224
227
|
|
|
225
228
|
private log = log;
|
|
226
229
|
|
|
@@ -247,6 +250,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
247
250
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
248
251
|
private isWaitingForNetworkReconnect: boolean = false;
|
|
249
252
|
|
|
253
|
+
private bufferStatusLowClosingFuture = new Future<never, UnexpectedConnectionState>();
|
|
254
|
+
|
|
250
255
|
constructor(private options: InternalRoomOptions) {
|
|
251
256
|
super();
|
|
252
257
|
this.log = getLogger(options.loggerName ?? LoggerNames.Engine);
|
|
@@ -280,6 +285,13 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
280
285
|
this.client.onParticipantUpdate = (updates) =>
|
|
281
286
|
this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
282
287
|
this.client.onJoined = (joinResponse) => this.emit(EngineEvent.Joined, joinResponse);
|
|
288
|
+
|
|
289
|
+
this.on(EngineEvent.Closing, () => {
|
|
290
|
+
this.bufferStatusLowClosingFuture.reject?.(new UnexpectedConnectionState('engine closed'));
|
|
291
|
+
});
|
|
292
|
+
// Swallow the rejection at the source so it doesn't surface as an unhandled promise rejection
|
|
293
|
+
// when no waitForBufferStatusLow callers are attached.
|
|
294
|
+
this.bufferStatusLowClosingFuture.promise.catch(() => {});
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
/** @internal */
|
|
@@ -321,9 +333,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
321
333
|
offerProto = toProtoSessionDescription(offer.offer, offer.offerId);
|
|
322
334
|
}
|
|
323
335
|
}
|
|
336
|
+
|
|
324
337
|
if (abortSignal?.aborted) {
|
|
325
338
|
throw ConnectionError.cancelled('Connection aborted');
|
|
326
339
|
}
|
|
340
|
+
|
|
341
|
+
if (!useV0Path && this.shouldFailOnV1Path) {
|
|
342
|
+
this.shouldFailOnV1Path = false;
|
|
343
|
+
throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc');
|
|
344
|
+
}
|
|
327
345
|
const joinResponse = await this.client.join(
|
|
328
346
|
url,
|
|
329
347
|
token,
|
|
@@ -383,6 +401,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
383
401
|
}
|
|
384
402
|
} else if (e.reason === ConnectionErrorReason.ServiceNotFound) {
|
|
385
403
|
this.log.warn(`Initial connection failed: ${e.message} – Retrying`);
|
|
404
|
+
if (this.pcManager) {
|
|
405
|
+
this.pcManager.onStateChange = undefined;
|
|
406
|
+
await this.cleanupPeerConnections();
|
|
407
|
+
}
|
|
386
408
|
return this.join(url, token, opts, abortSignal, true);
|
|
387
409
|
}
|
|
388
410
|
}
|
|
@@ -456,6 +478,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
456
478
|
async cleanupClient() {
|
|
457
479
|
await this.client.close();
|
|
458
480
|
this.client.resetCallbacks();
|
|
481
|
+
// Any in-flight addTrack requests are orphaned by the signal reconnect — the new session
|
|
482
|
+
// won't deliver `trackPublishedResponse` for them, so reject the pending resolvers and
|
|
483
|
+
// clear the map. Otherwise a subsequent `addTrack` call with the same client id (e.g. a
|
|
484
|
+
// publish retry after a `NegotiationError`) throws `TrackInvalidError`.
|
|
485
|
+
for (const cid of Object.keys(this.pendingTrackResolvers)) {
|
|
486
|
+
this.pendingTrackResolvers[cid].reject();
|
|
487
|
+
}
|
|
488
|
+
this.pendingTrackResolvers = {};
|
|
459
489
|
}
|
|
460
490
|
|
|
461
491
|
addTrack(req: AddTrackRequest): Promise<TrackInfo> {
|
|
@@ -519,8 +549,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
519
549
|
}
|
|
520
550
|
|
|
521
551
|
/* @internal */
|
|
522
|
-
|
|
523
|
-
this.
|
|
552
|
+
setRegionStrategy(strategy: RegionStrategy | undefined) {
|
|
553
|
+
this.regionStrategy = strategy;
|
|
524
554
|
}
|
|
525
555
|
|
|
526
556
|
private async configure(joinResponse?: JoinResponse, useSinglePeerConnection?: boolean) {
|
|
@@ -671,7 +701,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
671
701
|
|
|
672
702
|
this.client.onTokenRefresh = (token: string) => {
|
|
673
703
|
this.token = token;
|
|
674
|
-
this.
|
|
704
|
+
this.emit(EngineEvent.TokenRefreshed, token);
|
|
675
705
|
};
|
|
676
706
|
|
|
677
707
|
this.client.onRemoteMuteChanged = (trackSid: string, muted: boolean) => {
|
|
@@ -713,13 +743,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
713
743
|
|
|
714
744
|
this.client.onLeave = (leave: LeaveRequest) => {
|
|
715
745
|
this.log.debug('client leave request', { ...this.logContext, reason: leave?.reason });
|
|
716
|
-
if (leave.regions
|
|
746
|
+
if (leave.regions) {
|
|
717
747
|
this.log.debug('updating regions', this.logContext);
|
|
718
|
-
this.
|
|
719
|
-
updatedAtInMs: Date.now(),
|
|
720
|
-
maxAgeInMs: DEFAULT_MAX_AGE_MS,
|
|
721
|
-
regionSettings: leave.regions,
|
|
722
|
-
});
|
|
748
|
+
this.emit(EngineEvent.ServerRegionsReported, leave.regions);
|
|
723
749
|
}
|
|
724
750
|
switch (leave.action) {
|
|
725
751
|
case LeaveRequest_Action.DISCONNECT:
|
|
@@ -1124,10 +1150,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1124
1150
|
this.log.debug(`reconnecting in ${delay}ms`, this.logContext);
|
|
1125
1151
|
|
|
1126
1152
|
this.clearReconnectTimeout();
|
|
1127
|
-
if (this.token
|
|
1153
|
+
if (this.token) {
|
|
1128
1154
|
// token may have been refreshed, we do not want to recreate the regionUrlProvider
|
|
1129
1155
|
// since the current engine may have inherited a regional url
|
|
1130
|
-
this.
|
|
1156
|
+
this.emit(EngineEvent.TokenRefreshed, this.token);
|
|
1131
1157
|
}
|
|
1132
1158
|
this.reconnectTimeout = CriticalTimers.setTimeout(
|
|
1133
1159
|
() =>
|
|
@@ -1260,17 +1286,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1260
1286
|
throw new SignalReconnectError('Signal connection got severed during reconnect');
|
|
1261
1287
|
}
|
|
1262
1288
|
|
|
1263
|
-
this.
|
|
1289
|
+
this.regionStrategy?.resetAttempts();
|
|
1264
1290
|
// reconnect success
|
|
1265
1291
|
this.emit(EngineEvent.Restarted);
|
|
1266
1292
|
} catch (error) {
|
|
1267
|
-
const nextRegionUrl = await this.
|
|
1293
|
+
const nextRegionUrl = await this.regionStrategy?.getNextUrl();
|
|
1268
1294
|
if (nextRegionUrl) {
|
|
1269
1295
|
await this.restartConnection(nextRegionUrl);
|
|
1270
1296
|
return;
|
|
1271
1297
|
} else {
|
|
1272
1298
|
// no more regions to try (or we're not on cloud)
|
|
1273
|
-
this.
|
|
1299
|
+
this.regionStrategy?.resetAttempts();
|
|
1274
1300
|
throw error;
|
|
1275
1301
|
}
|
|
1276
1302
|
}
|
|
@@ -1565,23 +1591,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1565
1591
|
if (this.isBufferStatusLow(kind)) {
|
|
1566
1592
|
resolve();
|
|
1567
1593
|
} else {
|
|
1568
|
-
const onClosing = () => reject(new UnexpectedConnectionState('engine closed'));
|
|
1569
|
-
this.once(EngineEvent.Closing, onClosing);
|
|
1570
1594
|
const dc = this.dataChannelForKind(kind);
|
|
1571
1595
|
if (!dc) {
|
|
1572
1596
|
reject(new UnexpectedConnectionState(`DataChannel not found, kind: ${kind}`));
|
|
1573
1597
|
return;
|
|
1574
1598
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
resolve();
|
|
1580
|
-
},
|
|
1581
|
-
{
|
|
1582
|
-
once: true,
|
|
1583
|
-
},
|
|
1584
|
-
);
|
|
1599
|
+
this.bufferStatusLowClosingFuture.promise.catch((e) => reject(e));
|
|
1600
|
+
dc.addEventListener('bufferedamountlow', () => resolve(), {
|
|
1601
|
+
once: true,
|
|
1602
|
+
});
|
|
1585
1603
|
}
|
|
1586
1604
|
});
|
|
1587
1605
|
}
|
|
@@ -1860,6 +1878,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1860
1878
|
this.shouldFailNext = true;
|
|
1861
1879
|
}
|
|
1862
1880
|
|
|
1881
|
+
/* @internal */
|
|
1882
|
+
failNextV1Path() {
|
|
1883
|
+
// debugging method to fail the next connection attempt for /rtc/v1 to trigger the fallback version
|
|
1884
|
+
this.shouldFailOnV1Path = true;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1863
1887
|
private dataChannelsInfo(): DataChannelInfo[] {
|
|
1864
1888
|
const infos: DataChannelInfo[] = [];
|
|
1865
1889
|
const getInfo = (dc: RTCDataChannel | undefined, target: SignalTarget) => {
|
|
@@ -2004,8 +2028,15 @@ export type EngineEventCallbacks = {
|
|
|
2004
2028
|
dataTrackSubscriberHandles: (event: DataTrackSubscriberHandles) => void;
|
|
2005
2029
|
dataTrackPacketReceived: (packet: Uint8Array) => void;
|
|
2006
2030
|
joined: (joinResponse: JoinResponse) => void;
|
|
2031
|
+
tokenRefreshed: (token: string) => void;
|
|
2032
|
+
serverRegionsReported: (regions: RegionSettings) => void;
|
|
2007
2033
|
};
|
|
2008
2034
|
|
|
2035
|
+
export interface RegionStrategy {
|
|
2036
|
+
getNextUrl(abortSignal?: AbortSignal): Promise<string | null>;
|
|
2037
|
+
resetAttempts(): void;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2009
2040
|
function applyUserDataCompat(newObj: DataPacket, oldObj: UserPacket) {
|
|
2010
2041
|
const participantIdentity = newObj.participantIdentity
|
|
2011
2042
|
? newObj.participantIdentity
|
|
@@ -189,6 +189,13 @@ export class RegionUrlProvider {
|
|
|
189
189
|
|
|
190
190
|
updateToken(token: string) {
|
|
191
191
|
this.token = token;
|
|
192
|
+
const url = this.getServerUrl();
|
|
193
|
+
const settings = RegionUrlProvider.cache.get(url.hostname);
|
|
194
|
+
RegionUrlProvider.scheduleRefetch(
|
|
195
|
+
this.serverUrl,
|
|
196
|
+
this.token,
|
|
197
|
+
settings?.maxAgeInMs ?? DEFAULT_MAX_AGE_MS,
|
|
198
|
+
);
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
isCloud() {
|
package/src/room/Room.ts
CHANGED
|
@@ -47,8 +47,8 @@ import TypedPromise from '../utils/TypedPromise';
|
|
|
47
47
|
import { getBrowser } from '../utils/browserParser';
|
|
48
48
|
import { BackOffStrategy } from './BackOffStrategy';
|
|
49
49
|
import DeviceManager from './DeviceManager';
|
|
50
|
-
import RTCEngine, { DataChannelKind } from './RTCEngine';
|
|
51
|
-
import { RegionUrlProvider } from './RegionUrlProvider';
|
|
50
|
+
import RTCEngine, { DataChannelKind, type RegionStrategy } from './RTCEngine';
|
|
51
|
+
import { DEFAULT_MAX_AGE_MS, RegionUrlProvider } from './RegionUrlProvider';
|
|
52
52
|
import IncomingDataStreamManager from './data-stream/incoming/IncomingDataStreamManager';
|
|
53
53
|
import {
|
|
54
54
|
type ByteStreamHandler,
|
|
@@ -588,22 +588,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
588
588
|
this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
|
|
589
589
|
})
|
|
590
590
|
.on(EngineEvent.LocalTrackSubscribed, (subscribedSid) => {
|
|
591
|
-
|
|
592
|
-
.getTrackPublications()
|
|
593
|
-
.find(({ trackSid }) => trackSid === subscribedSid) as LocalTrackPublication | undefined;
|
|
594
|
-
if (!trackPublication) {
|
|
595
|
-
this.log.warn(
|
|
596
|
-
'could not find local track subscription for subscribed event',
|
|
597
|
-
this.logContext,
|
|
598
|
-
);
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication);
|
|
602
|
-
this.emitWhenConnected(
|
|
603
|
-
RoomEvent.LocalTrackSubscribed,
|
|
604
|
-
trackPublication,
|
|
605
|
-
this.localParticipant,
|
|
606
|
-
);
|
|
591
|
+
this.handleLocalTrackSubscribed(subscribedSid);
|
|
607
592
|
})
|
|
608
593
|
.on(EngineEvent.RoomMoved, (roomMoved) => {
|
|
609
594
|
this.log.debug('room moved', roomMoved);
|
|
@@ -683,6 +668,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
683
668
|
}),
|
|
684
669
|
);
|
|
685
670
|
this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
|
|
671
|
+
})
|
|
672
|
+
.on(EngineEvent.TokenRefreshed, (token) => {
|
|
673
|
+
this.regionUrlProvider?.updateToken(token);
|
|
674
|
+
})
|
|
675
|
+
.on(EngineEvent.ServerRegionsReported, (regions) => {
|
|
676
|
+
this.regionUrlProvider?.setServerReportedRegions({
|
|
677
|
+
regionSettings: regions,
|
|
678
|
+
updatedAtInMs: Date.now(),
|
|
679
|
+
maxAgeInMs: DEFAULT_MAX_AGE_MS,
|
|
680
|
+
});
|
|
686
681
|
});
|
|
687
682
|
|
|
688
683
|
if (this.localParticipant) {
|
|
@@ -696,6 +691,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
696
691
|
}
|
|
697
692
|
}
|
|
698
693
|
|
|
694
|
+
private createRegionStrategy(): RegionStrategy {
|
|
695
|
+
return {
|
|
696
|
+
getNextUrl: async (signal?: AbortSignal) =>
|
|
697
|
+
this.regionUrlProvider ? this.regionUrlProvider.getNextBestRegionUrl(signal) : null,
|
|
698
|
+
resetAttempts: () => this.regionUrlProvider?.resetAttempts(),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
699
702
|
/**
|
|
700
703
|
* getLocalDevices abstracts navigator.mediaDevices.enumerateDevices.
|
|
701
704
|
* In particular, it requests device permissions by default if needed
|
|
@@ -980,7 +983,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
980
983
|
this.maybeCreateEngine();
|
|
981
984
|
}
|
|
982
985
|
if (this.regionUrlProvider?.isCloud()) {
|
|
983
|
-
this.engine.
|
|
986
|
+
this.engine.setRegionStrategy(this.createRegionStrategy());
|
|
984
987
|
}
|
|
985
988
|
|
|
986
989
|
this.acquireAudioContext();
|
|
@@ -1137,6 +1140,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1137
1140
|
// @ts-expect-error function is private
|
|
1138
1141
|
await this.engine.client.handleOnClose('simulate disconnect');
|
|
1139
1142
|
break;
|
|
1143
|
+
case 'fail-on-v1-path':
|
|
1144
|
+
this.engine.failNextV1Path();
|
|
1145
|
+
break;
|
|
1140
1146
|
case 'speaker':
|
|
1141
1147
|
req = new SimulateScenario({
|
|
1142
1148
|
scenario: {
|
|
@@ -1623,6 +1629,65 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1623
1629
|
}
|
|
1624
1630
|
}
|
|
1625
1631
|
|
|
1632
|
+
private handleLocalTrackSubscribed(subscribedSid: string) {
|
|
1633
|
+
const findPublication = () =>
|
|
1634
|
+
this.localParticipant
|
|
1635
|
+
.getTrackPublications()
|
|
1636
|
+
.find(({ trackSid }) => trackSid === subscribedSid) as LocalTrackPublication | undefined;
|
|
1637
|
+
|
|
1638
|
+
const trackPublication = findPublication();
|
|
1639
|
+
if (trackPublication) {
|
|
1640
|
+
this.emitLocalTrackSubscribed(trackPublication);
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// the track publication may not be registered yet if the server signals
|
|
1645
|
+
// the subscription before publishTrack has finished adding the publication.
|
|
1646
|
+
// defer with a timeout until LocalTrackPublished fires for the matching trackSid
|
|
1647
|
+
this.log.debug('deferring LocalTrackSubscribed, publication not yet available', {
|
|
1648
|
+
...this.logContext,
|
|
1649
|
+
subscribedSid,
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
const TIMEOUT_MS = 10_000;
|
|
1653
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
1654
|
+
|
|
1655
|
+
const onPublished = (pub: LocalTrackPublication) => {
|
|
1656
|
+
if (pub.trackSid === subscribedSid) {
|
|
1657
|
+
cleanup();
|
|
1658
|
+
this.emitLocalTrackSubscribed(pub);
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
const cleanup = () => {
|
|
1663
|
+
clearTimeout(timer);
|
|
1664
|
+
this.localParticipant.off(ParticipantEvent.LocalTrackPublished, onPublished);
|
|
1665
|
+
this.off(RoomEvent.Disconnected, cleanup);
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
this.localParticipant.on(ParticipantEvent.LocalTrackPublished, onPublished);
|
|
1669
|
+
this.once(RoomEvent.Disconnected, cleanup);
|
|
1670
|
+
|
|
1671
|
+
timer = setTimeout(() => {
|
|
1672
|
+
cleanup();
|
|
1673
|
+
// final attempt in case the publication was added without emitting the event
|
|
1674
|
+
const pub = findPublication();
|
|
1675
|
+
if (pub) {
|
|
1676
|
+
this.emitLocalTrackSubscribed(pub);
|
|
1677
|
+
} else {
|
|
1678
|
+
this.log.warn(
|
|
1679
|
+
'could not find local track publication for LocalTrackSubscribed event after timeout',
|
|
1680
|
+
{ ...this.logContext, subscribedSid },
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
}, TIMEOUT_MS);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
private emitLocalTrackSubscribed(trackPublication: LocalTrackPublication) {
|
|
1687
|
+
this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication);
|
|
1688
|
+
this.emitWhenConnected(RoomEvent.LocalTrackSubscribed, trackPublication, this.localParticipant);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1626
1691
|
private handleRestarting = () => {
|
|
1627
1692
|
this.clearConnectionReconcile();
|
|
1628
1693
|
// in case we went from resuming to full-reconnect, make sure to reflect it on the isResuming flag
|
|
@@ -2253,10 +2318,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2253
2318
|
private createParticipant(identity: string, info?: ParticipantInfo): RemoteParticipant {
|
|
2254
2319
|
let participant: RemoteParticipant;
|
|
2255
2320
|
if (info) {
|
|
2256
|
-
participant = RemoteParticipant.fromParticipantInfo(
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2321
|
+
participant = RemoteParticipant.fromParticipantInfo(
|
|
2322
|
+
this.engine.client,
|
|
2323
|
+
info,
|
|
2324
|
+
{
|
|
2325
|
+
loggerContextCb: () => this.logContext,
|
|
2326
|
+
loggerName: this.options.loggerName,
|
|
2327
|
+
},
|
|
2328
|
+
this.incomingDataTrackManager,
|
|
2329
|
+
);
|
|
2260
2330
|
} else {
|
|
2261
2331
|
participant = new RemoteParticipant(
|
|
2262
2332
|
this.engine.client,
|