livekit-client 1.8.0 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. package/README.md +9 -7
  2. package/dist/livekit-client.esm.mjs +13550 -13342
  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/PCTransport.d.ts.map +1 -1
  19. package/dist/src/room/RTCEngine.d.ts +5 -3
  20. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  21. package/dist/src/room/Room.d.ts +20 -15
  22. package/dist/src/room/Room.d.ts.map +1 -1
  23. package/dist/src/room/events.d.ts +15 -0
  24. package/dist/src/room/events.d.ts.map +1 -1
  25. package/dist/src/room/participant/LocalParticipant.d.ts +14 -2
  26. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  27. package/dist/src/room/participant/Participant.d.ts +4 -2
  28. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  29. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -2
  30. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  31. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  32. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  34. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  35. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
  36. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  37. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  38. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  39. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  40. package/dist/src/room/track/create.d.ts.map +1 -1
  41. package/dist/src/room/track/options.d.ts +7 -1
  42. package/dist/src/room/track/options.d.ts.map +1 -1
  43. package/dist/src/room/types.d.ts +1 -0
  44. package/dist/src/room/types.d.ts.map +1 -1
  45. package/dist/src/room/utils.d.ts +3 -0
  46. package/dist/src/room/utils.d.ts.map +1 -1
  47. package/dist/ts4.2/src/api/SignalClient.d.ts +14 -10
  48. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  49. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +1 -1
  50. package/dist/ts4.2/src/index.d.ts +6 -7
  51. package/dist/ts4.2/src/proto/livekit_models.d.ts +5 -0
  52. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +32 -0
  53. package/dist/ts4.2/src/room/RTCEngine.d.ts +5 -3
  54. package/dist/ts4.2/src/room/Room.d.ts +20 -15
  55. package/dist/ts4.2/src/room/events.d.ts +15 -0
  56. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -2
  57. package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -2
  58. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -2
  59. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
  60. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
  61. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  62. package/dist/ts4.2/src/room/track/options.d.ts +7 -0
  63. package/dist/ts4.2/src/room/types.d.ts +1 -0
  64. package/dist/ts4.2/src/room/utils.d.ts +3 -0
  65. package/package.json +4 -3
  66. package/src/api/SignalClient.ts +38 -26
  67. package/src/connectionHelper/ConnectionCheck.ts +1 -2
  68. package/src/connectionHelper/checks/Checker.ts +1 -1
  69. package/src/connectionHelper/checks/reconnect.ts +1 -1
  70. package/src/index.ts +8 -10
  71. package/src/proto/livekit_models.ts +15 -0
  72. package/src/room/PCTransport.ts +40 -1
  73. package/src/room/RTCEngine.ts +32 -11
  74. package/src/room/RegionUrlProvider.ts +1 -1
  75. package/src/room/Room.ts +114 -70
  76. package/src/room/events.ts +17 -0
  77. package/src/room/participant/LocalParticipant.ts +65 -11
  78. package/src/room/participant/Participant.ts +27 -3
  79. package/src/room/participant/RemoteParticipant.ts +6 -3
  80. package/src/room/participant/publishUtils.test.ts +1 -1
  81. package/src/room/participant/publishUtils.ts +14 -6
  82. package/src/room/track/LocalAudioTrack.ts +1 -1
  83. package/src/room/track/LocalTrack.ts +2 -2
  84. package/src/room/track/LocalTrackPublication.ts +1 -1
  85. package/src/room/track/LocalVideoTrack.ts +3 -3
  86. package/src/room/track/RemoteAudioTrack.ts +1 -1
  87. package/src/room/track/RemoteVideoTrack.test.ts +1 -1
  88. package/src/room/track/RemoteVideoTrack.ts +3 -3
  89. package/src/room/track/create.ts +2 -2
  90. package/src/room/track/options.ts +14 -1
  91. package/src/room/types.ts +11 -0
  92. package/src/room/utils.ts +18 -8
@@ -22,6 +22,9 @@ import {
22
22
  SignalTarget,
23
23
  TrackPublishedResponse,
24
24
  } from '../proto/livekit_rtc';
25
+ import PCTransport, { PCEvents } from './PCTransport';
26
+ import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
27
+ import { RegionUrlProvider } from './RegionUrlProvider';
25
28
  import { roomConnectOptionDefaults } from './defaults';
26
29
  import {
27
30
  ConnectionError,
@@ -31,24 +34,21 @@ import {
31
34
  UnexpectedConnectionState,
32
35
  } from './errors';
33
36
  import { EngineEvent } from './events';
34
- import PCTransport, { PCEvents } from './PCTransport';
35
- import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
36
37
  import CriticalTimers from './timers';
37
38
  import type LocalTrack from './track/LocalTrack';
38
39
  import type LocalVideoTrack from './track/LocalVideoTrack';
39
40
  import type { SimulcastTrackInfo } from './track/LocalVideoTrack';
40
- import type { TrackPublishOptions, VideoCodec } from './track/options';
41
41
  import { Track } from './track/Track';
42
+ import type { TrackPublishOptions, VideoCodec } from './track/options';
42
43
  import {
44
+ Mutex,
43
45
  isCloud,
44
46
  isWeb,
45
- Mutex,
46
47
  sleep,
47
48
  supportsAddTrack,
48
49
  supportsSetCodecPreferences,
49
50
  supportsTransceiver,
50
51
  } from './utils';
51
- import { RegionUrlProvider } from './RegionUrlProvider';
52
52
 
53
53
  const lossyDataChannel = '_lossy';
54
54
  const reliableDataChannel = '_reliable';
@@ -75,6 +75,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
75
75
 
76
76
  peerConnectionTimeout: number = roomConnectOptionDefaults.peerConnectionTimeout;
77
77
 
78
+ fullReconnectOnNext: boolean = false;
79
+
78
80
  get isClosed() {
79
81
  return this._isClosed;
80
82
  }
@@ -118,8 +120,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
118
120
 
119
121
  private reconnectStart: number = 0;
120
122
 
121
- private fullReconnectOnNext: boolean = false;
122
-
123
123
  private clientConfiguration?: ClientConfiguration;
124
124
 
125
125
  private attemptingReconnect: boolean = false;
@@ -879,11 +879,13 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
879
879
  throw new Error('simulated failure');
880
880
  }
881
881
 
882
- await this.waitForPCReconnected();
883
882
  this.client.setReconnected();
883
+ this.emit(EngineEvent.SignalRestarted, joinResponse);
884
+
885
+ await this.waitForPCReconnected();
884
886
  this.regionUrlProvider?.resetAttempts();
885
887
  // reconnect success
886
- this.emit(EngineEvent.Restarted, joinResponse);
888
+ this.emit(EngineEvent.Restarted);
887
889
  } catch (error) {
888
890
  const nextRegionUrl = await this.regionUrlProvider?.getNextBestRegionUrl();
889
891
  if (nextRegionUrl) {
@@ -992,7 +994,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
992
994
  });
993
995
  }
994
996
 
995
- async waitForPCReconnected() {
997
+ private async waitForPCReconnected() {
996
998
  const startTime = Date.now();
997
999
  let now = startTime;
998
1000
  this.pcState = PCState.Reconnecting;
@@ -1022,6 +1024,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1022
1024
  throw new ConnectionError('could not establish PC connection');
1023
1025
  }
1024
1026
 
1027
+ waitForRestarted = () => {
1028
+ return new Promise<void>((resolve, reject) => {
1029
+ if (this.pcState === PCState.Connected) {
1030
+ resolve();
1031
+ }
1032
+ const onRestarted = () => {
1033
+ this.off(EngineEvent.Disconnected, onDisconnected);
1034
+ resolve();
1035
+ };
1036
+ const onDisconnected = () => {
1037
+ this.off(EngineEvent.Restarted, onRestarted);
1038
+ reject();
1039
+ };
1040
+ this.once(EngineEvent.Restarted, onRestarted);
1041
+ this.once(EngineEvent.Disconnected, onDisconnected);
1042
+ });
1043
+ };
1044
+
1025
1045
  /* @internal */
1026
1046
  async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
1027
1047
  const msg = DataPacket.encode(packet).finish();
@@ -1246,8 +1266,9 @@ export type EngineEventCallbacks = {
1246
1266
  resuming: () => void;
1247
1267
  resumed: () => void;
1248
1268
  restarting: () => void;
1249
- restarted: (joinResp: JoinResponse) => void;
1269
+ restarted: () => void;
1250
1270
  signalResumed: () => void;
1271
+ signalRestarted: (joinResp: JoinResponse) => void;
1251
1272
  closing: () => void;
1252
1273
  mediaTrackAdded: (
1253
1274
  track: MediaStreamTrack,
@@ -1,6 +1,6 @@
1
+ import log from '../logger';
1
2
  import type { RegionInfo, RegionSettings } from '../proto/livekit_rtc';
2
3
  import { ConnectionError, ConnectionErrorReason } from './errors';
3
- import log from '../logger';
4
4
  import { isCloud } from './utils';
5
5
 
6
6
  export class RegionUrlProvider {
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
 
@@ -134,6 +124,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
134
124
 
135
125
  private disconnectLock: Mutex;
136
126
 
127
+ private cachedParticipantSids: Array<string>;
128
+
137
129
  /**
138
130
  * Creates a new Room, the primary construct for a LiveKit session.
139
131
  * @param options
@@ -142,6 +134,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
142
134
  super();
143
135
  this.setMaxListeners(100);
144
136
  this.participants = new Map();
137
+ this.cachedParticipantSids = [];
145
138
  this.identityToSid = new Map();
146
139
  this.options = { ...roomOptionDefaults, ...options };
147
140
 
@@ -165,6 +158,36 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
165
158
  this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
166
159
  }
167
160
 
161
+ /**
162
+ * if the current room has a participant with `recorder: true` in its JWT grant
163
+ **/
164
+ get isRecording(): boolean {
165
+ return this.roomInfo?.activeRecording ?? false;
166
+ }
167
+
168
+ /** server assigned unique room id */
169
+ get sid(): string {
170
+ return this.roomInfo?.sid ?? '';
171
+ }
172
+
173
+ /** user assigned name, derived from JWT token */
174
+ get name(): string {
175
+ return this.roomInfo?.name ?? '';
176
+ }
177
+
178
+ /** room metadata */
179
+ get metadata(): string | undefined {
180
+ return this.roomInfo?.metadata;
181
+ }
182
+
183
+ get numParticipants(): number {
184
+ return this.roomInfo?.numParticipants ?? 0;
185
+ }
186
+
187
+ get numPublishers(): number {
188
+ return this.roomInfo?.numPublishers ?? 0;
189
+ }
190
+
168
191
  private maybeCreateEngine() {
169
192
  if (this.engine) {
170
193
  return;
@@ -195,11 +218,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
195
218
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
196
219
  this.emit(RoomEvent.Reconnecting);
197
220
  }
221
+ this.cachedParticipantSids = Array.from(this.participants.keys());
198
222
  })
199
223
  .on(EngineEvent.Resumed, () => {
200
224
  this.setAndEmitConnectionState(ConnectionState.Connected);
201
225
  this.emit(RoomEvent.Reconnected);
202
226
  this.updateSubscriptions();
227
+
228
+ // once reconnected, figure out if any participants connected during reconnect and emit events for it
229
+ const diffParticipants = Array.from(this.participants.values()).filter(
230
+ (p) => !this.cachedParticipantSids.includes(p.sid),
231
+ );
232
+ diffParticipants.forEach((p) => this.emit(RoomEvent.ParticipantConnected, p));
233
+ this.cachedParticipantSids = [];
203
234
  })
204
235
  .on(EngineEvent.SignalResumed, () => {
205
236
  if (this.state === ConnectionState.Reconnecting) {
@@ -207,7 +238,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
207
238
  }
208
239
  })
209
240
  .on(EngineEvent.Restarting, this.handleRestarting)
210
- .on(EngineEvent.Restarted, this.handleRestarted)
241
+ .on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
211
242
  .on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
212
243
  this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
213
244
  });
@@ -368,31 +399,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
368
399
  this.localParticipant.sid = pi.sid;
369
400
  this.localParticipant.identity = pi.identity;
370
401
 
371
- this.localParticipant.updateInfo(pi);
372
- // forward metadata changed for the local participant
373
- this.setupLocalParticipantEvents();
374
-
375
402
  // 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
- });
403
+ this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);
389
404
 
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);
405
+ if (joinResponse.room) {
406
+ this.handleRoomUpdate(joinResponse.room);
396
407
  }
397
408
  };
398
409
 
@@ -433,6 +444,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
433
444
  );
434
445
 
435
446
  this.applyJoinResponse(joinResponse);
447
+ // forward metadata changed for the local participant
448
+ this.setupLocalParticipantEvents();
436
449
  this.emit(RoomEvent.SignalConnected);
437
450
  } catch (err) {
438
451
  this.recreateEngine();
@@ -534,17 +547,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
534
547
  this.connectFuture = undefined;
535
548
  }
536
549
 
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
550
  /**
545
551
  * @internal for testing
546
552
  */
547
- async simulateScenario(scenario: string) {
553
+ async simulateScenario(scenario: SimulationScenario) {
548
554
  let postAction = () => {};
549
555
  let req: SimulateScenario | undefined;
550
556
  switch (scenario) {
@@ -593,6 +599,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
593
599
  this.engine.client.onClose('simulate resume-reconnect');
594
600
  }
595
601
  break;
602
+ case 'full-reconnect':
603
+ this.engine.fullReconnectOnNext = true;
604
+ await this.engine.client.close();
605
+ if (this.engine.client.onClose) {
606
+ this.engine.client.onClose('simulate full-reconnect');
607
+ }
608
+ break;
596
609
  case 'force-tcp':
597
610
  case 'force-tls':
598
611
  req = SimulateScenario.fromPartial({
@@ -734,6 +747,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
734
747
  private setupLocalParticipantEvents() {
735
748
  this.localParticipant
736
749
  .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
750
+ .on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
737
751
  .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
738
752
  .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
739
753
  .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
@@ -825,20 +839,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
825
839
  }
826
840
  };
827
841
 
828
- private handleRestarted = async (joinResponse: JoinResponse) => {
829
- log.debug(`reconnected to server`, {
842
+ private handleSignalRestarted = async (joinResponse: JoinResponse) => {
843
+ log.debug(`signal reconnected to server`, {
830
844
  region: joinResponse.serverRegion,
831
845
  });
832
846
 
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);
847
+ this.cachedParticipantSids = [];
848
+ this.applyJoinResponse(joinResponse);
841
849
 
850
+ try {
842
851
  // unpublish & republish tracks
843
852
  const localPubs: LocalTrackPublication[] = [];
844
853
  this.localParticipant.tracks.forEach((pub) => {
@@ -872,10 +881,24 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
872
881
  );
873
882
  } catch (error) {
874
883
  log.error('error trying to re-publish tracks after reconnection', { error });
875
- } finally {
876
- this.setAndEmitConnectionState(ConnectionState.Connected);
877
- this.emit(RoomEvent.Reconnected);
878
884
  }
885
+
886
+ try {
887
+ await this.engine.waitForRestarted();
888
+ log.debug(`fully reconnected to server`, {
889
+ region: joinResponse.serverRegion,
890
+ });
891
+ } catch {
892
+ // reconnection failed, handleDisconnect is being invoked already, just return here
893
+ return;
894
+ }
895
+ this.setAndEmitConnectionState(ConnectionState.Connected);
896
+ this.emit(RoomEvent.Reconnected);
897
+
898
+ // emit participant connected events after connection has been re-established
899
+ this.participants.forEach((participant) => {
900
+ this.emit(RoomEvent.ParticipantConnected, participant);
901
+ });
879
902
  };
880
903
 
881
904
  private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
@@ -901,6 +924,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
901
924
 
902
925
  this.localParticipant
903
926
  .off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
927
+ .off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
904
928
  .off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
905
929
  .off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
906
930
  .off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
@@ -1106,14 +1130,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1106
1130
  this.emit(RoomEvent.MediaDevicesChanged);
1107
1131
  };
1108
1132
 
1109
- private handleRoomUpdate = (r: RoomModel) => {
1110
- if (this._isRecording !== r.activeRecording) {
1111
- this._isRecording = r.activeRecording;
1112
- this.emit(RoomEvent.RecordingStatusChanged, r.activeRecording);
1133
+ private handleRoomUpdate = (room: RoomModel) => {
1134
+ const oldRoom = this.roomInfo;
1135
+ this.roomInfo = room;
1136
+ if (oldRoom && oldRoom.metadata !== room.metadata) {
1137
+ this.emitWhenConnected(RoomEvent.RoomMetadataChanged, room.metadata);
1113
1138
  }
1114
- if (this.metadata !== r.metadata) {
1115
- this.metadata = r.metadata;
1116
- this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
1139
+ if (oldRoom?.activeRecording !== room.activeRecording) {
1140
+ this.emitWhenConnected(RoomEvent.RecordingStatusChanged, room.activeRecording);
1117
1141
  }
1118
1142
  };
1119
1143
 
@@ -1222,6 +1246,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1222
1246
  .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
1223
1247
  this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant);
1224
1248
  })
1249
+ .on(ParticipantEvent.ParticipantNameChanged, (name) => {
1250
+ this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant);
1251
+ })
1225
1252
  .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
1226
1253
  this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
1227
1254
  })
@@ -1338,6 +1365,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1338
1365
  this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
1339
1366
  };
1340
1367
 
1368
+ private onLocalParticipantNameChanged = (name: string) => {
1369
+ this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant);
1370
+ };
1371
+
1341
1372
  private onLocalTrackMuted = (pub: TrackPublication) => {
1342
1373
  this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
1343
1374
  };
@@ -1392,7 +1423,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1392
1423
  ...options.participants,
1393
1424
  };
1394
1425
  this.handleDisconnect();
1395
- this.name = 'simulated-room';
1426
+ this.roomInfo = {
1427
+ sid: 'RM_SIMULATED',
1428
+ name: 'simulated-room',
1429
+ emptyTimeout: 0,
1430
+ maxParticipants: 0,
1431
+ creationTime: new Date().getTime(),
1432
+ metadata: '',
1433
+ numParticipants: 1,
1434
+ numPublishers: 1,
1435
+ turnPassword: '',
1436
+ enabledCodecs: [],
1437
+ activeRecording: false,
1438
+ };
1396
1439
 
1397
1440
  this.localParticipant.updateInfo(
1398
1441
  ParticipantInfo.fromPartial({
@@ -1537,6 +1580,7 @@ export type RoomEventCallbacks = {
1537
1580
  metadata: string | undefined,
1538
1581
  participant: RemoteParticipant | LocalParticipant,
1539
1582
  ) => void;
1583
+ participantNameChanged: (name: string, participant: RemoteParticipant | LocalParticipant) => void;
1540
1584
  participantPermissionsChanged: (
1541
1585
  prevPermissions: ParticipantPermission | undefined,
1542
1586
  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,36 @@ 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,
30
+ isCodecEqual,
29
31
  } from '../track/options';
30
- import { Track } from '../track/Track';
31
32
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
32
33
  import type { DataPublishOptions } from '../types';
33
- import { Future, isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
34
+ import { Future, isFireFox, isSVCCodec, isSafari, isWeb, supportsAV1, supportsVP9 } from '../utils';
34
35
  import Participant from './Participant';
35
36
  import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
37
+ import RemoteParticipant from './RemoteParticipant';
36
38
  import {
37
39
  computeTrackBackupEncodings,
38
40
  computeVideoEncodings,
39
41
  mediaTrackToLocalTrack,
40
42
  } from './publishUtils';
41
- import RemoteParticipant from './RemoteParticipant';
42
43
 
43
44
  export default class LocalParticipant extends Participant {
44
45
  audioTracks: Map<string, LocalTrackPublication>;
@@ -148,6 +149,26 @@ export default class LocalParticipant extends Participant {
148
149
  this.reconnectFuture = undefined;
149
150
  };
150
151
 
152
+ /**
153
+ * Sets and updates the metadata of the local participant.
154
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
155
+ * @param metadata
156
+ */
157
+ setMetadata(metadata: string): void {
158
+ super.setMetadata(metadata);
159
+ this.engine.client.sendUpdateLocalMetadata(metadata, this.name ?? '');
160
+ }
161
+
162
+ /**
163
+ * Sets and updates the name of the local participant.
164
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
165
+ * @param metadata
166
+ */
167
+ setName(name: string): void {
168
+ super.setName(name);
169
+ this.engine.client.sendUpdateLocalMetadata(this.metadata ?? '', name);
170
+ }
171
+
151
172
  /**
152
173
  * Enable or disable a participant's camera track.
153
174
  *
@@ -550,10 +571,13 @@ export default class LocalParticipant extends Participant {
550
571
  opts.simulcast = false;
551
572
  }
552
573
 
553
- // require full AV1 SVC support prior to using it
574
+ // require full AV1/VP9 SVC support prior to using it
554
575
  if (opts.videoCodec === 'av1' && !supportsAV1()) {
555
576
  opts.videoCodec = undefined;
556
577
  }
578
+ if (opts.videoCodec === 'vp9' && !supportsVP9()) {
579
+ opts.videoCodec = undefined;
580
+ }
557
581
 
558
582
  // handle track actions
559
583
  track.on(TrackEvent.Muted, this.onTrackMuted);
@@ -594,7 +618,7 @@ export default class LocalParticipant extends Participant {
594
618
  req.height = dims.height;
595
619
  // for svc codecs, disable simulcast and use vp8 for backup codec
596
620
  if (track instanceof LocalVideoTrack) {
597
- if (opts?.videoCodec === 'av1') {
621
+ if (isSVCCodec(opts.videoCodec)) {
598
622
  // set scalabilityMode to 'L3T3' by default
599
623
  opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
600
624
  }
@@ -640,6 +664,33 @@ export default class LocalParticipant extends Participant {
640
664
  }
641
665
 
642
666
  const ti = await this.engine.addTrack(req);
667
+ let primaryCodecSupported = false;
668
+ let backupCodecSupported = false;
669
+ ti.codecs.forEach((c) => {
670
+ if (isCodecEqual(c.mimeType, opts.videoCodec)) {
671
+ primaryCodecSupported = true;
672
+ } else if (opts.backupCodec && isCodecEqual(c.mimeType, opts.backupCodec.codec)) {
673
+ backupCodecSupported = true;
674
+ }
675
+ });
676
+
677
+ if (req.simulcastCodecs.length > 0) {
678
+ if (!primaryCodecSupported && !backupCodecSupported) {
679
+ throw Error('cannot publish track, codec not supported by server');
680
+ }
681
+
682
+ if (!primaryCodecSupported && opts.backupCodec) {
683
+ const backupCodec = opts.backupCodec;
684
+ opts = { ...opts };
685
+ log.debug(
686
+ `primary codec ${opts.videoCodec} not supported, fallback to ${backupCodec.codec}`,
687
+ );
688
+ opts.videoCodec = backupCodec.codec;
689
+ opts.videoEncoding = backupCodec.encoding;
690
+ encodings = simEncodings;
691
+ }
692
+ }
693
+
643
694
  const publication = new LocalTrackPublication(track.kind, ti, track);
644
695
  // save options for when it needs to be republished again
645
696
  publication.options = opts;
@@ -653,7 +704,7 @@ export default class LocalParticipant extends Participant {
653
704
  // store RTPSender
654
705
  track.sender = await this.engine.createSender(track, opts, encodings);
655
706
 
656
- if (track.codec === 'av1' && encodings && encodings[0]?.maxBitrate) {
707
+ if (track.codec && isSVCCodec(track.codec) && encodings && encodings[0]?.maxBitrate) {
657
708
  this.engine.publisher.setTrackCodecBitrate(
658
709
  req.cid,
659
710
  track.codec,
@@ -961,13 +1012,15 @@ export default class LocalParticipant extends Participant {
961
1012
  }
962
1013
 
963
1014
  /** @internal */
964
- updateInfo(info: ParticipantInfo) {
1015
+ updateInfo(info: ParticipantInfo): boolean {
965
1016
  if (info.sid !== this.sid) {
966
1017
  // drop updates that specify a wrong sid.
967
1018
  // the sid for local participant is only explicitly set on join and full reconnect
968
- return;
1019
+ return false;
1020
+ }
1021
+ if (!super.updateInfo(info)) {
1022
+ return false;
969
1023
  }
970
- super.updateInfo(info);
971
1024
 
972
1025
  // reconcile track mute status.
973
1026
  // if server's track mute status doesn't match actual, we'll have to update
@@ -986,6 +1039,7 @@ export default class LocalParticipant extends Participant {
986
1039
  }
987
1040
  }
988
1041
  });
1042
+ return true;
989
1043
  }
990
1044
 
991
1045
  private updateTrackSubscriptionPermissions = () => {