livekit-client 1.0.1 → 1.0.2

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 (140) hide show
  1. package/dist/livekit-client.esm.mjs +792 -197
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/{api → src/api}/RequestQueue.d.ts +0 -0
  6. package/dist/src/api/RequestQueue.d.ts.map +1 -0
  7. package/dist/{api → src/api}/SignalClient.d.ts +1 -1
  8. package/dist/src/api/SignalClient.d.ts.map +1 -0
  9. package/dist/{index.d.ts → src/index.d.ts} +0 -0
  10. package/dist/src/index.d.ts.map +1 -0
  11. package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
  12. package/dist/src/logger.d.ts.map +1 -0
  13. package/dist/{options.d.ts → src/options.d.ts} +0 -0
  14. package/dist/src/options.d.ts.map +1 -0
  15. package/dist/{proto → src/proto}/google/protobuf/timestamp.d.ts +0 -0
  16. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -0
  17. package/dist/{proto → src/proto}/livekit_models.d.ts +80 -0
  18. package/dist/src/proto/livekit_models.d.ts.map +1 -0
  19. package/dist/{proto → src/proto}/livekit_rtc.d.ts +661 -0
  20. package/dist/src/proto/livekit_rtc.d.ts.map +1 -0
  21. package/dist/{room → src/room}/DeviceManager.d.ts +0 -0
  22. package/dist/src/room/DeviceManager.d.ts.map +1 -0
  23. package/dist/{room → src/room}/PCTransport.d.ts +0 -0
  24. package/dist/src/room/PCTransport.d.ts.map +1 -0
  25. package/dist/{room → src/room}/RTCEngine.d.ts +1 -0
  26. package/dist/src/room/RTCEngine.d.ts.map +1 -0
  27. package/dist/{room → src/room}/Room.d.ts +2 -0
  28. package/dist/src/room/Room.d.ts.map +1 -0
  29. package/dist/{room → src/room}/errors.d.ts +0 -0
  30. package/dist/src/room/errors.d.ts.map +1 -0
  31. package/dist/{room → src/room}/events.d.ts +5 -1
  32. package/dist/src/room/events.d.ts.map +1 -0
  33. package/dist/{room → src/room}/participant/LocalParticipant.d.ts +4 -1
  34. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -0
  35. package/dist/{room → src/room}/participant/Participant.d.ts +0 -0
  36. package/dist/src/room/participant/Participant.d.ts.map +1 -0
  37. package/dist/{room → src/room}/participant/ParticipantTrackPermission.d.ts +0 -0
  38. package/dist/src/room/participant/ParticipantTrackPermission.d.ts.map +1 -0
  39. package/dist/{room → src/room}/participant/RemoteParticipant.d.ts +0 -0
  40. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -0
  41. package/dist/{room → src/room}/participant/publishUtils.d.ts +0 -0
  42. package/dist/src/room/participant/publishUtils.d.ts.map +1 -0
  43. package/dist/{room → src/room}/stats.d.ts +1 -0
  44. package/dist/src/room/stats.d.ts.map +1 -0
  45. package/dist/{room → src/room}/track/LocalAudioTrack.d.ts +0 -0
  46. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -0
  47. package/dist/{room → src/room}/track/LocalTrack.d.ts +3 -0
  48. package/dist/src/room/track/LocalTrack.d.ts.map +1 -0
  49. package/dist/{room → src/room}/track/LocalTrackPublication.d.ts +0 -0
  50. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -0
  51. package/dist/{room → src/room}/track/LocalVideoTrack.d.ts +17 -2
  52. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -0
  53. package/dist/{room → src/room}/track/RemoteAudioTrack.d.ts +0 -0
  54. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -0
  55. package/dist/{room → src/room}/track/RemoteTrack.d.ts +0 -1
  56. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -0
  57. package/dist/{room → src/room}/track/RemoteTrackPublication.d.ts +0 -0
  58. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -0
  59. package/dist/{room → src/room}/track/RemoteVideoTrack.d.ts +2 -0
  60. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -0
  61. package/dist/{room → src/room}/track/Track.d.ts +4 -0
  62. package/dist/src/room/track/Track.d.ts.map +1 -0
  63. package/dist/{room → src/room}/track/TrackPublication.d.ts +0 -0
  64. package/dist/src/room/track/TrackPublication.d.ts.map +1 -0
  65. package/dist/{room → src/room}/track/create.d.ts +0 -0
  66. package/dist/src/room/track/create.d.ts.map +1 -0
  67. package/dist/{room → src/room}/track/defaults.d.ts +0 -0
  68. package/dist/src/room/track/defaults.d.ts.map +1 -0
  69. package/dist/{room → src/room}/track/options.d.ts +2 -1
  70. package/dist/src/room/track/options.d.ts.map +1 -0
  71. package/dist/{room → src/room}/track/types.d.ts +0 -0
  72. package/dist/src/room/track/types.d.ts.map +1 -0
  73. package/dist/{room → src/room}/track/utils.d.ts +0 -0
  74. package/dist/src/room/track/utils.d.ts.map +1 -0
  75. package/dist/{room → src/room}/utils.d.ts +0 -0
  76. package/dist/src/room/utils.d.ts.map +1 -0
  77. package/dist/{test → src/test}/MockMediaStreamTrack.d.ts +0 -0
  78. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -0
  79. package/dist/{test → src/test}/mocks.d.ts +0 -0
  80. package/dist/src/test/mocks.d.ts.map +1 -0
  81. package/dist/src/version.d.ts +3 -0
  82. package/dist/src/version.d.ts.map +1 -0
  83. package/package.json +5 -2
  84. package/src/api/SignalClient.ts +2 -2
  85. package/src/proto/livekit_models.ts +90 -0
  86. package/src/proto/livekit_rtc.ts +235 -1
  87. package/src/room/RTCEngine.ts +30 -2
  88. package/src/room/Room.ts +60 -15
  89. package/src/room/events.ts +5 -0
  90. package/src/room/participant/LocalParticipant.ts +104 -23
  91. package/src/room/participant/RemoteParticipant.ts +1 -0
  92. package/src/room/stats.ts +2 -0
  93. package/src/room/track/LocalAudioTrack.ts +4 -0
  94. package/src/room/track/LocalTrack.ts +12 -5
  95. package/src/room/track/LocalVideoTrack.ts +143 -55
  96. package/src/room/track/RemoteTrack.ts +0 -2
  97. package/src/room/track/RemoteVideoTrack.ts +6 -0
  98. package/src/room/track/Track.ts +5 -0
  99. package/src/room/track/options.ts +2 -1
  100. package/src/version.ts +4 -2
  101. package/dist/api/RequestQueue.d.ts.map +0 -1
  102. package/dist/api/SignalClient.d.ts.map +0 -1
  103. package/dist/index.d.ts.map +0 -1
  104. package/dist/logger.d.ts.map +0 -1
  105. package/dist/options.d.ts.map +0 -1
  106. package/dist/proto/google/protobuf/timestamp.d.ts.map +0 -1
  107. package/dist/proto/livekit_models.d.ts.map +0 -1
  108. package/dist/proto/livekit_rtc.d.ts.map +0 -1
  109. package/dist/room/DeviceManager.d.ts.map +0 -1
  110. package/dist/room/PCTransport.d.ts.map +0 -1
  111. package/dist/room/RTCEngine.d.ts.map +0 -1
  112. package/dist/room/Room.d.ts.map +0 -1
  113. package/dist/room/errors.d.ts.map +0 -1
  114. package/dist/room/events.d.ts.map +0 -1
  115. package/dist/room/participant/LocalParticipant.d.ts.map +0 -1
  116. package/dist/room/participant/Participant.d.ts.map +0 -1
  117. package/dist/room/participant/ParticipantTrackPermission.d.ts.map +0 -1
  118. package/dist/room/participant/RemoteParticipant.d.ts.map +0 -1
  119. package/dist/room/participant/publishUtils.d.ts.map +0 -1
  120. package/dist/room/stats.d.ts.map +0 -1
  121. package/dist/room/track/LocalAudioTrack.d.ts.map +0 -1
  122. package/dist/room/track/LocalTrack.d.ts.map +0 -1
  123. package/dist/room/track/LocalTrackPublication.d.ts.map +0 -1
  124. package/dist/room/track/LocalVideoTrack.d.ts.map +0 -1
  125. package/dist/room/track/RemoteAudioTrack.d.ts.map +0 -1
  126. package/dist/room/track/RemoteTrack.d.ts.map +0 -1
  127. package/dist/room/track/RemoteTrackPublication.d.ts.map +0 -1
  128. package/dist/room/track/RemoteVideoTrack.d.ts.map +0 -1
  129. package/dist/room/track/Track.d.ts.map +0 -1
  130. package/dist/room/track/TrackPublication.d.ts.map +0 -1
  131. package/dist/room/track/create.d.ts.map +0 -1
  132. package/dist/room/track/defaults.d.ts.map +0 -1
  133. package/dist/room/track/options.d.ts.map +0 -1
  134. package/dist/room/track/types.d.ts.map +0 -1
  135. package/dist/room/track/utils.d.ts.map +0 -1
  136. package/dist/room/utils.d.ts.map +0 -1
  137. package/dist/test/MockMediaStreamTrack.d.ts.map +0 -1
  138. package/dist/test/mocks.d.ts.map +0 -1
  139. package/dist/version.d.ts +0 -3
  140. package/dist/version.d.ts.map +0 -1
package/src/room/Room.ts CHANGED
@@ -27,7 +27,9 @@ import Participant, { ConnectionQuality } from './participant/Participant';
27
27
  import RemoteParticipant from './participant/RemoteParticipant';
28
28
  import RTCEngine, { maxICEConnectTimeout } from './RTCEngine';
29
29
  import { audioDefaults, publishDefaults, videoDefaults } from './track/defaults';
30
+ import LocalAudioTrack from './track/LocalAudioTrack';
30
31
  import LocalTrackPublication from './track/LocalTrackPublication';
32
+ import LocalVideoTrack from './track/LocalVideoTrack';
31
33
  import RemoteTrackPublication from './track/RemoteTrackPublication';
32
34
  import { Track } from './track/Track';
33
35
  import { TrackPublication } from './track/TrackPublication';
@@ -84,6 +86,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
84
86
  /** options of room */
85
87
  options: RoomOptions;
86
88
 
89
+ private identityToSid: Map<string, string>;
90
+
87
91
  /** connect options of room */
88
92
  private connOptions?: RoomConnectOptions;
89
93
 
@@ -101,6 +105,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
101
105
  constructor(options?: RoomOptions) {
102
106
  super();
103
107
  this.participants = new Map();
108
+ this.identityToSid = new Map();
104
109
  this.options = options || {};
105
110
 
106
111
  switch (this.options?.publishDefaults?.videoCodec) {
@@ -157,8 +162,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
157
162
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
158
163
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
159
164
  .on(EngineEvent.Resuming, () => {
160
- this.setAndEmitConnectionState(ConnectionState.Reconnecting);
161
- this.emit(RoomEvent.Reconnecting);
165
+ if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
166
+ this.emit(RoomEvent.Reconnecting);
167
+ }
162
168
  })
163
169
  .on(EngineEvent.Resumed, () => {
164
170
  this.setAndEmitConnectionState(ConnectionState.Connected);
@@ -287,6 +293,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
287
293
  this.name = joinResponse.room!.name;
288
294
  this.sid = joinResponse.room!.sid;
289
295
  this.metadata = joinResponse.room!.metadata;
296
+ this.emit(RoomEvent.SignalConnected);
290
297
  } catch (err) {
291
298
  this.engine.close();
292
299
  this.setAndEmitConnectionState(ConnectionState.Disconnected);
@@ -338,10 +345,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
338
345
  return;
339
346
  }
340
347
  // send leave
341
- if (this.engine) {
348
+ if (this.engine?.client.isConnected) {
342
349
  this.engine.client.sendLeave();
350
+ }
351
+ // close engine (also closes client)
352
+ if (this.engine) {
343
353
  this.engine.close();
344
354
  }
355
+
345
356
  this.handleDisconnect(stopTracks);
346
357
  /* @ts-ignore */
347
358
  this.engine = undefined;
@@ -353,20 +364,20 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
353
364
  * @returns
354
365
  */
355
366
  getParticipantByIdentity(identity: string): Participant | undefined {
356
- for (const [, p] of this.participants) {
357
- if (p.identity === identity) {
358
- return p;
359
- }
360
- }
361
367
  if (this.localParticipant.identity === identity) {
362
368
  return this.localParticipant;
363
369
  }
370
+ const sid = this.identityToSid.get(identity);
371
+ if (sid) {
372
+ return this.participants.get(sid);
373
+ }
364
374
  }
365
375
 
366
376
  /**
367
377
  * @internal for testing
368
378
  */
369
379
  simulateScenario(scenario: string) {
380
+ let postAction = () => {};
370
381
  let req: SimulateScenario | undefined;
371
382
  switch (scenario) {
372
383
  case 'speaker':
@@ -389,10 +400,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
389
400
  migration: true,
390
401
  });
391
402
  break;
403
+ case 'switch-candidate':
404
+ req = SimulateScenario.fromPartial({
405
+ switchCandidateProtocol: 1,
406
+ });
407
+ postAction = () => {
408
+ this.engine.publisher?.createAndSendOffer({ iceRestart: true });
409
+ };
410
+ break;
392
411
  default:
393
412
  }
394
413
  if (req) {
395
414
  this.engine.client.sendSimulateScenario(req);
415
+ postAction();
396
416
  }
397
417
  }
398
418
 
@@ -512,8 +532,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
512
532
  }
513
533
 
514
534
  private handleRestarting = () => {
515
- this.setAndEmitConnectionState(ConnectionState.Reconnecting);
516
- this.emit(RoomEvent.Reconnecting);
535
+ if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
536
+ this.emit(RoomEvent.Reconnecting);
537
+ }
517
538
 
518
539
  // also unwind existing participants & existing subscriptions
519
540
  for (const p of this.participants.values()) {
@@ -522,7 +543,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
522
543
  };
523
544
 
524
545
  private handleRestarted = async (joinResponse: JoinResponse) => {
525
- log.debug(`reconnected to server region ${joinResponse.serverRegion}`);
546
+ log.debug(`reconnected to server`, {
547
+ region: joinResponse.serverRegion,
548
+ });
526
549
  this.setAndEmitConnectionState(ConnectionState.Connected);
527
550
  this.emit(RoomEvent.Reconnected);
528
551
 
@@ -546,7 +569,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
546
569
  localPubs.map(async (pub) => {
547
570
  const track = pub.track!;
548
571
  this.localParticipant.unpublishTrack(track, false);
549
- this.localParticipant.publishTrack(track, pub.options);
572
+ if (!track.isMuted) {
573
+ if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
574
+ // we need to restart the track before publishing, often a full reconnect
575
+ // is necessary because computer had gone to sleep.
576
+ log.debug('restarting existing track', {
577
+ track: pub.trackSid,
578
+ });
579
+ await track.restartTrack();
580
+ }
581
+ await this.localParticipant.publishTrack(track, pub.options);
582
+ }
550
583
  }),
551
584
  );
552
585
  };
@@ -593,6 +626,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
593
626
  return;
594
627
  }
595
628
 
629
+ // ensure identity <=> sid mapping
630
+ const sid = this.identityToSid.get(info.identity);
631
+ if (sid && sid !== info.sid) {
632
+ // sid had changed, need to remove previous participant
633
+ this.handleParticipantDisconnected(sid, this.participants.get(sid));
634
+ }
635
+
596
636
  let remoteParticipant = this.participants.get(info.sid);
597
637
  const isNewParticipant = !remoteParticipant;
598
638
 
@@ -603,6 +643,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
603
643
  if (info.state === ParticipantInfo_State.DISCONNECTED) {
604
644
  this.handleParticipantDisconnected(info.sid, remoteParticipant);
605
645
  } else if (isNewParticipant) {
646
+ this.identityToSid.set(info.identity, info.sid);
606
647
  // fire connected event
607
648
  this.emit(RoomEvent.ParticipantConnected, remoteParticipant);
608
649
  } else {
@@ -619,8 +660,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
619
660
  return;
620
661
  }
621
662
 
663
+ this.identityToSid.delete(participant.identity);
622
664
  participant.tracks.forEach((publication) => {
623
- participant.unpublishTrack(publication.trackSid);
665
+ participant.unpublishTrack(publication.trackSid, true);
624
666
  });
625
667
  this.emit(RoomEvent.ParticipantDisconnected, participant);
626
668
  }
@@ -913,12 +955,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
913
955
  }
914
956
  }
915
957
 
916
- private setAndEmitConnectionState(state: ConnectionState) {
958
+ private setAndEmitConnectionState(state: ConnectionState): boolean {
917
959
  if (state === this.state) {
918
- return;
960
+ // unchanged
961
+ return false;
919
962
  }
920
963
  this.state = state;
921
964
  this.emit(RoomEvent.ConnectionStateChanged, this.state);
965
+ return true;
922
966
  }
923
967
 
924
968
  // /** @internal */
@@ -991,4 +1035,5 @@ export type RoomEventCallbacks = {
991
1035
  participant: RemoteParticipant,
992
1036
  ) => void;
993
1037
  audioPlaybackChanged: (playing: boolean) => void;
1038
+ signalConnected: () => void;
994
1039
  };
@@ -230,6 +230,11 @@ export enum RoomEvent {
230
230
  * args: (prevPermissions: [[ParticipantPermission]], participant: [[Participant]])
231
231
  */
232
232
  ParticipantPermissionsChanged = 'participantPermissionsChanged',
233
+
234
+ /**
235
+ * Signal connected, can publish tracks.
236
+ */
237
+ SignalConnected = 'signalConnected',
233
238
  }
234
239
 
235
240
  export enum ParticipantEvent {
@@ -10,12 +10,15 @@ import {
10
10
  TrackUnpublishedResponse,
11
11
  } from '../../proto/livekit_rtc';
12
12
  import { TrackInvalidError, UnexpectedConnectionState } from '../errors';
13
- import { ParticipantEvent, TrackEvent } from '../events';
13
+ import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
14
14
  import RTCEngine from '../RTCEngine';
15
15
  import LocalAudioTrack from '../track/LocalAudioTrack';
16
16
  import LocalTrack from '../track/LocalTrack';
17
17
  import LocalTrackPublication from '../track/LocalTrackPublication';
18
- import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
18
+ import LocalVideoTrack, {
19
+ SimulcastTrackInfo,
20
+ videoLayersFromEncodings,
21
+ } from '../track/LocalVideoTrack';
19
22
  import {
20
23
  CreateLocalTracksOptions,
21
24
  ScreenShareCaptureOptions,
@@ -31,6 +34,7 @@ import { ParticipantTrackPermission, trackPermissionToProto } from './Participan
31
34
  import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
32
35
  import RemoteParticipant from './RemoteParticipant';
33
36
 
37
+ const compatibleCodecForSVC = 'vp8';
34
38
  export default class LocalParticipant extends Participant {
35
39
  audioTracks: Map<string, LocalTrackPublication>;
36
40
 
@@ -47,6 +51,10 @@ export default class LocalParticipant extends Participant {
47
51
 
48
52
  private engine: RTCEngine;
49
53
 
54
+ private participantTrackPermissions: Array<ParticipantTrackPermission> = [];
55
+
56
+ private allParticipantsAllowedToSubscribe: boolean = true;
57
+
50
58
  // keep a pointer to room options
51
59
  private roomOptions?: RoomOptions;
52
60
 
@@ -74,6 +82,11 @@ export default class LocalParticipant extends Participant {
74
82
  this.engine.client.onSubscribedQualityUpdate = this.handleSubscribedQualityUpdate;
75
83
 
76
84
  this.engine.client.onLocalTrackUnpublished = this.handleLocalTrackUnpublished;
85
+
86
+ this.engine
87
+ .on(EngineEvent.Connected, this.updateTrackSubscriptionPermissions)
88
+ .on(EngineEvent.Restarted, this.updateTrackSubscriptionPermissions)
89
+ .on(EngineEvent.Resumed, this.updateTrackSubscriptionPermissions);
77
90
  }
78
91
 
79
92
  get lastCameraError(): Error | undefined {
@@ -384,7 +397,7 @@ export default class LocalParticipant extends Participant {
384
397
  // handle track actions
385
398
  track.on(TrackEvent.Muted, this.onTrackMuted);
386
399
  track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
387
- track.on(TrackEvent.Ended, this.onTrackUnpublish);
400
+ track.on(TrackEvent.Ended, this.handleTrackEnded);
388
401
  track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
389
402
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
390
403
 
@@ -401,6 +414,8 @@ export default class LocalParticipant extends Participant {
401
414
 
402
415
  // compute encodings and layers for video
403
416
  let encodings: RTCRtpEncodingParameters[] | undefined;
417
+ let simEncodings: RTCRtpEncodingParameters[] | undefined;
418
+ let simulcastTracks: SimulcastTrackInfo[] | undefined;
404
419
  if (track.kind === Track.Kind.Video) {
405
420
  // TODO: support react native, which doesn't expose getSettings
406
421
  const settings = track.mediaStreamTrack.getSettings();
@@ -409,16 +424,38 @@ export default class LocalParticipant extends Participant {
409
424
  // width and height should be defined for video
410
425
  req.width = width ?? 0;
411
426
  req.height = height ?? 0;
412
- // for svc codecs, disable simulcast and enable scalability L3T3
413
- // by default
414
- if (track instanceof LocalVideoTrack) {
415
- if (opts?.videoCodec === 'vp9' || opts?.videoCodec === 'av1') {
416
- opts.simulcast = false;
417
- opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
418
- } else {
419
- // other codecs, unset scalability
420
- opts.scalabilityMode = undefined;
421
- }
427
+ // for svc codecs, disable simulcast and use vp8 for backup codec
428
+ if (
429
+ track instanceof LocalVideoTrack &&
430
+ (opts?.videoCodec === 'vp9' || opts?.videoCodec === 'av1')
431
+ ) {
432
+ // set scalabilityMode to 'L3T3' by default
433
+ opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
434
+
435
+ // add backup codec track
436
+ const simOpts = { ...opts };
437
+ simOpts.simulcast = true;
438
+ simOpts.scalabilityMode = undefined;
439
+ simEncodings = computeVideoEncodings(
440
+ track.source === Track.Source.ScreenShare,
441
+ width,
442
+ height,
443
+ simOpts,
444
+ );
445
+ const simulcastTrack = track.addSimulcastTrack(compatibleCodecForSVC, simEncodings);
446
+ simulcastTracks = [simulcastTrack];
447
+ req.simulcastCodecs = [
448
+ {
449
+ codec: opts.videoCodec,
450
+ cid: track.mediaStreamTrack.id,
451
+ enableSimulcastLayers: true,
452
+ },
453
+ {
454
+ codec: simulcastTrack.codec,
455
+ cid: simulcastTrack.mediaStreamTrack.id,
456
+ enableSimulcastLayers: true,
457
+ },
458
+ ];
422
459
  }
423
460
 
424
461
  encodings = computeVideoEncodings(
@@ -427,7 +464,7 @@ export default class LocalParticipant extends Participant {
427
464
  height,
428
465
  opts,
429
466
  );
430
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
467
+ req.layers = videoLayersFromEncodings(req.width, req.height, simEncodings ?? encodings);
431
468
  } else if (track.kind === Track.Kind.Audio && opts.audioBitrate) {
432
469
  encodings = [
433
470
  {
@@ -442,6 +479,8 @@ export default class LocalParticipant extends Participant {
442
479
 
443
480
  const ti = await this.engine.addTrack(req);
444
481
  const publication = new LocalTrackPublication(track.kind, ti, track);
482
+ // save options for when it needs to be republished again
483
+ publication.options = opts;
445
484
  track.sid = ti.sid;
446
485
 
447
486
  if (!this.engine.publisher) {
@@ -452,13 +491,32 @@ export default class LocalParticipant extends Participant {
452
491
  if (encodings) {
453
492
  transceiverInit.sendEncodings = encodings;
454
493
  }
455
- const transceiver = this.engine.publisher.pc.addTransceiver(
494
+ // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
495
+ const transceiver = await this.engine.publisher.pc.addTransceiver(
456
496
  track.mediaStreamTrack,
457
497
  transceiverInit,
458
498
  );
459
- if (opts.videoCodec) {
499
+ if (track.kind === Track.Kind.Video && opts.videoCodec) {
460
500
  this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
501
+ track.codec = opts.videoCodec;
461
502
  }
503
+
504
+ const localTrack = track as LocalVideoTrack;
505
+ if (simulcastTracks) {
506
+ for await (const simulcastTrack of simulcastTracks) {
507
+ const simTransceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
508
+ if (simulcastTrack.encodings) {
509
+ simTransceiverInit.sendEncodings = simulcastTrack.encodings;
510
+ }
511
+ const simTransceiver = await this.engine.publisher!.pc.addTransceiver(
512
+ simulcastTrack.mediaStreamTrack,
513
+ simTransceiverInit,
514
+ );
515
+ this.setPreferredCodec(simTransceiver, localTrack.kind, simulcastTrack.codec);
516
+ localTrack.setSimulcastTrackSender(simulcastTrack.codec, simTransceiver.sender);
517
+ }
518
+ }
519
+
462
520
  this.engine.negotiate();
463
521
 
464
522
  // store RTPSender
@@ -468,6 +526,7 @@ export default class LocalParticipant extends Participant {
468
526
  } else if (track instanceof LocalAudioTrack) {
469
527
  track.startMonitor();
470
528
  }
529
+
471
530
  this.addTrackPublication(publication);
472
531
 
473
532
  // send event for publication
@@ -494,9 +553,10 @@ export default class LocalParticipant extends Participant {
494
553
 
495
554
  track = publication.track;
496
555
 
556
+ track.sender = undefined;
497
557
  track.off(TrackEvent.Muted, this.onTrackMuted);
498
558
  track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
499
- track.off(TrackEvent.Ended, this.onTrackUnpublish);
559
+ track.off(TrackEvent.Ended, this.handleTrackEnded);
500
560
  track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
501
561
  track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
502
562
 
@@ -509,7 +569,7 @@ export default class LocalParticipant extends Participant {
509
569
 
510
570
  const { mediaStreamTrack } = track;
511
571
 
512
- if (this.engine.publisher) {
572
+ if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed') {
513
573
  const senders = this.engine.publisher.pc.getSenders();
514
574
  senders.forEach((sender) => {
515
575
  if (sender.track === mediaStreamTrack) {
@@ -613,11 +673,23 @@ export default class LocalParticipant extends Participant {
613
673
  allParticipantsAllowed: boolean,
614
674
  participantTrackPermissions: ParticipantTrackPermission[] = [],
615
675
  ) {
676
+ this.participantTrackPermissions = participantTrackPermissions;
677
+ this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
678
+ if (this.engine.client.isConnected) {
679
+ this.updateTrackSubscriptionPermissions();
680
+ }
681
+ }
682
+
683
+ private updateTrackSubscriptionPermissions = () => {
684
+ log.debug('updating track subscription permissions', {
685
+ allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
686
+ participantTrackPermissions: this.participantTrackPermissions,
687
+ });
616
688
  this.engine.client.sendUpdateSubscriptionPermissions(
617
- allParticipantsAllowed,
618
- participantTrackPermissions.map((p) => trackPermissionToProto(p)),
689
+ this.allParticipantsAllowedToSubscribe,
690
+ this.participantTrackPermissions.map((p) => trackPermissionToProto(p)),
619
691
  );
620
- }
692
+ };
621
693
 
622
694
  /** @internal */
623
695
  private onTrackUnmuted = (track: LocalTrack) => {
@@ -661,7 +733,11 @@ export default class LocalParticipant extends Participant {
661
733
  });
662
734
  return;
663
735
  }
664
- pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
736
+ if (update.subscribedCodecs.length > 0) {
737
+ pub.videoTrack?.setPublishingCodecs(update.subscribedCodecs);
738
+ } else if (update.subscribedQualities.length > 0) {
739
+ pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
740
+ }
665
741
  };
666
742
 
667
743
  private handleLocalTrackUnpublished = (unpublished: TrackUnpublishedResponse) => {
@@ -676,7 +752,10 @@ export default class LocalParticipant extends Participant {
676
752
  this.unpublishTrack(track.track!);
677
753
  };
678
754
 
679
- private onTrackUnpublish = (track: LocalTrack) => {
755
+ private handleTrackEnded = (track: LocalTrack) => {
756
+ log.debug('unpublishing local track due to TrackEnded', {
757
+ track: track.sid,
758
+ });
680
759
  this.unpublishTrack(track);
681
760
  };
682
761
 
@@ -714,6 +793,7 @@ export default class LocalParticipant extends Participant {
714
793
  }
715
794
  const cap = RTCRtpSender.getCapabilities(kind);
716
795
  if (!cap) return;
796
+ log.debug('get capabilities', cap);
717
797
  let selected: RTCRtpCodecCapability | undefined;
718
798
  const codecs: RTCRtpCodecCapability[] = [];
719
799
  cap.codecs.forEach((c) => {
@@ -738,6 +818,7 @@ export default class LocalParticipant extends Participant {
738
818
  }
739
819
  codecs.push(c);
740
820
  });
821
+
741
822
  if (selected && 'setCodecPreferences' in transceiver) {
742
823
  // @ts-ignore
743
824
  codecs.unshift(selected);
@@ -42,6 +42,7 @@ export default class RemoteParticipant extends Participant {
42
42
 
43
43
  // register action events
44
44
  publication.on(TrackEvent.UpdateSettings, (settings: UpdateTrackSettings) => {
45
+ log.debug('send update settings', settings);
45
46
  this.signalClient.sendUpdateTrackSettings(settings);
46
47
  });
47
48
  publication.on(TrackEvent.UpdateSubscription, (sub: UpdateSubscription) => {
package/src/room/stats.ts CHANGED
@@ -104,6 +104,8 @@ export interface VideoReceiverStats extends ReceiverStats {
104
104
  pliCount?: number;
105
105
 
106
106
  nackCount?: number;
107
+
108
+ decoderImplementation?: string;
107
109
  }
108
110
 
109
111
  export function computeBitrate<T extends ReceiverStats | SenderStats>(
@@ -1,6 +1,7 @@
1
1
  import log from '../../logger';
2
2
  import { TrackEvent } from '../events';
3
3
  import { AudioSenderStats, computeBitrate, monitorFrequency } from '../stats';
4
+ import { isWeb } from '../utils';
4
5
  import LocalTrack from './LocalTrack';
5
6
  import { AudioCaptureOptions } from './options';
6
7
  import { Track } from './Track';
@@ -68,6 +69,9 @@ export default class LocalAudioTrack extends LocalTrack {
68
69
 
69
70
  /* @internal */
70
71
  startMonitor() {
72
+ if (!isWeb()) {
73
+ return;
74
+ }
71
75
  setTimeout(() => {
72
76
  this.monitorSender();
73
77
  }, monitorFrequency);
@@ -2,6 +2,7 @@ import log from '../../logger';
2
2
  import DeviceManager from '../DeviceManager';
3
3
  import { TrackInvalidError } from '../errors';
4
4
  import { TrackEvent } from '../events';
5
+ import { VideoCodec } from './options';
5
6
  import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile } from '../utils';
6
7
  import { attachToElement, detachTrack, Track } from './Track';
7
8
 
@@ -9,6 +10,9 @@ export default class LocalTrack extends Track {
9
10
  /** @internal */
10
11
  sender?: RTCRtpSender;
11
12
 
13
+ /** @internal */
14
+ codec?: VideoCodec;
15
+
12
16
  protected constraints: MediaTrackConstraints;
13
17
 
14
18
  protected wasMuted: boolean;
@@ -94,7 +98,9 @@ export default class LocalTrack extends Track {
94
98
  track.addEventListener('ended', this.handleEnded);
95
99
  log.debug('replace MediaStreamTrack');
96
100
 
97
- await this.sender.replaceTrack(track);
101
+ if (this.sender) {
102
+ await this.sender.replaceTrack(track);
103
+ }
98
104
  this._mediaStreamTrack = track;
99
105
 
100
106
  this.attachedElements.forEach((el) => {
@@ -106,9 +112,6 @@ export default class LocalTrack extends Track {
106
112
  }
107
113
 
108
114
  protected async restart(constraints?: MediaTrackConstraints): Promise<LocalTrack> {
109
- if (!this.sender) {
110
- throw new TrackInvalidError('unable to restart an unpublished track');
111
- }
112
115
  if (!constraints) {
113
116
  constraints = this.constraints;
114
117
  }
@@ -141,7 +144,11 @@ export default class LocalTrack extends Track {
141
144
  newTrack.addEventListener('ended', this.handleEnded);
142
145
  log.debug('re-acquired MediaStreamTrack');
143
146
 
144
- await this.sender.replaceTrack(newTrack);
147
+ if (this.sender) {
148
+ // Track can be restarted after it's unpublished
149
+ await this.sender.replaceTrack(newTrack);
150
+ }
151
+
145
152
  this._mediaStreamTrack = newTrack;
146
153
 
147
154
  this.attachedElements.forEach((el) => {