livekit-client 1.15.11 → 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 +1573 -1479
- 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 +0 -2
- 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 +12 -15
- 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 +0 -2
- 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 +12 -15
- 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 +2 -6
- 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 +145 -114
- 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;
|
@@ -153,6 +152,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
153
152
|
|
154
153
|
private bufferedEvents: Array<any> = [];
|
155
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,8 +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.
|
164
|
+
this.remoteParticipants = new Map();
|
165
|
+
this.sidToIdentity = new Map();
|
165
166
|
this.options = { ...roomOptionDefaults, ...options };
|
166
167
|
|
167
168
|
this.log = getLogger(this.options.loggerName ?? LoggerNames.Room);
|
@@ -245,7 +246,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
245
246
|
private get logContext() {
|
246
247
|
return {
|
247
248
|
room: this.name,
|
248
|
-
roomSid: this.sid,
|
249
|
+
roomSid: this.roomInfo?.sid,
|
249
250
|
identity: this.localParticipant.identity,
|
250
251
|
};
|
251
252
|
}
|
@@ -257,9 +258,30 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
257
258
|
return this.roomInfo?.activeRecording ?? false;
|
258
259
|
}
|
259
260
|
|
260
|
-
/**
|
261
|
-
|
262
|
-
|
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
|
+
});
|
263
285
|
}
|
264
286
|
|
265
287
|
/** user assigned name, derived from JWT token */
|
@@ -308,20 +330,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
308
330
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
309
331
|
.on(EngineEvent.Resuming, () => {
|
310
332
|
this.clearConnectionReconcile();
|
311
|
-
|
312
|
-
|
313
|
-
}
|
333
|
+
this.isResuming = true;
|
334
|
+
this.log.info('Resuming signal connection', this.logContext);
|
314
335
|
})
|
315
336
|
.on(EngineEvent.Resumed, () => {
|
316
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
317
|
-
this.emit(RoomEvent.Reconnected);
|
318
337
|
this.registerConnectionReconcile();
|
338
|
+
this.isResuming = false;
|
339
|
+
this.log.info('Resumed signal connection', this.logContext);
|
319
340
|
this.updateSubscriptions();
|
320
341
|
this.emitBufferedEvents();
|
321
342
|
})
|
322
343
|
.on(EngineEvent.SignalResumed, () => {
|
323
344
|
this.bufferedEvents = [];
|
324
|
-
if (this.state === ConnectionState.Reconnecting) {
|
345
|
+
if (this.state === ConnectionState.Reconnecting || this.isResuming) {
|
325
346
|
this.sendSyncState();
|
326
347
|
}
|
327
348
|
})
|
@@ -508,7 +529,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
508
529
|
token,
|
509
530
|
{
|
510
531
|
autoSubscribe: connectOptions.autoSubscribe,
|
511
|
-
publishOnly: connectOptions.publishOnly,
|
512
532
|
adaptiveStream:
|
513
533
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
514
534
|
maxRetries: connectOptions.maxRetries,
|
@@ -571,7 +591,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
571
591
|
opts: RoomConnectOptions | undefined,
|
572
592
|
abortController: AbortController,
|
573
593
|
) => {
|
574
|
-
if (
|
594
|
+
if (
|
595
|
+
this.state === ConnectionState.Reconnecting ||
|
596
|
+
this.isResuming ||
|
597
|
+
this.engine?.pendingReconnect
|
598
|
+
) {
|
575
599
|
this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
|
576
600
|
// make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
|
577
601
|
this.recreateEngine();
|
@@ -673,7 +697,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
673
697
|
});
|
674
698
|
if (
|
675
699
|
this.state === ConnectionState.Connecting ||
|
676
|
-
this.state === ConnectionState.Reconnecting
|
700
|
+
this.state === ConnectionState.Reconnecting ||
|
701
|
+
this.isResuming
|
677
702
|
) {
|
678
703
|
// try aborting pending connection attempt
|
679
704
|
this.log.warn('abort connection attempt', this.logContext);
|
@@ -707,10 +732,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
707
732
|
if (this.localParticipant.identity === identity) {
|
708
733
|
return this.localParticipant;
|
709
734
|
}
|
710
|
-
|
711
|
-
if (sid) {
|
712
|
-
return this.participants.get(sid);
|
713
|
-
}
|
735
|
+
return this.remoteParticipants.get(identity);
|
714
736
|
}
|
715
737
|
|
716
738
|
private clearConnectionFutures() {
|
@@ -891,8 +913,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
891
913
|
elements.push(dummyAudioEl);
|
892
914
|
}
|
893
915
|
|
894
|
-
this.
|
895
|
-
p.
|
916
|
+
this.remoteParticipants.forEach((p) => {
|
917
|
+
p.audioTrackPublications.forEach((t) => {
|
896
918
|
if (t.track) {
|
897
919
|
t.track.attachedElements.forEach((e) => {
|
898
920
|
elements.push(e);
|
@@ -918,8 +940,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
918
940
|
|
919
941
|
startVideo = async () => {
|
920
942
|
const elements: HTMLMediaElement[] = [];
|
921
|
-
for (const p of this.
|
922
|
-
p.
|
943
|
+
for (const p of this.remoteParticipants.values()) {
|
944
|
+
p.videoTrackPublications.forEach((tr) => {
|
923
945
|
tr.track?.attachedElements.forEach((el) => {
|
924
946
|
if (!elements.includes(el)) {
|
925
947
|
elements.push(el);
|
@@ -957,15 +979,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
957
979
|
return !this.isVideoPlaybackBlocked;
|
958
980
|
}
|
959
981
|
|
960
|
-
/**
|
961
|
-
* Returns the active audio output device used in this room.
|
962
|
-
* @return the previously successfully set audio output device ID or an empty string if the default device is used.
|
963
|
-
* @deprecated use `getActiveDevice('audiooutput')` instead
|
964
|
-
*/
|
965
|
-
getActiveAudioOutputDevice(): string {
|
966
|
-
return this.options.audioOutput?.deviceId ?? '';
|
967
|
-
}
|
968
|
-
|
969
982
|
getActiveDevice(kind: MediaDeviceKind): string | undefined {
|
970
983
|
return this.localParticipant.activeDeviceMap.get(kind);
|
971
984
|
}
|
@@ -988,7 +1001,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
988
1001
|
const prevDeviceId = this.options.audioCaptureDefaults!.deviceId;
|
989
1002
|
this.options.audioCaptureDefaults!.deviceId = deviceConstraint;
|
990
1003
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
991
|
-
const tracks = Array.from(this.localParticipant.
|
1004
|
+
const tracks = Array.from(this.localParticipant.audioTrackPublications.values()).filter(
|
992
1005
|
(track) => track.source === Track.Source.Microphone,
|
993
1006
|
);
|
994
1007
|
try {
|
@@ -1003,7 +1016,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1003
1016
|
const prevDeviceId = this.options.videoCaptureDefaults!.deviceId;
|
1004
1017
|
this.options.videoCaptureDefaults!.deviceId = deviceConstraint;
|
1005
1018
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1006
|
-
const tracks = Array.from(this.localParticipant.
|
1019
|
+
const tracks = Array.from(this.localParticipant.videoTrackPublications.values()).filter(
|
1007
1020
|
(track) => track.source === Track.Source.Camera,
|
1008
1021
|
);
|
1009
1022
|
try {
|
@@ -1016,8 +1029,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1016
1029
|
}
|
1017
1030
|
} else if (kind === 'audiooutput') {
|
1018
1031
|
if (
|
1019
|
-
(!supportsSetSinkId() && !this.options.
|
1020
|
-
(this.options.
|
1032
|
+
(!supportsSetSinkId() && !this.options.webAudioMix) ||
|
1033
|
+
(this.options.webAudioMix && this.audioContext && !('setSinkId' in this.audioContext))
|
1021
1034
|
) {
|
1022
1035
|
throw new Error('cannot switch audio output, setSinkId not supported');
|
1023
1036
|
}
|
@@ -1027,12 +1040,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1027
1040
|
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1028
1041
|
|
1029
1042
|
try {
|
1030
|
-
if (this.options.
|
1043
|
+
if (this.options.webAudioMix) {
|
1031
1044
|
// @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
|
1032
1045
|
this.audioContext?.setSinkId(deviceId);
|
1033
1046
|
} else {
|
1034
1047
|
await Promise.all(
|
1035
|
-
Array.from(this.
|
1048
|
+
Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
|
1036
1049
|
);
|
1037
1050
|
}
|
1038
1051
|
} catch (e) {
|
@@ -1069,10 +1082,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1069
1082
|
this.engine?.close();
|
1070
1083
|
/* @ts-ignore */
|
1071
1084
|
this.engine = undefined;
|
1085
|
+
this.isResuming = false;
|
1072
1086
|
|
1073
1087
|
// clear out existing remote participants, since they may have attached
|
1074
1088
|
// the old engine
|
1075
|
-
this.
|
1089
|
+
this.remoteParticipants.clear();
|
1090
|
+
this.sidToIdentity.clear();
|
1076
1091
|
this.bufferedEvents = [];
|
1077
1092
|
this.maybeCreateEngine();
|
1078
1093
|
}
|
@@ -1107,23 +1122,25 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1107
1122
|
return;
|
1108
1123
|
}
|
1109
1124
|
const parts = unpackStreamId(stream.id);
|
1110
|
-
const
|
1125
|
+
const participantSid = parts[0];
|
1111
1126
|
let streamId = parts[1];
|
1112
1127
|
let trackId = mediaTrack.id;
|
1113
1128
|
// firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream
|
1114
1129
|
// and generates its own track id instead of infer from sdp track id.
|
1115
1130
|
if (streamId && streamId.startsWith('TR')) trackId = streamId;
|
1116
1131
|
|
1117
|
-
if (
|
1132
|
+
if (participantSid === this.localParticipant.sid) {
|
1118
1133
|
this.log.warn('tried to create RemoteParticipant for local participant', this.logContext);
|
1119
1134
|
return;
|
1120
1135
|
}
|
1121
1136
|
|
1122
|
-
const participant = this.
|
1137
|
+
const participant = Array.from(this.remoteParticipants.values()).find(
|
1138
|
+
(p) => p.sid === participantSid,
|
1139
|
+
) as RemoteParticipant | undefined;
|
1123
1140
|
|
1124
1141
|
if (!participant) {
|
1125
1142
|
this.log.error(
|
1126
|
-
`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}`,
|
1127
1144
|
this.logContext,
|
1128
1145
|
);
|
1129
1146
|
return;
|
@@ -1148,9 +1165,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1148
1165
|
|
1149
1166
|
private handleRestarting = () => {
|
1150
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
|
+
|
1151
1171
|
// also unwind existing participants & existing subscriptions
|
1152
|
-
for (const p of this.
|
1153
|
-
this.handleParticipantDisconnected(p.
|
1172
|
+
for (const p of this.remoteParticipants.values()) {
|
1173
|
+
this.handleParticipantDisconnected(p.identity, p);
|
1154
1174
|
}
|
1155
1175
|
|
1156
1176
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
@@ -1195,6 +1215,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1195
1215
|
|
1196
1216
|
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
|
1197
1217
|
this.clearConnectionReconcile();
|
1218
|
+
this.isResuming = false;
|
1198
1219
|
this.bufferedEvents = [];
|
1199
1220
|
if (this.state === ConnectionState.Disconnected) {
|
1200
1221
|
return;
|
@@ -1203,13 +1224,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1203
1224
|
this.regionUrl = undefined;
|
1204
1225
|
|
1205
1226
|
try {
|
1206
|
-
this.
|
1207
|
-
p.
|
1227
|
+
this.remoteParticipants.forEach((p) => {
|
1228
|
+
p.trackPublications.forEach((pub) => {
|
1208
1229
|
p.unpublishTrack(pub.trackSid);
|
1209
1230
|
});
|
1210
1231
|
});
|
1211
1232
|
|
1212
|
-
this.localParticipant.
|
1233
|
+
this.localParticipant.trackPublications.forEach((pub) => {
|
1213
1234
|
if (pub.track) {
|
1214
1235
|
this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
|
1215
1236
|
}
|
@@ -1234,13 +1255,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1234
1255
|
this.onLocalParticipantPermissionsChanged,
|
1235
1256
|
);
|
1236
1257
|
|
1237
|
-
this.localParticipant.
|
1238
|
-
this.localParticipant.
|
1239
|
-
this.localParticipant.
|
1258
|
+
this.localParticipant.trackPublications.clear();
|
1259
|
+
this.localParticipant.videoTrackPublications.clear();
|
1260
|
+
this.localParticipant.audioTrackPublications.clear();
|
1240
1261
|
|
1241
|
-
this.
|
1262
|
+
this.remoteParticipants.clear();
|
1263
|
+
this.sidToIdentity.clear();
|
1242
1264
|
this.activeSpeakers = [];
|
1243
|
-
if (this.audioContext && typeof this.options.
|
1265
|
+
if (this.audioContext && typeof this.options.webAudioMix === 'boolean') {
|
1244
1266
|
this.audioContext.close();
|
1245
1267
|
this.audioContext = undefined;
|
1246
1268
|
}
|
@@ -1264,39 +1286,32 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1264
1286
|
return;
|
1265
1287
|
}
|
1266
1288
|
|
1267
|
-
//
|
1268
|
-
|
1269
|
-
if (
|
1270
|
-
|
1271
|
-
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) ?? '';
|
1272
1293
|
}
|
1273
1294
|
|
1274
|
-
let remoteParticipant = this.
|
1275
|
-
const isNewParticipant = !remoteParticipant;
|
1295
|
+
let remoteParticipant = this.remoteParticipants.get(info.identity);
|
1276
1296
|
|
1277
1297
|
// when it's disconnected, send updates
|
1278
1298
|
if (info.state === ParticipantInfo_State.DISCONNECTED) {
|
1279
|
-
this.handleParticipantDisconnected(info.
|
1299
|
+
this.handleParticipantDisconnected(info.identity, remoteParticipant);
|
1280
1300
|
} else {
|
1281
1301
|
// create participant if doesn't exist
|
1282
|
-
remoteParticipant = this.getOrCreateParticipant(info.
|
1283
|
-
if (!isNewParticipant) {
|
1284
|
-
// just update, no events
|
1285
|
-
remoteParticipant.updateInfo(info);
|
1286
|
-
}
|
1302
|
+
remoteParticipant = this.getOrCreateParticipant(info.identity, info);
|
1287
1303
|
}
|
1288
1304
|
});
|
1289
1305
|
};
|
1290
1306
|
|
1291
|
-
private handleParticipantDisconnected(
|
1307
|
+
private handleParticipantDisconnected(identity: string, participant?: RemoteParticipant) {
|
1292
1308
|
// remove and send event
|
1293
|
-
this.
|
1309
|
+
this.remoteParticipants.delete(identity);
|
1294
1310
|
if (!participant) {
|
1295
1311
|
return;
|
1296
1312
|
}
|
1297
1313
|
|
1298
|
-
|
1299
|
-
participant.tracks.forEach((publication) => {
|
1314
|
+
participant.trackPublications.forEach((publication) => {
|
1300
1315
|
participant.unpublishTrack(publication.trackSid, true);
|
1301
1316
|
});
|
1302
1317
|
this.emit(RoomEvent.ParticipantDisconnected, participant);
|
@@ -1313,7 +1328,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1313
1328
|
this.localParticipant.setIsSpeaking(true);
|
1314
1329
|
activeSpeakers.push(this.localParticipant);
|
1315
1330
|
} else {
|
1316
|
-
const p = this.
|
1331
|
+
const p = this.getRemoteParticipantBySid(speaker.sid);
|
1317
1332
|
if (p) {
|
1318
1333
|
p.audioLevel = speaker.level;
|
1319
1334
|
p.setIsSpeaking(true);
|
@@ -1326,7 +1341,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1326
1341
|
this.localParticipant.audioLevel = 0;
|
1327
1342
|
this.localParticipant.setIsSpeaking(false);
|
1328
1343
|
}
|
1329
|
-
this.
|
1344
|
+
this.remoteParticipants.forEach((p) => {
|
1330
1345
|
if (!seenSids[p.sid]) {
|
1331
1346
|
p.audioLevel = 0;
|
1332
1347
|
p.setIsSpeaking(false);
|
@@ -1344,7 +1359,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1344
1359
|
lastSpeakers.set(p.sid, p);
|
1345
1360
|
});
|
1346
1361
|
speakerUpdates.forEach((speaker) => {
|
1347
|
-
let p: Participant | undefined = this.
|
1362
|
+
let p: Participant | undefined = this.getRemoteParticipantBySid(speaker.sid);
|
1348
1363
|
if (speaker.sid === this.localParticipant.sid) {
|
1349
1364
|
p = this.localParticipant;
|
1350
1365
|
}
|
@@ -1368,11 +1383,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1368
1383
|
|
1369
1384
|
private handleStreamStateUpdate = (streamStateUpdate: StreamStateUpdate) => {
|
1370
1385
|
streamStateUpdate.streamStates.forEach((streamState) => {
|
1371
|
-
const participant = this.
|
1386
|
+
const participant = this.getRemoteParticipantBySid(streamState.participantSid);
|
1372
1387
|
if (!participant) {
|
1373
1388
|
return;
|
1374
1389
|
}
|
1375
|
-
const pub = participant.
|
1390
|
+
const pub = participant.getTrackPublicationBySid(streamState.trackSid);
|
1376
1391
|
if (!pub || !pub.track) {
|
1377
1392
|
return;
|
1378
1393
|
}
|
@@ -1388,11 +1403,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1388
1403
|
};
|
1389
1404
|
|
1390
1405
|
private handleSubscriptionPermissionUpdate = (update: SubscriptionPermissionUpdate) => {
|
1391
|
-
const participant = this.
|
1406
|
+
const participant = this.getRemoteParticipantBySid(update.participantSid);
|
1392
1407
|
if (!participant) {
|
1393
1408
|
return;
|
1394
1409
|
}
|
1395
|
-
const pub = participant.
|
1410
|
+
const pub = participant.getTrackPublicationBySid(update.trackSid);
|
1396
1411
|
if (!pub) {
|
1397
1412
|
return;
|
1398
1413
|
}
|
@@ -1401,13 +1416,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1401
1416
|
};
|
1402
1417
|
|
1403
1418
|
private handleSubscriptionError = (update: SubscriptionResponse) => {
|
1404
|
-
const participant = Array.from(this.
|
1405
|
-
p.
|
1419
|
+
const participant = Array.from(this.remoteParticipants.values()).find((p) =>
|
1420
|
+
p.trackPublications.has(update.trackSid),
|
1406
1421
|
);
|
1407
1422
|
if (!participant) {
|
1408
1423
|
return;
|
1409
1424
|
}
|
1410
|
-
const pub = participant.
|
1425
|
+
const pub = participant.getTrackPublicationBySid(update.trackSid);
|
1411
1426
|
if (!pub) {
|
1412
1427
|
return;
|
1413
1428
|
}
|
@@ -1417,7 +1432,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1417
1432
|
|
1418
1433
|
private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
|
1419
1434
|
// find the participant
|
1420
|
-
const participant = this.
|
1435
|
+
const participant = this.remoteParticipants.get(userPacket.participantIdentity);
|
1421
1436
|
|
1422
1437
|
this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
|
1423
1438
|
|
@@ -1477,7 +1492,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1477
1492
|
this.localParticipant.setConnectionQuality(info.quality);
|
1478
1493
|
return;
|
1479
1494
|
}
|
1480
|
-
const participant = this.
|
1495
|
+
const participant = this.getRemoteParticipantBySid(info.participantSid);
|
1481
1496
|
if (participant) {
|
1482
1497
|
participant.setConnectionQuality(info.quality);
|
1483
1498
|
}
|
@@ -1485,12 +1500,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1485
1500
|
};
|
1486
1501
|
|
1487
1502
|
private async acquireAudioContext() {
|
1488
|
-
if (
|
1489
|
-
typeof this.options.expWebAudioMix !== 'boolean' &&
|
1490
|
-
this.options.expWebAudioMix.audioContext
|
1491
|
-
) {
|
1503
|
+
if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) {
|
1492
1504
|
// override audio context with custom audio context if supplied by user
|
1493
|
-
this.audioContext = this.options.
|
1505
|
+
this.audioContext = this.options.webAudioMix.audioContext;
|
1494
1506
|
} else if (!this.audioContext || this.audioContext.state === 'closed') {
|
1495
1507
|
// by using an AudioContext, it reduces lag on audio elements
|
1496
1508
|
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
|
@@ -1507,8 +1519,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1507
1519
|
}
|
1508
1520
|
}
|
1509
1521
|
|
1510
|
-
if (this.options.
|
1511
|
-
this.
|
1522
|
+
if (this.options.webAudioMix) {
|
1523
|
+
this.remoteParticipants.forEach((participant) =>
|
1524
|
+
participant.setAudioContext(this.audioContext),
|
1525
|
+
);
|
1512
1526
|
}
|
1513
1527
|
|
1514
1528
|
this.localParticipant.setAudioContext(this.audioContext);
|
@@ -1520,17 +1534,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1520
1534
|
}
|
1521
1535
|
}
|
1522
1536
|
|
1523
|
-
private createParticipant(
|
1537
|
+
private createParticipant(identity: string, info?: ParticipantInfo): RemoteParticipant {
|
1524
1538
|
let participant: RemoteParticipant;
|
1525
1539
|
if (info) {
|
1526
1540
|
participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
|
1527
1541
|
} else {
|
1528
|
-
participant = new RemoteParticipant(this.engine.client,
|
1542
|
+
participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
|
1529
1543
|
loggerContextCb: () => this.logContext,
|
1530
1544
|
loggerName: this.options.loggerName,
|
1531
1545
|
});
|
1532
1546
|
}
|
1533
|
-
if (this.options.
|
1547
|
+
if (this.options.webAudioMix) {
|
1534
1548
|
participant.setAudioContext(this.audioContext);
|
1535
1549
|
}
|
1536
1550
|
if (this.options.audioOutput?.deviceId) {
|
@@ -1541,14 +1555,21 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1541
1555
|
return participant;
|
1542
1556
|
}
|
1543
1557
|
|
1544
|
-
private getOrCreateParticipant(
|
1545
|
-
if (this.
|
1546
|
-
|
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;
|
1547
1568
|
}
|
1548
|
-
const participant = this.createParticipant(
|
1549
|
-
this.
|
1569
|
+
const participant = this.createParticipant(identity, info);
|
1570
|
+
this.remoteParticipants.set(identity, participant);
|
1550
1571
|
|
1551
|
-
this.
|
1572
|
+
this.sidToIdentity.set(info.sid, info.identity);
|
1552
1573
|
// if we have valid info and the participant wasn't in the map before, we can assume the participant is new
|
1553
1574
|
// firing here to make sure that `ParticipantConnected` fires before the initial track events
|
1554
1575
|
this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
|
@@ -1634,11 +1655,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1634
1655
|
}
|
1635
1656
|
|
1636
1657
|
private sendSyncState() {
|
1637
|
-
const remoteTracks = Array.from(this.
|
1638
|
-
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
|
1639
1660
|
return acc;
|
1640
1661
|
}, [] as RemoteTrackPublication[]);
|
1641
|
-
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
|
1642
1663
|
this.engine.sendSyncState(remoteTracks, localTracks);
|
1643
1664
|
}
|
1644
1665
|
|
@@ -1647,8 +1668,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1647
1668
|
* subscription settings.
|
1648
1669
|
*/
|
1649
1670
|
private updateSubscriptions() {
|
1650
|
-
for (const p of this.
|
1651
|
-
for (const pub of p.
|
1671
|
+
for (const p of this.remoteParticipants.values()) {
|
1672
|
+
for (const pub of p.videoTrackPublications.values()) {
|
1652
1673
|
if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
|
1653
1674
|
pub.emitTrackUpdate();
|
1654
1675
|
}
|
@@ -1656,6 +1677,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1656
1677
|
}
|
1657
1678
|
}
|
1658
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
|
+
|
1659
1687
|
private registerConnectionReconcile() {
|
1660
1688
|
this.clearConnectionReconcile();
|
1661
1689
|
let consecutiveFailures = 0;
|
@@ -1717,11 +1745,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1717
1745
|
event: E,
|
1718
1746
|
...args: Parameters<RoomEventCallbacks[E]>
|
1719
1747
|
): boolean {
|
1720
|
-
if (
|
1721
|
-
|
1722
|
-
|
1748
|
+
if (
|
1749
|
+
this.state === ConnectionState.Reconnecting ||
|
1750
|
+
this.isResuming ||
|
1751
|
+
!this.engine ||
|
1752
|
+
this.engine.pendingReconnect
|
1753
|
+
) {
|
1723
1754
|
// in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected
|
1724
1755
|
this.bufferedEvents.push([event, args]);
|
1756
|
+
} else if (this.state === ConnectionState.Connected) {
|
1757
|
+
return this.emit(event, ...args);
|
1725
1758
|
}
|
1726
1759
|
return false;
|
1727
1760
|
}
|
@@ -1952,8 +1985,6 @@ export type RoomEventCallbacks = {
|
|
1952
1985
|
reconnecting: () => void;
|
1953
1986
|
reconnected: () => void;
|
1954
1987
|
disconnected: (reason?: DisconnectReason) => void;
|
1955
|
-
/** @deprecated stateChanged has been renamed to connectionStateChanged */
|
1956
|
-
stateChanged: (state: ConnectionState) => void;
|
1957
1988
|
connectionStateChanged: (state: ConnectionState) => void;
|
1958
1989
|
mediaDevicesChanged: () => void;
|
1959
1990
|
participantConnected: (participant: RemoteParticipant) => void;
|
package/src/room/defaults.ts
CHANGED
@@ -10,10 +10,6 @@ import { AudioPresets, ScreenSharePresets, VideoPresets } from './track/options'
|
|
10
10
|
export const defaultVideoCodec = 'vp8';
|
11
11
|
|
12
12
|
export const publishDefaults: TrackPublishDefaults = {
|
13
|
-
/**
|
14
|
-
* @deprecated
|
15
|
-
*/
|
16
|
-
audioBitrate: AudioPresets.music.maxBitrate,
|
17
13
|
audioPreset: AudioPresets.music,
|
18
14
|
dtx: true,
|
19
15
|
red: true,
|
@@ -41,7 +37,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
|
|
41
37
|
stopLocalTrackOnUnpublish: true,
|
42
38
|
reconnectPolicy: new DefaultReconnectPolicy(),
|
43
39
|
disconnectOnPageLeave: true,
|
44
|
-
|
40
|
+
webAudioMix: true,
|
45
41
|
} as const;
|
46
42
|
|
47
43
|
export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
|
package/src/room/events.ts
CHANGED
@@ -38,11 +38,6 @@ export enum RoomEvent {
|
|
38
38
|
*/
|
39
39
|
ConnectionStateChanged = 'connectionStateChanged',
|
40
40
|
|
41
|
-
/**
|
42
|
-
* @deprecated StateChanged has been renamed to ConnectionStateChanged
|
43
|
-
*/
|
44
|
-
StateChanged = 'connectionStateChanged',
|
45
|
-
|
46
41
|
/**
|
47
42
|
* When input or output devices on the machine have changed.
|
48
43
|
*/
|