livekit-client 1.15.12 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +27 -15
- package/dist/livekit-client.esm.mjs +1750 -1581
- 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 +56 -1
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc_pb.d.ts +38 -0
- package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -2
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.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 +8 -5
- 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 +56 -1
- package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +38 -0
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
- 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 +8 -5
- 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 +10 -9
- 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 +78 -0
- package/src/proto/livekit_rtc_pb.ts +53 -0
- package/src/room/PCTransport.ts +3 -13
- package/src/room/PCTransportManager.ts +2 -3
- package/src/room/RTCEngine.ts +11 -1
- package/src/room/Room.ts +145 -114
- package/src/room/defaults.ts +1 -5
- package/src/room/events.ts +8 -6
- 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
@@ -27,7 +27,14 @@ export enum RoomEvent {
|
|
27
27
|
|
28
28
|
/**
|
29
29
|
* When disconnected from room. This fires when room.disconnect() is called or
|
30
|
-
* when an unrecoverable connection issue had occured
|
30
|
+
* when an unrecoverable connection issue had occured.
|
31
|
+
*
|
32
|
+
* DisconnectReason can be used to determine why the participant was disconnected. Notable reasons are
|
33
|
+
* - DUPLICATE_IDENTITY: another client with the same identity has joined the room
|
34
|
+
* - PARTICIPANT_REMOVED: participant was removed by RemoveParticipant API
|
35
|
+
* - ROOM_DELETED: the room has ended via DeleteRoom API
|
36
|
+
*
|
37
|
+
* args: ([[DisconnectReason]])
|
31
38
|
*/
|
32
39
|
Disconnected = 'disconnected',
|
33
40
|
|
@@ -38,11 +45,6 @@ export enum RoomEvent {
|
|
38
45
|
*/
|
39
46
|
ConnectionStateChanged = 'connectionStateChanged',
|
40
47
|
|
41
|
-
/**
|
42
|
-
* @deprecated StateChanged has been renamed to ConnectionStateChanged
|
43
|
-
*/
|
44
|
-
StateChanged = 'connectionStateChanged',
|
45
|
-
|
46
48
|
/**
|
47
49
|
* When input or output devices on the machine have changed.
|
48
50
|
*/
|