livekit-client 2.0.2 → 2.0.4

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