livekit-client 1.8.0 → 1.9.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 (82) hide show
  1. package/README.md +3 -2
  2. package/dist/livekit-client.esm.mjs +13470 -13341
  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 +11 -10
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  9. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  10. package/dist/src/connectionHelper/checks/Checker.d.ts +1 -1
  11. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  12. package/dist/src/index.d.ts +5 -7
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/proto/livekit_models.d.ts +5 -0
  15. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  16. package/dist/src/proto/livekit_rtc.d.ts +32 -0
  17. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  18. package/dist/src/room/RTCEngine.d.ts +5 -3
  19. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  20. package/dist/src/room/Room.d.ts +19 -15
  21. package/dist/src/room/Room.d.ts.map +1 -1
  22. package/dist/src/room/events.d.ts +15 -0
  23. package/dist/src/room/events.d.ts.map +1 -1
  24. package/dist/src/room/participant/LocalParticipant.d.ts +14 -2
  25. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  26. package/dist/src/room/participant/Participant.d.ts +4 -2
  27. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  28. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -2
  29. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  30. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  31. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  32. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  33. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
  35. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  36. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  37. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  38. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  39. package/dist/src/room/track/create.d.ts.map +1 -1
  40. package/dist/src/room/types.d.ts +1 -0
  41. package/dist/src/room/types.d.ts.map +1 -1
  42. package/dist/ts4.2/src/api/SignalClient.d.ts +14 -10
  43. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  44. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +1 -1
  45. package/dist/ts4.2/src/index.d.ts +6 -7
  46. package/dist/ts4.2/src/proto/livekit_models.d.ts +5 -0
  47. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +32 -0
  48. package/dist/ts4.2/src/room/RTCEngine.d.ts +5 -3
  49. package/dist/ts4.2/src/room/Room.d.ts +19 -15
  50. package/dist/ts4.2/src/room/events.d.ts +15 -0
  51. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -2
  52. package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -2
  53. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -2
  54. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
  55. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
  56. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  57. package/dist/ts4.2/src/room/types.d.ts +1 -0
  58. package/package.json +4 -3
  59. package/src/api/SignalClient.ts +38 -26
  60. package/src/connectionHelper/ConnectionCheck.ts +1 -2
  61. package/src/connectionHelper/checks/Checker.ts +1 -1
  62. package/src/connectionHelper/checks/reconnect.ts +1 -1
  63. package/src/index.ts +8 -10
  64. package/src/proto/livekit_models.ts +15 -0
  65. package/src/room/RTCEngine.ts +32 -11
  66. package/src/room/RegionUrlProvider.ts +1 -1
  67. package/src/room/Room.ts +102 -70
  68. package/src/room/events.ts +17 -0
  69. package/src/room/participant/LocalParticipant.ts +30 -7
  70. package/src/room/participant/Participant.ts +27 -3
  71. package/src/room/participant/RemoteParticipant.ts +6 -3
  72. package/src/room/participant/publishUtils.test.ts +1 -1
  73. package/src/room/participant/publishUtils.ts +1 -1
  74. package/src/room/track/LocalAudioTrack.ts +1 -1
  75. package/src/room/track/LocalTrack.ts +2 -2
  76. package/src/room/track/LocalTrackPublication.ts +1 -1
  77. package/src/room/track/LocalVideoTrack.ts +3 -3
  78. package/src/room/track/RemoteAudioTrack.ts +1 -1
  79. package/src/room/track/RemoteVideoTrack.test.ts +1 -1
  80. package/src/room/track/RemoteVideoTrack.ts +3 -3
  81. package/src/room/track/create.ts +2 -2
  82. package/src/room/types.ts +11 -0
package/src/room/Room.ts CHANGED
@@ -29,6 +29,9 @@ import {
29
29
  StreamStateUpdate,
30
30
  SubscriptionPermissionUpdate,
31
31
  } from '../proto/livekit_rtc';
32
+ import DeviceManager from './DeviceManager';
33
+ import RTCEngine from './RTCEngine';
34
+ import { RegionUrlProvider } from './RegionUrlProvider';
32
35
  import {
33
36
  audioDefaults,
34
37
  publishDefaults,
@@ -36,14 +39,12 @@ import {
36
39
  roomOptionDefaults,
37
40
  videoDefaults,
38
41
  } from './defaults';
39
- import DeviceManager from './DeviceManager';
40
42
  import { ConnectionError, ConnectionErrorReason, UnsupportedServer } from './errors';
41
43
  import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
42
44
  import LocalParticipant from './participant/LocalParticipant';
43
45
  import type Participant from './participant/Participant';
44
46
  import type { ConnectionQuality } from './participant/Participant';
45
47
  import RemoteParticipant from './participant/RemoteParticipant';
46
- import RTCEngine from './RTCEngine';
47
48
  import LocalAudioTrack from './track/LocalAudioTrack';
48
49
  import LocalTrackPublication from './track/LocalTrackPublication';
49
50
  import LocalVideoTrack from './track/LocalVideoTrack';
@@ -53,18 +54,17 @@ import { Track } from './track/Track';
53
54
  import type { TrackPublication } from './track/TrackPublication';
54
55
  import type { AdaptiveStreamSettings } from './track/types';
55
56
  import { getNewAudioContext } from './track/utils';
56
- import type { SimulationOptions } from './types';
57
+ import type { SimulationOptions, SimulationScenario } from './types';
57
58
  import {
58
- createDummyVideoStreamTrack,
59
59
  Future,
60
+ Mutex,
61
+ createDummyVideoStreamTrack,
60
62
  getEmptyAudioStreamTrack,
61
63
  isCloud,
62
64
  isWeb,
63
- Mutex,
64
65
  supportsSetSinkId,
65
66
  unpackStreamId,
66
67
  } from './utils';
67
- import { RegionUrlProvider } from './RegionUrlProvider';
68
68
 
69
69
  export enum ConnectionState {
70
70
  Disconnected = 'disconnected',
@@ -99,23 +99,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
99
99
  /** @internal */
100
100
  engine!: RTCEngine;
101
101
 
102
- // available after connected
103
- /** server assigned unique room id */
104
- sid: string = '';
105
-
106
- /** user assigned name, derived from JWT token */
107
- name: string = '';
108
-
109
102
  /** the current participant */
110
103
  localParticipant: LocalParticipant;
111
104
 
112
- /** room metadata */
113
- metadata: string | undefined = undefined;
114
-
115
105
  /** options of room */
116
106
  options: InternalRoomOptions;
117
107
 
118
- private _isRecording: boolean = false;
108
+ private roomInfo?: RoomModel;
119
109
 
120
110
  private identityToSid: Map<string, string>;
121
111
 
@@ -165,6 +155,36 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
165
155
  this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
166
156
  }
167
157
 
158
+ /**
159
+ * if the current room has a participant with `recorder: true` in its JWT grant
160
+ **/
161
+ get isRecording(): boolean {
162
+ return this.roomInfo?.activeRecording ?? false;
163
+ }
164
+
165
+ /** server assigned unique room id */
166
+ get sid(): string {
167
+ return this.roomInfo?.sid ?? '';
168
+ }
169
+
170
+ /** user assigned name, derived from JWT token */
171
+ get name(): string {
172
+ return this.roomInfo?.name ?? '';
173
+ }
174
+
175
+ /** room metadata */
176
+ get metadata(): string | undefined {
177
+ return this.roomInfo?.metadata;
178
+ }
179
+
180
+ get numParticipants(): number {
181
+ return this.roomInfo?.numParticipants ?? 0;
182
+ }
183
+
184
+ get numPublishers(): number {
185
+ return this.roomInfo?.numPublishers ?? 0;
186
+ }
187
+
168
188
  private maybeCreateEngine() {
169
189
  if (this.engine) {
170
190
  return;
@@ -207,7 +227,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
207
227
  }
208
228
  })
209
229
  .on(EngineEvent.Restarting, this.handleRestarting)
210
- .on(EngineEvent.Restarted, this.handleRestarted)
230
+ .on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
211
231
  .on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
212
232
  this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
213
233
  });
@@ -368,31 +388,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
368
388
  this.localParticipant.sid = pi.sid;
369
389
  this.localParticipant.identity = pi.identity;
370
390
 
371
- this.localParticipant.updateInfo(pi);
372
- // forward metadata changed for the local participant
373
- this.setupLocalParticipantEvents();
374
-
375
391
  // populate remote participants, these should not trigger new events
376
- joinResponse.otherParticipants.forEach((info) => {
377
- if (
378
- info.sid !== this.localParticipant.sid &&
379
- info.identity !== this.localParticipant.identity
380
- ) {
381
- this.getOrCreateParticipant(info.sid, info);
382
- } else {
383
- log.warn('received info to create local participant as remote participant', {
384
- info,
385
- localParticipant: this.localParticipant,
386
- });
387
- }
388
- });
392
+ this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);
389
393
 
390
- this.name = joinResponse.room!.name;
391
- this.sid = joinResponse.room!.sid;
392
- this.metadata = joinResponse.room!.metadata;
393
- if (this._isRecording !== joinResponse.room!.activeRecording) {
394
- this._isRecording = joinResponse.room!.activeRecording;
395
- this.emit(RoomEvent.RecordingStatusChanged, joinResponse.room!.activeRecording);
394
+ if (joinResponse.room) {
395
+ this.handleRoomUpdate(joinResponse.room);
396
396
  }
397
397
  };
398
398
 
@@ -433,6 +433,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
433
433
  );
434
434
 
435
435
  this.applyJoinResponse(joinResponse);
436
+ // forward metadata changed for the local participant
437
+ this.setupLocalParticipantEvents();
436
438
  this.emit(RoomEvent.SignalConnected);
437
439
  } catch (err) {
438
440
  this.recreateEngine();
@@ -534,17 +536,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
534
536
  this.connectFuture = undefined;
535
537
  }
536
538
 
537
- /**
538
- * if the current room has a participant with `recorder: true` in its JWT grant
539
- **/
540
- get isRecording() {
541
- return this._isRecording;
542
- }
543
-
544
539
  /**
545
540
  * @internal for testing
546
541
  */
547
- async simulateScenario(scenario: string) {
542
+ async simulateScenario(scenario: SimulationScenario) {
548
543
  let postAction = () => {};
549
544
  let req: SimulateScenario | undefined;
550
545
  switch (scenario) {
@@ -593,6 +588,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
593
588
  this.engine.client.onClose('simulate resume-reconnect');
594
589
  }
595
590
  break;
591
+ case 'full-reconnect':
592
+ this.engine.fullReconnectOnNext = true;
593
+ await this.engine.client.close();
594
+ if (this.engine.client.onClose) {
595
+ this.engine.client.onClose('simulate full-reconnect');
596
+ }
597
+ break;
596
598
  case 'force-tcp':
597
599
  case 'force-tls':
598
600
  req = SimulateScenario.fromPartial({
@@ -734,6 +736,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
734
736
  private setupLocalParticipantEvents() {
735
737
  this.localParticipant
736
738
  .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
739
+ .on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
737
740
  .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
738
741
  .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
739
742
  .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
@@ -825,20 +828,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
825
828
  }
826
829
  };
827
830
 
828
- private handleRestarted = async (joinResponse: JoinResponse) => {
829
- log.debug(`reconnected to server`, {
831
+ private handleSignalRestarted = async (joinResponse: JoinResponse) => {
832
+ log.debug(`signal reconnected to server`, {
830
833
  region: joinResponse.serverRegion,
831
834
  });
832
835
 
833
- try {
834
- // rehydrate participants
835
- if (joinResponse.participant) {
836
- // with a restart, the sid will have changed, we'll map our understanding to it
837
- this.localParticipant.sid = joinResponse.participant.sid;
838
- this.handleParticipantUpdates([joinResponse.participant]);
839
- }
840
- this.handleParticipantUpdates(joinResponse.otherParticipants);
836
+ this.applyJoinResponse(joinResponse);
841
837
 
838
+ try {
842
839
  // unpublish & republish tracks
843
840
  const localPubs: LocalTrackPublication[] = [];
844
841
  this.localParticipant.tracks.forEach((pub) => {
@@ -872,10 +869,24 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
872
869
  );
873
870
  } catch (error) {
874
871
  log.error('error trying to re-publish tracks after reconnection', { error });
875
- } finally {
876
- this.setAndEmitConnectionState(ConnectionState.Connected);
877
- this.emit(RoomEvent.Reconnected);
878
872
  }
873
+
874
+ try {
875
+ await this.engine.waitForRestarted();
876
+ log.debug(`fully reconnected to server`, {
877
+ region: joinResponse.serverRegion,
878
+ });
879
+ } catch {
880
+ // reconnection failed, handleDisconnect is being invoked already, just return here
881
+ return;
882
+ }
883
+ this.setAndEmitConnectionState(ConnectionState.Connected);
884
+ this.emit(RoomEvent.Reconnected);
885
+
886
+ // emit participant connected events after connection has been re-established
887
+ this.participants.forEach((participant) => {
888
+ this.emit(RoomEvent.ParticipantConnected, participant);
889
+ });
879
890
  };
880
891
 
881
892
  private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
@@ -901,6 +912,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
901
912
 
902
913
  this.localParticipant
903
914
  .off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
915
+ .off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
904
916
  .off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
905
917
  .off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
906
918
  .off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
@@ -1106,14 +1118,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1106
1118
  this.emit(RoomEvent.MediaDevicesChanged);
1107
1119
  };
1108
1120
 
1109
- private handleRoomUpdate = (r: RoomModel) => {
1110
- if (this._isRecording !== r.activeRecording) {
1111
- this._isRecording = r.activeRecording;
1112
- this.emit(RoomEvent.RecordingStatusChanged, r.activeRecording);
1121
+ private handleRoomUpdate = (room: RoomModel) => {
1122
+ const oldRoom = this.roomInfo;
1123
+ this.roomInfo = room;
1124
+ if (oldRoom && oldRoom.metadata !== room.metadata) {
1125
+ this.emitWhenConnected(RoomEvent.RoomMetadataChanged, room.metadata);
1113
1126
  }
1114
- if (this.metadata !== r.metadata) {
1115
- this.metadata = r.metadata;
1116
- this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
1127
+ if (oldRoom?.activeRecording !== room.activeRecording) {
1128
+ this.emitWhenConnected(RoomEvent.RecordingStatusChanged, room.activeRecording);
1117
1129
  }
1118
1130
  };
1119
1131
 
@@ -1222,6 +1234,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1222
1234
  .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
1223
1235
  this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant);
1224
1236
  })
1237
+ .on(ParticipantEvent.ParticipantNameChanged, (name) => {
1238
+ this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant);
1239
+ })
1225
1240
  .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
1226
1241
  this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
1227
1242
  })
@@ -1338,6 +1353,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1338
1353
  this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
1339
1354
  };
1340
1355
 
1356
+ private onLocalParticipantNameChanged = (name: string) => {
1357
+ this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant);
1358
+ };
1359
+
1341
1360
  private onLocalTrackMuted = (pub: TrackPublication) => {
1342
1361
  this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
1343
1362
  };
@@ -1392,7 +1411,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1392
1411
  ...options.participants,
1393
1412
  };
1394
1413
  this.handleDisconnect();
1395
- this.name = 'simulated-room';
1414
+ this.roomInfo = {
1415
+ sid: 'RM_SIMULATED',
1416
+ name: 'simulated-room',
1417
+ emptyTimeout: 0,
1418
+ maxParticipants: 0,
1419
+ creationTime: new Date().getTime(),
1420
+ metadata: '',
1421
+ numParticipants: 1,
1422
+ numPublishers: 1,
1423
+ turnPassword: '',
1424
+ enabledCodecs: [],
1425
+ activeRecording: false,
1426
+ };
1396
1427
 
1397
1428
  this.localParticipant.updateInfo(
1398
1429
  ParticipantInfo.fromPartial({
@@ -1537,6 +1568,7 @@ export type RoomEventCallbacks = {
1537
1568
  metadata: string | undefined,
1538
1569
  participant: RemoteParticipant | LocalParticipant,
1539
1570
  ) => void;
1571
+ participantNameChanged: (name: string, participant: RemoteParticipant | LocalParticipant) => void;
1540
1572
  participantPermissionsChanged: (
1541
1573
  prevPermissions: ParticipantPermission | undefined,
1542
1574
  participant: RemoteParticipant | LocalParticipant,
@@ -168,6 +168,14 @@ export enum RoomEvent {
168
168
  */
169
169
  ParticipantMetadataChanged = 'participantMetadataChanged',
170
170
 
171
+ /**
172
+ * Participant's display name changed
173
+ *
174
+ * args: (name: string, [[Participant]])
175
+ *
176
+ */
177
+ ParticipantNameChanged = 'participantNameChanged',
178
+
171
179
  /**
172
180
  * Room metadata is a simple way for app-specific state to be pushed to
173
181
  * all users.
@@ -359,6 +367,14 @@ export enum ParticipantEvent {
359
367
  */
360
368
  ParticipantMetadataChanged = 'participantMetadataChanged',
361
369
 
370
+ /**
371
+ * Participant's display name changed
372
+ *
373
+ * args: (name: string, [[Participant]])
374
+ *
375
+ */
376
+ ParticipantNameChanged = 'participantNameChanged',
377
+
362
378
  /**
363
379
  * Data received from this participant as sender.
364
380
  * Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
@@ -433,6 +449,7 @@ export enum EngineEvent {
433
449
  Restarting = 'restarting',
434
450
  Restarted = 'restarted',
435
451
  SignalResumed = 'signalResumed',
452
+ SignalRestarted = 'signalRestarted',
436
453
  Closing = 'closing',
437
454
  MediaTrackAdded = 'mediaTrackAdded',
438
455
  ActiveSpeakersUpdate = 'activeSpeakersUpdate',
@@ -10,35 +10,35 @@ import {
10
10
  TrackPublishedResponse,
11
11
  TrackUnpublishedResponse,
12
12
  } from '../../proto/livekit_rtc';
13
+ import type RTCEngine from '../RTCEngine';
13
14
  import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
14
15
  import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
15
- import type RTCEngine from '../RTCEngine';
16
16
  import LocalAudioTrack from '../track/LocalAudioTrack';
17
17
  import LocalTrack from '../track/LocalTrack';
18
18
  import LocalTrackPublication from '../track/LocalTrackPublication';
19
19
  import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
20
+ import { Track } from '../track/Track';
20
21
  import {
21
22
  AudioCaptureOptions,
22
23
  BackupVideoCodec,
23
24
  CreateLocalTracksOptions,
24
- isBackupCodec,
25
25
  ScreenShareCaptureOptions,
26
26
  ScreenSharePresets,
27
27
  TrackPublishOptions,
28
28
  VideoCaptureOptions,
29
+ isBackupCodec,
29
30
  } from '../track/options';
30
- import { Track } from '../track/Track';
31
31
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
32
32
  import type { DataPublishOptions } from '../types';
33
33
  import { Future, isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
34
34
  import Participant from './Participant';
35
35
  import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
36
+ import RemoteParticipant from './RemoteParticipant';
36
37
  import {
37
38
  computeTrackBackupEncodings,
38
39
  computeVideoEncodings,
39
40
  mediaTrackToLocalTrack,
40
41
  } from './publishUtils';
41
- import RemoteParticipant from './RemoteParticipant';
42
42
 
43
43
  export default class LocalParticipant extends Participant {
44
44
  audioTracks: Map<string, LocalTrackPublication>;
@@ -148,6 +148,26 @@ export default class LocalParticipant extends Participant {
148
148
  this.reconnectFuture = undefined;
149
149
  };
150
150
 
151
+ /**
152
+ * Sets and updates the metadata of the local participant.
153
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
154
+ * @param metadata
155
+ */
156
+ setMetadata(metadata: string): void {
157
+ super.setMetadata(metadata);
158
+ this.engine.client.sendUpdateLocalMetadata(metadata, this.name ?? '');
159
+ }
160
+
161
+ /**
162
+ * Sets and updates the name of the local participant.
163
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
164
+ * @param metadata
165
+ */
166
+ setName(name: string): void {
167
+ super.setName(name);
168
+ this.engine.client.sendUpdateLocalMetadata(this.metadata ?? '', name);
169
+ }
170
+
151
171
  /**
152
172
  * Enable or disable a participant's camera track.
153
173
  *
@@ -961,13 +981,15 @@ export default class LocalParticipant extends Participant {
961
981
  }
962
982
 
963
983
  /** @internal */
964
- updateInfo(info: ParticipantInfo) {
984
+ updateInfo(info: ParticipantInfo): boolean {
965
985
  if (info.sid !== this.sid) {
966
986
  // drop updates that specify a wrong sid.
967
987
  // the sid for local participant is only explicitly set on join and full reconnect
968
- return;
988
+ return false;
989
+ }
990
+ if (!super.updateInfo(info)) {
991
+ return false;
969
992
  }
970
- super.updateInfo(info);
971
993
 
972
994
  // reconcile track mute status.
973
995
  // if server's track mute status doesn't match actual, we'll have to update
@@ -986,6 +1008,7 @@ export default class LocalParticipant extends Participant {
986
1008
  }
987
1009
  }
988
1010
  });
1011
+ return true;
989
1012
  }
990
1013
 
991
1014
  private updateTrackSubscriptionPermissions = () => {
@@ -2,10 +2,10 @@ import { EventEmitter } from 'events';
2
2
  import type TypedEmitter from 'typed-emitter';
3
3
  import log from '../../logger';
4
4
  import {
5
- ConnectionQuality as ProtoQuality,
6
5
  DataPacket_Kind,
7
6
  ParticipantInfo,
8
7
  ParticipantPermission,
8
+ ConnectionQuality as ProtoQuality,
9
9
  } from '../../proto/livekit_models';
10
10
  import { ParticipantEvent, TrackEvent } from '../events';
11
11
  import type LocalTrackPublication from '../track/LocalTrackPublication';
@@ -144,10 +144,23 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
144
144
  }
145
145
 
146
146
  /** @internal */
147
- updateInfo(info: ParticipantInfo) {
147
+ updateInfo(info: ParticipantInfo): boolean {
148
+ // it's possible the update could be applied out of order due to await
149
+ // during reconnect sequences. when that happens, it's possible for server
150
+ // to have sent more recent version of participant info while JS is waiting
151
+ // to process the existing payload.
152
+ // when the participant sid remains the same, and we already have a later version
153
+ // of the payload, they can be safely skipped
154
+ if (
155
+ this.participantInfo &&
156
+ this.participantInfo.sid === info.sid &&
157
+ this.participantInfo.version > info.version
158
+ ) {
159
+ return false;
160
+ }
148
161
  this.identity = info.identity;
149
162
  this.sid = info.sid;
150
- this.name = info.name;
163
+ this.setName(info.name);
151
164
  this.setMetadata(info.metadata);
152
165
  if (info.permission) {
153
166
  this.setPermissions(info.permission);
@@ -155,6 +168,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
155
168
  // set this last so setMetadata can detect changes
156
169
  this.participantInfo = info;
157
170
  log.trace('update participant info', { info });
171
+ return true;
158
172
  }
159
173
 
160
174
  /** @internal */
@@ -168,6 +182,15 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
168
182
  }
169
183
  }
170
184
 
185
+ protected setName(name: string) {
186
+ const changed = this.name !== name;
187
+ this.name = name;
188
+
189
+ if (changed) {
190
+ this.emit(ParticipantEvent.ParticipantNameChanged, name);
191
+ }
192
+ }
193
+
171
194
  /** @internal */
172
195
  setPermissions(permissions: ParticipantPermission): boolean {
173
196
  const prevPermissions = this.permissions;
@@ -250,6 +273,7 @@ export type ParticipantEventCallbacks = {
250
273
  localTrackPublished: (publication: LocalTrackPublication) => void;
251
274
  localTrackUnpublished: (publication: LocalTrackPublication) => void;
252
275
  participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
276
+ participantNameChanged: (name: string) => void;
253
277
  dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
254
278
  isSpeakingChanged: (speaking: boolean) => void;
255
279
  connectionQualityChanged: (connectionQuality: ConnectionQuality) => void;
@@ -3,13 +3,13 @@ import log from '../../logger';
3
3
  import type { ParticipantInfo } from '../../proto/livekit_models';
4
4
  import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
5
5
  import { ParticipantEvent, TrackEvent } from '../events';
6
- import type { AudioOutputOptions } from '../track/options';
7
6
  import RemoteAudioTrack from '../track/RemoteAudioTrack';
8
7
  import type RemoteTrack from '../track/RemoteTrack';
9
8
  import RemoteTrackPublication from '../track/RemoteTrackPublication';
10
9
  import RemoteVideoTrack from '../track/RemoteVideoTrack';
11
10
  import { Track } from '../track/Track';
12
11
  import type { TrackPublication } from '../track/TrackPublication';
12
+ import type { AudioOutputOptions } from '../track/options';
13
13
  import type { AdaptiveStreamSettings } from '../track/types';
14
14
  import Participant, { ParticipantEventCallbacks } from './Participant';
15
15
 
@@ -215,8 +215,10 @@ export default class RemoteParticipant extends Participant {
215
215
  }
216
216
 
217
217
  /** @internal */
218
- updateInfo(info: ParticipantInfo) {
219
- super.updateInfo(info);
218
+ updateInfo(info: ParticipantInfo): boolean {
219
+ if (!super.updateInfo(info)) {
220
+ return false;
221
+ }
220
222
 
221
223
  // we are getting a list of all available tracks, reconcile in here
222
224
  // and send out events for changes
@@ -277,6 +279,7 @@ export default class RemoteParticipant extends Participant {
277
279
  newTracks.forEach((publication) => {
278
280
  this.emit(ParticipantEvent.TrackPublished, publication);
279
281
  });
282
+ return true;
280
283
  }
281
284
 
282
285
  /** @internal */
@@ -3,8 +3,8 @@ import {
3
3
  computeDefaultScreenShareSimulcastPresets,
4
4
  computeVideoEncodings,
5
5
  determineAppropriateEncoding,
6
- presets169,
7
6
  presets43,
7
+ presets169,
8
8
  presetsForResolution,
9
9
  presetsScreenShare,
10
10
  sortPresets,
@@ -2,6 +2,7 @@ import log from '../../logger';
2
2
  import { TrackInvalidError } from '../errors';
3
3
  import LocalAudioTrack from '../track/LocalAudioTrack';
4
4
  import LocalVideoTrack from '../track/LocalVideoTrack';
5
+ import { Track } from '../track/Track';
5
6
  import {
6
7
  BackupVideoCodec,
7
8
  ScreenSharePresets,
@@ -12,7 +13,6 @@ import {
12
13
  VideoPresets,
13
14
  VideoPresets43,
14
15
  } from '../track/options';
15
- import { Track } from '../track/Track';
16
16
 
17
17
  /** @internal */
18
18
  export function mediaTrackToLocalTrack(
@@ -3,8 +3,8 @@ import { TrackEvent } from '../events';
3
3
  import { AudioSenderStats, computeBitrate, monitorFrequency } from '../stats';
4
4
  import { isWeb } from '../utils';
5
5
  import LocalTrack from './LocalTrack';
6
- import type { AudioCaptureOptions } from './options';
7
6
  import { Track } from './Track';
7
+ import type { AudioCaptureOptions } from './options';
8
8
  import { constraintsForOptions, detectSilence } from './utils';
9
9
 
10
10
  export default class LocalAudioTrack extends LocalTrack {
@@ -3,14 +3,14 @@ import DeviceManager from '../DeviceManager';
3
3
  import { TrackInvalidError } from '../errors';
4
4
  import { TrackEvent } from '../events';
5
5
  import {
6
+ Mutex,
6
7
  getEmptyAudioStreamTrack,
7
8
  getEmptyVideoStreamTrack,
8
9
  isMobile,
9
- Mutex,
10
10
  sleep,
11
11
  } from '../utils';
12
+ import { Track, attachToElement, detachTrack } from './Track';
12
13
  import type { VideoCodec } from './options';
13
- import { attachToElement, detachTrack, Track } from './Track';
14
14
 
15
15
  const defaultDimensionsTimeout = 2 * 1000;
16
16
 
@@ -3,9 +3,9 @@ import { TrackEvent } from '../events';
3
3
  import type LocalAudioTrack from './LocalAudioTrack';
4
4
  import type LocalTrack from './LocalTrack';
5
5
  import type LocalVideoTrack from './LocalVideoTrack';
6
- import type { TrackPublishOptions } from './options';
7
6
  import type { Track } from './Track';
8
7
  import { TrackPublication } from './TrackPublication';
8
+ import type { TrackPublishOptions } from './options';
9
9
 
10
10
  export default class LocalTrackPublication extends TrackPublication {
11
11
  track?: LocalTrack = undefined;
@@ -2,11 +2,11 @@ import type { SignalClient } from '../../api/SignalClient';
2
2
  import log from '../../logger';
3
3
  import { VideoLayer, VideoQuality } from '../../proto/livekit_models';
4
4
  import type { SubscribedCodec, SubscribedQuality } from '../../proto/livekit_rtc';
5
- import { computeBitrate, monitorFrequency, VideoSenderStats } from '../stats';
6
- import { isFireFox, isMobile, isWeb, Mutex } from '../utils';
5
+ import { VideoSenderStats, computeBitrate, monitorFrequency } from '../stats';
6
+ import { Mutex, isFireFox, isMobile, isWeb } from '../utils';
7
7
  import LocalTrack from './LocalTrack';
8
- import type { VideoCaptureOptions, VideoCodec } from './options';
9
8
  import { Track } from './Track';
9
+ import type { VideoCaptureOptions, VideoCodec } from './options';
10
10
  import { constraintsForOptions } from './utils';
11
11
 
12
12
  export class SimulcastTrackInfo {
@@ -2,9 +2,9 @@ import log from '../../logger';
2
2
  import { TrackEvent } from '../events';
3
3
  import { AudioReceiverStats, computeBitrate } from '../stats';
4
4
  import { supportsSetSinkId } from '../utils';
5
- import type { AudioOutputOptions } from './options';
6
5
  import RemoteTrack from './RemoteTrack';
7
6
  import { Track } from './Track';
7
+ import type { AudioOutputOptions } from './options';
8
8
 
9
9
  export default class RemoteAudioTrack extends RemoteTrack {
10
10
  private prevStats?: AudioReceiverStats;
@@ -1,6 +1,6 @@
1
+ import MockMediaStreamTrack from '../../test/MockMediaStreamTrack';
1
2
  import { TrackEvent } from '../events';
2
3
  import RemoteVideoTrack, { ElementInfo } from './RemoteVideoTrack';
3
- import MockMediaStreamTrack from '../../test/MockMediaStreamTrack';
4
4
  import type { Track } from './Track';
5
5
 
6
6
  jest.useFakeTimers();