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.
Files changed (86) hide show
  1. package/README.md +27 -15
  2. package/dist/livekit-client.esm.mjs +1750 -1581
  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 +56 -1
  13. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  14. package/dist/src/proto/livekit_rtc_pb.d.ts +38 -0
  15. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
  16. package/dist/src/room/PCTransport.d.ts +1 -2
  17. package/dist/src/room/PCTransport.d.ts.map +1 -1
  18. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  19. package/dist/src/room/RTCEngine.d.ts +1 -0
  20. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  21. package/dist/src/room/Room.d.ts +12 -15
  22. package/dist/src/room/Room.d.ts.map +1 -1
  23. package/dist/src/room/defaults.d.ts.map +1 -1
  24. package/dist/src/room/events.d.ts +8 -5
  25. package/dist/src/room/events.d.ts.map +1 -1
  26. package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
  27. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  28. package/dist/src/room/participant/Participant.d.ts +6 -10
  29. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  30. package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
  31. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  32. package/dist/src/room/timers.d.ts +4 -5
  33. package/dist/src/room/timers.d.ts.map +1 -1
  34. package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
  35. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
  37. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  38. package/dist/src/room/track/Track.d.ts +5 -0
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/options.d.ts +0 -5
  41. package/dist/src/room/track/options.d.ts.map +1 -1
  42. package/dist/src/room/types.d.ts +11 -3
  43. package/dist/src/room/types.d.ts.map +1 -1
  44. package/dist/src/version.d.ts +1 -1
  45. package/dist/ts4.2/src/api/SignalClient.d.ts +0 -2
  46. package/dist/ts4.2/src/index.d.ts +3 -3
  47. package/dist/ts4.2/src/options.d.ts +3 -9
  48. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +56 -1
  49. package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +38 -0
  50. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
  51. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  52. package/dist/ts4.2/src/room/Room.d.ts +12 -15
  53. package/dist/ts4.2/src/room/events.d.ts +8 -5
  54. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
  55. package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
  56. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
  57. package/dist/ts4.2/src/room/timers.d.ts +4 -5
  58. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
  59. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
  60. package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
  61. package/dist/ts4.2/src/room/track/options.d.ts +0 -5
  62. package/dist/ts4.2/src/room/types.d.ts +11 -3
  63. package/dist/ts4.2/src/version.d.ts +1 -1
  64. package/package.json +10 -9
  65. package/src/api/SignalClient.ts +2 -6
  66. package/src/e2ee/E2eeManager.ts +2 -2
  67. package/src/index.ts +2 -4
  68. package/src/options.ts +3 -10
  69. package/src/proto/livekit_models_pb.ts +78 -0
  70. package/src/proto/livekit_rtc_pb.ts +53 -0
  71. package/src/room/PCTransport.ts +3 -13
  72. package/src/room/PCTransportManager.ts +2 -3
  73. package/src/room/RTCEngine.ts +11 -1
  74. package/src/room/Room.ts +145 -114
  75. package/src/room/defaults.ts +1 -5
  76. package/src/room/events.ts +8 -6
  77. package/src/room/participant/LocalParticipant.ts +36 -77
  78. package/src/room/participant/Participant.ts +23 -24
  79. package/src/room/participant/RemoteParticipant.ts +27 -24
  80. package/src/room/track/LocalVideoTrack.test.ts +1 -1
  81. package/src/room/track/LocalVideoTrack.ts +11 -7
  82. package/src/room/track/RemoteTrackPublication.ts +2 -7
  83. package/src/room/track/Track.ts +10 -1
  84. package/src/room/track/options.ts +0 -6
  85. package/src/room/types.ts +11 -4
  86. 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 = {
@@ -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
  */