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.
Files changed (76) hide show
  1. package/README.md +21 -17
  2. package/dist/livekit-client.esm.mjs +1573 -1479
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/api/SignalClient.d.ts +0 -2
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/index.d.ts +3 -3
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/options.d.ts +3 -9
  11. package/dist/src/options.d.ts.map +1 -1
  12. package/dist/src/proto/livekit_models_pb.d.ts +47 -0
  13. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  14. package/dist/src/room/RTCEngine.d.ts +1 -0
  15. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  16. package/dist/src/room/Room.d.ts +12 -15
  17. package/dist/src/room/Room.d.ts.map +1 -1
  18. package/dist/src/room/defaults.d.ts.map +1 -1
  19. package/dist/src/room/events.d.ts +0 -4
  20. package/dist/src/room/events.d.ts.map +1 -1
  21. package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
  22. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  23. package/dist/src/room/participant/Participant.d.ts +6 -10
  24. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  25. package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
  26. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  27. package/dist/src/room/timers.d.ts +4 -5
  28. package/dist/src/room/timers.d.ts.map +1 -1
  29. package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
  30. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
  32. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  33. package/dist/src/room/track/Track.d.ts +5 -0
  34. package/dist/src/room/track/Track.d.ts.map +1 -1
  35. package/dist/src/room/track/options.d.ts +0 -5
  36. package/dist/src/room/track/options.d.ts.map +1 -1
  37. package/dist/src/room/types.d.ts +11 -3
  38. package/dist/src/room/types.d.ts.map +1 -1
  39. package/dist/src/version.d.ts +1 -1
  40. package/dist/ts4.2/src/api/SignalClient.d.ts +0 -2
  41. package/dist/ts4.2/src/index.d.ts +3 -3
  42. package/dist/ts4.2/src/options.d.ts +3 -9
  43. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +47 -0
  44. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  45. package/dist/ts4.2/src/room/Room.d.ts +12 -15
  46. package/dist/ts4.2/src/room/events.d.ts +0 -4
  47. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
  48. package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
  49. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
  50. package/dist/ts4.2/src/room/timers.d.ts +4 -5
  51. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
  52. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
  53. package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
  54. package/dist/ts4.2/src/room/track/options.d.ts +0 -5
  55. package/dist/ts4.2/src/room/types.d.ts +11 -3
  56. package/dist/ts4.2/src/version.d.ts +1 -1
  57. package/package.json +8 -7
  58. package/src/api/SignalClient.ts +2 -6
  59. package/src/e2ee/E2eeManager.ts +2 -2
  60. package/src/index.ts +2 -4
  61. package/src/options.ts +3 -10
  62. package/src/proto/livekit_models_pb.ts +66 -0
  63. package/src/room/RTCEngine.ts +6 -1
  64. package/src/room/Room.ts +145 -114
  65. package/src/room/defaults.ts +1 -5
  66. package/src/room/events.ts +0 -5
  67. package/src/room/participant/LocalParticipant.ts +36 -77
  68. package/src/room/participant/Participant.ts +23 -24
  69. package/src/room/participant/RemoteParticipant.ts +27 -24
  70. package/src/room/track/LocalVideoTrack.test.ts +1 -1
  71. package/src/room/track/LocalVideoTrack.ts +11 -7
  72. package/src/room/track/RemoteTrackPublication.ts +2 -7
  73. package/src/room/track/Track.ts +10 -1
  74. package/src/room/track/options.ts +0 -6
  75. package/src/room/types.ts +11 -4
  76. 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
- /** map of sid: [[RemoteParticipant]] */
103
- participants: Map<string, RemoteParticipant>;
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 identityToSid: Map<string, string>;
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.participants = new Map();
164
- this.identityToSid = new Map();
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
- /** server assigned unique room id */
261
- get sid(): string {
262
- return this.roomInfo?.sid ?? '';
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
- if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
312
- this.emit(RoomEvent.Reconnecting);
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 (this.state === ConnectionState.Reconnecting) {
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
- const sid = this.identityToSid.get(identity);
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.participants.forEach((p) => {
895
- p.audioTracks.forEach((t) => {
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.participants.values()) {
922
- p.videoTracks.forEach((tr) => {
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.audioTracks.values()).filter(
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.videoTracks.values()).filter(
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.expWebAudioMix) ||
1020
- (this.options.expWebAudioMix && this.audioContext && !('setSinkId' in this.audioContext))
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.expWebAudioMix) {
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.participants.values()).map((p) => p.setAudioOutput({ deviceId })),
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.participants.clear();
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 participantId = parts[0];
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 (participantId === this.localParticipant.sid) {
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.participants.get(participantId) as RemoteParticipant | undefined;
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: ${participantId}`,
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.participants.values()) {
1153
- this.handleParticipantDisconnected(p.sid, 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.participants.forEach((p) => {
1207
- p.tracks.forEach((pub) => {
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.tracks.forEach((pub) => {
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.tracks.clear();
1238
- this.localParticipant.videoTracks.clear();
1239
- this.localParticipant.audioTracks.clear();
1258
+ this.localParticipant.trackPublications.clear();
1259
+ this.localParticipant.videoTrackPublications.clear();
1260
+ this.localParticipant.audioTrackPublications.clear();
1240
1261
 
1241
- this.participants.clear();
1262
+ this.remoteParticipants.clear();
1263
+ this.sidToIdentity.clear();
1242
1264
  this.activeSpeakers = [];
1243
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
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
- // ensure identity <=> sid mapping
1268
- const sid = this.identityToSid.get(info.identity);
1269
- if (sid && sid !== info.sid) {
1270
- // sid had changed, need to remove previous participant
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.participants.get(info.sid);
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.sid, remoteParticipant);
1299
+ this.handleParticipantDisconnected(info.identity, remoteParticipant);
1280
1300
  } else {
1281
1301
  // create participant if doesn't exist
1282
- remoteParticipant = this.getOrCreateParticipant(info.sid, 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(sid: string, participant?: RemoteParticipant) {
1307
+ private handleParticipantDisconnected(identity: string, participant?: RemoteParticipant) {
1292
1308
  // remove and send event
1293
- this.participants.delete(sid);
1309
+ this.remoteParticipants.delete(identity);
1294
1310
  if (!participant) {
1295
1311
  return;
1296
1312
  }
1297
1313
 
1298
- this.identityToSid.delete(participant.identity);
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.participants.get(speaker.sid);
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.participants.forEach((p) => {
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.participants.get(speaker.sid);
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.participants.get(streamState.participantSid);
1386
+ const participant = this.getRemoteParticipantBySid(streamState.participantSid);
1372
1387
  if (!participant) {
1373
1388
  return;
1374
1389
  }
1375
- const pub = participant.getTrackPublication(streamState.trackSid);
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.participants.get(update.participantSid);
1406
+ const participant = this.getRemoteParticipantBySid(update.participantSid);
1392
1407
  if (!participant) {
1393
1408
  return;
1394
1409
  }
1395
- const pub = participant.getTrackPublication(update.trackSid);
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.participants.values()).find((p) =>
1405
- p.tracks.has(update.trackSid),
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.getTrackPublication(update.trackSid);
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.participants.get(userPacket.participantSid);
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.participants.get(info.participantSid);
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.expWebAudioMix.audioContext;
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.expWebAudioMix) {
1511
- this.participants.forEach((participant) => participant.setAudioContext(this.audioContext));
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(id: string, info?: ParticipantInfo): RemoteParticipant {
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, id, '', undefined, undefined, {
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.expWebAudioMix) {
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(id: string, info: ParticipantInfo): RemoteParticipant {
1545
- if (this.participants.has(id)) {
1546
- return this.participants.get(id) as RemoteParticipant;
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(id, info);
1549
- this.participants.set(id, participant);
1569
+ const participant = this.createParticipant(identity, info);
1570
+ this.remoteParticipants.set(identity, participant);
1550
1571
 
1551
- this.identityToSid.set(info.identity, info.sid);
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.participants.values()).reduce((acc, participant) => {
1638
- acc.push(...(participant.getTracks() as RemoteTrackPublication[])); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
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.getTracks() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
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.participants.values()) {
1651
- for (const pub of p.videoTracks.values()) {
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 (this.state === ConnectionState.Connected) {
1721
- return this.emit(event, ...args);
1722
- } else if (this.state === ConnectionState.Reconnecting) {
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;
@@ -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
- expWebAudioMix: false,
40
+ webAudioMix: true,
45
41
  } as const;
46
42
 
47
43
  export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
@@ -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
  */