livekit-client 1.7.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. package/README.md +21 -1
  2. package/dist/livekit-client.esm.mjs +14241 -12994
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/api/SignalClient.d.ts +11 -10
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  9. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  10. package/dist/src/connectionHelper/checks/Checker.d.ts +1 -1
  11. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  12. package/dist/src/index.d.ts +7 -7
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  15. package/dist/src/proto/livekit_models.d.ts +37 -0
  16. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  17. package/dist/src/proto/livekit_rtc.d.ts +347 -75
  18. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  19. package/dist/src/room/RTCEngine.d.ts +12 -3
  20. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  21. package/dist/src/room/ReconnectPolicy.d.ts +1 -0
  22. package/dist/src/room/ReconnectPolicy.d.ts.map +1 -1
  23. package/dist/src/room/RegionUrlProvider.d.ts +14 -0
  24. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -0
  25. package/dist/src/room/Room.d.ts +23 -15
  26. package/dist/src/room/Room.d.ts.map +1 -1
  27. package/dist/src/room/errors.d.ts +2 -1
  28. package/dist/src/room/errors.d.ts.map +1 -1
  29. package/dist/src/room/events.d.ts +23 -2
  30. package/dist/src/room/events.d.ts.map +1 -1
  31. package/dist/src/room/participant/LocalParticipant.d.ts +14 -2
  32. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  33. package/dist/src/room/participant/Participant.d.ts +4 -2
  34. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  35. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -2
  36. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  37. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  38. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  39. package/dist/src/room/track/LocalTrack.d.ts +4 -3
  40. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  41. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
  42. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  43. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  44. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  45. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  46. package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -1
  47. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  48. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
  49. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  50. package/dist/src/room/track/Track.d.ts +3 -1
  51. package/dist/src/room/track/Track.d.ts.map +1 -1
  52. package/dist/src/room/track/create.d.ts.map +1 -1
  53. package/dist/src/room/types.d.ts +5 -0
  54. package/dist/src/room/types.d.ts.map +1 -1
  55. package/dist/src/room/utils.d.ts +4 -0
  56. package/dist/src/room/utils.d.ts.map +1 -1
  57. package/dist/ts4.2/src/api/SignalClient.d.ts +14 -10
  58. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  59. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +1 -1
  60. package/dist/ts4.2/src/index.d.ts +7 -6
  61. package/dist/ts4.2/src/proto/livekit_models.d.ts +37 -0
  62. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +380 -84
  63. package/dist/ts4.2/src/room/RTCEngine.d.ts +12 -3
  64. package/dist/ts4.2/src/room/ReconnectPolicy.d.ts +1 -0
  65. package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +14 -0
  66. package/dist/ts4.2/src/room/Room.d.ts +23 -15
  67. package/dist/ts4.2/src/room/errors.d.ts +2 -1
  68. package/dist/ts4.2/src/room/events.d.ts +23 -2
  69. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -2
  70. package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -2
  71. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -2
  72. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +4 -3
  73. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
  74. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  75. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -1
  76. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
  77. package/dist/ts4.2/src/room/track/Track.d.ts +3 -1
  78. package/dist/ts4.2/src/room/types.d.ts +5 -0
  79. package/dist/ts4.2/src/room/utils.d.ts +4 -0
  80. package/package.json +21 -20
  81. package/src/api/SignalClient.ts +41 -29
  82. package/src/connectionHelper/ConnectionCheck.ts +1 -2
  83. package/src/connectionHelper/checks/Checker.ts +1 -1
  84. package/src/connectionHelper/checks/reconnect.ts +1 -1
  85. package/src/index.ts +9 -8
  86. package/src/proto/google/protobuf/timestamp.ts +15 -6
  87. package/src/proto/livekit_models.ts +917 -221
  88. package/src/proto/livekit_rtc.ts +1053 -279
  89. package/src/room/RTCEngine.ts +171 -47
  90. package/src/room/ReconnectPolicy.ts +2 -0
  91. package/src/room/RegionUrlProvider.ts +73 -0
  92. package/src/room/Room.ts +278 -177
  93. package/src/room/errors.ts +1 -0
  94. package/src/room/events.ts +24 -0
  95. package/src/room/participant/LocalParticipant.ts +30 -7
  96. package/src/room/participant/Participant.ts +27 -3
  97. package/src/room/participant/RemoteParticipant.ts +6 -3
  98. package/src/room/participant/publishUtils.test.ts +1 -1
  99. package/src/room/participant/publishUtils.ts +1 -1
  100. package/src/room/track/LocalAudioTrack.ts +14 -7
  101. package/src/room/track/LocalTrack.ts +23 -9
  102. package/src/room/track/LocalTrackPublication.ts +1 -1
  103. package/src/room/track/LocalVideoTrack.ts +15 -9
  104. package/src/room/track/RemoteAudioTrack.ts +1 -1
  105. package/src/room/track/RemoteTrackPublication.ts +4 -3
  106. package/src/room/track/RemoteVideoTrack.test.ts +1 -1
  107. package/src/room/track/RemoteVideoTrack.ts +8 -7
  108. package/src/room/track/Track.ts +46 -31
  109. package/src/room/track/create.ts +2 -2
  110. package/src/room/types.ts +17 -0
  111. package/src/room/utils.ts +53 -0
@@ -168,6 +168,14 @@ export enum RoomEvent {
168
168
  */
169
169
  ParticipantMetadataChanged = 'participantMetadataChanged',
170
170
 
171
+ /**
172
+ * Participant's display name changed
173
+ *
174
+ * args: (name: string, [[Participant]])
175
+ *
176
+ */
177
+ ParticipantNameChanged = 'participantNameChanged',
178
+
171
179
  /**
172
180
  * Room metadata is a simple way for app-specific state to be pushed to
173
181
  * all users.
@@ -264,6 +272,12 @@ export enum RoomEvent {
264
272
  * args: (isRecording: boolean)
265
273
  */
266
274
  RecordingStatusChanged = 'recordingStatusChanged',
275
+
276
+ /**
277
+ * Emits whenever the current buffer status of a data channel changes
278
+ * args: (isLow: boolean, kind: [[DataPacket_Kind]])
279
+ */
280
+ DCBufferStatusChanged = 'dcBufferStatusChanged',
267
281
  }
268
282
 
269
283
  export enum ParticipantEvent {
@@ -353,6 +367,14 @@ export enum ParticipantEvent {
353
367
  */
354
368
  ParticipantMetadataChanged = 'participantMetadataChanged',
355
369
 
370
+ /**
371
+ * Participant's display name changed
372
+ *
373
+ * args: (name: string, [[Participant]])
374
+ *
375
+ */
376
+ ParticipantNameChanged = 'participantNameChanged',
377
+
356
378
  /**
357
379
  * Data received from this participant as sender.
358
380
  * Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
@@ -427,10 +449,12 @@ export enum EngineEvent {
427
449
  Restarting = 'restarting',
428
450
  Restarted = 'restarted',
429
451
  SignalResumed = 'signalResumed',
452
+ SignalRestarted = 'signalRestarted',
430
453
  Closing = 'closing',
431
454
  MediaTrackAdded = 'mediaTrackAdded',
432
455
  ActiveSpeakersUpdate = 'activeSpeakersUpdate',
433
456
  DataPacketReceived = 'dataPacketReceived',
457
+ DCBufferStatusChanged = 'dcBufferStatusChanged',
434
458
  }
435
459
 
436
460
  export enum TrackEvent {
@@ -10,35 +10,35 @@ import {
10
10
  TrackPublishedResponse,
11
11
  TrackUnpublishedResponse,
12
12
  } from '../../proto/livekit_rtc';
13
+ import type RTCEngine from '../RTCEngine';
13
14
  import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
14
15
  import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
15
- import type RTCEngine from '../RTCEngine';
16
16
  import LocalAudioTrack from '../track/LocalAudioTrack';
17
17
  import LocalTrack from '../track/LocalTrack';
18
18
  import LocalTrackPublication from '../track/LocalTrackPublication';
19
19
  import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
20
+ import { Track } from '../track/Track';
20
21
  import {
21
22
  AudioCaptureOptions,
22
23
  BackupVideoCodec,
23
24
  CreateLocalTracksOptions,
24
- isBackupCodec,
25
25
  ScreenShareCaptureOptions,
26
26
  ScreenSharePresets,
27
27
  TrackPublishOptions,
28
28
  VideoCaptureOptions,
29
+ isBackupCodec,
29
30
  } from '../track/options';
30
- import { Track } from '../track/Track';
31
31
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
32
32
  import type { DataPublishOptions } from '../types';
33
33
  import { Future, isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
34
34
  import Participant from './Participant';
35
35
  import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
36
+ import RemoteParticipant from './RemoteParticipant';
36
37
  import {
37
38
  computeTrackBackupEncodings,
38
39
  computeVideoEncodings,
39
40
  mediaTrackToLocalTrack,
40
41
  } from './publishUtils';
41
- import RemoteParticipant from './RemoteParticipant';
42
42
 
43
43
  export default class LocalParticipant extends Participant {
44
44
  audioTracks: Map<string, LocalTrackPublication>;
@@ -148,6 +148,26 @@ export default class LocalParticipant extends Participant {
148
148
  this.reconnectFuture = undefined;
149
149
  };
150
150
 
151
+ /**
152
+ * Sets and updates the metadata of the local participant.
153
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
154
+ * @param metadata
155
+ */
156
+ setMetadata(metadata: string): void {
157
+ super.setMetadata(metadata);
158
+ this.engine.client.sendUpdateLocalMetadata(metadata, this.name ?? '');
159
+ }
160
+
161
+ /**
162
+ * Sets and updates the name of the local participant.
163
+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
164
+ * @param metadata
165
+ */
166
+ setName(name: string): void {
167
+ super.setName(name);
168
+ this.engine.client.sendUpdateLocalMetadata(this.metadata ?? '', name);
169
+ }
170
+
151
171
  /**
152
172
  * Enable or disable a participant's camera track.
153
173
  *
@@ -961,13 +981,15 @@ export default class LocalParticipant extends Participant {
961
981
  }
962
982
 
963
983
  /** @internal */
964
- updateInfo(info: ParticipantInfo) {
984
+ updateInfo(info: ParticipantInfo): boolean {
965
985
  if (info.sid !== this.sid) {
966
986
  // drop updates that specify a wrong sid.
967
987
  // the sid for local participant is only explicitly set on join and full reconnect
968
- return;
988
+ return false;
989
+ }
990
+ if (!super.updateInfo(info)) {
991
+ return false;
969
992
  }
970
- super.updateInfo(info);
971
993
 
972
994
  // reconcile track mute status.
973
995
  // if server's track mute status doesn't match actual, we'll have to update
@@ -986,6 +1008,7 @@ export default class LocalParticipant extends Participant {
986
1008
  }
987
1009
  }
988
1010
  });
1011
+ return true;
989
1012
  }
990
1013
 
991
1014
  private updateTrackSubscriptionPermissions = () => {
@@ -2,10 +2,10 @@ import { EventEmitter } from 'events';
2
2
  import type TypedEmitter from 'typed-emitter';
3
3
  import log from '../../logger';
4
4
  import {
5
- ConnectionQuality as ProtoQuality,
6
5
  DataPacket_Kind,
7
6
  ParticipantInfo,
8
7
  ParticipantPermission,
8
+ ConnectionQuality as ProtoQuality,
9
9
  } from '../../proto/livekit_models';
10
10
  import { ParticipantEvent, TrackEvent } from '../events';
11
11
  import type LocalTrackPublication from '../track/LocalTrackPublication';
@@ -144,10 +144,23 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
144
144
  }
145
145
 
146
146
  /** @internal */
147
- updateInfo(info: ParticipantInfo) {
147
+ updateInfo(info: ParticipantInfo): boolean {
148
+ // it's possible the update could be applied out of order due to await
149
+ // during reconnect sequences. when that happens, it's possible for server
150
+ // to have sent more recent version of participant info while JS is waiting
151
+ // to process the existing payload.
152
+ // when the participant sid remains the same, and we already have a later version
153
+ // of the payload, they can be safely skipped
154
+ if (
155
+ this.participantInfo &&
156
+ this.participantInfo.sid === info.sid &&
157
+ this.participantInfo.version > info.version
158
+ ) {
159
+ return false;
160
+ }
148
161
  this.identity = info.identity;
149
162
  this.sid = info.sid;
150
- this.name = info.name;
163
+ this.setName(info.name);
151
164
  this.setMetadata(info.metadata);
152
165
  if (info.permission) {
153
166
  this.setPermissions(info.permission);
@@ -155,6 +168,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
155
168
  // set this last so setMetadata can detect changes
156
169
  this.participantInfo = info;
157
170
  log.trace('update participant info', { info });
171
+ return true;
158
172
  }
159
173
 
160
174
  /** @internal */
@@ -168,6 +182,15 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
168
182
  }
169
183
  }
170
184
 
185
+ protected setName(name: string) {
186
+ const changed = this.name !== name;
187
+ this.name = name;
188
+
189
+ if (changed) {
190
+ this.emit(ParticipantEvent.ParticipantNameChanged, name);
191
+ }
192
+ }
193
+
171
194
  /** @internal */
172
195
  setPermissions(permissions: ParticipantPermission): boolean {
173
196
  const prevPermissions = this.permissions;
@@ -250,6 +273,7 @@ export type ParticipantEventCallbacks = {
250
273
  localTrackPublished: (publication: LocalTrackPublication) => void;
251
274
  localTrackUnpublished: (publication: LocalTrackPublication) => void;
252
275
  participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
276
+ participantNameChanged: (name: string) => void;
253
277
  dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
254
278
  isSpeakingChanged: (speaking: boolean) => void;
255
279
  connectionQualityChanged: (connectionQuality: ConnectionQuality) => void;
@@ -3,13 +3,13 @@ import log from '../../logger';
3
3
  import type { ParticipantInfo } from '../../proto/livekit_models';
4
4
  import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
5
5
  import { ParticipantEvent, TrackEvent } from '../events';
6
- import type { AudioOutputOptions } from '../track/options';
7
6
  import RemoteAudioTrack from '../track/RemoteAudioTrack';
8
7
  import type RemoteTrack from '../track/RemoteTrack';
9
8
  import RemoteTrackPublication from '../track/RemoteTrackPublication';
10
9
  import RemoteVideoTrack from '../track/RemoteVideoTrack';
11
10
  import { Track } from '../track/Track';
12
11
  import type { TrackPublication } from '../track/TrackPublication';
12
+ import type { AudioOutputOptions } from '../track/options';
13
13
  import type { AdaptiveStreamSettings } from '../track/types';
14
14
  import Participant, { ParticipantEventCallbacks } from './Participant';
15
15
 
@@ -215,8 +215,10 @@ export default class RemoteParticipant extends Participant {
215
215
  }
216
216
 
217
217
  /** @internal */
218
- updateInfo(info: ParticipantInfo) {
219
- super.updateInfo(info);
218
+ updateInfo(info: ParticipantInfo): boolean {
219
+ if (!super.updateInfo(info)) {
220
+ return false;
221
+ }
220
222
 
221
223
  // we are getting a list of all available tracks, reconcile in here
222
224
  // and send out events for changes
@@ -277,6 +279,7 @@ export default class RemoteParticipant extends Participant {
277
279
  newTracks.forEach((publication) => {
278
280
  this.emit(ParticipantEvent.TrackPublished, publication);
279
281
  });
282
+ return true;
280
283
  }
281
284
 
282
285
  /** @internal */
@@ -3,8 +3,8 @@ import {
3
3
  computeDefaultScreenShareSimulcastPresets,
4
4
  computeVideoEncodings,
5
5
  determineAppropriateEncoding,
6
- presets169,
7
6
  presets43,
7
+ presets169,
8
8
  presetsForResolution,
9
9
  presetsScreenShare,
10
10
  sortPresets,
@@ -2,6 +2,7 @@ import log from '../../logger';
2
2
  import { TrackInvalidError } from '../errors';
3
3
  import LocalAudioTrack from '../track/LocalAudioTrack';
4
4
  import LocalVideoTrack from '../track/LocalVideoTrack';
5
+ import { Track } from '../track/Track';
5
6
  import {
6
7
  BackupVideoCodec,
7
8
  ScreenSharePresets,
@@ -12,7 +13,6 @@ import {
12
13
  VideoPresets,
13
14
  VideoPresets43,
14
15
  } from '../track/options';
15
- import { Track } from '../track/Track';
16
16
 
17
17
  /** @internal */
18
18
  export function mediaTrackToLocalTrack(
@@ -3,8 +3,8 @@ import { TrackEvent } from '../events';
3
3
  import { AudioSenderStats, computeBitrate, monitorFrequency } from '../stats';
4
4
  import { isWeb } from '../utils';
5
5
  import LocalTrack from './LocalTrack';
6
- import type { AudioCaptureOptions } from './options';
7
6
  import { Track } from './Track';
7
+ import type { AudioCaptureOptions } from './options';
8
8
  import { constraintsForOptions, detectSilence } from './utils';
9
9
 
10
10
  export default class LocalAudioTrack extends LocalTrack {
@@ -39,7 +39,8 @@ export default class LocalAudioTrack extends LocalTrack {
39
39
  }
40
40
 
41
41
  async mute(): Promise<LocalAudioTrack> {
42
- await this.muteQueue.run(async () => {
42
+ const unlock = await this.muteLock.lock();
43
+ try {
43
44
  // disabled special handling as it will cause BT headsets to switch communication modes
44
45
  if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
45
46
  log.debug('stopping mic track');
@@ -47,12 +48,15 @@ export default class LocalAudioTrack extends LocalTrack {
47
48
  this._mediaStreamTrack.stop();
48
49
  }
49
50
  await super.mute();
50
- });
51
- return this;
51
+ return this;
52
+ } finally {
53
+ unlock();
54
+ }
52
55
  }
53
56
 
54
57
  async unmute(): Promise<LocalAudioTrack> {
55
- await this.muteQueue.run(async () => {
58
+ const unlock = await this.muteLock.lock();
59
+ try {
56
60
  if (
57
61
  this.source === Track.Source.Microphone &&
58
62
  (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended') &&
@@ -62,8 +66,11 @@ export default class LocalAudioTrack extends LocalTrack {
62
66
  await this.restartTrack();
63
67
  }
64
68
  await super.unmute();
65
- });
66
- return this;
69
+
70
+ return this;
71
+ } finally {
72
+ unlock();
73
+ }
67
74
  }
68
75
 
69
76
  async restartTrack(options?: AudioCaptureOptions) {
@@ -1,11 +1,16 @@
1
- import Queue from 'async-await-queue';
2
1
  import log from '../../logger';
3
2
  import DeviceManager from '../DeviceManager';
4
3
  import { TrackInvalidError } from '../errors';
5
4
  import { TrackEvent } from '../events';
6
- import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile, sleep } from '../utils';
5
+ import {
6
+ Mutex,
7
+ getEmptyAudioStreamTrack,
8
+ getEmptyVideoStreamTrack,
9
+ isMobile,
10
+ sleep,
11
+ } from '../utils';
12
+ import { Track, attachToElement, detachTrack } from './Track';
7
13
  import type { VideoCodec } from './options';
8
- import { attachToElement, detachTrack, Track } from './Track';
9
14
 
10
15
  const defaultDimensionsTimeout = 2 * 1000;
11
16
 
@@ -22,7 +27,9 @@ export default abstract class LocalTrack extends Track {
22
27
 
23
28
  protected providedByUser: boolean;
24
29
 
25
- protected muteQueue: Queue;
30
+ protected muteLock: Mutex;
31
+
32
+ protected pauseUpstreamLock: Mutex;
26
33
 
27
34
  /**
28
35
  *
@@ -42,7 +49,8 @@ export default abstract class LocalTrack extends Track {
42
49
  this.constraints = constraints ?? mediaTrack.getConstraints();
43
50
  this.reacquireTrack = false;
44
51
  this.providedByUser = userProvidedTrack;
45
- this.muteQueue = new Queue();
52
+ this.muteLock = new Mutex();
53
+ this.pauseUpstreamLock = new Mutex();
46
54
  }
47
55
 
48
56
  get id(): string {
@@ -246,7 +254,8 @@ export default abstract class LocalTrack extends Track {
246
254
  };
247
255
 
248
256
  async pauseUpstream() {
249
- this.muteQueue.run(async () => {
257
+ const unlock = await this.pauseUpstreamLock.lock();
258
+ try {
250
259
  if (this._isUpstreamPaused === true) {
251
260
  return;
252
261
  }
@@ -260,11 +269,14 @@ export default abstract class LocalTrack extends Track {
260
269
  const emptyTrack =
261
270
  this.kind === Track.Kind.Audio ? getEmptyAudioStreamTrack() : getEmptyVideoStreamTrack();
262
271
  await this.sender.replaceTrack(emptyTrack);
263
- });
272
+ } finally {
273
+ unlock();
274
+ }
264
275
  }
265
276
 
266
277
  async resumeUpstream() {
267
- this.muteQueue.run(async () => {
278
+ const unlock = await this.pauseUpstreamLock.lock();
279
+ try {
268
280
  if (this._isUpstreamPaused === false) {
269
281
  return;
270
282
  }
@@ -276,7 +288,9 @@ export default abstract class LocalTrack extends Track {
276
288
  this.emit(TrackEvent.UpstreamResumed, this);
277
289
 
278
290
  await this.sender.replaceTrack(this._mediaStreamTrack);
279
- });
291
+ } finally {
292
+ unlock();
293
+ }
280
294
  }
281
295
 
282
296
  protected abstract monitorSender(): void;
@@ -3,9 +3,9 @@ import { TrackEvent } from '../events';
3
3
  import type LocalAudioTrack from './LocalAudioTrack';
4
4
  import type LocalTrack from './LocalTrack';
5
5
  import type LocalVideoTrack from './LocalVideoTrack';
6
- import type { TrackPublishOptions } from './options';
7
6
  import type { Track } from './Track';
8
7
  import { TrackPublication } from './TrackPublication';
8
+ import type { TrackPublishOptions } from './options';
9
9
 
10
10
  export default class LocalTrackPublication extends TrackPublication {
11
11
  track?: LocalTrack = undefined;
@@ -2,11 +2,11 @@ import type { SignalClient } from '../../api/SignalClient';
2
2
  import log from '../../logger';
3
3
  import { VideoLayer, VideoQuality } from '../../proto/livekit_models';
4
4
  import type { SubscribedCodec, SubscribedQuality } from '../../proto/livekit_rtc';
5
- import { computeBitrate, monitorFrequency, VideoSenderStats } from '../stats';
6
- import { isFireFox, isMobile, isWeb, Mutex } from '../utils';
5
+ import { VideoSenderStats, computeBitrate, monitorFrequency } from '../stats';
6
+ import { Mutex, isFireFox, isMobile, isWeb } from '../utils';
7
7
  import LocalTrack from './LocalTrack';
8
- import type { VideoCaptureOptions, VideoCodec } from './options';
9
8
  import { Track } from './Track';
9
+ import type { VideoCaptureOptions, VideoCodec } from './options';
10
10
  import { constraintsForOptions } from './utils';
11
11
 
12
12
  export class SimulcastTrackInfo {
@@ -97,26 +97,32 @@ export default class LocalVideoTrack extends LocalTrack {
97
97
  }
98
98
 
99
99
  async mute(): Promise<LocalVideoTrack> {
100
- await this.muteQueue.run(async () => {
100
+ const unlock = await this.muteLock.lock();
101
+ try {
101
102
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
102
103
  log.debug('stopping camera track');
103
104
  // also stop the track, so that camera indicator is turned off
104
105
  this._mediaStreamTrack.stop();
105
106
  }
106
107
  await super.mute();
107
- });
108
- return this;
108
+ return this;
109
+ } finally {
110
+ unlock();
111
+ }
109
112
  }
110
113
 
111
114
  async unmute(): Promise<LocalVideoTrack> {
112
- await this.muteQueue.run(async () => {
115
+ const unlock = await this.muteLock.lock();
116
+ try {
113
117
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
114
118
  log.debug('reacquiring camera track');
115
119
  await this.restartTrack();
116
120
  }
117
121
  await super.unmute();
118
- });
119
- return this;
122
+ return this;
123
+ } finally {
124
+ unlock();
125
+ }
120
126
  }
121
127
 
122
128
  async getSenderStats(): Promise<VideoSenderStats[]> {
@@ -2,9 +2,9 @@ import log from '../../logger';
2
2
  import { TrackEvent } from '../events';
3
3
  import { AudioReceiverStats, computeBitrate } from '../stats';
4
4
  import { supportsSetSinkId } from '../utils';
5
- import type { AudioOutputOptions } from './options';
6
5
  import RemoteTrack from './RemoteTrack';
7
6
  import { Track } from './Track';
7
+ import type { AudioOutputOptions } from './options';
8
8
 
9
9
  export default class RemoteAudioTrack extends RemoteTrack {
10
10
  private prevStats?: AudioReceiverStats;
@@ -4,7 +4,7 @@ import { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc
4
4
  import { TrackEvent } from '../events';
5
5
  import type RemoteTrack from './RemoteTrack';
6
6
  import RemoteVideoTrack from './RemoteVideoTrack';
7
- import type { Track } from './Track';
7
+ import { Track } from './Track';
8
8
  import { TrackPublication } from './TrackPublication';
9
9
 
10
10
  export default class RemoteTrackPublication extends TrackPublication {
@@ -181,6 +181,7 @@ export default class RemoteTrackPublication extends TrackPublication {
181
181
  prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
182
182
  prevTrack.off(TrackEvent.Ended, this.handleEnded);
183
183
  prevTrack.detach();
184
+ prevTrack.stopMonitor();
184
185
  this.emit(TrackEvent.Unsubscribed, prevTrack);
185
186
  }
186
187
  super.setTrack(track);
@@ -238,8 +239,8 @@ export default class RemoteTrackPublication extends TrackPublication {
238
239
  }
239
240
 
240
241
  private isManualOperationAllowed(): boolean {
241
- if (this.isAdaptiveStream) {
242
- log.warn('adaptive stream is enabled, cannot change track settings', {
242
+ if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
243
+ log.warn('adaptive stream is enabled, cannot change video track settings', {
243
244
  trackSid: this.trackSid,
244
245
  });
245
246
  return false;
@@ -1,6 +1,6 @@
1
+ import MockMediaStreamTrack from '../../test/MockMediaStreamTrack';
1
2
  import { TrackEvent } from '../events';
2
3
  import RemoteVideoTrack, { ElementInfo } from './RemoteVideoTrack';
3
- import MockMediaStreamTrack from '../../test/MockMediaStreamTrack';
4
4
  import type { Track } from './Track';
5
5
 
6
6
  jest.useFakeTimers();
@@ -1,16 +1,17 @@
1
1
  import { debounce } from 'ts-debounce';
2
2
  import log from '../../logger';
3
3
  import { TrackEvent } from '../events';
4
- import { computeBitrate, VideoReceiverStats } from '../stats';
4
+ import { VideoReceiverStats, computeBitrate } from '../stats';
5
5
  import CriticalTimers from '../timers';
6
6
  import {
7
+ ObservableMediaElement,
8
+ getDevicePixelRatio,
7
9
  getIntersectionObserver,
8
10
  getResizeObserver,
9
11
  isWeb,
10
- ObservableMediaElement,
11
12
  } from '../utils';
12
13
  import RemoteTrack from './RemoteTrack';
13
- import { attachToElement, detachTrack, Track } from './Track';
14
+ import { Track, attachToElement, detachTrack } from './Track';
14
15
  import type { AdaptiveStreamSettings } from './types';
15
16
 
16
17
  const REACTION_DELAY = 100;
@@ -26,7 +27,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
26
27
 
27
28
  private lastDimensions?: Track.Dimensions;
28
29
 
29
- private hasUsedAttach: boolean = false;
30
+ private isObserved: boolean = false;
30
31
 
31
32
  constructor(
32
33
  mediaTrack: MediaStreamTrack,
@@ -43,7 +44,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
43
44
  }
44
45
 
45
46
  get mediaStreamTrack() {
46
- if (this.isAdaptiveStream && !this.hasUsedAttach) {
47
+ if (this.isAdaptiveStream && !this.isObserved) {
47
48
  log.warn(
48
49
  'When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start',
49
50
  );
@@ -83,7 +84,6 @@ export default class RemoteVideoTrack extends RemoteTrack {
83
84
  const elementInfo = new HTMLElementInfo(element);
84
85
  this.observeElementInfo(elementInfo);
85
86
  }
86
- this.hasUsedAttach = true;
87
87
  return element;
88
88
  }
89
89
 
@@ -110,6 +110,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
110
110
  // the tab comes into focus for the first time.
111
111
  this.debouncedHandleResize();
112
112
  this.updateVisibility();
113
+ this.isObserved = true;
113
114
  } else {
114
115
  log.warn('visibility resize observer not triggered');
115
116
  }
@@ -253,7 +254,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
253
254
  let maxHeight = 0;
254
255
  for (const info of this.elementInfos) {
255
256
  const pixelDensity = this.adaptiveStreamSettings?.pixelDensity ?? 1;
256
- const pixelDensityValue = pixelDensity === 'screen' ? window.devicePixelRatio : pixelDensity;
257
+ const pixelDensityValue = pixelDensity === 'screen' ? getDevicePixelRatio() : pixelDensity;
257
258
  const currentElementWidth = info.width() * pixelDensityValue;
258
259
  const currentElementHeight = info.height() * pixelDensityValue;
259
260
  if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {