livekit-client 2.0.2 → 2.0.4

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 (73) 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 +53 -17
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +158 -65
  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.map +1 -1
  10. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  11. package/dist/src/e2ee/KeyProvider.d.ts +1 -1
  12. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  13. package/dist/src/e2ee/types.d.ts +2 -0
  14. package/dist/src/e2ee/types.d.ts.map +1 -1
  15. package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -0
  16. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  17. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  18. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  19. package/dist/src/index.d.ts +2 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/logger.d.ts +2 -0
  22. package/dist/src/logger.d.ts.map +1 -1
  23. package/dist/src/room/DeviceManager.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts +1 -0
  25. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  26. package/dist/src/room/Room.d.ts +1 -0
  27. package/dist/src/room/Room.d.ts.map +1 -1
  28. package/dist/src/room/events.d.ts +7 -2
  29. package/dist/src/room/events.d.ts.map +1 -1
  30. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/LocalTrack.d.ts +4 -1
  32. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/Track.d.ts +2 -0
  35. package/dist/src/room/track/Track.d.ts.map +1 -1
  36. package/dist/src/room/track/options.d.ts +10 -0
  37. package/dist/src/room/track/options.d.ts.map +1 -1
  38. package/dist/src/room/track/types.d.ts +4 -0
  39. package/dist/src/room/track/types.d.ts.map +1 -1
  40. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +1 -1
  41. package/dist/ts4.2/src/e2ee/types.d.ts +2 -0
  42. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +1 -0
  43. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  44. package/dist/ts4.2/src/index.d.ts +2 -2
  45. package/dist/ts4.2/src/logger.d.ts +2 -0
  46. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  47. package/dist/ts4.2/src/room/Room.d.ts +1 -0
  48. package/dist/ts4.2/src/room/events.d.ts +7 -2
  49. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +4 -1
  50. package/dist/ts4.2/src/room/track/Track.d.ts +2 -0
  51. package/dist/ts4.2/src/room/track/options.d.ts +10 -0
  52. package/dist/ts4.2/src/room/track/types.d.ts +4 -0
  53. package/package.json +1 -1
  54. package/src/api/SignalClient.ts +1 -0
  55. package/src/e2ee/E2eeManager.ts +2 -1
  56. package/src/e2ee/KeyProvider.ts +6 -1
  57. package/src/e2ee/types.ts +2 -0
  58. package/src/e2ee/worker/FrameCryptor.ts +26 -0
  59. package/src/e2ee/worker/ParticipantKeyHandler.ts +9 -5
  60. package/src/e2ee/worker/e2ee.worker.ts +17 -17
  61. package/src/index.ts +2 -1
  62. package/src/logger.ts +2 -0
  63. package/src/room/DeviceManager.ts +10 -1
  64. package/src/room/RTCEngine.ts +14 -0
  65. package/src/room/Room.ts +29 -4
  66. package/src/room/events.ts +5 -0
  67. package/src/room/participant/LocalParticipant.ts +4 -4
  68. package/src/room/track/LocalAudioTrack.ts +11 -0
  69. package/src/room/track/LocalTrack.ts +62 -36
  70. package/src/room/track/LocalVideoTrack.ts +10 -0
  71. package/src/room/track/Track.ts +2 -0
  72. package/src/room/track/options.ts +41 -8
  73. package/src/room/track/types.ts +5 -0
@@ -8,6 +8,7 @@ import { Mutex, compareVersions, isMobile, sleep } from '../utils';
8
8
  import { Track, attachToElement, detachTrack } from './Track';
9
9
  import type { VideoCodec } from './options';
10
10
  import type { TrackProcessor } from './processor/types';
11
+ import type { ReplaceTrackOptions } from './types';
11
12
 
12
13
  const defaultDimensionsTimeout = 1000;
13
14
 
@@ -42,6 +43,8 @@ export default abstract class LocalTrack<
42
43
 
43
44
  protected audioContext?: AudioContext;
44
45
 
46
+ private restartLock: Mutex;
47
+
45
48
  /**
46
49
  *
47
50
  * @param mediaTrack
@@ -62,6 +65,7 @@ export default abstract class LocalTrack<
62
65
  this.muteLock = new Mutex();
63
66
  this.pauseUpstreamLock = new Mutex();
64
67
  this.processorLock = new Mutex();
68
+ this.restartLock = new Mutex();
65
69
  this.setMediaStreamTrack(mediaTrack, true);
66
70
 
67
71
  // added to satisfy TS compiler, constraints are synced with MediaStreamTrack
@@ -221,62 +225,83 @@ export default abstract class LocalTrack<
221
225
  return this;
222
226
  }
223
227
 
224
- async replaceTrack(track: MediaStreamTrack, userProvidedTrack = true) {
228
+ async replaceTrack(track: MediaStreamTrack, options?: ReplaceTrackOptions): Promise<typeof this>;
229
+ async replaceTrack(track: MediaStreamTrack, userProvidedTrack?: boolean): Promise<typeof this>;
230
+ async replaceTrack(
231
+ track: MediaStreamTrack,
232
+ userProvidedOrOptions: boolean | ReplaceTrackOptions | undefined,
233
+ ) {
225
234
  if (!this.sender) {
226
235
  throw new TrackInvalidError('unable to replace an unpublished track');
227
236
  }
228
237
 
238
+ let userProvidedTrack: boolean | undefined;
239
+ let stopProcessor: boolean | undefined;
240
+
241
+ if (typeof userProvidedOrOptions === 'boolean') {
242
+ userProvidedTrack = userProvidedOrOptions;
243
+ } else if (userProvidedOrOptions !== undefined) {
244
+ userProvidedTrack = userProvidedOrOptions.userProvidedTrack;
245
+ stopProcessor = userProvidedOrOptions.stopProcessor;
246
+ }
247
+
248
+ this.providedByUser = userProvidedTrack ?? true;
249
+
229
250
  this.log.debug('replace MediaStreamTrack', this.logContext);
230
251
  await this.setMediaStreamTrack(track);
231
252
  // this must be synced *after* setting mediaStreamTrack above, since it relies
232
253
  // on the previous state in order to cleanup
233
- this.providedByUser = userProvidedTrack;
234
254
 
235
- if (this.processor) {
255
+ if (stopProcessor && this.processor) {
236
256
  await this.stopProcessor();
237
257
  }
238
258
  return this;
239
259
  }
240
260
 
241
261
  protected async restart(constraints?: MediaTrackConstraints) {
242
- if (!constraints) {
243
- constraints = this._constraints;
244
- }
245
- this.log.debug('restarting track with constraints', { ...this.logContext, constraints });
262
+ const unlock = await this.restartLock.lock();
263
+ try {
264
+ if (!constraints) {
265
+ constraints = this._constraints;
266
+ }
267
+ this.log.debug('restarting track with constraints', { ...this.logContext, constraints });
246
268
 
247
- const streamConstraints: MediaStreamConstraints = {
248
- audio: false,
249
- video: false,
250
- };
269
+ const streamConstraints: MediaStreamConstraints = {
270
+ audio: false,
271
+ video: false,
272
+ };
251
273
 
252
- if (this.kind === Track.Kind.Video) {
253
- streamConstraints.video = constraints;
254
- } else {
255
- streamConstraints.audio = constraints;
256
- }
274
+ if (this.kind === Track.Kind.Video) {
275
+ streamConstraints.video = constraints;
276
+ } else {
277
+ streamConstraints.audio = constraints;
278
+ }
257
279
 
258
- // these steps are duplicated from setMediaStreamTrack because we must stop
259
- // the previous tracks before new tracks can be acquired
260
- this.attachedElements.forEach((el) => {
261
- detachTrack(this.mediaStreamTrack, el);
262
- });
263
- this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
264
- // on Safari, the old audio track must be stopped before attempting to acquire
265
- // the new track, otherwise the new track will stop with
266
- // 'A MediaStreamTrack ended due to a capture failure`
267
- this._mediaStreamTrack.stop();
280
+ // these steps are duplicated from setMediaStreamTrack because we must stop
281
+ // the previous tracks before new tracks can be acquired
282
+ this.attachedElements.forEach((el) => {
283
+ detachTrack(this.mediaStreamTrack, el);
284
+ });
285
+ this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
286
+ // on Safari, the old audio track must be stopped before attempting to acquire
287
+ // the new track, otherwise the new track will stop with
288
+ // 'A MediaStreamTrack ended due to a capture failure`
289
+ this._mediaStreamTrack.stop();
268
290
 
269
- // create new track and attach
270
- const mediaStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
271
- const newTrack = mediaStream.getTracks()[0];
272
- newTrack.addEventListener('ended', this.handleEnded);
273
- this.log.debug('re-acquired MediaStreamTrack', this.logContext);
291
+ // create new track and attach
292
+ const mediaStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
293
+ const newTrack = mediaStream.getTracks()[0];
294
+ newTrack.addEventListener('ended', this.handleEnded);
295
+ this.log.debug('re-acquired MediaStreamTrack', this.logContext);
274
296
 
275
- await this.setMediaStreamTrack(newTrack);
276
- this._constraints = constraints;
297
+ await this.setMediaStreamTrack(newTrack);
298
+ this._constraints = constraints;
277
299
 
278
- this.emit(TrackEvent.Restarted, this);
279
- return this;
300
+ this.emit(TrackEvent.Restarted, this);
301
+ return this;
302
+ } finally {
303
+ unlock();
304
+ }
280
305
  }
281
306
 
282
307
  protected setTrackMuted(muted: boolean) {
@@ -459,6 +484,7 @@ export default abstract class LocalTrack<
459
484
  }
460
485
  await this.sender?.replaceTrack(this.processor.processedTrack);
461
486
  }
487
+ this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
462
488
  } finally {
463
489
  unlock();
464
490
  }
@@ -484,8 +510,8 @@ export default abstract class LocalTrack<
484
510
  this.processor = undefined;
485
511
  this.processorElement?.remove();
486
512
  this.processorElement = undefined;
487
-
488
513
  await this.restart();
514
+ this.emit(TrackEvent.TrackProcessorUpdate);
489
515
  }
490
516
 
491
517
  protected abstract monitorSender(): void;
@@ -118,6 +118,11 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
118
118
  async mute(): Promise<typeof this> {
119
119
  const unlock = await this.muteLock.lock();
120
120
  try {
121
+ if (this.isMuted) {
122
+ this.log.debug('Track already muted', this.logContext);
123
+ return this;
124
+ }
125
+
121
126
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
122
127
  this.log.debug('stopping camera track', this.logContext);
123
128
  // also stop the track, so that camera indicator is turned off
@@ -133,6 +138,11 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
133
138
  async unmute(): Promise<typeof this> {
134
139
  const unlock = await this.muteLock.lock();
135
140
  try {
141
+ if (!this.isMuted) {
142
+ this.log.debug('Track already unmuted', this.logContext);
143
+ return this;
144
+ }
145
+
136
146
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
137
147
  this.log.debug('reacquiring camera track', this.logContext);
138
148
  await this.restartTrack();
@@ -11,6 +11,7 @@ import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
11
11
  import { TrackEvent } from '../events';
12
12
  import type { LoggerOptions } from '../types';
13
13
  import { isFireFox, isSafari, isWeb } from '../utils';
14
+ import type { TrackProcessor } from './processor/types';
14
15
  import { getLogContextFromTrack } from './utils';
15
16
 
16
17
  const BACKGROUND_REACTION_DELAY = 5000;
@@ -502,4 +503,5 @@ export type TrackEventCallbacks = {
502
503
  elementDetached: (element: HTMLMediaElement) => void;
503
504
  upstreamPaused: (track: any) => void;
504
505
  upstreamResumed: (track: any) => void;
506
+ trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
505
507
  };
@@ -257,6 +257,15 @@ export interface VideoEncoding {
257
257
  priority?: RTCPriorityType;
258
258
  }
259
259
 
260
+ export interface VideoPresetOptions {
261
+ width: number;
262
+ height: number;
263
+ aspectRatio?: number;
264
+ maxBitrate: number;
265
+ maxFramerate?: number;
266
+ priority?: RTCPriorityType;
267
+ }
268
+
260
269
  export class VideoPreset {
261
270
  encoding: VideoEncoding;
262
271
 
@@ -264,20 +273,44 @@ export class VideoPreset {
264
273
 
265
274
  height: number;
266
275
 
276
+ aspectRatio?: number;
277
+
278
+ constructor(videoPresetOptions: VideoPresetOptions);
267
279
  constructor(
268
280
  width: number,
269
281
  height: number,
270
282
  maxBitrate: number,
271
283
  maxFramerate?: number,
272
284
  priority?: RTCPriorityType,
285
+ );
286
+ constructor(
287
+ widthOrOptions: number | VideoPresetOptions,
288
+ height?: number,
289
+ maxBitrate?: number,
290
+ maxFramerate?: number,
291
+ priority?: RTCPriorityType,
273
292
  ) {
274
- this.width = width;
275
- this.height = height;
276
- this.encoding = {
277
- maxBitrate,
278
- maxFramerate,
279
- priority,
280
- };
293
+ if (typeof widthOrOptions === 'object') {
294
+ this.width = widthOrOptions.width;
295
+ this.height = widthOrOptions.height;
296
+ this.aspectRatio = widthOrOptions.aspectRatio;
297
+ this.encoding = {
298
+ maxBitrate: widthOrOptions.maxBitrate,
299
+ maxFramerate: widthOrOptions.maxFramerate,
300
+ priority: widthOrOptions.priority,
301
+ };
302
+ } else if (height !== undefined && maxBitrate !== undefined) {
303
+ this.width = widthOrOptions;
304
+ this.height = height;
305
+ this.aspectRatio = widthOrOptions / height;
306
+ this.encoding = {
307
+ maxBitrate,
308
+ maxFramerate,
309
+ priority,
310
+ };
311
+ } else {
312
+ throw new TypeError('Unsupported options: provide at least width, height and maxBitrate');
313
+ }
281
314
  }
282
315
 
283
316
  get resolution(): VideoResolution {
@@ -285,7 +318,7 @@ export class VideoPreset {
285
318
  width: this.width,
286
319
  height: this.height,
287
320
  frameRate: this.encoding.maxFramerate,
288
- aspectRatio: this.width / this.height,
321
+ aspectRatio: this.aspectRatio,
289
322
  };
290
323
  }
291
324
  }
@@ -23,3 +23,8 @@ export type AdaptiveStreamSettings = {
23
23
  */
24
24
  pauseVideoInBackground?: boolean;
25
25
  };
26
+
27
+ export interface ReplaceTrackOptions {
28
+ userProvidedTrack?: boolean;
29
+ stopProcessor?: boolean;
30
+ }