livekit-client 1.10.0 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. package/dist/livekit-client.esm.mjs +504 -527
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/connectionHelper/ConnectionCheck.d.ts +2 -3
  6. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/checks/Checker.d.ts +2 -3
  8. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  9. package/dist/src/room/PCTransport.d.ts +1 -1
  10. package/dist/src/room/PCTransport.d.ts.map +1 -1
  11. package/dist/src/room/RTCEngine.d.ts +2 -4
  12. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  13. package/dist/src/room/Room.d.ts +3 -4
  14. package/dist/src/room/Room.d.ts.map +1 -1
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/participant/Participant.d.ts +2 -4
  17. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  18. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -1
  19. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  20. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalTrack.d.ts +3 -2
  22. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  23. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  24. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  25. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
  26. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/Track.d.ts +2 -4
  28. package/dist/src/room/track/Track.d.ts.map +1 -1
  29. package/dist/src/room/track/TrackPublication.d.ts +2 -4
  30. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  31. package/dist/src/room/track/options.d.ts +13 -4
  32. package/dist/src/room/track/options.d.ts.map +1 -1
  33. package/dist/src/room/track/types.d.ts +2 -1
  34. package/dist/src/room/track/types.d.ts.map +1 -1
  35. package/dist/src/room/utils.d.ts.map +1 -1
  36. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +2 -3
  37. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +2 -3
  38. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
  39. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -4
  40. package/dist/ts4.2/src/room/Room.d.ts +3 -4
  41. package/dist/ts4.2/src/room/participant/Participant.d.ts +2 -4
  42. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -1
  43. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -2
  44. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  45. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
  46. package/dist/ts4.2/src/room/track/Track.d.ts +2 -4
  47. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -4
  48. package/dist/ts4.2/src/room/track/options.d.ts +13 -4
  49. package/dist/ts4.2/src/room/track/types.d.ts +2 -1
  50. package/package.json +2 -3
  51. package/src/connectionHelper/ConnectionCheck.ts +2 -3
  52. package/src/connectionHelper/checks/Checker.ts +2 -3
  53. package/src/logger.ts +4 -4
  54. package/src/room/PCTransport.ts +1 -1
  55. package/src/room/RTCEngine.ts +4 -4
  56. package/src/room/Room.ts +41 -11
  57. package/src/room/participant/LocalParticipant.ts +11 -3
  58. package/src/room/participant/Participant.ts +2 -4
  59. package/src/room/participant/RemoteParticipant.ts +4 -3
  60. package/src/room/participant/publishUtils.ts +16 -18
  61. package/src/room/track/LocalTrack.ts +68 -46
  62. package/src/room/track/LocalVideoTrack.ts +9 -7
  63. package/src/room/track/RemoteVideoTrack.ts +23 -6
  64. package/src/room/track/Track.ts +2 -4
  65. package/src/room/track/TrackPublication.ts +2 -4
  66. package/src/room/track/options.ts +13 -4
  67. package/src/room/track/types.ts +2 -1
  68. package/src/room/utils.ts +6 -3
@@ -47,12 +47,16 @@ export default abstract class LocalTrack extends Track {
47
47
  userProvidedTrack = false,
48
48
  ) {
49
49
  super(mediaTrack, kind);
50
- this._mediaStreamTrack.addEventListener('ended', this.handleEnded);
51
- this.constraints = constraints ?? mediaTrack.getConstraints();
52
50
  this.reacquireTrack = false;
53
51
  this.providedByUser = userProvidedTrack;
54
52
  this.muteLock = new Mutex();
55
53
  this.pauseUpstreamLock = new Mutex();
54
+ // added to satisfy TS compiler, constraints are synced with MediaStreamTrack
55
+ this.constraints = mediaTrack.getConstraints();
56
+ this.setMediaStreamTrack(mediaTrack);
57
+ if (constraints) {
58
+ this.constraints = constraints;
59
+ }
56
60
  }
57
61
 
58
62
  get id(): string {
@@ -88,6 +92,50 @@ export default abstract class LocalTrack extends Track {
88
92
  return this.processor?.processedTrack ?? this._mediaStreamTrack;
89
93
  }
90
94
 
95
+ private async setMediaStreamTrack(newTrack: MediaStreamTrack) {
96
+ if (newTrack === this._mediaStreamTrack) {
97
+ return;
98
+ }
99
+ if (this._mediaStreamTrack) {
100
+ // detach
101
+ this.attachedElements.forEach((el) => {
102
+ detachTrack(this._mediaStreamTrack, el);
103
+ });
104
+ this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
105
+ this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
106
+ this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
107
+ if (!this.providedByUser) {
108
+ this._mediaStreamTrack.stop();
109
+ }
110
+ }
111
+
112
+ this.mediaStream = new MediaStream([newTrack]);
113
+ if (newTrack) {
114
+ newTrack.addEventListener('ended', this.handleEnded);
115
+ // when underlying track emits mute, it indicates that the device is unable
116
+ // to produce media. In this case we'll need to signal with remote that
117
+ // the track is "muted"
118
+ // note this is different from LocalTrack.mute because we do not want to
119
+ // touch MediaStreamTrack.enabled
120
+ newTrack.addEventListener('mute', this.pauseUpstream);
121
+ newTrack.addEventListener('unmute', this.resumeUpstream);
122
+ this.constraints = newTrack.getConstraints();
123
+ }
124
+ if (this.sender) {
125
+ await this.sender.replaceTrack(newTrack);
126
+ }
127
+ this._mediaStreamTrack = newTrack;
128
+ if (newTrack) {
129
+ // sync muted state with the enabled state of the newly provided track
130
+ this._mediaStreamTrack.enabled = !this.isMuted;
131
+ // when a valid track is replace, we'd want to start producing
132
+ await this.resumeUpstream();
133
+ this.attachedElements.forEach((el) => {
134
+ attachToElement(newTrack, el);
135
+ });
136
+ }
137
+ }
138
+
91
139
  async waitForDimensions(timeout = defaultDimensionsTimeout): Promise<Track.Dimensions> {
92
140
  if (this.kind === Track.Kind.Audio) {
93
141
  throw new Error('cannot get dimensions for audio tracks');
@@ -133,37 +181,12 @@ export default abstract class LocalTrack extends Track {
133
181
  throw new TrackInvalidError('unable to replace an unpublished track');
134
182
  }
135
183
 
136
- // detach
137
- this.attachedElements.forEach((el) => {
138
- detachTrack(this._mediaStreamTrack, el);
139
- });
140
- this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
141
- // on Safari, the old audio track must be stopped before attempting to acquire
142
- // the new track, otherwise the new track will stop with
143
- // 'A MediaStreamTrack ended due to a capture failure`
144
- if (!this.providedByUser) {
145
- this._mediaStreamTrack.stop();
146
- }
147
-
148
- track.addEventListener('ended', this.handleEnded);
149
184
  log.debug('replace MediaStreamTrack');
150
-
151
- if (this.sender) {
152
- await this.sender.replaceTrack(track);
153
- }
154
- this._mediaStreamTrack = track;
155
-
156
- // sync muted state with the enabled state of the newly provided track
157
- this._mediaStreamTrack.enabled = !this.isMuted;
158
-
159
- await this.resumeUpstream();
160
-
161
- this.attachedElements.forEach((el) => {
162
- attachToElement(track, el);
163
- });
164
-
165
- this.mediaStream = new MediaStream([track]);
185
+ this.setMediaStreamTrack(track);
186
+ // this must be synced *after* setting mediaStreamTrack above, since it relies
187
+ // on the previous state in order to cleanup
166
188
  this.providedByUser = userProvidedTrack;
189
+
167
190
  if (this.processor) {
168
191
  await this.stopProcessor();
169
192
  }
@@ -187,7 +210,8 @@ export default abstract class LocalTrack extends Track {
187
210
  streamConstraints.audio = constraints;
188
211
  }
189
212
 
190
- // detach
213
+ // these steps are duplicated from setMediaStreamTrack because we must stop
214
+ // the previous tracks before new tracks can be acquired
191
215
  this.attachedElements.forEach((el) => {
192
216
  detachTrack(this.mediaStreamTrack, el);
193
217
  });
@@ -203,16 +227,7 @@ export default abstract class LocalTrack extends Track {
203
227
  newTrack.addEventListener('ended', this.handleEnded);
204
228
  log.debug('re-acquired MediaStreamTrack');
205
229
 
206
- if (this.sender) {
207
- // Track can be restarted after it's unpublished
208
- await this.sender.replaceTrack(newTrack);
209
- }
210
-
211
- this._mediaStreamTrack = newTrack;
212
-
213
- await this.resumeUpstream();
214
-
215
- this.mediaStream = mediaStream;
230
+ this.setMediaStreamTrack(newTrack);
216
231
  this.constraints = constraints;
217
232
  if (this.processor) {
218
233
  const processor = this.processor;
@@ -263,11 +278,17 @@ export default abstract class LocalTrack extends Track {
263
278
  if (this.isInBackground) {
264
279
  this.reacquireTrack = true;
265
280
  }
281
+ this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
282
+ this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
266
283
  this.emit(TrackEvent.Ended, this);
267
284
  };
268
285
 
269
286
  stop() {
270
287
  super.stop();
288
+
289
+ this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
290
+ this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
291
+ this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
271
292
  this.processor?.destroy();
272
293
  this.processor = undefined;
273
294
  }
@@ -278,7 +299,7 @@ export default abstract class LocalTrack extends Track {
278
299
  * the server.
279
300
  * this API is unsupported on Safari < 12 due to a bug
280
301
  **/
281
- async pauseUpstream() {
302
+ pauseUpstream = async () => {
282
303
  const unlock = await this.pauseUpstreamLock.lock();
283
304
  try {
284
305
  if (this._isUpstreamPaused === true) {
@@ -300,9 +321,9 @@ export default abstract class LocalTrack extends Track {
300
321
  } finally {
301
322
  unlock();
302
323
  }
303
- }
324
+ };
304
325
 
305
- async resumeUpstream() {
326
+ resumeUpstream = async () => {
306
327
  const unlock = await this.pauseUpstreamLock.lock();
307
328
  try {
308
329
  if (this._isUpstreamPaused === false) {
@@ -315,11 +336,12 @@ export default abstract class LocalTrack extends Track {
315
336
  this._isUpstreamPaused = false;
316
337
  this.emit(TrackEvent.UpstreamResumed, this);
317
338
 
339
+ // this operation is noop if mediastreamtrack is already being sent
318
340
  await this.sender.replaceTrack(this._mediaStreamTrack);
319
341
  } finally {
320
342
  unlock();
321
343
  }
322
- }
344
+ };
323
345
 
324
346
  /**
325
347
  * Sets a processor on this track.
@@ -351,8 +351,13 @@ async function setPublishingLayersForSender(
351
351
 
352
352
  let hasChanged = false;
353
353
 
354
+ /* disable closable spatial layer as it has video blur / frozen issue with current server / client
355
+ 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
356
+ low resolution frame and recover very quickly, but noticable
357
+ 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
358
+ const closableSpatial = false;
354
359
  /* @ts-ignore */
355
- if (encodings.length === 1 && encodings[0].scalabilityMode) {
360
+ if (closableSpatial && encodings[0].scalabilityMode) {
356
361
  // svc dynacast encodings
357
362
  const encoding = encodings[0];
358
363
  /* @ts-ignore */
@@ -372,10 +377,7 @@ async function setPublishingLayersForSender(
372
377
  } else if (!encoding.active /* || mode.spatial !== maxQuality + 1*/) {
373
378
  hasChanged = true;
374
379
  encoding.active = true;
375
- /* disable closable spatial layer as it has video blur/frozen issue with current server/client
376
- 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
377
- low resolution frame and recover very quickly, but noticable
378
- 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable
380
+ /*
379
381
  @ts-ignore
380
382
  const originalMode = new ScalabilityMode(senderEncodings[0].scalabilityMode)
381
383
  mode.spatial = maxQuality + 1;
@@ -456,6 +458,7 @@ export function videoLayersFromEncodings(
456
458
  width: number,
457
459
  height: number,
458
460
  encodings?: RTCRtpEncodingParameters[],
461
+ svc?: boolean,
459
462
  ): VideoLayer[] {
460
463
  // default to a single layer, HQ
461
464
  if (!encodings) {
@@ -470,8 +473,7 @@ export function videoLayersFromEncodings(
470
473
  ];
471
474
  }
472
475
 
473
- /* @ts-ignore */
474
- if (encodings.length === 1 && encodings[0].scalabilityMode) {
476
+ if (svc) {
475
477
  // svc layers
476
478
  /* @ts-ignore */
477
479
  const sm = new ScalabilityMode(encodings[0].scalabilityMode);
@@ -1,11 +1,11 @@
1
1
  import { debounce } from 'ts-debounce';
2
2
  import log from '../../logger';
3
3
  import { TrackEvent } from '../events';
4
- import { computeBitrate } from '../stats';
5
4
  import type { VideoReceiverStats } from '../stats';
5
+ import { computeBitrate } from '../stats';
6
6
  import CriticalTimers from '../timers';
7
- import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
8
7
  import type { ObservableMediaElement } from '../utils';
8
+ import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
9
9
  import RemoteTrack from './RemoteTrack';
10
10
  import { Track, attachToElement, detachTrack } from './Track';
11
11
  import type { AdaptiveStreamSettings } from './types';
@@ -248,11 +248,10 @@ export default class RemoteVideoTrack extends RemoteTrack {
248
248
  private updateDimensions() {
249
249
  let maxWidth = 0;
250
250
  let maxHeight = 0;
251
+ const pixelDensity = this.getPixelDensity();
251
252
  for (const info of this.elementInfos) {
252
- const pixelDensity = this.adaptiveStreamSettings?.pixelDensity ?? 1;
253
- const pixelDensityValue = pixelDensity === 'screen' ? getDevicePixelRatio() : pixelDensity;
254
- const currentElementWidth = info.width() * pixelDensityValue;
255
- const currentElementHeight = info.height() * pixelDensityValue;
253
+ const currentElementWidth = info.width() * pixelDensity;
254
+ const currentElementHeight = info.height() * pixelDensity;
256
255
  if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
257
256
  maxWidth = currentElementWidth;
258
257
  maxHeight = currentElementHeight;
@@ -270,6 +269,24 @@ export default class RemoteVideoTrack extends RemoteTrack {
270
269
 
271
270
  this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
272
271
  }
272
+
273
+ private getPixelDensity(): number {
274
+ const pixelDensity = this.adaptiveStreamSettings?.pixelDensity;
275
+ if (pixelDensity === 'screen') {
276
+ return getDevicePixelRatio();
277
+ } else if (!pixelDensity) {
278
+ // when unset, we'll pick a sane default here.
279
+ // for higher pixel density devices (mobile phones, etc), we'll use 2
280
+ // otherwise it defaults to 1
281
+ const devicePixelRatio = getDevicePixelRatio();
282
+ if (devicePixelRatio > 2) {
283
+ return 2;
284
+ } else {
285
+ return 1;
286
+ }
287
+ }
288
+ return pixelDensity;
289
+ }
273
290
  }
274
291
 
275
292
  export interface ElementInfo {
@@ -1,5 +1,4 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEventEmitter from 'typed-emitter';
1
+ import EventEmitter from 'eventemitter3';
3
2
  import type { SignalClient } from '../../api/SignalClient';
4
3
  import log from '../../logger';
5
4
  import { TrackSource, TrackType } from '../../proto/livekit_models';
@@ -13,7 +12,7 @@ const BACKGROUND_REACTION_DELAY = 5000;
13
12
  // Safari tracks which audio elements have been "blessed" by the user.
14
13
  const recycledElements: Array<HTMLAudioElement> = [];
15
14
 
16
- export abstract class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
15
+ export abstract class Track extends EventEmitter<TrackEventCallbacks> {
17
16
  kind: Track.Kind;
18
17
 
19
18
  attachedElements: HTMLMediaElement[] = [];
@@ -52,7 +51,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
52
51
 
53
52
  protected constructor(mediaTrack: MediaStreamTrack, kind: Track.Kind) {
54
53
  super();
55
- this.setMaxListeners(100);
56
54
  this.kind = kind;
57
55
  this._mediaStreamTrack = mediaTrack;
58
56
  this._mediaStreamID = mediaTrack.id;
@@ -1,5 +1,4 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEventEmitter from 'typed-emitter';
1
+ import EventEmitter from 'eventemitter3';
3
2
  import log from '../../logger';
4
3
  import type { SubscriptionError, TrackInfo } from '../../proto/livekit_models';
5
4
  import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
@@ -11,7 +10,7 @@ import type RemoteTrack from './RemoteTrack';
11
10
  import RemoteVideoTrack from './RemoteVideoTrack';
12
11
  import { Track } from './Track';
13
12
 
14
- export class TrackPublication extends (EventEmitter as new () => TypedEventEmitter<PublicationEventCallbacks>) {
13
+ export class TrackPublication extends EventEmitter<PublicationEventCallbacks> {
15
14
  kind: Track.Kind;
16
15
 
17
16
  trackName: string;
@@ -38,7 +37,6 @@ export class TrackPublication extends (EventEmitter as new () => TypedEventEmitt
38
37
 
39
38
  constructor(kind: Track.Kind, id: string, name: string) {
40
39
  super();
41
- this.setMaxListeners(100);
42
40
  this.kind = kind;
43
41
  this.trackSid = id;
44
42
  this.trackName = name;
@@ -63,10 +63,19 @@ export interface TrackPublishDefaults {
63
63
  scalabilityMode?: ScalabilityMode;
64
64
 
65
65
  /**
66
- * custom video simulcast layers for camera tracks, defaults to h180, h360
67
- * You can specify up to two custom layers that will be used instead of
68
- * the LiveKit default layers.
69
- * Note: the layers need to be ordered from lowest to highest quality
66
+ * Up to two additional simulcast layers to publish in addition to the original
67
+ * Track.
68
+ * When left blank, it defaults to h180, h360.
69
+ * If a SVC codec is used (VP9 or AV1), this field has no effect.
70
+ *
71
+ * To publish three total layers, you would specify:
72
+ * {
73
+ * videoEncoding: {...}, // encoding of the primary layer
74
+ * videoSimulcastLayers: [
75
+ * VideoPresets.h540,
76
+ * VideoPresets.h216,
77
+ * ],
78
+ * }
70
79
  */
71
80
  videoSimulcastLayers?: Array<VideoPreset>;
72
81
 
@@ -8,7 +8,8 @@ export type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
8
8
 
9
9
  export type AdaptiveStreamSettings = {
10
10
  /**
11
- * Set a custom pixel density, defaults to 1
11
+ * Set a custom pixel density. Defaults to 2 for high density screens (3+) or
12
+ * 1 otherwise.
12
13
  * When streaming videos on a ultra high definition screen this setting
13
14
  * let's you account for the devicePixelRatio of those screens.
14
15
  * Set it to `screen` to use the actual pixel density of the screen
package/src/room/utils.ts CHANGED
@@ -261,7 +261,7 @@ export function getEmptyVideoStreamTrack() {
261
261
  if (!emptyVideoStreamTrack) {
262
262
  emptyVideoStreamTrack = createDummyVideoStreamTrack();
263
263
  }
264
- return emptyVideoStreamTrack;
264
+ return emptyVideoStreamTrack.clone();
265
265
  }
266
266
 
267
267
  export function createDummyVideoStreamTrack(
@@ -301,8 +301,11 @@ export function getEmptyAudioStreamTrack() {
301
301
  // implementation adapted from https://blog.mozilla.org/webrtc/warm-up-with-replacetrack/
302
302
  const ctx = new AudioContext();
303
303
  const oscillator = ctx.createOscillator();
304
+ const gain = ctx.createGain();
305
+ gain.gain.setValueAtTime(0, 0);
304
306
  const dst = ctx.createMediaStreamDestination();
305
- oscillator.connect(dst);
307
+ oscillator.connect(gain);
308
+ gain.connect(dst);
306
309
  oscillator.start();
307
310
  [emptyAudioStreamTrack] = dst.stream.getAudioTracks();
308
311
  if (!emptyAudioStreamTrack) {
@@ -310,7 +313,7 @@ export function getEmptyAudioStreamTrack() {
310
313
  }
311
314
  emptyAudioStreamTrack.enabled = false;
312
315
  }
313
- return emptyAudioStreamTrack;
316
+ return emptyAudioStreamTrack.clone();
314
317
  }
315
318
 
316
319
  export class Future<T> {