livekit-client 2.0.1 → 2.0.3

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