livekit-client 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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();