livekit-client 1.12.3 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +198 -107
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +515 -192
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/api/SignalClient.d.ts +2 -5
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -1
  13. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +9 -3
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  16. package/dist/src/e2ee/KeyProvider.d.ts +10 -7
  17. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  18. package/dist/src/e2ee/constants.d.ts +2 -0
  19. package/dist/src/e2ee/constants.d.ts.map +1 -1
  20. package/dist/src/e2ee/events.d.ts +34 -0
  21. package/dist/src/e2ee/events.d.ts.map +1 -0
  22. package/dist/src/e2ee/index.d.ts +1 -0
  23. package/dist/src/e2ee/index.d.ts.map +1 -1
  24. package/dist/src/e2ee/types.d.ts +23 -33
  25. package/dist/src/e2ee/types.d.ts.map +1 -1
  26. package/dist/src/e2ee/utils.d.ts +1 -0
  27. package/dist/src/e2ee/utils.d.ts.map +1 -1
  28. package/dist/src/e2ee/worker/FrameCryptor.d.ts +18 -13
  29. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  30. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
  31. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  32. package/dist/src/e2ee/worker/SifGuard.d.ts +11 -0
  33. package/dist/src/e2ee/worker/SifGuard.d.ts.map +1 -0
  34. package/dist/src/options.d.ts +5 -0
  35. package/dist/src/options.d.ts.map +1 -1
  36. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  37. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
  38. package/dist/src/room/DeviceManager.d.ts +1 -0
  39. package/dist/src/room/DeviceManager.d.ts.map +1 -1
  40. package/dist/src/room/PCTransport.d.ts.map +1 -1
  41. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  42. package/dist/src/room/Room.d.ts +1 -1
  43. package/dist/src/room/Room.d.ts.map +1 -1
  44. package/dist/src/room/defaults.d.ts.map +1 -1
  45. package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
  46. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  47. package/dist/src/room/participant/Participant.d.ts +5 -0
  48. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  49. package/dist/src/room/participant/RemoteParticipant.d.ts +0 -5
  50. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  51. package/dist/src/room/timers.d.ts +2 -2
  52. package/dist/src/room/timers.d.ts.map +1 -1
  53. package/dist/src/room/track/LocalAudioTrack.d.ts +9 -1
  54. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  55. package/dist/src/room/track/LocalTrack.d.ts +3 -3
  56. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  57. package/dist/src/room/track/LocalVideoTrack.d.ts +6 -0
  58. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  59. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  60. package/dist/src/room/track/processor/types.d.ts +14 -2
  61. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  62. package/dist/src/room/types.d.ts +1 -1
  63. package/dist/src/room/types.d.ts.map +1 -1
  64. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -5
  65. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +9 -3
  66. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +10 -7
  67. package/dist/ts4.2/src/e2ee/constants.d.ts +2 -0
  68. package/dist/ts4.2/src/e2ee/events.d.ts +34 -0
  69. package/dist/ts4.2/src/e2ee/index.d.ts +1 -0
  70. package/dist/ts4.2/src/e2ee/types.d.ts +23 -33
  71. package/dist/ts4.2/src/e2ee/utils.d.ts +1 -0
  72. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +18 -13
  73. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
  74. package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +11 -0
  75. package/dist/ts4.2/src/options.d.ts +5 -0
  76. package/dist/ts4.2/src/room/DeviceManager.d.ts +1 -0
  77. package/dist/ts4.2/src/room/Room.d.ts +1 -1
  78. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
  79. package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -0
  80. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +0 -5
  81. package/dist/ts4.2/src/room/timers.d.ts +2 -2
  82. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +9 -1
  83. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
  84. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +6 -0
  85. package/dist/ts4.2/src/room/track/processor/types.d.ts +14 -2
  86. package/dist/ts4.2/src/room/types.d.ts +1 -1
  87. package/package.json +15 -16
  88. package/src/api/SignalClient.ts +13 -9
  89. package/src/connectionHelper/checks/turn.ts +1 -0
  90. package/src/connectionHelper/checks/webrtc.ts +9 -7
  91. package/src/connectionHelper/checks/websocket.ts +1 -0
  92. package/src/e2ee/E2eeManager.ts +129 -76
  93. package/src/e2ee/KeyProvider.ts +31 -16
  94. package/src/e2ee/constants.ts +3 -0
  95. package/src/e2ee/events.ts +48 -0
  96. package/src/e2ee/index.ts +1 -0
  97. package/src/e2ee/types.ts +27 -41
  98. package/src/e2ee/utils.ts +9 -0
  99. package/src/e2ee/worker/FrameCryptor.ts +90 -47
  100. package/src/e2ee/worker/ParticipantKeyHandler.ts +25 -26
  101. package/src/e2ee/worker/SifGuard.ts +47 -0
  102. package/src/e2ee/worker/e2ee.worker.ts +75 -68
  103. package/src/options.ts +6 -0
  104. package/src/proto/livekit_models_pb.ts +14 -0
  105. package/src/proto/livekit_rtc_pb.ts +14 -0
  106. package/src/room/DeviceManager.ts +7 -2
  107. package/src/room/PCTransport.ts +12 -2
  108. package/src/room/RTCEngine.ts +3 -2
  109. package/src/room/Room.ts +47 -22
  110. package/src/room/defaults.ts +1 -0
  111. package/src/room/participant/LocalParticipant.ts +18 -2
  112. package/src/room/participant/Participant.ts +16 -0
  113. package/src/room/participant/RemoteParticipant.ts +0 -12
  114. package/src/room/track/LocalAudioTrack.ts +45 -0
  115. package/src/room/track/LocalTrack.ts +22 -14
  116. package/src/room/track/LocalVideoTrack.ts +39 -0
  117. package/src/room/track/RemoteAudioTrack.ts +9 -1
  118. package/src/room/track/RemoteTrackPublication.ts +2 -2
  119. package/src/room/track/facingMode.ts +1 -1
  120. package/src/room/track/processor/types.ts +18 -2
  121. package/src/room/types.ts +5 -1
package/src/room/Room.ts CHANGED
@@ -209,10 +209,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
209
209
  */
210
210
  async setE2EEEnabled(enabled: boolean) {
211
211
  if (this.e2eeManager) {
212
- await Promise.all([
213
- this.localParticipant.setE2EEEnabled(enabled),
214
- this.e2eeManager.setParticipantCryptorEnabled(enabled),
215
- ]);
212
+ await Promise.all([this.localParticipant.setE2EEEnabled(enabled)]);
213
+ if (this.localParticipant.identity !== '') {
214
+ this.e2eeManager.setParticipantCryptorEnabled(enabled, this.localParticipant.identity);
215
+ }
216
216
  } else {
217
217
  throw Error('e2ee not configured, please set e2ee settings within the room options');
218
218
  }
@@ -230,7 +230,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
230
230
  this.emit(RoomEvent.ParticipantEncryptionStatusChanged, enabled, participant);
231
231
  },
232
232
  );
233
- this.e2eeManager.on(EncryptionEvent.Error, (error) =>
233
+ this.e2eeManager.on(EncryptionEvent.EncryptionError, (error) =>
234
234
  this.emit(RoomEvent.EncryptionError, error),
235
235
  );
236
236
  this.e2eeManager?.setup(this);
@@ -424,13 +424,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
424
424
  this.abortController.abort();
425
425
  }
426
426
 
427
- this.abortController = new AbortController();
427
+ // explicit creation as local var needed to satisfy TS compiler when passing it to `attemptConnection` further down
428
+ const abortController = new AbortController();
429
+ this.abortController = abortController;
428
430
 
429
431
  // at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again
430
432
  unlockDisconnect?.();
431
433
 
432
434
  try {
433
- await this.attemptConnection(regionUrl ?? url, token, opts, this.abortController);
435
+ await this.attemptConnection(regionUrl ?? url, token, opts, abortController);
434
436
  this.abortController = undefined;
435
437
  resolve();
436
438
  } catch (e) {
@@ -500,6 +502,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
500
502
  typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
501
503
  maxRetries: connectOptions.maxRetries,
502
504
  e2eeEnabled: !!this.e2eeManager,
505
+ websocketTimeout: connectOptions.websocketTimeout,
503
506
  },
504
507
  abortController.signal,
505
508
  );
@@ -540,6 +543,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
540
543
  if (joinResponse.room) {
541
544
  this.handleRoomUpdate(joinResponse.room);
542
545
  }
546
+
547
+ if (this.options.e2ee && this.e2eeManager) {
548
+ this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
549
+ }
543
550
  };
544
551
 
545
552
  private attemptConnection = async (
@@ -692,7 +699,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
692
699
  /**
693
700
  * @internal for testing
694
701
  */
695
- async simulateScenario(scenario: SimulationScenario) {
702
+ async simulateScenario(scenario: SimulationScenario, arg?: any) {
696
703
  let postAction = () => {};
697
704
  let req: SimulateScenario | undefined;
698
705
  switch (scenario) {
@@ -762,6 +769,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
762
769
  }
763
770
  };
764
771
  break;
772
+ case 'subscriber-bandwidth':
773
+ if (arg === undefined || typeof arg !== 'number') {
774
+ throw new Error('subscriber-bandwidth requires a number as argument');
775
+ }
776
+ req = new SimulateScenario({
777
+ scenario: {
778
+ case: 'subscriberBandwidth',
779
+ value: BigInt(arg),
780
+ },
781
+ });
782
+ break;
765
783
  default:
766
784
  }
767
785
  if (req) {
@@ -782,7 +800,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
782
800
  * - `getUserMedia`
783
801
  */
784
802
  async startAudio() {
785
- await this.acquireAudioContext();
786
803
  const elements: Array<HTMLMediaElement> = [];
787
804
  const browser = getBrowser();
788
805
  if (browser && browser.os === 'iOS') {
@@ -831,12 +848,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
831
848
  });
832
849
 
833
850
  try {
834
- await Promise.all(
835
- elements.map((e) => {
851
+ await Promise.all([
852
+ this.acquireAudioContext(),
853
+ ...elements.map((e) => {
836
854
  e.muted = false;
837
855
  return e.play();
838
856
  }),
839
- );
857
+ ]);
840
858
  this.handleAudioPlaybackStarted();
841
859
  } catch (err) {
842
860
  this.handleAudioPlaybackFailed(err);
@@ -1007,7 +1025,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1007
1025
  log.warn('tried to create RemoteParticipant for local participant');
1008
1026
  return;
1009
1027
  }
1010
- const participant = this.getOrCreateParticipant(participantId);
1028
+
1029
+ const participant = this.participants.get(participantId) as RemoteParticipant | undefined;
1030
+
1031
+ if (!participant) {
1032
+ log.error(
1033
+ `Tried to add a track for a participant, that's not present. Sid: ${participantId}`,
1034
+ );
1035
+ return;
1036
+ }
1011
1037
 
1012
1038
  let adaptiveStreamSettings: AdaptiveStreamSettings | undefined;
1013
1039
  if (this.options.adaptiveStream) {
@@ -1405,6 +1431,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1405
1431
  this.participants.forEach((participant) => participant.setAudioContext(this.audioContext));
1406
1432
  }
1407
1433
 
1434
+ this.localParticipant.setAudioContext(this.audioContext);
1435
+
1408
1436
  const newContextIsRunning = this.audioContext?.state === 'running';
1409
1437
  if (newContextIsRunning !== this.canPlaybackAudio) {
1410
1438
  this.audioEnabled = newContextIsRunning;
@@ -1425,20 +1453,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1425
1453
  return participant;
1426
1454
  }
1427
1455
 
1428
- private getOrCreateParticipant(id: string, info?: ParticipantInfo): RemoteParticipant {
1456
+ private getOrCreateParticipant(id: string, info: ParticipantInfo): RemoteParticipant {
1429
1457
  if (this.participants.has(id)) {
1430
1458
  return this.participants.get(id) as RemoteParticipant;
1431
1459
  }
1432
- // it's possible for the RTC track to arrive before signaling data
1433
- // when this happens, we'll create the participant and make the track work
1434
1460
  const participant = this.createParticipant(id, info);
1435
1461
  this.participants.set(id, participant);
1436
- if (info) {
1437
- this.identityToSid.set(info.identity, info.sid);
1438
- // if we have valid info and the participant wasn't in the map before, we can assume the participant is new
1439
- // firing here to make sure that `ParticipantConnected` fires before the initial track events
1440
- this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
1441
- }
1462
+
1463
+ this.identityToSid.set(info.identity, info.sid);
1464
+ // if we have valid info and the participant wasn't in the map before, we can assume the participant is new
1465
+ // firing here to make sure that `ParticipantConnected` fires before the initial track events
1466
+ this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
1442
1467
 
1443
1468
  // also forward events
1444
1469
  // trackPublished is only fired for tracks added after both local participant
@@ -46,4 +46,5 @@ export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
46
46
  autoSubscribe: true,
47
47
  maxRetries: 1,
48
48
  peerConnectionTimeout: 15_000,
49
+ websocketTimeout: 15_000,
49
50
  } as const;
@@ -104,6 +104,10 @@ export default class LocalParticipant extends Participant {
104
104
  return this.microphoneError;
105
105
  }
106
106
 
107
+ get isE2EEEnabled(): boolean {
108
+ return this.encryptionType !== Encryption_Type.NONE;
109
+ }
110
+
107
111
  getTrack(source: Track.Source): LocalTrackPublication | undefined {
108
112
  const track = super.getTrack(source);
109
113
  if (track) {
@@ -457,7 +461,12 @@ export default class LocalParticipant extends Participant {
457
461
  screenVideo.source = Track.Source.ScreenShare;
458
462
  const localTracks: Array<LocalTrack> = [screenVideo];
459
463
  if (stream.getAudioTracks().length > 0) {
460
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false);
464
+ const screenAudio = new LocalAudioTrack(
465
+ stream.getAudioTracks()[0],
466
+ undefined,
467
+ false,
468
+ this.audioContext,
469
+ );
461
470
  screenAudio.source = Track.Source.ScreenShareAudio;
462
471
  localTracks.push(screenAudio);
463
472
  }
@@ -505,7 +514,7 @@ export default class LocalParticipant extends Participant {
505
514
  if (track instanceof MediaStreamTrack) {
506
515
  switch (track.kind) {
507
516
  case 'audio':
508
- track = new LocalAudioTrack(track, defaultConstraints, true);
517
+ track = new LocalAudioTrack(track, defaultConstraints, true, this.audioContext);
509
518
  break;
510
519
  case 'video':
511
520
  track = new LocalVideoTrack(track, defaultConstraints, true);
@@ -515,6 +524,10 @@ export default class LocalParticipant extends Participant {
515
524
  }
516
525
  }
517
526
 
527
+ if (track instanceof LocalAudioTrack) {
528
+ track.setAudioContext(this.audioContext);
529
+ }
530
+
518
531
  // is it already published? if so skip
519
532
  let existingPublication: LocalTrackPublication | undefined;
520
533
  this.tracks.forEach((publication) => {
@@ -673,6 +686,9 @@ export default class LocalParticipant extends Participant {
673
686
 
674
687
  // set up backup
675
688
  if (opts.videoCodec && opts.backupCodec && opts.videoCodec !== opts.backupCodec.codec) {
689
+ if (!this.roomOptions.dynacast) {
690
+ this.roomOptions.dynacast = true;
691
+ }
676
692
  const simOpts = { ...opts };
677
693
  simOpts.simulcast = true;
678
694
  simEncodings = computeTrackBackupEncodings(track, opts.backupCodec.codec, simOpts);
@@ -9,7 +9,9 @@ import {
9
9
  SubscriptionError,
10
10
  } from '../../proto/livekit_models_pb';
11
11
  import { ParticipantEvent, TrackEvent } from '../events';
12
+ import LocalAudioTrack from '../track/LocalAudioTrack';
12
13
  import type LocalTrackPublication from '../track/LocalTrackPublication';
14
+ import RemoteAudioTrack from '../track/RemoteAudioTrack';
13
15
  import type RemoteTrack from '../track/RemoteTrack';
14
16
  import type RemoteTrackPublication from '../track/RemoteTrackPublication';
15
17
  import { Track } from '../track/Track';
@@ -69,6 +71,8 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
69
71
 
70
72
  private _connectionQuality: ConnectionQuality = ConnectionQuality.Unknown;
71
73
 
74
+ protected audioContext?: AudioContext;
75
+
72
76
  get isEncrypted() {
73
77
  return this.tracks.size > 0 && Array.from(this.tracks.values()).every((tr) => tr.isEncrypted);
74
78
  }
@@ -238,6 +242,18 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
238
242
  }
239
243
  }
240
244
 
245
+ /**
246
+ * @internal
247
+ */
248
+ setAudioContext(ctx: AudioContext | undefined) {
249
+ this.audioContext = ctx;
250
+ this.audioTracks.forEach(
251
+ (track) =>
252
+ (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) &&
253
+ track.track.setAudioContext(ctx),
254
+ );
255
+ }
256
+
241
257
  protected addTrackPublication(publication: TrackPublication) {
242
258
  // forward publication driven events
243
259
  publication.on(TrackEvent.Muted, () => {
@@ -25,8 +25,6 @@ export default class RemoteParticipant extends Participant {
25
25
 
26
26
  private volumeMap: Map<Track.Source, number>;
27
27
 
28
- private audioContext?: AudioContext;
29
-
30
28
  private audioOutput?: AudioOutputOptions;
31
29
 
32
30
  /** @internal */
@@ -324,16 +322,6 @@ export default class RemoteParticipant extends Participant {
324
322
  }
325
323
  }
326
324
 
327
- /**
328
- * @internal
329
- */
330
- setAudioContext(ctx: AudioContext | undefined) {
331
- this.audioContext = ctx;
332
- this.audioTracks.forEach(
333
- (track) => track.track instanceof RemoteAudioTrack && track.track.setAudioContext(ctx),
334
- );
335
- }
336
-
337
325
  /**
338
326
  * @internal
339
327
  */
@@ -6,12 +6,15 @@ import { isWeb, unwrapConstraint } from '../utils';
6
6
  import LocalTrack from './LocalTrack';
7
7
  import { Track } from './Track';
8
8
  import type { AudioCaptureOptions } from './options';
9
+ import type { TrackProcessor } from './processor/types';
9
10
  import { constraintsForOptions, detectSilence } from './utils';
10
11
 
11
12
  export default class LocalAudioTrack extends LocalTrack {
12
13
  /** @internal */
13
14
  stopOnMute: boolean = false;
14
15
 
16
+ private audioContext?: AudioContext;
17
+
15
18
  private prevStats?: AudioSenderStats;
16
19
 
17
20
  /**
@@ -24,8 +27,10 @@ export default class LocalAudioTrack extends LocalTrack {
24
27
  mediaTrack: MediaStreamTrack,
25
28
  constraints?: MediaTrackConstraints,
26
29
  userProvidedTrack = true,
30
+ audioContext?: AudioContext,
27
31
  ) {
28
32
  super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack);
33
+ this.audioContext = audioContext;
29
34
  this.checkForSilence();
30
35
  }
31
36
 
@@ -133,6 +138,46 @@ export default class LocalAudioTrack extends LocalTrack {
133
138
  this.prevStats = stats;
134
139
  };
135
140
 
141
+ async setProcessor(processor: TrackProcessor<typeof this.kind>) {
142
+ const unlock = await this.processorLock.lock();
143
+ try {
144
+ if (!this.audioContext) {
145
+ throw Error(
146
+ 'Audio context needs to be set on LocalAudioTrack in order to enable processors',
147
+ );
148
+ }
149
+ if (this.processor) {
150
+ await this.stopProcessor();
151
+ }
152
+ if (this.kind === 'unknown') {
153
+ throw TypeError('cannot set processor on track of unknown kind');
154
+ }
155
+
156
+ const processorOptions = {
157
+ kind: this.kind,
158
+ track: this._mediaStreamTrack,
159
+ audioContext: this.audioContext,
160
+ };
161
+ log.debug(`setting up audio processor ${processor.name}`);
162
+
163
+ await processor.init(processorOptions);
164
+ this.processor = processor;
165
+ if (this.processor.processedTrack) {
166
+ await this.sender?.replaceTrack(this.processor.processedTrack);
167
+ }
168
+ } finally {
169
+ unlock();
170
+ }
171
+ }
172
+
173
+ /**
174
+ * @internal
175
+ * @experimental
176
+ */
177
+ setAudioContext(audioContext: AudioContext | undefined) {
178
+ this.audioContext = audioContext;
179
+ }
180
+
136
181
  async getSenderStats(): Promise<AudioSenderStats | undefined> {
137
182
  if (!this.sender?.getStats) {
138
183
  return undefined;
@@ -128,8 +128,23 @@ export default abstract class LocalTrack extends Track {
128
128
  newTrack.addEventListener('unmute', this.handleTrackUnmuteEvent);
129
129
  this._constraints = newTrack.getConstraints();
130
130
  }
131
+ let processedTrack: MediaStreamTrack | undefined;
132
+ if (this.processor && newTrack && this.processorElement) {
133
+ log.debug('restarting processor');
134
+ if (this.kind === 'unknown') {
135
+ throw TypeError('cannot set processor on track of unknown kind');
136
+ }
137
+
138
+ attachToElement(newTrack, this.processorElement);
139
+ await this.processor.restart({
140
+ track: newTrack,
141
+ kind: this.kind,
142
+ element: this.processorElement,
143
+ });
144
+ processedTrack = this.processor.processedTrack;
145
+ }
131
146
  if (this.sender) {
132
- await this.sender.replaceTrack(newTrack);
147
+ await this.sender.replaceTrack(processedTrack ?? newTrack);
133
148
  }
134
149
  this._mediaStreamTrack = newTrack;
135
150
  if (newTrack) {
@@ -138,7 +153,7 @@ export default abstract class LocalTrack extends Track {
138
153
  // when a valid track is replace, we'd want to start producing
139
154
  await this.resumeUpstream();
140
155
  this.attachedElements.forEach((el) => {
141
- attachToElement(newTrack, el);
156
+ attachToElement(processedTrack ?? newTrack, el);
142
157
  });
143
158
  }
144
159
  }
@@ -236,14 +251,7 @@ export default abstract class LocalTrack extends Track {
236
251
 
237
252
  await this.setMediaStreamTrack(newTrack);
238
253
  this._constraints = constraints;
239
- if (this.processor) {
240
- const processor = this.processor;
241
- await this.setProcessor(processor);
242
- } else {
243
- this.attachedElements.forEach((el) => {
244
- attachToElement(this._mediaStreamTrack, el);
245
- });
246
- }
254
+
247
255
  this.emit(TrackEvent.Restarted, this);
248
256
  return this;
249
257
  }
@@ -320,7 +328,7 @@ export default abstract class LocalTrack extends Track {
320
328
  * the server.
321
329
  * this API is unsupported on Safari < 12 due to a bug
322
330
  **/
323
- pauseUpstream = async () => {
331
+ async pauseUpstream() {
324
332
  const unlock = await this.pauseUpstreamLock.lock();
325
333
  try {
326
334
  if (this._isUpstreamPaused === true) {
@@ -342,9 +350,9 @@ export default abstract class LocalTrack extends Track {
342
350
  } finally {
343
351
  unlock();
344
352
  }
345
- };
353
+ }
346
354
 
347
- resumeUpstream = async () => {
355
+ async resumeUpstream() {
348
356
  const unlock = await this.pauseUpstreamLock.lock();
349
357
  try {
350
358
  if (this._isUpstreamPaused === false) {
@@ -362,7 +370,7 @@ export default abstract class LocalTrack extends Track {
362
370
  } finally {
363
371
  unlock();
364
372
  }
365
- };
373
+ }
366
374
 
367
375
  /**
368
376
  * Sets a processor on this track.
@@ -9,6 +9,7 @@ import { Mutex, isFireFox, isMobile, isWeb, unwrapConstraint } from '../utils';
9
9
  import LocalTrack from './LocalTrack';
10
10
  import { Track } from './Track';
11
11
  import type { VideoCaptureOptions, VideoCodec } from './options';
12
+ import type { TrackProcessor } from './processor/types';
12
13
  import { constraintsForOptions } from './utils';
13
14
 
14
15
  export class SimulcastTrackInfo {
@@ -98,6 +99,20 @@ export default class LocalVideoTrack extends LocalTrack {
98
99
  super.stop();
99
100
  }
100
101
 
102
+ async pauseUpstream() {
103
+ await super.pauseUpstream();
104
+ for await (const sc of this.simulcastCodecs.values()) {
105
+ await sc.sender?.replaceTrack(null);
106
+ }
107
+ }
108
+
109
+ async resumeUpstream() {
110
+ await super.resumeUpstream();
111
+ for await (const sc of this.simulcastCodecs.values()) {
112
+ await sc.sender?.replaceTrack(sc.mediaStreamTrack);
113
+ }
114
+ }
115
+
101
116
  async mute(): Promise<LocalVideoTrack> {
102
117
  const unlock = await this.muteLock.lock();
103
118
  try {
@@ -127,6 +142,13 @@ export default class LocalVideoTrack extends LocalTrack {
127
142
  }
128
143
  }
129
144
 
145
+ protected setTrackMuted(muted: boolean) {
146
+ super.setTrackMuted(muted);
147
+ for (const sc of this.simulcastCodecs.values()) {
148
+ sc.mediaStreamTrack.enabled = !muted;
149
+ }
150
+ }
151
+
130
152
  async getSenderStats(): Promise<VideoSenderStats[]> {
131
153
  if (!this.sender?.getStats) {
132
154
  return [];
@@ -211,6 +233,23 @@ export default class LocalVideoTrack extends LocalTrack {
211
233
  }
212
234
  }
213
235
  await this.restart(constraints);
236
+
237
+ for await (const sc of this.simulcastCodecs.values()) {
238
+ if (sc.sender) {
239
+ sc.mediaStreamTrack = this.mediaStreamTrack.clone();
240
+ await sc.sender.replaceTrack(sc.mediaStreamTrack);
241
+ }
242
+ }
243
+ }
244
+
245
+ async setProcessor(processor: TrackProcessor<Track.Kind>, showProcessedStreamLocally = true) {
246
+ await super.setProcessor(processor, showProcessedStreamLocally);
247
+
248
+ if (this.processor?.processedTrack) {
249
+ for await (const sc of this.simulcastCodecs.values()) {
250
+ await sc.sender?.replaceTrack(this.processor.processedTrack);
251
+ }
252
+ }
214
253
  }
215
254
 
216
255
  addSimulcastTrack(codec: VideoCodec, encodings?: RTCRtpEncodingParameters[]): SimulcastTrackInfo {
@@ -2,7 +2,7 @@ import log from '../../logger';
2
2
  import { TrackEvent } from '../events';
3
3
  import { computeBitrate } from '../stats';
4
4
  import type { AudioReceiverStats } from '../stats';
5
- import { supportsSetSinkId } from '../utils';
5
+ import { isReactNative, supportsSetSinkId } from '../utils';
6
6
  import RemoteTrack from './RemoteTrack';
7
7
  import { Track } from './Track';
8
8
  import type { AudioOutputOptions } from './options';
@@ -48,6 +48,10 @@ export default class RemoteAudioTrack extends RemoteTrack {
48
48
  el.volume = volume;
49
49
  }
50
50
  }
51
+ if (isReactNative()) {
52
+ // @ts-ignore
53
+ this._mediaStreamTrack._setVolume(volume);
54
+ }
51
55
  this.elementVolume = volume;
52
56
  }
53
57
 
@@ -58,6 +62,10 @@ export default class RemoteAudioTrack extends RemoteTrack {
58
62
  if (this.elementVolume) {
59
63
  return this.elementVolume;
60
64
  }
65
+ if (isReactNative()) {
66
+ // RN volume value defaults to 1.0 if hasn't been changed.
67
+ return 1.0;
68
+ }
61
69
  let highestVolume = 0;
62
70
  this.attachedElements.forEach((element) => {
63
71
  if (element.volume > highestVolume) {
@@ -297,8 +297,8 @@ export default class RemoteTrackPublication extends TrackPublication {
297
297
  fps: this.fps,
298
298
  });
299
299
  if (this.videoDimensions) {
300
- settings.width = this.videoDimensions.width;
301
- settings.height = this.videoDimensions.height;
300
+ settings.width = Math.ceil(this.videoDimensions.width);
301
+ settings.height = Math.ceil(this.videoDimensions.height);
302
302
  } else if (this.currentVideoQuality !== undefined) {
303
303
  settings.quality = this.currentVideoQuality;
304
304
  } else {
@@ -1,4 +1,4 @@
1
- import log from 'loglevel';
1
+ import log from '../../logger';
2
2
  import LocalTrack from './LocalTrack';
3
3
  import type { VideoCaptureOptions } from './options';
4
4
 
@@ -12,9 +12,25 @@ export type ProcessorOptions<T extends Track.Kind> = {
12
12
  /**
13
13
  * @experimental
14
14
  */
15
- export interface TrackProcessor<T extends Track.Kind> {
15
+ export interface AudioProcessorOptions extends ProcessorOptions<Track.Kind.Audio> {
16
+ audioContext: AudioContext;
17
+ }
18
+
19
+ /**
20
+ * @experimental
21
+ */
22
+ export interface VideoProcessorOptions extends ProcessorOptions<Track.Kind.Video> {}
23
+
24
+ /**
25
+ * @experimental
26
+ */
27
+ export interface TrackProcessor<
28
+ T extends Track.Kind,
29
+ U extends ProcessorOptions<T> = ProcessorOptions<T>,
30
+ > {
16
31
  name: string;
17
- init: (opts: ProcessorOptions<T>) => void;
32
+ init: (opts: U) => Promise<void>;
33
+ restart: (opts: U) => Promise<void>;
18
34
  destroy: () => Promise<void>;
19
35
  processedTrack?: MediaStreamTrack;
20
36
  }
package/src/room/types.ts CHANGED
@@ -36,4 +36,8 @@ export type SimulationScenario =
36
36
  | 'resume-reconnect'
37
37
  | 'force-tcp'
38
38
  | 'force-tls'
39
- | 'full-reconnect';
39
+ | 'full-reconnect'
40
+ // overrides server-side bandwidth estimator with set bandwidth
41
+ // this can be used to test application behavior when congested or
42
+ // to disable congestion control entirely (by setting bandwidth to 100Mbps)
43
+ | 'subscriber-bandwidth';