livekit-client 2.0.1 → 2.0.3

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 (67) 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 -18
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +94 -57
  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/e2ee/KeyProvider.d.ts +1 -1
  10. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  11. package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -0
  12. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  13. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  14. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  15. package/dist/src/room/Room.d.ts +1 -0
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/events.d.ts +5 -1
  18. package/dist/src/room/events.d.ts.map +1 -1
  19. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  20. package/dist/src/room/track/LocalAudioTrack.d.ts +8 -7
  21. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  22. package/dist/src/room/track/LocalTrack.d.ts +11 -9
  23. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  24. package/dist/src/room/track/LocalTrackPublication.d.ts +2 -2
  25. package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
  26. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/RemoteAudioTrack.d.ts +2 -1
  28. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  29. package/dist/src/room/track/RemoteTrack.d.ts +2 -2
  30. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/RemoteVideoTrack.d.ts +2 -1
  32. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/Track.d.ts +5 -3
  34. package/dist/src/room/track/Track.d.ts.map +1 -1
  35. package/dist/src/room/track/processor/types.d.ts +4 -0
  36. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  37. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +1 -1
  38. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +1 -0
  39. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  40. package/dist/ts4.2/src/room/Room.d.ts +1 -0
  41. package/dist/ts4.2/src/room/events.d.ts +5 -1
  42. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +8 -7
  43. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +11 -9
  44. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +2 -2
  45. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
  46. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +2 -1
  47. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +2 -2
  48. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +2 -1
  49. package/dist/ts4.2/src/room/track/Track.d.ts +5 -3
  50. package/dist/ts4.2/src/room/track/processor/types.d.ts +4 -0
  51. package/package.json +1 -1
  52. package/src/api/SignalClient.ts +1 -1
  53. package/src/e2ee/KeyProvider.ts +6 -1
  54. package/src/e2ee/worker/FrameCryptor.ts +26 -0
  55. package/src/e2ee/worker/ParticipantKeyHandler.ts +9 -5
  56. package/src/e2ee/worker/e2ee.worker.ts +17 -14
  57. package/src/room/Room.ts +21 -4
  58. package/src/room/events.ts +4 -0
  59. package/src/room/participant/LocalParticipant.ts +0 -1
  60. package/src/room/track/LocalAudioTrack.ts +9 -11
  61. package/src/room/track/LocalTrack.ts +78 -56
  62. package/src/room/track/LocalVideoTrack.ts +3 -3
  63. package/src/room/track/RemoteAudioTrack.ts +1 -1
  64. package/src/room/track/RemoteTrack.ts +4 -2
  65. package/src/room/track/RemoteVideoTrack.ts +1 -1
  66. package/src/room/track/Track.ts +7 -3
  67. package/src/room/track/processor/types.ts +4 -0
@@ -11,7 +11,9 @@ import type { TrackProcessor } from './processor/types';
11
11
 
12
12
  const defaultDimensionsTimeout = 1000;
13
13
 
14
- export default abstract class LocalTrack extends Track {
14
+ export default abstract class LocalTrack<
15
+ TrackKind extends Track.Kind = Track.Kind,
16
+ > extends Track<TrackKind> {
15
17
  /** @internal */
16
18
  sender?: RTCRtpSender;
17
19
 
@@ -34,10 +36,14 @@ export default abstract class LocalTrack extends Track {
34
36
 
35
37
  protected processorElement?: HTMLMediaElement;
36
38
 
37
- protected processor?: TrackProcessor<this['kind']>;
39
+ protected processor?: TrackProcessor<TrackKind, any>;
38
40
 
39
41
  protected processorLock: Mutex;
40
42
 
43
+ protected audioContext?: AudioContext;
44
+
45
+ private restartLock: Mutex;
46
+
41
47
  /**
42
48
  *
43
49
  * @param mediaTrack
@@ -47,7 +53,7 @@ export default abstract class LocalTrack extends Track {
47
53
  */
48
54
  protected constructor(
49
55
  mediaTrack: MediaStreamTrack,
50
- kind: Track.Kind,
56
+ kind: TrackKind,
51
57
  constraints?: MediaTrackConstraints,
52
58
  userProvidedTrack = false,
53
59
  loggerOptions?: LoggerOptions,
@@ -58,6 +64,7 @@ export default abstract class LocalTrack extends Track {
58
64
  this.muteLock = new Mutex();
59
65
  this.pauseUpstreamLock = new Mutex();
60
66
  this.processorLock = new Mutex();
67
+ this.restartLock = new Mutex();
61
68
  this.setMediaStreamTrack(mediaTrack, true);
62
69
 
63
70
  // added to satisfy TS compiler, constraints are synced with MediaStreamTrack
@@ -128,21 +135,28 @@ export default abstract class LocalTrack extends Track {
128
135
  this._constraints = newTrack.getConstraints();
129
136
  }
130
137
  let processedTrack: MediaStreamTrack | undefined;
131
- if (this.processor && newTrack && this.processorElement) {
132
- this.log.debug('restarting processor', this.logContext);
133
- if (this.kind === 'unknown') {
134
- throw TypeError('cannot set processor on track of unknown kind');
135
- }
138
+ if (this.processor && newTrack) {
139
+ const unlock = await this.processorLock.lock();
140
+ try {
141
+ this.log.debug('restarting processor', this.logContext);
142
+ if (this.kind === 'unknown') {
143
+ throw TypeError('cannot set processor on track of unknown kind');
144
+ }
136
145
 
137
- attachToElement(newTrack, this.processorElement);
138
- // ensure the processorElement itself stays muted
139
- this.processorElement.muted = true;
140
- await this.processor.restart({
141
- track: newTrack,
142
- kind: this.kind,
143
- element: this.processorElement,
144
- });
145
- processedTrack = this.processor.processedTrack;
146
+ if (this.processorElement) {
147
+ attachToElement(newTrack, this.processorElement);
148
+ // ensure the processorElement itself stays muted
149
+ this.processorElement.muted = true;
150
+ }
151
+ await this.processor.restart({
152
+ track: newTrack,
153
+ kind: this.kind,
154
+ element: this.processorElement,
155
+ });
156
+ processedTrack = this.processor.processedTrack;
157
+ } finally {
158
+ unlock();
159
+ }
146
160
  }
147
161
  if (this.sender) {
148
162
  await this.sender.replaceTrack(processedTrack ?? newTrack);
@@ -200,17 +214,17 @@ export default abstract class LocalTrack extends Track {
200
214
  return DeviceManager.getInstance().normalizeDeviceId(kind, deviceId, groupId);
201
215
  }
202
216
 
203
- async mute(): Promise<LocalTrack> {
217
+ async mute() {
204
218
  this.setTrackMuted(true);
205
219
  return this;
206
220
  }
207
221
 
208
- async unmute(): Promise<LocalTrack> {
222
+ async unmute() {
209
223
  this.setTrackMuted(false);
210
224
  return this;
211
225
  }
212
226
 
213
- async replaceTrack(track: MediaStreamTrack, userProvidedTrack = true): Promise<LocalTrack> {
227
+ async replaceTrack(track: MediaStreamTrack, userProvidedTrack = true) {
214
228
  if (!this.sender) {
215
229
  throw new TrackInvalidError('unable to replace an unpublished track');
216
230
  }
@@ -227,45 +241,50 @@ export default abstract class LocalTrack extends Track {
227
241
  return this;
228
242
  }
229
243
 
230
- protected async restart(constraints?: MediaTrackConstraints): Promise<LocalTrack> {
231
- if (!constraints) {
232
- constraints = this._constraints;
233
- }
234
- this.log.debug('restarting track with constraints', { ...this.logContext, constraints });
244
+ protected async restart(constraints?: MediaTrackConstraints) {
245
+ const unlock = await this.restartLock.lock();
246
+ try {
247
+ if (!constraints) {
248
+ constraints = this._constraints;
249
+ }
250
+ this.log.debug('restarting track with constraints', { ...this.logContext, constraints });
235
251
 
236
- const streamConstraints: MediaStreamConstraints = {
237
- audio: false,
238
- video: false,
239
- };
252
+ const streamConstraints: MediaStreamConstraints = {
253
+ audio: false,
254
+ video: false,
255
+ };
240
256
 
241
- if (this.kind === Track.Kind.Video) {
242
- streamConstraints.video = constraints;
243
- } else {
244
- streamConstraints.audio = constraints;
245
- }
257
+ if (this.kind === Track.Kind.Video) {
258
+ streamConstraints.video = constraints;
259
+ } else {
260
+ streamConstraints.audio = constraints;
261
+ }
246
262
 
247
- // these steps are duplicated from setMediaStreamTrack because we must stop
248
- // the previous tracks before new tracks can be acquired
249
- this.attachedElements.forEach((el) => {
250
- detachTrack(this.mediaStreamTrack, el);
251
- });
252
- this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
253
- // on Safari, the old audio track must be stopped before attempting to acquire
254
- // the new track, otherwise the new track will stop with
255
- // 'A MediaStreamTrack ended due to a capture failure`
256
- this._mediaStreamTrack.stop();
263
+ // these steps are duplicated from setMediaStreamTrack because we must stop
264
+ // the previous tracks before new tracks can be acquired
265
+ this.attachedElements.forEach((el) => {
266
+ detachTrack(this.mediaStreamTrack, el);
267
+ });
268
+ this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
269
+ // on Safari, the old audio track must be stopped before attempting to acquire
270
+ // the new track, otherwise the new track will stop with
271
+ // 'A MediaStreamTrack ended due to a capture failure`
272
+ this._mediaStreamTrack.stop();
257
273
 
258
- // create new track and attach
259
- const mediaStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
260
- const newTrack = mediaStream.getTracks()[0];
261
- newTrack.addEventListener('ended', this.handleEnded);
262
- this.log.debug('re-acquired MediaStreamTrack', this.logContext);
274
+ // create new track and attach
275
+ const mediaStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
276
+ const newTrack = mediaStream.getTracks()[0];
277
+ newTrack.addEventListener('ended', this.handleEnded);
278
+ this.log.debug('re-acquired MediaStreamTrack', this.logContext);
263
279
 
264
- await this.setMediaStreamTrack(newTrack);
265
- this._constraints = constraints;
280
+ await this.setMediaStreamTrack(newTrack);
281
+ this._constraints = constraints;
266
282
 
267
- this.emit(TrackEvent.Restarted, this);
268
- return this;
283
+ this.emit(TrackEvent.Restarted, this);
284
+ return this;
285
+ } finally {
286
+ unlock();
287
+ }
269
288
  }
270
289
 
271
290
  protected setTrackMuted(muted: boolean) {
@@ -408,7 +427,7 @@ export default abstract class LocalTrack extends Track {
408
427
  * @param showProcessedStreamLocally
409
428
  * @returns
410
429
  */
411
- async setProcessor(processor: TrackProcessor<this['kind']>, showProcessedStreamLocally = true) {
430
+ async setProcessor(processor: TrackProcessor<TrackKind>, showProcessedStreamLocally = true) {
412
431
  const unlock = await this.processorLock.lock();
413
432
  try {
414
433
  this.log.debug('setting up processor', this.logContext);
@@ -418,7 +437,8 @@ export default abstract class LocalTrack extends Track {
418
437
  if (this.kind === 'unknown') {
419
438
  throw TypeError('cannot set processor on track of unknown kind');
420
439
  }
421
- this.processorElement = this.processorElement ?? document.createElement(this.kind);
440
+ this.processorElement =
441
+ this.processorElement ?? (document.createElement(this.kind) as HTMLMediaElement);
422
442
 
423
443
  attachToElement(this._mediaStreamTrack, this.processorElement);
424
444
  this.processorElement.muted = true;
@@ -433,6 +453,7 @@ export default abstract class LocalTrack extends Track {
433
453
  kind: this.kind,
434
454
  track: this._mediaStreamTrack,
435
455
  element: this.processorElement,
456
+ audioContext: this.audioContext,
436
457
  };
437
458
 
438
459
  await processor.init(processorOptions);
@@ -446,6 +467,7 @@ export default abstract class LocalTrack extends Track {
446
467
  }
447
468
  await this.sender?.replaceTrack(this.processor.processedTrack);
448
469
  }
470
+ this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
449
471
  } finally {
450
472
  unlock();
451
473
  }
@@ -471,8 +493,8 @@ export default abstract class LocalTrack extends Track {
471
493
  this.processor = undefined;
472
494
  this.processorElement?.remove();
473
495
  this.processorElement = undefined;
474
-
475
496
  await this.restart();
497
+ this.emit(TrackEvent.TrackProcessorUpdate);
476
498
  }
477
499
 
478
500
  protected abstract monitorSender(): void;
@@ -30,7 +30,7 @@ export class SimulcastTrackInfo {
30
30
 
31
31
  const refreshSubscribedCodecAfterNewCodec = 5000;
32
32
 
33
- export default class LocalVideoTrack extends LocalTrack {
33
+ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
34
34
  /* @internal */
35
35
  signalClient?: SignalClient;
36
36
 
@@ -115,7 +115,7 @@ export default class LocalVideoTrack extends LocalTrack {
115
115
  }
116
116
  }
117
117
 
118
- async mute(): Promise<LocalVideoTrack> {
118
+ async mute(): Promise<typeof this> {
119
119
  const unlock = await this.muteLock.lock();
120
120
  try {
121
121
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
@@ -130,7 +130,7 @@ export default class LocalVideoTrack extends LocalTrack {
130
130
  }
131
131
  }
132
132
 
133
- async unmute(): Promise<LocalVideoTrack> {
133
+ async unmute(): Promise<typeof this> {
134
134
  const unlock = await this.muteLock.lock();
135
135
  try {
136
136
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
@@ -7,7 +7,7 @@ import RemoteTrack from './RemoteTrack';
7
7
  import { Track } from './Track';
8
8
  import type { AudioOutputOptions } from './options';
9
9
 
10
- export default class RemoteAudioTrack extends RemoteTrack {
10
+ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
11
11
  private prevStats?: AudioReceiverStats;
12
12
 
13
13
  private elementVolume: number | undefined;
@@ -3,14 +3,16 @@ import { monitorFrequency } from '../stats';
3
3
  import type { LoggerOptions } from '../types';
4
4
  import { Track } from './Track';
5
5
 
6
- export default abstract class RemoteTrack extends Track {
6
+ export default abstract class RemoteTrack<
7
+ TrackKind extends Track.Kind = Track.Kind,
8
+ > extends Track<TrackKind> {
7
9
  /** @internal */
8
10
  receiver?: RTCRtpReceiver;
9
11
 
10
12
  constructor(
11
13
  mediaTrack: MediaStreamTrack,
12
14
  sid: string,
13
- kind: Track.Kind,
15
+ kind: TrackKind,
14
16
  receiver?: RTCRtpReceiver,
15
17
  loggerOptions?: LoggerOptions,
16
18
  ) {
@@ -12,7 +12,7 @@ import type { AdaptiveStreamSettings } from './types';
12
12
 
13
13
  const REACTION_DELAY = 100;
14
14
 
15
- export default class RemoteVideoTrack extends RemoteTrack {
15
+ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
16
16
  private prevStats?: VideoReceiverStats;
17
17
 
18
18
  private elementInfos: ElementInfo[] = [];
@@ -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;
@@ -24,8 +25,10 @@ export enum VideoQuality {
24
25
  MEDIUM = ProtoQuality.MEDIUM,
25
26
  HIGH = ProtoQuality.HIGH,
26
27
  }
27
- export abstract class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
28
- kind: Track.Kind;
28
+ export abstract class Track<
29
+ TrackKind extends Track.Kind = Track.Kind,
30
+ > extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
31
+ readonly kind: TrackKind;
29
32
 
30
33
  attachedElements: HTMLMediaElement[] = [];
31
34
 
@@ -67,7 +70,7 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
67
70
 
68
71
  protected constructor(
69
72
  mediaTrack: MediaStreamTrack,
70
- kind: Track.Kind,
73
+ kind: TrackKind,
71
74
  loggerOptions: LoggerOptions = {},
72
75
  ) {
73
76
  super();
@@ -500,4 +503,5 @@ export type TrackEventCallbacks = {
500
503
  elementDetached: (element: HTMLMediaElement) => void;
501
504
  upstreamPaused: (track: any) => void;
502
505
  upstreamResumed: (track: any) => void;
506
+ trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
503
507
  };
@@ -1,3 +1,4 @@
1
+ import type Room from '../../Room';
1
2
  import type { Track } from '../Track';
2
3
 
3
4
  /**
@@ -7,6 +8,7 @@ export type ProcessorOptions<T extends Track.Kind> = {
7
8
  kind: T;
8
9
  track: MediaStreamTrack;
9
10
  element?: HTMLMediaElement;
11
+ audioContext?: AudioContext;
10
12
  };
11
13
 
12
14
  /**
@@ -33,4 +35,6 @@ export interface TrackProcessor<
33
35
  restart: (opts: U) => Promise<void>;
34
36
  destroy: () => Promise<void>;
35
37
  processedTrack?: MediaStreamTrack;
38
+ onPublish?: (room: Room) => Promise<void>;
39
+ onUnpublish?: () => Promise<void>;
36
40
  }