livekit-client 1.15.10 → 2.0.0
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/README.md +21 -17
- package/dist/livekit-client.esm.mjs +1603 -1493
- 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 +1 -3
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +3 -9
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/livekit_models_pb.d.ts +47 -0
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +14 -16
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +0 -4
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +6 -10
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +4 -5
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +5 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +0 -5
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +11 -3
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +1 -3
- package/dist/ts4.2/src/index.d.ts +3 -3
- package/dist/ts4.2/src/options.d.ts +3 -9
- package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +47 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +14 -16
- package/dist/ts4.2/src/room/events.d.ts +0 -4
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
- package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
- package/dist/ts4.2/src/room/timers.d.ts +4 -5
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
- package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
- package/dist/ts4.2/src/room/track/options.d.ts +0 -5
- package/dist/ts4.2/src/room/types.d.ts +11 -3
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +8 -7
- package/src/api/SignalClient.ts +10 -10
- package/src/e2ee/E2eeManager.ts +2 -2
- package/src/index.ts +2 -4
- package/src/options.ts +3 -10
- package/src/proto/livekit_models_pb.ts +66 -0
- package/src/room/RTCEngine.ts +6 -1
- package/src/room/Room.ts +169 -129
- package/src/room/defaults.ts +1 -5
- package/src/room/events.ts +0 -5
- package/src/room/participant/LocalParticipant.ts +36 -77
- package/src/room/participant/Participant.ts +23 -24
- package/src/room/participant/RemoteParticipant.ts +27 -24
- package/src/room/track/LocalVideoTrack.test.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +11 -7
- package/src/room/track/RemoteTrackPublication.ts +2 -7
- package/src/room/track/Track.ts +10 -1
- package/src/room/track/options.ts +0 -6
- package/src/room/types.ts +11 -4
- package/src/version.ts +1 -1
package/src/room/Room.ts
CHANGED
@@ -85,9 +85,6 @@ export enum ConnectionState {
|
|
85
85
|
|
86
86
|
const connectionReconcileFrequency = 2 * 1000;
|
87
87
|
|
88
|
-
/** @deprecated RoomState has been renamed to [[ConnectionState]] */
|
89
|
-
export const RoomState = ConnectionState;
|
90
|
-
|
91
88
|
/**
|
92
89
|
* In LiveKit, a room is the logical grouping for a list of participants.
|
93
90
|
* Participants in a room can publish tracks, and subscribe to others' tracks.
|
@@ -99,8 +96,10 @@ export const RoomState = ConnectionState;
|
|
99
96
|
class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
|
100
97
|
state: ConnectionState = ConnectionState.Disconnected;
|
101
98
|
|
102
|
-
/**
|
103
|
-
|
99
|
+
/**
|
100
|
+
* map of identity: [[RemoteParticipant]]
|
101
|
+
*/
|
102
|
+
remoteParticipants: Map<string, RemoteParticipant>;
|
104
103
|
|
105
104
|
/**
|
106
105
|
* list of participants that are actively speaking. when this changes
|
@@ -122,7 +121,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
122
121
|
|
123
122
|
private roomInfo?: RoomModel;
|
124
123
|
|
125
|
-
private
|
124
|
+
private sidToIdentity: Map<string, string>;
|
126
125
|
|
127
126
|
/** connect options of room */
|
128
127
|
private connOptions?: InternalRoomConnectOptions;
|
@@ -141,8 +140,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
141
140
|
|
142
141
|
private e2eeManager: E2EEManager | undefined;
|
143
142
|
|
144
|
-
private cachedParticipantSids: Array<string>;
|
145
|
-
|
146
143
|
private connectionReconcileInterval?: ReturnType<typeof setInterval>;
|
147
144
|
|
148
145
|
private regionUrlProvider?: RegionUrlProvider;
|
@@ -153,6 +150,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
153
150
|
|
154
151
|
private log = log;
|
155
152
|
|
153
|
+
private bufferedEvents: Array<any> = [];
|
154
|
+
|
155
|
+
private isResuming: boolean = false;
|
156
|
+
|
156
157
|
/**
|
157
158
|
* Creates a new Room, the primary construct for a LiveKit session.
|
158
159
|
* @param options
|
@@ -160,9 +161,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
160
161
|
constructor(options?: RoomOptions) {
|
161
162
|
super();
|
162
163
|
this.setMaxListeners(100);
|
163
|
-
this.
|
164
|
-
this.
|
165
|
-
this.identityToSid = new Map();
|
164
|
+
this.remoteParticipants = new Map();
|
165
|
+
this.sidToIdentity = new Map();
|
166
166
|
this.options = { ...roomOptionDefaults, ...options };
|
167
167
|
|
168
168
|
this.log = getLogger(this.options.loggerName ?? LoggerNames.Room);
|
@@ -246,7 +246,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
246
246
|
private get logContext() {
|
247
247
|
return {
|
248
248
|
room: this.name,
|
249
|
-
roomSid: this.sid,
|
249
|
+
roomSid: this.roomInfo?.sid,
|
250
250
|
identity: this.localParticipant.identity,
|
251
251
|
};
|
252
252
|
}
|
@@ -258,9 +258,30 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
258
258
|
return this.roomInfo?.activeRecording ?? false;
|
259
259
|
}
|
260
260
|
|
261
|
-
/**
|
262
|
-
|
263
|
-
|
261
|
+
/**
|
262
|
+
* server assigned unique room id.
|
263
|
+
* returns once a sid has been issued by the server.
|
264
|
+
*/
|
265
|
+
async getSid(): Promise<string> {
|
266
|
+
if (this.state === ConnectionState.Disconnected) {
|
267
|
+
return '';
|
268
|
+
}
|
269
|
+
if (this.roomInfo && this.roomInfo.sid !== '') {
|
270
|
+
return this.roomInfo.sid;
|
271
|
+
}
|
272
|
+
return new Promise((resolve, reject) => {
|
273
|
+
const handleRoomUpdate = (roomInfo: RoomModel) => {
|
274
|
+
if (roomInfo.sid !== '') {
|
275
|
+
this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
|
276
|
+
resolve(roomInfo.sid);
|
277
|
+
}
|
278
|
+
};
|
279
|
+
this.engine.on(EngineEvent.RoomUpdate, handleRoomUpdate);
|
280
|
+
this.once(RoomEvent.Disconnected, () => {
|
281
|
+
this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
|
282
|
+
reject('Room disconnected before room server id was available');
|
283
|
+
});
|
284
|
+
});
|
264
285
|
}
|
265
286
|
|
266
287
|
/** user assigned name, derived from JWT token */
|
@@ -309,26 +330,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
309
330
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
310
331
|
.on(EngineEvent.Resuming, () => {
|
311
332
|
this.clearConnectionReconcile();
|
312
|
-
|
313
|
-
|
314
|
-
}
|
315
|
-
this.cachedParticipantSids = Array.from(this.participants.keys());
|
333
|
+
this.isResuming = true;
|
334
|
+
this.log.info('Resuming signal connection', this.logContext);
|
316
335
|
})
|
317
336
|
.on(EngineEvent.Resumed, () => {
|
318
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
319
|
-
this.emit(RoomEvent.Reconnected);
|
320
337
|
this.registerConnectionReconcile();
|
338
|
+
this.isResuming = false;
|
339
|
+
this.log.info('Resumed signal connection', this.logContext);
|
321
340
|
this.updateSubscriptions();
|
322
|
-
|
323
|
-
// once reconnected, figure out if any participants connected during reconnect and emit events for it
|
324
|
-
const diffParticipants = Array.from(this.participants.values()).filter(
|
325
|
-
(p) => !this.cachedParticipantSids.includes(p.sid),
|
326
|
-
);
|
327
|
-
diffParticipants.forEach((p) => this.emit(RoomEvent.ParticipantConnected, p));
|
328
|
-
this.cachedParticipantSids = [];
|
341
|
+
this.emitBufferedEvents();
|
329
342
|
})
|
330
343
|
.on(EngineEvent.SignalResumed, () => {
|
331
|
-
|
344
|
+
this.bufferedEvents = [];
|
345
|
+
if (this.state === ConnectionState.Reconnecting || this.isResuming) {
|
332
346
|
this.sendSyncState();
|
333
347
|
}
|
334
348
|
})
|
@@ -515,7 +529,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
515
529
|
token,
|
516
530
|
{
|
517
531
|
autoSubscribe: connectOptions.autoSubscribe,
|
518
|
-
publishOnly: connectOptions.publishOnly,
|
519
532
|
adaptiveStream:
|
520
533
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
521
534
|
maxRetries: connectOptions.maxRetries,
|
@@ -578,7 +591,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
578
591
|
opts: RoomConnectOptions | undefined,
|
579
592
|
abortController: AbortController,
|
580
593
|
) => {
|
581
|
-
if (
|
594
|
+
if (
|
595
|
+
this.state === ConnectionState.Reconnecting ||
|
596
|
+
this.isResuming ||
|
597
|
+
this.engine?.pendingReconnect
|
598
|
+
) {
|
582
599
|
this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
|
583
600
|
// make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
|
584
601
|
this.recreateEngine();
|
@@ -680,7 +697,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
680
697
|
});
|
681
698
|
if (
|
682
699
|
this.state === ConnectionState.Connecting ||
|
683
|
-
this.state === ConnectionState.Reconnecting
|
700
|
+
this.state === ConnectionState.Reconnecting ||
|
701
|
+
this.isResuming
|
684
702
|
) {
|
685
703
|
// try aborting pending connection attempt
|
686
704
|
this.log.warn('abort connection attempt', this.logContext);
|
@@ -714,10 +732,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
714
732
|
if (this.localParticipant.identity === identity) {
|
715
733
|
return this.localParticipant;
|
716
734
|
}
|
717
|
-
|
718
|
-
if (sid) {
|
719
|
-
return this.participants.get(sid);
|
720
|
-
}
|
735
|
+
return this.remoteParticipants.get(identity);
|
721
736
|
}
|
722
737
|
|
723
738
|
private clearConnectionFutures() {
|
@@ -881,17 +896,25 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
881
896
|
}
|
882
897
|
// set the srcObject to null on page hide in order to prevent lock screen controls to show up for it
|
883
898
|
dummyAudioEl.srcObject = document.hidden ? null : stream;
|
899
|
+
if (!document.hidden) {
|
900
|
+
this.log.debug(
|
901
|
+
'page visible again, triggering startAudio to resume playback and update playback status',
|
902
|
+
this.logContext,
|
903
|
+
);
|
904
|
+
this.startAudio();
|
905
|
+
}
|
884
906
|
});
|
885
907
|
document.body.append(dummyAudioEl);
|
886
908
|
this.once(RoomEvent.Disconnected, () => {
|
887
909
|
dummyAudioEl?.remove();
|
910
|
+
dummyAudioEl = null;
|
888
911
|
});
|
889
912
|
}
|
890
913
|
elements.push(dummyAudioEl);
|
891
914
|
}
|
892
915
|
|
893
|
-
this.
|
894
|
-
p.
|
916
|
+
this.remoteParticipants.forEach((p) => {
|
917
|
+
p.audioTrackPublications.forEach((t) => {
|
895
918
|
if (t.track) {
|
896
919
|
t.track.attachedElements.forEach((e) => {
|
897
920
|
elements.push(e);
|
@@ -917,8 +940,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
917
940
|
|
918
941
|
startVideo = async () => {
|
919
942
|
const elements: HTMLMediaElement[] = [];
|
920
|
-
for (const p of this.
|
921
|
-
p.
|
943
|
+
for (const p of this.remoteParticipants.values()) {
|
944
|
+
p.videoTrackPublications.forEach((tr) => {
|
922
945
|
tr.track?.attachedElements.forEach((el) => {
|
923
946
|
if (!elements.includes(el)) {
|
924
947
|
elements.push(el);
|
@@ -956,15 +979,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
956
979
|
return !this.isVideoPlaybackBlocked;
|
957
980
|
}
|
958
981
|
|
959
|
-
/**
|
960
|
-
* Returns the active audio output device used in this room.
|
961
|
-
* @return the previously successfully set audio output device ID or an empty string if the default device is used.
|
962
|
-
* @deprecated use `getActiveDevice('audiooutput')` instead
|
963
|
-
*/
|
964
|
-
getActiveAudioOutputDevice(): string {
|
965
|
-
return this.options.audioOutput?.deviceId ?? '';
|
966
|
-
}
|
967
|
-
|
968
982
|
getActiveDevice(kind: MediaDeviceKind): string | undefined {
|
969
983
|
return this.localParticipant.activeDeviceMap.get(kind);
|
970
984
|
}
|
@@ -987,7 +1001,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
987
1001
|
const prevDeviceId = this.options.audioCaptureDefaults!.deviceId;
|
988
1002
|
this.options.audioCaptureDefaults!.deviceId = deviceConstraint;
|
989
1003
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
990
|
-
const tracks = Array.from(this.localParticipant.
|
1004
|
+
const tracks = Array.from(this.localParticipant.audioTrackPublications.values()).filter(
|
991
1005
|
(track) => track.source === Track.Source.Microphone,
|
992
1006
|
);
|
993
1007
|
try {
|
@@ -1002,7 +1016,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1002
1016
|
const prevDeviceId = this.options.videoCaptureDefaults!.deviceId;
|
1003
1017
|
this.options.videoCaptureDefaults!.deviceId = deviceConstraint;
|
1004
1018
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1005
|
-
const tracks = Array.from(this.localParticipant.
|
1019
|
+
const tracks = Array.from(this.localParticipant.videoTrackPublications.values()).filter(
|
1006
1020
|
(track) => track.source === Track.Source.Camera,
|
1007
1021
|
);
|
1008
1022
|
try {
|
@@ -1015,8 +1029,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1015
1029
|
}
|
1016
1030
|
} else if (kind === 'audiooutput') {
|
1017
1031
|
if (
|
1018
|
-
(!supportsSetSinkId() && !this.options.
|
1019
|
-
(this.options.
|
1032
|
+
(!supportsSetSinkId() && !this.options.webAudioMix) ||
|
1033
|
+
(this.options.webAudioMix && this.audioContext && !('setSinkId' in this.audioContext))
|
1020
1034
|
) {
|
1021
1035
|
throw new Error('cannot switch audio output, setSinkId not supported');
|
1022
1036
|
}
|
@@ -1026,12 +1040,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1026
1040
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1027
1041
|
|
1028
1042
|
try {
|
1029
|
-
if (this.options.
|
1043
|
+
if (this.options.webAudioMix) {
|
1030
1044
|
// @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
|
1031
1045
|
this.audioContext?.setSinkId(deviceId);
|
1032
1046
|
} else {
|
1033
1047
|
await Promise.all(
|
1034
|
-
Array.from(this.
|
1048
|
+
Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
|
1035
1049
|
);
|
1036
1050
|
}
|
1037
1051
|
} catch (e) {
|
@@ -1068,10 +1082,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1068
1082
|
this.engine?.close();
|
1069
1083
|
/* @ts-ignore */
|
1070
1084
|
this.engine = undefined;
|
1085
|
+
this.isResuming = false;
|
1071
1086
|
|
1072
1087
|
// clear out existing remote participants, since they may have attached
|
1073
1088
|
// the old engine
|
1074
|
-
this.
|
1089
|
+
this.remoteParticipants.clear();
|
1090
|
+
this.sidToIdentity.clear();
|
1091
|
+
this.bufferedEvents = [];
|
1075
1092
|
this.maybeCreateEngine();
|
1076
1093
|
}
|
1077
1094
|
|
@@ -1105,23 +1122,25 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1105
1122
|
return;
|
1106
1123
|
}
|
1107
1124
|
const parts = unpackStreamId(stream.id);
|
1108
|
-
const
|
1125
|
+
const participantSid = parts[0];
|
1109
1126
|
let streamId = parts[1];
|
1110
1127
|
let trackId = mediaTrack.id;
|
1111
1128
|
// firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream
|
1112
1129
|
// and generates its own track id instead of infer from sdp track id.
|
1113
1130
|
if (streamId && streamId.startsWith('TR')) trackId = streamId;
|
1114
1131
|
|
1115
|
-
if (
|
1132
|
+
if (participantSid === this.localParticipant.sid) {
|
1116
1133
|
this.log.warn('tried to create RemoteParticipant for local participant', this.logContext);
|
1117
1134
|
return;
|
1118
1135
|
}
|
1119
1136
|
|
1120
|
-
const participant = this.
|
1137
|
+
const participant = Array.from(this.remoteParticipants.values()).find(
|
1138
|
+
(p) => p.sid === participantSid,
|
1139
|
+
) as RemoteParticipant | undefined;
|
1121
1140
|
|
1122
1141
|
if (!participant) {
|
1123
1142
|
this.log.error(
|
1124
|
-
`Tried to add a track for a participant, that's not present. Sid: ${
|
1143
|
+
`Tried to add a track for a participant, that's not present. Sid: ${participantSid}`,
|
1125
1144
|
this.logContext,
|
1126
1145
|
);
|
1127
1146
|
return;
|
@@ -1146,9 +1165,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1146
1165
|
|
1147
1166
|
private handleRestarting = () => {
|
1148
1167
|
this.clearConnectionReconcile();
|
1168
|
+
// in case we went from resuming to full-reconnect, make sure to reflect it on the isResuming flag
|
1169
|
+
this.isResuming = false;
|
1170
|
+
|
1149
1171
|
// also unwind existing participants & existing subscriptions
|
1150
|
-
for (const p of this.
|
1151
|
-
this.handleParticipantDisconnected(p.
|
1172
|
+
for (const p of this.remoteParticipants.values()) {
|
1173
|
+
this.handleParticipantDisconnected(p.identity, p);
|
1152
1174
|
}
|
1153
1175
|
|
1154
1176
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
@@ -1161,8 +1183,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1161
1183
|
...this.logContext,
|
1162
1184
|
region: joinResponse.serverRegion,
|
1163
1185
|
});
|
1186
|
+
this.bufferedEvents = [];
|
1164
1187
|
|
1165
|
-
this.cachedParticipantSids = [];
|
1166
1188
|
this.applyJoinResponse(joinResponse);
|
1167
1189
|
|
1168
1190
|
try {
|
@@ -1188,15 +1210,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1188
1210
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
1189
1211
|
this.emit(RoomEvent.Reconnected);
|
1190
1212
|
this.registerConnectionReconcile();
|
1191
|
-
|
1192
|
-
// emit participant connected events after connection has been re-established
|
1193
|
-
this.participants.forEach((participant) => {
|
1194
|
-
this.emit(RoomEvent.ParticipantConnected, participant);
|
1195
|
-
});
|
1213
|
+
this.emitBufferedEvents();
|
1196
1214
|
};
|
1197
1215
|
|
1198
1216
|
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
|
1199
1217
|
this.clearConnectionReconcile();
|
1218
|
+
this.isResuming = false;
|
1219
|
+
this.bufferedEvents = [];
|
1200
1220
|
if (this.state === ConnectionState.Disconnected) {
|
1201
1221
|
return;
|
1202
1222
|
}
|
@@ -1204,13 +1224,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1204
1224
|
this.regionUrl = undefined;
|
1205
1225
|
|
1206
1226
|
try {
|
1207
|
-
this.
|
1208
|
-
p.
|
1227
|
+
this.remoteParticipants.forEach((p) => {
|
1228
|
+
p.trackPublications.forEach((pub) => {
|
1209
1229
|
p.unpublishTrack(pub.trackSid);
|
1210
1230
|
});
|
1211
1231
|
});
|
1212
1232
|
|
1213
|
-
this.localParticipant.
|
1233
|
+
this.localParticipant.trackPublications.forEach((pub) => {
|
1214
1234
|
if (pub.track) {
|
1215
1235
|
this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
|
1216
1236
|
}
|
@@ -1235,13 +1255,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1235
1255
|
this.onLocalParticipantPermissionsChanged,
|
1236
1256
|
);
|
1237
1257
|
|
1238
|
-
this.localParticipant.
|
1239
|
-
this.localParticipant.
|
1240
|
-
this.localParticipant.
|
1258
|
+
this.localParticipant.trackPublications.clear();
|
1259
|
+
this.localParticipant.videoTrackPublications.clear();
|
1260
|
+
this.localParticipant.audioTrackPublications.clear();
|
1241
1261
|
|
1242
|
-
this.
|
1262
|
+
this.remoteParticipants.clear();
|
1263
|
+
this.sidToIdentity.clear();
|
1243
1264
|
this.activeSpeakers = [];
|
1244
|
-
if (this.audioContext && typeof this.options.
|
1265
|
+
if (this.audioContext && typeof this.options.webAudioMix === 'boolean') {
|
1245
1266
|
this.audioContext.close();
|
1246
1267
|
this.audioContext = undefined;
|
1247
1268
|
}
|
@@ -1265,39 +1286,32 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1265
1286
|
return;
|
1266
1287
|
}
|
1267
1288
|
|
1268
|
-
//
|
1269
|
-
|
1270
|
-
if (
|
1271
|
-
|
1272
|
-
this.handleParticipantDisconnected(sid, this.participants.get(sid));
|
1289
|
+
// LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
|
1290
|
+
// so we try to map an empty identity to an already known sID manually
|
1291
|
+
if (info.identity === '') {
|
1292
|
+
info.identity = this.sidToIdentity.get(info.sid) ?? '';
|
1273
1293
|
}
|
1274
1294
|
|
1275
|
-
let remoteParticipant = this.
|
1276
|
-
const isNewParticipant = !remoteParticipant;
|
1295
|
+
let remoteParticipant = this.remoteParticipants.get(info.identity);
|
1277
1296
|
|
1278
1297
|
// when it's disconnected, send updates
|
1279
1298
|
if (info.state === ParticipantInfo_State.DISCONNECTED) {
|
1280
|
-
this.handleParticipantDisconnected(info.
|
1299
|
+
this.handleParticipantDisconnected(info.identity, remoteParticipant);
|
1281
1300
|
} else {
|
1282
1301
|
// create participant if doesn't exist
|
1283
|
-
remoteParticipant = this.getOrCreateParticipant(info.
|
1284
|
-
if (!isNewParticipant) {
|
1285
|
-
// just update, no events
|
1286
|
-
remoteParticipant.updateInfo(info);
|
1287
|
-
}
|
1302
|
+
remoteParticipant = this.getOrCreateParticipant(info.identity, info);
|
1288
1303
|
}
|
1289
1304
|
});
|
1290
1305
|
};
|
1291
1306
|
|
1292
|
-
private handleParticipantDisconnected(
|
1307
|
+
private handleParticipantDisconnected(identity: string, participant?: RemoteParticipant) {
|
1293
1308
|
// remove and send event
|
1294
|
-
this.
|
1309
|
+
this.remoteParticipants.delete(identity);
|
1295
1310
|
if (!participant) {
|
1296
1311
|
return;
|
1297
1312
|
}
|
1298
1313
|
|
1299
|
-
|
1300
|
-
participant.tracks.forEach((publication) => {
|
1314
|
+
participant.trackPublications.forEach((publication) => {
|
1301
1315
|
participant.unpublishTrack(publication.trackSid, true);
|
1302
1316
|
});
|
1303
1317
|
this.emit(RoomEvent.ParticipantDisconnected, participant);
|
@@ -1314,7 +1328,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1314
1328
|
this.localParticipant.setIsSpeaking(true);
|
1315
1329
|
activeSpeakers.push(this.localParticipant);
|
1316
1330
|
} else {
|
1317
|
-
const p = this.
|
1331
|
+
const p = this.getRemoteParticipantBySid(speaker.sid);
|
1318
1332
|
if (p) {
|
1319
1333
|
p.audioLevel = speaker.level;
|
1320
1334
|
p.setIsSpeaking(true);
|
@@ -1327,7 +1341,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1327
1341
|
this.localParticipant.audioLevel = 0;
|
1328
1342
|
this.localParticipant.setIsSpeaking(false);
|
1329
1343
|
}
|
1330
|
-
this.
|
1344
|
+
this.remoteParticipants.forEach((p) => {
|
1331
1345
|
if (!seenSids[p.sid]) {
|
1332
1346
|
p.audioLevel = 0;
|
1333
1347
|
p.setIsSpeaking(false);
|
@@ -1345,7 +1359,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1345
1359
|
lastSpeakers.set(p.sid, p);
|
1346
1360
|
});
|
1347
1361
|
speakerUpdates.forEach((speaker) => {
|
1348
|
-
let p: Participant | undefined = this.
|
1362
|
+
let p: Participant | undefined = this.getRemoteParticipantBySid(speaker.sid);
|
1349
1363
|
if (speaker.sid === this.localParticipant.sid) {
|
1350
1364
|
p = this.localParticipant;
|
1351
1365
|
}
|
@@ -1369,11 +1383,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1369
1383
|
|
1370
1384
|
private handleStreamStateUpdate = (streamStateUpdate: StreamStateUpdate) => {
|
1371
1385
|
streamStateUpdate.streamStates.forEach((streamState) => {
|
1372
|
-
const participant = this.
|
1386
|
+
const participant = this.getRemoteParticipantBySid(streamState.participantSid);
|
1373
1387
|
if (!participant) {
|
1374
1388
|
return;
|
1375
1389
|
}
|
1376
|
-
const pub = participant.
|
1390
|
+
const pub = participant.getTrackPublicationBySid(streamState.trackSid);
|
1377
1391
|
if (!pub || !pub.track) {
|
1378
1392
|
return;
|
1379
1393
|
}
|
@@ -1389,11 +1403,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1389
1403
|
};
|
1390
1404
|
|
1391
1405
|
private handleSubscriptionPermissionUpdate = (update: SubscriptionPermissionUpdate) => {
|
1392
|
-
const participant = this.
|
1406
|
+
const participant = this.getRemoteParticipantBySid(update.participantSid);
|
1393
1407
|
if (!participant) {
|
1394
1408
|
return;
|
1395
1409
|
}
|
1396
|
-
const pub = participant.
|
1410
|
+
const pub = participant.getTrackPublicationBySid(update.trackSid);
|
1397
1411
|
if (!pub) {
|
1398
1412
|
return;
|
1399
1413
|
}
|
@@ -1402,13 +1416,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1402
1416
|
};
|
1403
1417
|
|
1404
1418
|
private handleSubscriptionError = (update: SubscriptionResponse) => {
|
1405
|
-
const participant = Array.from(this.
|
1406
|
-
p.
|
1419
|
+
const participant = Array.from(this.remoteParticipants.values()).find((p) =>
|
1420
|
+
p.trackPublications.has(update.trackSid),
|
1407
1421
|
);
|
1408
1422
|
if (!participant) {
|
1409
1423
|
return;
|
1410
1424
|
}
|
1411
|
-
const pub = participant.
|
1425
|
+
const pub = participant.getTrackPublicationBySid(update.trackSid);
|
1412
1426
|
if (!pub) {
|
1413
1427
|
return;
|
1414
1428
|
}
|
@@ -1418,7 +1432,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1418
1432
|
|
1419
1433
|
private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
|
1420
1434
|
// find the participant
|
1421
|
-
const participant = this.
|
1435
|
+
const participant = this.remoteParticipants.get(userPacket.participantIdentity);
|
1422
1436
|
|
1423
1437
|
this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
|
1424
1438
|
|
@@ -1478,7 +1492,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1478
1492
|
this.localParticipant.setConnectionQuality(info.quality);
|
1479
1493
|
return;
|
1480
1494
|
}
|
1481
|
-
const participant = this.
|
1495
|
+
const participant = this.getRemoteParticipantBySid(info.participantSid);
|
1482
1496
|
if (participant) {
|
1483
1497
|
participant.setConnectionQuality(info.quality);
|
1484
1498
|
}
|
@@ -1486,12 +1500,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1486
1500
|
};
|
1487
1501
|
|
1488
1502
|
private async acquireAudioContext() {
|
1489
|
-
if (
|
1490
|
-
typeof this.options.expWebAudioMix !== 'boolean' &&
|
1491
|
-
this.options.expWebAudioMix.audioContext
|
1492
|
-
) {
|
1503
|
+
if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) {
|
1493
1504
|
// override audio context with custom audio context if supplied by user
|
1494
|
-
this.audioContext = this.options.
|
1505
|
+
this.audioContext = this.options.webAudioMix.audioContext;
|
1495
1506
|
} else if (!this.audioContext || this.audioContext.state === 'closed') {
|
1496
1507
|
// by using an AudioContext, it reduces lag on audio elements
|
1497
1508
|
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
|
@@ -1508,8 +1519,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1508
1519
|
}
|
1509
1520
|
}
|
1510
1521
|
|
1511
|
-
if (this.options.
|
1512
|
-
this.
|
1522
|
+
if (this.options.webAudioMix) {
|
1523
|
+
this.remoteParticipants.forEach((participant) =>
|
1524
|
+
participant.setAudioContext(this.audioContext),
|
1525
|
+
);
|
1513
1526
|
}
|
1514
1527
|
|
1515
1528
|
this.localParticipant.setAudioContext(this.audioContext);
|
@@ -1521,17 +1534,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1521
1534
|
}
|
1522
1535
|
}
|
1523
1536
|
|
1524
|
-
private createParticipant(
|
1537
|
+
private createParticipant(identity: string, info?: ParticipantInfo): RemoteParticipant {
|
1525
1538
|
let participant: RemoteParticipant;
|
1526
1539
|
if (info) {
|
1527
1540
|
participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
|
1528
1541
|
} else {
|
1529
|
-
participant = new RemoteParticipant(this.engine.client,
|
1542
|
+
participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
|
1530
1543
|
loggerContextCb: () => this.logContext,
|
1531
1544
|
loggerName: this.options.loggerName,
|
1532
1545
|
});
|
1533
1546
|
}
|
1534
|
-
if (this.options.
|
1547
|
+
if (this.options.webAudioMix) {
|
1535
1548
|
participant.setAudioContext(this.audioContext);
|
1536
1549
|
}
|
1537
1550
|
if (this.options.audioOutput?.deviceId) {
|
@@ -1542,14 +1555,21 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1542
1555
|
return participant;
|
1543
1556
|
}
|
1544
1557
|
|
1545
|
-
private getOrCreateParticipant(
|
1546
|
-
if (this.
|
1547
|
-
|
1558
|
+
private getOrCreateParticipant(identity: string, info: ParticipantInfo): RemoteParticipant {
|
1559
|
+
if (this.remoteParticipants.has(identity)) {
|
1560
|
+
const existingParticipant = this.remoteParticipants.get(identity)!;
|
1561
|
+
if (info) {
|
1562
|
+
const wasUpdated = existingParticipant.updateInfo(info);
|
1563
|
+
if (wasUpdated) {
|
1564
|
+
this.sidToIdentity.set(info.sid, info.identity);
|
1565
|
+
}
|
1566
|
+
}
|
1567
|
+
return existingParticipant;
|
1548
1568
|
}
|
1549
|
-
const participant = this.createParticipant(
|
1550
|
-
this.
|
1569
|
+
const participant = this.createParticipant(identity, info);
|
1570
|
+
this.remoteParticipants.set(identity, participant);
|
1551
1571
|
|
1552
|
-
this.
|
1572
|
+
this.sidToIdentity.set(info.sid, info.identity);
|
1553
1573
|
// if we have valid info and the participant wasn't in the map before, we can assume the participant is new
|
1554
1574
|
// firing here to make sure that `ParticipantConnected` fires before the initial track events
|
1555
1575
|
this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
|
@@ -1635,11 +1655,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1635
1655
|
}
|
1636
1656
|
|
1637
1657
|
private sendSyncState() {
|
1638
|
-
const remoteTracks = Array.from(this.
|
1639
|
-
acc.push(...(participant.
|
1658
|
+
const remoteTracks = Array.from(this.remoteParticipants.values()).reduce((acc, participant) => {
|
1659
|
+
acc.push(...(participant.getTrackPublications() as RemoteTrackPublication[])); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
|
1640
1660
|
return acc;
|
1641
1661
|
}, [] as RemoteTrackPublication[]);
|
1642
|
-
const localTracks = this.localParticipant.
|
1662
|
+
const localTracks = this.localParticipant.getTrackPublications() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
|
1643
1663
|
this.engine.sendSyncState(remoteTracks, localTracks);
|
1644
1664
|
}
|
1645
1665
|
|
@@ -1648,8 +1668,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1648
1668
|
* subscription settings.
|
1649
1669
|
*/
|
1650
1670
|
private updateSubscriptions() {
|
1651
|
-
for (const p of this.
|
1652
|
-
for (const pub of p.
|
1671
|
+
for (const p of this.remoteParticipants.values()) {
|
1672
|
+
for (const pub of p.videoTrackPublications.values()) {
|
1653
1673
|
if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
|
1654
1674
|
pub.emitTrackUpdate();
|
1655
1675
|
}
|
@@ -1657,6 +1677,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1657
1677
|
}
|
1658
1678
|
}
|
1659
1679
|
|
1680
|
+
private getRemoteParticipantBySid(sid: string): RemoteParticipant | undefined {
|
1681
|
+
const identity = this.sidToIdentity.get(sid);
|
1682
|
+
if (identity) {
|
1683
|
+
return this.remoteParticipants.get(identity);
|
1684
|
+
}
|
1685
|
+
}
|
1686
|
+
|
1660
1687
|
private registerConnectionReconcile() {
|
1661
1688
|
this.clearConnectionReconcile();
|
1662
1689
|
let consecutiveFailures = 0;
|
@@ -1707,11 +1734,26 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1707
1734
|
return true;
|
1708
1735
|
}
|
1709
1736
|
|
1737
|
+
private emitBufferedEvents() {
|
1738
|
+
this.bufferedEvents.forEach(([ev, args]) => {
|
1739
|
+
this.emit(ev, ...args);
|
1740
|
+
});
|
1741
|
+
this.bufferedEvents = [];
|
1742
|
+
}
|
1743
|
+
|
1710
1744
|
private emitWhenConnected<E extends keyof RoomEventCallbacks>(
|
1711
1745
|
event: E,
|
1712
1746
|
...args: Parameters<RoomEventCallbacks[E]>
|
1713
1747
|
): boolean {
|
1714
|
-
if (
|
1748
|
+
if (
|
1749
|
+
this.state === ConnectionState.Reconnecting ||
|
1750
|
+
this.isResuming ||
|
1751
|
+
!this.engine ||
|
1752
|
+
this.engine.pendingReconnect
|
1753
|
+
) {
|
1754
|
+
// in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected
|
1755
|
+
this.bufferedEvents.push([event, args]);
|
1756
|
+
} else if (this.state === ConnectionState.Connected) {
|
1715
1757
|
return this.emit(event, ...args);
|
1716
1758
|
}
|
1717
1759
|
return false;
|
@@ -1943,8 +1985,6 @@ export type RoomEventCallbacks = {
|
|
1943
1985
|
reconnecting: () => void;
|
1944
1986
|
reconnected: () => void;
|
1945
1987
|
disconnected: (reason?: DisconnectReason) => void;
|
1946
|
-
/** @deprecated stateChanged has been renamed to connectionStateChanged */
|
1947
|
-
stateChanged: (state: ConnectionState) => void;
|
1948
1988
|
connectionStateChanged: (state: ConnectionState) => void;
|
1949
1989
|
mediaDevicesChanged: () => void;
|
1950
1990
|
participantConnected: (participant: RemoteParticipant) => void;
|