livekit-client 1.12.2 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) 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 +83 -9
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +2239 -1975
  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 +2 -5
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -1
  13. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +5 -0
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  16. package/dist/src/e2ee/KeyProvider.d.ts +4 -2
  17. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  18. package/dist/src/e2ee/constants.d.ts +2 -0
  19. package/dist/src/e2ee/constants.d.ts.map +1 -1
  20. package/dist/src/e2ee/types.d.ts +7 -1
  21. package/dist/src/e2ee/types.d.ts.map +1 -1
  22. package/dist/src/e2ee/utils.d.ts +1 -0
  23. package/dist/src/e2ee/utils.d.ts.map +1 -1
  24. package/dist/src/e2ee/worker/FrameCryptor.d.ts +4 -2
  25. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  26. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  27. package/dist/src/e2ee/worker/SifGuard.d.ts +11 -0
  28. package/dist/src/e2ee/worker/SifGuard.d.ts.map +1 -0
  29. package/dist/src/options.d.ts +5 -0
  30. package/dist/src/options.d.ts.map +1 -1
  31. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  32. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
  33. package/dist/src/room/DeviceManager.d.ts +1 -0
  34. package/dist/src/room/DeviceManager.d.ts.map +1 -1
  35. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  36. package/dist/src/room/Room.d.ts +1 -1
  37. package/dist/src/room/Room.d.ts.map +1 -1
  38. package/dist/src/room/defaults.d.ts.map +1 -1
  39. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  40. package/dist/src/room/participant/Participant.d.ts +5 -0
  41. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  42. package/dist/src/room/participant/RemoteParticipant.d.ts +0 -5
  43. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  44. package/dist/src/room/participant/publishUtils.d.ts +1 -1
  45. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  46. package/dist/src/room/timers.d.ts +2 -2
  47. package/dist/src/room/timers.d.ts.map +1 -1
  48. package/dist/src/room/track/LocalAudioTrack.d.ts +9 -1
  49. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  50. package/dist/src/room/track/LocalTrack.d.ts +3 -3
  51. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  52. package/dist/src/room/track/LocalVideoTrack.d.ts +6 -0
  53. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  54. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  55. package/dist/src/room/track/options.d.ts +1 -0
  56. package/dist/src/room/track/options.d.ts.map +1 -1
  57. package/dist/src/room/track/processor/types.d.ts +13 -2
  58. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  59. package/dist/src/room/types.d.ts +1 -1
  60. package/dist/src/room/types.d.ts.map +1 -1
  61. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -5
  62. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +5 -0
  63. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +4 -2
  64. package/dist/ts4.2/src/e2ee/constants.d.ts +2 -0
  65. package/dist/ts4.2/src/e2ee/types.d.ts +7 -1
  66. package/dist/ts4.2/src/e2ee/utils.d.ts +1 -0
  67. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +4 -2
  68. package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +11 -0
  69. package/dist/ts4.2/src/options.d.ts +5 -0
  70. package/dist/ts4.2/src/room/DeviceManager.d.ts +1 -0
  71. package/dist/ts4.2/src/room/Room.d.ts +1 -1
  72. package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -0
  73. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +0 -5
  74. package/dist/ts4.2/src/room/participant/publishUtils.d.ts +1 -1
  75. package/dist/ts4.2/src/room/timers.d.ts +2 -2
  76. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +9 -1
  77. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
  78. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +6 -0
  79. package/dist/ts4.2/src/room/track/options.d.ts +1 -0
  80. package/dist/ts4.2/src/room/track/processor/types.d.ts +13 -2
  81. package/dist/ts4.2/src/room/types.d.ts +1 -1
  82. package/package.json +15 -16
  83. package/src/api/SignalClient.ts +13 -9
  84. package/src/connectionHelper/checks/turn.ts +1 -0
  85. package/src/connectionHelper/checks/webrtc.ts +9 -7
  86. package/src/connectionHelper/checks/websocket.ts +1 -0
  87. package/src/e2ee/E2eeManager.ts +27 -2
  88. package/src/e2ee/KeyProvider.ts +9 -4
  89. package/src/e2ee/constants.ts +3 -0
  90. package/src/e2ee/types.ts +9 -1
  91. package/src/e2ee/utils.ts +9 -0
  92. package/src/e2ee/worker/FrameCryptor.ts +46 -17
  93. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -0
  94. package/src/e2ee/worker/SifGuard.ts +47 -0
  95. package/src/e2ee/worker/e2ee.worker.ts +14 -0
  96. package/src/options.ts +6 -0
  97. package/src/proto/livekit_models_pb.ts +14 -0
  98. package/src/proto/livekit_rtc_pb.ts +14 -0
  99. package/src/room/DeviceManager.ts +7 -2
  100. package/src/room/RTCEngine.ts +7 -5
  101. package/src/room/Room.ts +27 -7
  102. package/src/room/defaults.ts +1 -0
  103. package/src/room/participant/LocalParticipant.ts +14 -2
  104. package/src/room/participant/Participant.ts +16 -0
  105. package/src/room/participant/RemoteParticipant.ts +0 -12
  106. package/src/room/participant/publishUtils.ts +4 -2
  107. package/src/room/track/LocalAudioTrack.ts +45 -0
  108. package/src/room/track/LocalTrack.ts +4 -4
  109. package/src/room/track/LocalVideoTrack.ts +49 -8
  110. package/src/room/track/RemoteAudioTrack.ts +9 -1
  111. package/src/room/track/RemoteTrackPublication.ts +2 -2
  112. package/src/room/track/options.ts +17 -16
  113. package/src/room/track/processor/types.ts +17 -2
  114. package/src/room/types.ts +5 -1
@@ -3465,10 +3465,19 @@ Timestamp.fields = proto3.util.newFieldList(() => [{
3465
3465
  T: 5 /* ScalarType.INT32 */
3466
3466
  }]);
3467
3467
 
3468
- // @generated by protoc-gen-es v1.3.0 with parameter "target=ts"
3469
- // @generated from file livekit_models.proto (package livekit, syntax proto3)
3470
- /* eslint-disable */
3471
- // @ts-nocheck
3468
+ // Copyright 2023 LiveKit, Inc.
3469
+ //
3470
+ // Licensed under the Apache License, Version 2.0 (the "License");
3471
+ // you may not use this file except in compliance with the License.
3472
+ // You may obtain a copy of the License at
3473
+ //
3474
+ // http://www.apache.org/licenses/LICENSE-2.0
3475
+ //
3476
+ // Unless required by applicable law or agreed to in writing, software
3477
+ // distributed under the License is distributed on an "AS IS" BASIS,
3478
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3479
+ // See the License for the specific language governing permissions and
3480
+ // limitations under the License.
3472
3481
  /**
3473
3482
  * @generated from enum livekit.AudioCodec
3474
3483
  */
@@ -6447,7 +6456,7 @@ function detectBrowser(window) {
6447
6456
  };
6448
6457
 
6449
6458
  // Fail early if it's not a browser
6450
- if (typeof window === 'undefined' || !window.navigator) {
6459
+ if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) {
6451
6460
  result.browser = 'Not a browser.';
6452
6461
  return result;
6453
6462
  }
@@ -9424,10 +9433,19 @@ adapterFactory({
9424
9433
  window: typeof window === 'undefined' ? undefined : window
9425
9434
  });
9426
9435
 
9427
- // @generated by protoc-gen-es v1.3.0 with parameter "target=ts"
9428
- // @generated from file livekit_rtc.proto (package livekit, syntax proto3)
9429
- /* eslint-disable */
9430
- // @ts-nocheck
9436
+ // Copyright 2023 LiveKit, Inc.
9437
+ //
9438
+ // Licensed under the Apache License, Version 2.0 (the "License");
9439
+ // you may not use this file except in compliance with the License.
9440
+ // You may obtain a copy of the License at
9441
+ //
9442
+ // http://www.apache.org/licenses/LICENSE-2.0
9443
+ //
9444
+ // Unless required by applicable law or agreed to in writing, software
9445
+ // distributed under the License is distributed on an "AS IS" BASIS,
9446
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9447
+ // See the License for the specific language governing permissions and
9448
+ // limitations under the License.
9431
9449
  /**
9432
9450
  * @generated from enum livekit.SignalTarget
9433
9451
  */
@@ -11823,7 +11841,7 @@ function getMatch(exp, ua) {
11823
11841
  return match && match.length >= id && match[id] || '';
11824
11842
  }
11825
11843
 
11826
- var version$1 = "1.12.2";
11844
+ var version$1 = "1.13.0";
11827
11845
 
11828
11846
  const version = version$1;
11829
11847
  const protocolVersion = 9;
@@ -11880,11 +11898,11 @@ var AudioPresets;
11880
11898
  * Sane presets for video resolution/encoding
11881
11899
  */
11882
11900
  const VideoPresets = {
11883
- h90: new VideoPreset(160, 90, 60000, 15),
11884
- h180: new VideoPreset(320, 180, 120000, 15),
11885
- h216: new VideoPreset(384, 216, 180000, 15),
11886
- h360: new VideoPreset(640, 360, 300000, 20),
11887
- h540: new VideoPreset(960, 540, 600000, 25),
11901
+ h90: new VideoPreset(160, 90, 90000, 20),
11902
+ h180: new VideoPreset(320, 180, 160000, 20),
11903
+ h216: new VideoPreset(384, 216, 180000, 20),
11904
+ h360: new VideoPreset(640, 360, 450000, 20),
11905
+ h540: new VideoPreset(960, 540, 800000, 25),
11888
11906
  h720: new VideoPreset(1280, 720, 1700000, 30),
11889
11907
  h1080: new VideoPreset(1920, 1080, 3000000, 30),
11890
11908
  h1440: new VideoPreset(2560, 1440, 5000000, 30),
@@ -11894,22 +11912,23 @@ const VideoPresets = {
11894
11912
  * Four by three presets
11895
11913
  */
11896
11914
  const VideoPresets43 = {
11897
- h120: new VideoPreset(160, 120, 80000, 15),
11898
- h180: new VideoPreset(240, 180, 100000, 15),
11899
- h240: new VideoPreset(320, 240, 150000, 15),
11915
+ h120: new VideoPreset(160, 120, 70000, 20),
11916
+ h180: new VideoPreset(240, 180, 125000, 20),
11917
+ h240: new VideoPreset(320, 240, 140000, 20),
11900
11918
  h360: new VideoPreset(480, 360, 225000, 20),
11901
- h480: new VideoPreset(640, 480, 300000, 20),
11902
- h540: new VideoPreset(720, 540, 450000, 25),
11903
- h720: new VideoPreset(960, 720, 1500000, 30),
11904
- h1080: new VideoPreset(1440, 1080, 2500000, 30),
11905
- h1440: new VideoPreset(1920, 1440, 3500000, 30)
11919
+ h480: new VideoPreset(640, 480, 500000, 20),
11920
+ h540: new VideoPreset(720, 540, 600000, 25),
11921
+ h720: new VideoPreset(960, 720, 1300000, 30),
11922
+ h1080: new VideoPreset(1440, 1080, 2300000, 30),
11923
+ h1440: new VideoPreset(1920, 1440, 3800000, 30)
11906
11924
  };
11907
11925
  const ScreenSharePresets = {
11908
11926
  h360fps3: new VideoPreset(640, 360, 200000, 3, 'medium'),
11909
11927
  h720fps5: new VideoPreset(1280, 720, 400000, 5, 'medium'),
11910
- h720fps15: new VideoPreset(1280, 720, 1000000, 15, 'medium'),
11911
- h1080fps15: new VideoPreset(1920, 1080, 1500000, 15, 'medium'),
11912
- h1080fps30: new VideoPreset(1920, 1080, 3000000, 30, 'medium')
11928
+ h720fps15: new VideoPreset(1280, 720, 1500000, 15, 'medium'),
11929
+ h720fps30: new VideoPreset(1280, 720, 2000000, 30, 'medium'),
11930
+ h1080fps15: new VideoPreset(1920, 1080, 2500000, 15, 'medium'),
11931
+ h1080fps30: new VideoPreset(1920, 1080, 4000000, 30, 'medium')
11913
11932
  };
11914
11933
 
11915
11934
  /**
@@ -13379,8 +13398,13 @@ class SignalClient {
13379
13398
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
13380
13399
  const abortHandler = () => __awaiter(this, void 0, void 0, function* () {
13381
13400
  this.close();
13401
+ clearTimeout(wsTimeout);
13382
13402
  reject(new ConnectionError('room connection has been cancelled (signal)'));
13383
13403
  });
13404
+ const wsTimeout = setTimeout(() => {
13405
+ this.close();
13406
+ reject(new ConnectionError('room connection has timed out (signal)'));
13407
+ }, opts.websocketTimeout);
13384
13408
  if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
13385
13409
  abortHandler();
13386
13410
  }
@@ -13391,8 +13415,12 @@ class SignalClient {
13391
13415
  }
13392
13416
  this.ws = new WebSocket(url + params);
13393
13417
  this.ws.binaryType = 'arraybuffer';
13418
+ this.ws.onopen = () => {
13419
+ clearTimeout(wsTimeout);
13420
+ };
13394
13421
  this.ws.onerror = ev => __awaiter(this, void 0, void 0, function* () {
13395
13422
  if (!this.isConnected) {
13423
+ clearTimeout(wsTimeout);
13396
13424
  try {
13397
13425
  const resp = yield fetch("http".concat(url.substring(2), "/validate").concat(params));
13398
13426
  if (resp.status.toFixed(0).startsWith('4')) {
@@ -13922,6 +13950,12 @@ function createKeyMaterialFromString(password) {
13922
13950
  return keyMaterial;
13923
13951
  });
13924
13952
  }
13953
+ function createKeyMaterialFromBuffer(cryptoBuffer) {
13954
+ return __awaiter(this, void 0, void 0, function* () {
13955
+ const keyMaterial = yield crypto.subtle.importKey('raw', cryptoBuffer, 'HKDF', false, ['deriveBits', 'deriveKey']);
13956
+ return keyMaterial;
13957
+ });
13958
+ }
13925
13959
  function getAlgoOptions(algorithmName, salt) {
13926
13960
  const textEncoder = new TextEncoder();
13927
13961
  const encodedSalt = textEncoder.encode(salt);
@@ -14046,12 +14080,14 @@ class ExternalE2EEKeyProvider extends BaseKeyProvider {
14046
14080
  super(opts);
14047
14081
  }
14048
14082
  /**
14049
- * Accepts a passphrase that's used to create the crypto keys
14083
+ * Accepts a passphrase that's used to create the crypto keys.
14084
+ * When passing in a string, PBKDF2 is used.
14085
+ * Also accepts an Array buffer of cryptographically random numbers that uses HKDF.
14050
14086
  * @param key
14051
14087
  */
14052
14088
  setKey(key) {
14053
14089
  return __awaiter(this, void 0, void 0, function* () {
14054
- const derivedKey = yield createKeyMaterialFromString(key);
14090
+ const derivedKey = typeof key === 'string' ? yield createKeyMaterialFromString(key) : yield createKeyMaterialFromBuffer(key);
14055
14091
  this.onSetEncryptionKey(derivedKey);
14056
14092
  });
14057
14093
  }
@@ -14134,9 +14170,9 @@ class DeviceManager {
14134
14170
  }
14135
14171
  }
14136
14172
  let devices = yield navigator.mediaDevices.enumerateDevices();
14137
- if (requestPermissions && kind && (
14173
+ if (requestPermissions &&
14138
14174
  // for safari we need to skip this check, as otherwise it will re-acquire user media and fail on iOS https://bugs.webkit.org/show_bug.cgi?id=179363
14139
- !DeviceManager.userMediaPromiseMap.get(kind) || !isSafari())) {
14175
+ !(isSafari() && this.hasDeviceInUse(kind))) {
14140
14176
  const isDummyDeviceOrEmpty = devices.length === 0 || devices.some(device => {
14141
14177
  const noLabel = device.label === '';
14142
14178
  const isRelevant = kind ? device.kind === kind : true;
@@ -14172,6 +14208,9 @@ class DeviceManager {
14172
14208
  return device === null || device === void 0 ? void 0 : device.deviceId;
14173
14209
  });
14174
14210
  }
14211
+ hasDeviceInUse(kind) {
14212
+ return kind ? DeviceManager.userMediaPromiseMap.has(kind) : DeviceManager.userMediaPromiseMap.size > 0;
14213
+ }
14175
14214
  }
14176
14215
  DeviceManager.mediaDeviceKinds = ['audioinput', 'audiooutput', 'videoinput'];
14177
14216
  DeviceManager.userMediaPromiseMap = new Map();
@@ -14208,52 +14247,6 @@ class LocalTrack extends Track {
14208
14247
  this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent);
14209
14248
  this.emit(TrackEvent.Ended, this);
14210
14249
  };
14211
- /**
14212
- * pauses publishing to the server without disabling the local MediaStreamTrack
14213
- * this is used to display a user's own video locally while pausing publishing to
14214
- * the server.
14215
- * this API is unsupported on Safari < 12 due to a bug
14216
- **/
14217
- this.pauseUpstream = () => __awaiter(this, void 0, void 0, function* () {
14218
- const unlock = yield this.pauseUpstreamLock.lock();
14219
- try {
14220
- if (this._isUpstreamPaused === true) {
14221
- return;
14222
- }
14223
- if (!this.sender) {
14224
- livekitLogger.warn('unable to pause upstream for an unpublished track');
14225
- return;
14226
- }
14227
- this._isUpstreamPaused = true;
14228
- this.emit(TrackEvent.UpstreamPaused, this);
14229
- const browser = getBrowser();
14230
- if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
14231
- // https://bugs.webkit.org/show_bug.cgi?id=184911
14232
- throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
14233
- }
14234
- yield this.sender.replaceTrack(null);
14235
- } finally {
14236
- unlock();
14237
- }
14238
- });
14239
- this.resumeUpstream = () => __awaiter(this, void 0, void 0, function* () {
14240
- const unlock = yield this.pauseUpstreamLock.lock();
14241
- try {
14242
- if (this._isUpstreamPaused === false) {
14243
- return;
14244
- }
14245
- if (!this.sender) {
14246
- livekitLogger.warn('unable to resume upstream for an unpublished track');
14247
- return;
14248
- }
14249
- this._isUpstreamPaused = false;
14250
- this.emit(TrackEvent.UpstreamResumed, this);
14251
- // this operation is noop if mediastreamtrack is already being sent
14252
- yield this.sender.replaceTrack(this._mediaStreamTrack);
14253
- } finally {
14254
- unlock();
14255
- }
14256
- });
14257
14250
  this.reacquireTrack = false;
14258
14251
  this.providedByUser = userProvidedTrack;
14259
14252
  this.muteLock = new Mutex();
@@ -14484,6 +14477,56 @@ class LocalTrack extends Track {
14484
14477
  (_a = this.processor) === null || _a === void 0 ? void 0 : _a.destroy();
14485
14478
  this.processor = undefined;
14486
14479
  }
14480
+ /**
14481
+ * pauses publishing to the server without disabling the local MediaStreamTrack
14482
+ * this is used to display a user's own video locally while pausing publishing to
14483
+ * the server.
14484
+ * this API is unsupported on Safari < 12 due to a bug
14485
+ **/
14486
+ pauseUpstream() {
14487
+ return __awaiter(this, void 0, void 0, function* () {
14488
+ const unlock = yield this.pauseUpstreamLock.lock();
14489
+ try {
14490
+ if (this._isUpstreamPaused === true) {
14491
+ return;
14492
+ }
14493
+ if (!this.sender) {
14494
+ livekitLogger.warn('unable to pause upstream for an unpublished track');
14495
+ return;
14496
+ }
14497
+ this._isUpstreamPaused = true;
14498
+ this.emit(TrackEvent.UpstreamPaused, this);
14499
+ const browser = getBrowser();
14500
+ if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
14501
+ // https://bugs.webkit.org/show_bug.cgi?id=184911
14502
+ throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
14503
+ }
14504
+ yield this.sender.replaceTrack(null);
14505
+ } finally {
14506
+ unlock();
14507
+ }
14508
+ });
14509
+ }
14510
+ resumeUpstream() {
14511
+ return __awaiter(this, void 0, void 0, function* () {
14512
+ const unlock = yield this.pauseUpstreamLock.lock();
14513
+ try {
14514
+ if (this._isUpstreamPaused === false) {
14515
+ return;
14516
+ }
14517
+ if (!this.sender) {
14518
+ livekitLogger.warn('unable to resume upstream for an unpublished track');
14519
+ return;
14520
+ }
14521
+ this._isUpstreamPaused = false;
14522
+ this.emit(TrackEvent.UpstreamResumed, this);
14523
+ // this operation is noop if mediastreamtrack is already being sent
14524
+ yield this.sender.replaceTrack(this._mediaStreamTrack);
14525
+ } finally {
14526
+ unlock();
14527
+ }
14528
+ });
14529
+ }
14487
14530
  /**
14488
14531
  * Sets a processor on this track.
14489
14532
  * See https://github.com/livekit/track-processors-js for example usage
@@ -14658,6 +14701,16 @@ class E2EEManager extends eventsExports.EventEmitter {
14658
14701
  }
14659
14702
  });
14660
14703
  }
14704
+ /**
14705
+ * @internal
14706
+ */
14707
+ setSifTrailer(trailer) {
14708
+ if (!trailer || trailer.length === 0) {
14709
+ livekitLogger.warn("ignoring server sent trailer as it's empty");
14710
+ } else {
14711
+ this.postSifTrailer(trailer);
14712
+ }
14713
+ }
14661
14714
  setupEngine(engine) {
14662
14715
  engine.on(EngineEvent.RTPVideoMapUpdate, rtpMap => {
14663
14716
  this.postRTPMap(rtpMap);
@@ -14737,6 +14790,18 @@ class E2EEManager extends eventsExports.EventEmitter {
14737
14790
  };
14738
14791
  this.worker.postMessage(msg);
14739
14792
  }
14793
+ postSifTrailer(trailer) {
14794
+ if (!this.worker) {
14795
+ throw Error('could not post SIF trailer, worker is missing');
14796
+ }
14797
+ const msg = {
14798
+ kind: 'setSifTrailer',
14799
+ data: {
14800
+ trailer
14801
+ }
14802
+ };
14803
+ this.worker.postMessage(msg);
14804
+ }
14740
14805
  setupE2EEReceiver(track, remoteId, trackInfo) {
14741
14806
  if (!track.receiver) {
14742
14807
  return;
@@ -14826,7 +14891,7 @@ class E2EEManager extends eventsExports.EventEmitter {
14826
14891
  return;
14827
14892
  }
14828
14893
  if (isScriptTransformSupported()) {
14829
- livekitLogger.warn('initialize script transform');
14894
+ livekitLogger.info('initialize script transform');
14830
14895
  const options = {
14831
14896
  kind: 'encode',
14832
14897
  participantId,
@@ -14836,7 +14901,7 @@ class E2EEManager extends eventsExports.EventEmitter {
14836
14901
  // @ts-ignore
14837
14902
  sender.transform = new RTCRtpScriptTransform(this.worker, options);
14838
14903
  } else {
14839
- livekitLogger.warn('initialize encoded streams');
14904
+ livekitLogger.info('initialize encoded streams');
14840
14905
  // @ts-ignore
14841
14906
  const senderStreams = sender.createEncodedStreams();
14842
14907
  const msg = {
@@ -15940,7 +16005,8 @@ const roomOptionDefaults = {
15940
16005
  const roomConnectOptionDefaults = {
15941
16006
  autoSubscribe: true,
15942
16007
  maxRetries: 1,
15943
- peerConnectionTimeout: 15000
16008
+ peerConnectionTimeout: 15000,
16009
+ websocketTimeout: 15000
15944
16010
  };
15945
16011
 
15946
16012
  const lossyDataChannel = '_lossy';
@@ -16029,13 +16095,13 @@ class RTCEngine extends eventsExports.EventEmitter {
16029
16095
  this.handleDataError = event => {
16030
16096
  const channel = event.currentTarget;
16031
16097
  const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
16032
- if (event instanceof ErrorEvent) {
16098
+ if (event instanceof ErrorEvent && event.error) {
16033
16099
  const {
16034
16100
  error
16035
16101
  } = event.error;
16036
16102
  livekitLogger.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), error);
16037
16103
  } else {
16038
- livekitLogger.error("Unknown DataChannel Error on ".concat(channelKind), event);
16104
+ livekitLogger.error("Unknown DataChannel error on ".concat(channelKind), event);
16039
16105
  }
16040
16106
  };
16041
16107
  this.handleBufferedAmountLow = event => {
@@ -16138,8 +16204,10 @@ class RTCEngine extends eventsExports.EventEmitter {
16138
16204
  this.url = url;
16139
16205
  this.token = token;
16140
16206
  this.signalOpts = opts;
16207
+ this.maxJoinAttempts = opts.maxRetries;
16141
16208
  try {
16142
16209
  this.joinAttempts += 1;
16210
+ this.setupSignalClientCallbacks();
16143
16211
  const joinResponse = yield this.client.join(url, token, opts, abortSignal);
16144
16212
  this._isClosed = false;
16145
16213
  this.latestJoinResponse = joinResponse;
@@ -16151,7 +16219,6 @@ class RTCEngine extends eventsExports.EventEmitter {
16151
16219
  if (!this.subscriberPrimary) {
16152
16220
  this.negotiate();
16153
16221
  }
16154
- this.setupSignalClientCallbacks();
16155
16222
  this.clientConfiguration = joinResponse.clientConfiguration;
16156
16223
  return joinResponse;
16157
16224
  } catch (e) {
@@ -16423,13 +16490,13 @@ class RTCEngine extends eventsExports.EventEmitter {
16423
16490
  });
16424
16491
  this.client.onLocalTrackPublished = res => {
16425
16492
  livekitLogger.debug('received trackPublishedResponse', res);
16426
- const {
16427
- resolve
16428
- } = this.pendingTrackResolvers[res.cid];
16429
- if (!resolve) {
16493
+ if (!this.pendingTrackResolvers[res.cid]) {
16430
16494
  livekitLogger.error("missing track resolver for ".concat(res.cid));
16431
16495
  return;
16432
16496
  }
16497
+ const {
16498
+ resolve
16499
+ } = this.pendingTrackResolvers[res.cid];
16433
16500
  delete this.pendingTrackResolvers[res.cid];
16434
16501
  resolve(res.track);
16435
16502
  };
@@ -17156,6 +17223,7 @@ class LocalAudioTrack extends LocalTrack {
17156
17223
  */
17157
17224
  constructor(mediaTrack, constraints) {
17158
17225
  let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
17226
+ let audioContext = arguments.length > 3 ? arguments[3] : undefined;
17159
17227
  super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack);
17160
17228
  /** @internal */
17161
17229
  this.stopOnMute = false;
@@ -17178,6 +17246,7 @@ class LocalAudioTrack extends LocalTrack {
17178
17246
  }
17179
17247
  this.prevStats = stats;
17180
17248
  });
17249
+ this.audioContext = audioContext;
17181
17250
  this.checkForSilence();
17182
17251
  }
17183
17252
  setDeviceId(deviceId) {
@@ -17273,6 +17342,43 @@ class LocalAudioTrack extends LocalTrack {
17273
17342
  this.monitorSender();
17274
17343
  }, monitorFrequency);
17275
17344
  }
17345
+ setProcessor(processor) {
17346
+ var _a;
17347
+ return __awaiter(this, void 0, void 0, function* () {
17348
+ const unlock = yield this.processorLock.lock();
17349
+ try {
17350
+ if (!this.audioContext) {
17351
+ throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors');
17352
+ }
17353
+ if (this.processor) {
17354
+ yield this.stopProcessor();
17355
+ }
17356
+ if (this.kind === 'unknown') {
17357
+ throw TypeError('cannot set processor on track of unknown kind');
17358
+ }
17359
+ const processorOptions = {
17360
+ kind: this.kind,
17361
+ track: this._mediaStreamTrack,
17362
+ audioContext: this.audioContext
17363
+ };
17364
+ livekitLogger.debug("setting up audio processor ".concat(processor.name));
17365
+ yield processor.init(processorOptions);
17366
+ this.processor = processor;
17367
+ if (this.processor.processedTrack) {
17368
+ yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
17369
+ }
17370
+ } finally {
17371
+ unlock();
17372
+ }
17373
+ });
17374
+ }
17375
+ /**
17376
+ * @internal
17377
+ * @experimental
17378
+ */
17379
+ setAudioContext(audioContext) {
17380
+ this.audioContext = audioContext;
17381
+ }
17276
17382
  getSenderStats() {
17277
17383
  var _a;
17278
17384
  return __awaiter(this, void 0, void 0, function* () {
@@ -17312,823 +17418,718 @@ class LocalAudioTrack extends LocalTrack {
17312
17418
  }
17313
17419
  }
17314
17420
 
17315
- const refreshSubscribedCodecAfterNewCodec = 5000;
17316
- class LocalVideoTrack extends LocalTrack {
17317
- /**
17318
- *
17319
- * @param mediaTrack
17320
- * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
17321
- * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
17322
- */
17323
- constructor(mediaTrack, constraints) {
17324
- let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
17325
- super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
17326
- /* @internal */
17327
- this.simulcastCodecs = new Map();
17328
- this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
17329
- if (!this.sender) {
17330
- this._currentBitrate = 0;
17331
- return;
17332
- }
17333
- let stats;
17334
- try {
17335
- stats = yield this.getSenderStats();
17336
- } catch (e) {
17337
- livekitLogger.error('could not get audio sender stats', {
17338
- error: e
17339
- });
17340
- return;
17341
- }
17342
- const statsMap = new Map(stats.map(s => [s.rid, s]));
17343
- if (this.prevStats) {
17344
- let totalBitrate = 0;
17345
- statsMap.forEach((s, key) => {
17346
- var _a;
17347
- const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
17348
- totalBitrate += computeBitrate(s, prev);
17349
- });
17350
- this._currentBitrate = totalBitrate;
17351
- }
17352
- this.prevStats = statsMap;
17353
- });
17354
- this.senderLock = new Mutex();
17355
- }
17356
- get isSimulcast() {
17357
- if (this.sender && this.sender.getParameters().encodings.length > 1) {
17358
- return true;
17359
- }
17360
- return false;
17421
+ /** @internal */
17422
+ function mediaTrackToLocalTrack(mediaStreamTrack, constraints) {
17423
+ switch (mediaStreamTrack.kind) {
17424
+ case 'audio':
17425
+ return new LocalAudioTrack(mediaStreamTrack, constraints, false);
17426
+ case 'video':
17427
+ return new LocalVideoTrack(mediaStreamTrack, constraints, false);
17428
+ default:
17429
+ throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
17361
17430
  }
17362
- /* @internal */
17363
- startMonitor(signalClient) {
17431
+ }
17432
+ /* @internal */
17433
+ const presets169 = Object.values(VideoPresets);
17434
+ /* @internal */
17435
+ const presets43 = Object.values(VideoPresets43);
17436
+ /* @internal */
17437
+ const presetsScreenShare = Object.values(ScreenSharePresets);
17438
+ /* @internal */
17439
+ const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
17440
+ /* @internal */
17441
+ const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
17442
+ /* @internal */
17443
+ const computeDefaultScreenShareSimulcastPresets = fromPreset => {
17444
+ const layers = [{
17445
+ scaleResolutionDownBy: 2,
17446
+ fps: 3
17447
+ }];
17448
+ return layers.map(t => {
17364
17449
  var _a;
17365
- this.signalClient = signalClient;
17366
- if (!isWeb()) {
17367
- return;
17368
- }
17369
- // save original encodings
17370
- // TODO : merge simulcast tracks stats
17371
- const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
17372
- if (params) {
17373
- this.encodings = params.encodings;
17374
- }
17375
- if (this.monitorInterval) {
17376
- return;
17377
- }
17378
- this.monitorInterval = setInterval(() => {
17379
- this.monitorSender();
17380
- }, monitorFrequency);
17381
- }
17382
- stop() {
17383
- this._mediaStreamTrack.getConstraints();
17384
- this.simulcastCodecs.forEach(trackInfo => {
17385
- trackInfo.mediaStreamTrack.stop();
17386
- });
17387
- super.stop();
17450
+ return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / t.fps)))), t.fps, fromPreset.encoding.priority);
17451
+ });
17452
+ };
17453
+ // /**
17454
+ // *
17455
+ // * @internal
17456
+ // * @experimental
17457
+ // */
17458
+ // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
17459
+ // // use vp8 as a default
17460
+ // const vp8 = determineAppropriateEncoding(false, width, height);
17461
+ // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
17462
+ // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
17463
+ // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
17464
+ // return {
17465
+ // vp8,
17466
+ // vp9,
17467
+ // h264,
17468
+ // av1,
17469
+ // };
17470
+ // };
17471
+ const videoRids = ['q', 'h', 'f'];
17472
+ /* @internal */
17473
+ function computeVideoEncodings(isScreenShare, width, height, options) {
17474
+ var _a, _b;
17475
+ let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
17476
+ if (isScreenShare) {
17477
+ videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
17388
17478
  }
17389
- mute() {
17390
- const _super = Object.create(null, {
17391
- mute: {
17392
- get: () => super.mute
17393
- }
17394
- });
17395
- return __awaiter(this, void 0, void 0, function* () {
17396
- const unlock = yield this.muteLock.lock();
17397
- try {
17398
- if (this.source === Track.Source.Camera && !this.isUserProvided) {
17399
- livekitLogger.debug('stopping camera track');
17400
- // also stop the track, so that camera indicator is turned off
17401
- this._mediaStreamTrack.stop();
17402
- }
17403
- yield _super.mute.call(this);
17404
- return this;
17405
- } finally {
17406
- unlock();
17407
- }
17408
- });
17479
+ const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
17480
+ const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
17481
+ const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
17482
+ if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
17483
+ // when we aren't simulcasting or svc, will need to return a single encoding without
17484
+ // capping bandwidth. we always require a encoding for dynacast
17485
+ return [{}];
17409
17486
  }
17410
- unmute() {
17411
- const _super = Object.create(null, {
17412
- unmute: {
17413
- get: () => super.unmute
17414
- }
17415
- });
17416
- return __awaiter(this, void 0, void 0, function* () {
17417
- const unlock = yield this.muteLock.lock();
17418
- try {
17419
- if (this.source === Track.Source.Camera && !this.isUserProvided) {
17420
- livekitLogger.debug('reacquiring camera track');
17421
- yield this.restartTrack();
17422
- }
17423
- yield _super.unmute.call(this);
17424
- return this;
17425
- } finally {
17426
- unlock();
17427
- }
17428
- });
17487
+ if (!videoEncoding) {
17488
+ // find the right encoding based on width/height
17489
+ videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
17490
+ livekitLogger.debug('using video encoding', videoEncoding);
17429
17491
  }
17430
- getSenderStats() {
17431
- var _a;
17432
- return __awaiter(this, void 0, void 0, function* () {
17433
- if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
17434
- return [];
17435
- }
17436
- const items = [];
17437
- const stats = yield this.sender.getStats();
17438
- stats.forEach(v => {
17439
- var _a;
17440
- if (v.type === 'outbound-rtp') {
17441
- const vs = {
17442
- type: 'video',
17443
- streamId: v.id,
17444
- frameHeight: v.frameHeight,
17445
- frameWidth: v.frameWidth,
17446
- firCount: v.firCount,
17447
- pliCount: v.pliCount,
17448
- nackCount: v.nackCount,
17449
- packetsSent: v.packetsSent,
17450
- bytesSent: v.bytesSent,
17451
- framesSent: v.framesSent,
17452
- timestamp: v.timestamp,
17453
- rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
17454
- retransmittedPacketsSent: v.retransmittedPacketsSent,
17455
- qualityLimitationReason: v.qualityLimitationReason,
17456
- qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges
17457
- };
17458
- // locate the appropriate remote-inbound-rtp item
17459
- const r = stats.get(v.remoteId);
17460
- if (r) {
17461
- vs.jitter = r.jitter;
17462
- vs.packetsLost = r.packetsLost;
17463
- vs.roundTripTime = r.roundTripTime;
17464
- }
17465
- items.push(vs);
17466
- }
17492
+ const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
17493
+ if (scalabilityMode && isSVCCodec(videoCodec)) {
17494
+ livekitLogger.debug("using svc with scalabilityMode ".concat(scalabilityMode));
17495
+ const sm = new ScalabilityMode(scalabilityMode);
17496
+ const encodings = [];
17497
+ if (sm.spatial > 3) {
17498
+ throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
17499
+ }
17500
+ for (let i = 0; i < sm.spatial; i += 1) {
17501
+ encodings.push({
17502
+ rid: videoRids[2 - i],
17503
+ maxBitrate: videoEncoding.maxBitrate / Math.pow(3, i),
17504
+ /* @ts-ignore */
17505
+ maxFramerate: original.encoding.maxFramerate
17467
17506
  });
17468
- return items;
17469
- });
17470
- }
17471
- setPublishingQuality(maxQuality) {
17472
- const qualities = [];
17473
- for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
17474
- qualities.push(new SubscribedQuality({
17475
- quality: q,
17476
- enabled: q <= maxQuality
17477
- }));
17478
17507
  }
17479
- livekitLogger.debug("setting publishing quality. max quality ".concat(maxQuality));
17480
- this.setPublishingLayers(qualities);
17481
- }
17482
- setDeviceId(deviceId) {
17483
- return __awaiter(this, void 0, void 0, function* () {
17484
- if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
17485
- return true;
17486
- }
17487
- this._constraints.deviceId = deviceId;
17488
- // when video is muted, underlying media stream track is stopped and
17489
- // will be restarted later
17490
- if (!this.isMuted) {
17491
- yield this.restartTrack();
17492
- }
17493
- return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
17494
- });
17508
+ /* @ts-ignore */
17509
+ encodings[0].scalabilityMode = scalabilityMode;
17510
+ livekitLogger.debug('encodings', encodings);
17511
+ return encodings;
17495
17512
  }
17496
- restartTrack(options) {
17497
- return __awaiter(this, void 0, void 0, function* () {
17498
- let constraints;
17499
- if (options) {
17500
- const streamConstraints = constraintsForOptions({
17501
- video: options
17502
- });
17503
- if (typeof streamConstraints.video !== 'boolean') {
17504
- constraints = streamConstraints.video;
17505
- }
17506
- }
17507
- yield this.restart(constraints);
17508
- });
17513
+ if (!useSimulcast) {
17514
+ return [videoEncoding];
17509
17515
  }
17510
- addSimulcastTrack(codec, encodings) {
17511
- if (this.simulcastCodecs.has(codec)) {
17512
- throw new Error("".concat(codec, " already added"));
17513
- }
17514
- const simulcastCodecInfo = {
17515
- codec,
17516
- mediaStreamTrack: this.mediaStreamTrack.clone(),
17517
- sender: undefined,
17518
- encodings
17519
- };
17520
- this.simulcastCodecs.set(codec, simulcastCodecInfo);
17521
- return simulcastCodecInfo;
17516
+ let presets = [];
17517
+ if (isScreenShare) {
17518
+ presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
17519
+ } else {
17520
+ presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
17522
17521
  }
17523
- setSimulcastTrackSender(codec, sender) {
17524
- const simulcastCodecInfo = this.simulcastCodecs.get(codec);
17525
- if (!simulcastCodecInfo) {
17526
- return;
17522
+ let midPreset;
17523
+ if (presets.length > 0) {
17524
+ const lowPreset = presets[0];
17525
+ if (presets.length > 1) {
17526
+ [, midPreset] = presets;
17527
17527
  }
17528
- simulcastCodecInfo.sender = sender;
17529
- // browser will reenable disabled codec/layers after new codec has been published,
17530
- // so refresh subscribedCodecs after publish a new codec
17531
- setTimeout(() => {
17532
- if (this.subscribedCodecs) {
17533
- this.setPublishingCodecs(this.subscribedCodecs);
17534
- }
17535
- }, refreshSubscribedCodecAfterNewCodec);
17536
- }
17537
- /**
17538
- * @internal
17539
- * Sets codecs that should be publishing
17540
- */
17541
- setPublishingCodecs(codecs) {
17542
- var _a, codecs_1, codecs_1_1;
17543
- var _b, e_1, _c, _d;
17544
- return __awaiter(this, void 0, void 0, function* () {
17545
- livekitLogger.debug('setting publishing codecs', {
17546
- codecs,
17547
- currentCodec: this.codec
17548
- });
17549
- // only enable simulcast codec for preference codec setted
17550
- if (!this.codec && codecs.length > 0) {
17551
- yield this.setPublishingLayers(codecs[0].qualities);
17552
- return [];
17553
- }
17554
- this.subscribedCodecs = codecs;
17555
- const newCodecs = [];
17556
- try {
17557
- for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
17558
- _d = codecs_1_1.value;
17559
- _a = false;
17560
- const codec = _d;
17561
- if (!this.codec || this.codec === codec.codec) {
17562
- yield this.setPublishingLayers(codec.qualities);
17563
- } else {
17564
- const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
17565
- livekitLogger.debug("try setPublishingCodec for ".concat(codec.codec), simulcastCodecInfo);
17566
- if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
17567
- for (const q of codec.qualities) {
17568
- if (q.enabled) {
17569
- newCodecs.push(codec.codec);
17570
- break;
17571
- }
17572
- }
17573
- } else if (simulcastCodecInfo.encodings) {
17574
- livekitLogger.debug("try setPublishingLayersForSender ".concat(codec.codec));
17575
- yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock);
17576
- }
17577
- }
17578
- }
17579
- } catch (e_1_1) {
17580
- e_1 = {
17581
- error: e_1_1
17582
- };
17583
- } finally {
17584
- try {
17585
- if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
17586
- } finally {
17587
- if (e_1) throw e_1.error;
17588
- }
17589
- }
17590
- return newCodecs;
17591
- });
17592
- }
17593
- /**
17594
- * @internal
17595
- * Sets layers that should be publishing
17596
- */
17597
- setPublishingLayers(qualities) {
17598
- return __awaiter(this, void 0, void 0, function* () {
17599
- livekitLogger.debug('setting publishing layers', qualities);
17600
- if (!this.sender || !this.encodings) {
17601
- return;
17602
- }
17603
- yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock);
17604
- });
17605
- }
17606
- handleAppVisibilityChanged() {
17607
- const _super = Object.create(null, {
17608
- handleAppVisibilityChanged: {
17609
- get: () => super.handleAppVisibilityChanged
17610
- }
17611
- });
17612
- return __awaiter(this, void 0, void 0, function* () {
17613
- yield _super.handleAppVisibilityChanged.call(this);
17614
- if (!isMobile()) return;
17615
- if (this.isInBackground && this.source === Track.Source.Camera) {
17616
- this._mediaStreamTrack.enabled = false;
17617
- }
17618
- });
17619
- }
17620
- }
17621
- function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock) {
17622
- return __awaiter(this, void 0, void 0, function* () {
17623
- const unlock = yield senderLock.lock();
17624
- livekitLogger.debug('setPublishingLayersForSender', {
17625
- sender,
17626
- qualities,
17627
- senderEncodings
17628
- });
17629
- try {
17630
- const params = sender.getParameters();
17631
- const {
17632
- encodings
17633
- } = params;
17634
- if (!encodings) {
17635
- return;
17636
- }
17637
- if (encodings.length !== senderEncodings.length) {
17638
- livekitLogger.warn('cannot set publishing layers, encodings mismatch');
17639
- return;
17640
- }
17641
- let hasChanged = false;
17642
- /* disable closable spatial layer as it has video blur / frozen issue with current server / client
17643
- 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
17644
- low resolution frame and recover very quickly, but noticable
17645
- 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
17646
- const closableSpatial = false;
17647
- /* @ts-ignore */
17648
- if (closableSpatial && encodings[0].scalabilityMode) ; else {
17649
- // simulcast dynacast encodings
17650
- encodings.forEach((encoding, idx) => {
17651
- var _a;
17652
- let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
17653
- if (rid === '') {
17654
- rid = 'q';
17655
- }
17656
- const quality = videoQualityForRid(rid);
17657
- const subscribedQuality = qualities.find(q => q.quality === quality);
17658
- if (!subscribedQuality) {
17659
- return;
17660
- }
17661
- if (encoding.active !== subscribedQuality.enabled) {
17662
- hasChanged = true;
17663
- encoding.active = subscribedQuality.enabled;
17664
- livekitLogger.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'));
17665
- // FireFox does not support setting encoding.active to false, so we
17666
- // have a workaround of lowering its bitrate and resolution to the min.
17667
- if (isFireFox()) {
17668
- if (subscribedQuality.enabled) {
17669
- encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
17670
- encoding.maxBitrate = senderEncodings[idx].maxBitrate;
17671
- /* @ts-ignore */
17672
- encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
17673
- } else {
17674
- encoding.scaleResolutionDownBy = 4;
17675
- encoding.maxBitrate = 10;
17676
- /* @ts-ignore */
17677
- encoding.maxFrameRate = 2;
17678
- }
17679
- }
17680
- }
17681
- });
17682
- }
17683
- if (hasChanged) {
17684
- params.encodings = encodings;
17685
- livekitLogger.debug("setting encodings", params.encodings);
17686
- yield sender.setParameters(params);
17687
- }
17688
- } finally {
17689
- unlock();
17528
+ // NOTE:
17529
+ // 1. Ordering of these encodings is important. Chrome seems
17530
+ // to use the index into encodings to decide which layer
17531
+ // to disable when CPU constrained.
17532
+ // So encodings should be ordered in increasing spatial
17533
+ // resolution order.
17534
+ // 2. ion-sfu translates rids into layers. So, all encodings
17535
+ // should have the base layer `q` and then more added
17536
+ // based on other conditions.
17537
+ const size = Math.max(width, height);
17538
+ if (size >= 960 && midPreset) {
17539
+ return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
17690
17540
  }
17691
- });
17541
+ if (size >= 480) {
17542
+ return encodingsFromPresets(width, height, [lowPreset, original]);
17543
+ }
17544
+ }
17545
+ return encodingsFromPresets(width, height, [original]);
17692
17546
  }
17693
- function videoQualityForRid(rid) {
17694
- switch (rid) {
17695
- case 'f':
17696
- return VideoQuality.HIGH;
17697
- case 'h':
17698
- return VideoQuality.MEDIUM;
17699
- case 'q':
17700
- return VideoQuality.LOW;
17701
- default:
17702
- return VideoQuality.HIGH;
17547
+ function computeTrackBackupEncodings(track, videoCodec, opts) {
17548
+ var _a, _b, _c, _d;
17549
+ if (!opts.backupCodec || opts.backupCodec.codec === opts.videoCodec) {
17550
+ // backup codec publishing is disabled
17551
+ return;
17552
+ }
17553
+ if (videoCodec !== opts.backupCodec.codec) {
17554
+ livekitLogger.warn('requested a different codec than specified as backup', {
17555
+ serverRequested: videoCodec,
17556
+ backup: opts.backupCodec.codec
17557
+ });
17703
17558
  }
17559
+ opts.videoCodec = videoCodec;
17560
+ // use backup encoding setting as videoEncoding for backup codec publishing
17561
+ opts.videoEncoding = opts.backupCodec.encoding;
17562
+ const settings = track.mediaStreamTrack.getSettings();
17563
+ const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
17564
+ const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
17565
+ const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
17566
+ return encodings;
17704
17567
  }
17705
- function videoLayersFromEncodings(width, height, encodings, svc) {
17706
- // default to a single layer, HQ
17707
- if (!encodings) {
17708
- return [new VideoLayer({
17709
- quality: VideoQuality.HIGH,
17710
- width,
17711
- height,
17712
- bitrate: 0,
17713
- ssrc: 0
17714
- })];
17568
+ /* @internal */
17569
+ function determineAppropriateEncoding(isScreenShare, width, height, codec) {
17570
+ const presets = presetsForResolution(isScreenShare, width, height);
17571
+ let {
17572
+ encoding
17573
+ } = presets[0];
17574
+ // handle portrait by swapping dimensions
17575
+ const size = Math.max(width, height);
17576
+ for (let i = 0; i < presets.length; i += 1) {
17577
+ const preset = presets[i];
17578
+ encoding = preset.encoding;
17579
+ if (preset.width >= size) {
17580
+ break;
17581
+ }
17715
17582
  }
17716
- if (svc) {
17717
- // svc layers
17718
- /* @ts-ignore */
17719
- const sm = new ScalabilityMode(encodings[0].scalabilityMode);
17720
- const layers = [];
17721
- for (let i = 0; i < sm.spatial; i += 1) {
17722
- layers.push(new VideoLayer({
17723
- quality: VideoQuality.HIGH - i,
17724
- width: width / Math.pow(2, i),
17725
- height: height / Math.pow(2, i),
17726
- bitrate: encodings[0].maxBitrate ? encodings[0].maxBitrate / Math.pow(3, i) : 0,
17727
- ssrc: 0
17728
- }));
17583
+ // presets are based on the assumption of vp8 as a codec
17584
+ // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
17585
+ // users should override these with ones that are optimized for their use case
17586
+ // NOTE: SVC codec bitrates are inclusive of all scalability layers. while
17587
+ // bitrate for non-SVC codecs does not include other simulcast layers.
17588
+ if (codec) {
17589
+ switch (codec) {
17590
+ case 'av1':
17591
+ encoding = Object.assign({}, encoding);
17592
+ encoding.maxBitrate = encoding.maxBitrate * 0.7;
17593
+ break;
17594
+ case 'vp9':
17595
+ encoding = Object.assign({}, encoding);
17596
+ encoding.maxBitrate = encoding.maxBitrate * 0.85;
17597
+ break;
17729
17598
  }
17730
- return layers;
17731
17599
  }
17732
- return encodings.map(encoding => {
17733
- var _a, _b, _c;
17734
- const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
17735
- let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
17736
- return new VideoLayer({
17737
- quality,
17738
- width: width / scale,
17739
- height: height / scale,
17740
- bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
17741
- ssrc: 0
17742
- });
17743
- });
17600
+ return encoding;
17744
17601
  }
17745
-
17746
- class RemoteTrack extends Track {
17747
- constructor(mediaTrack, sid, kind, receiver) {
17748
- super(mediaTrack, kind);
17749
- this.sid = sid;
17750
- this.receiver = receiver;
17602
+ /* @internal */
17603
+ function presetsForResolution(isScreenShare, width, height) {
17604
+ if (isScreenShare) {
17605
+ return presetsScreenShare;
17751
17606
  }
17752
- /** @internal */
17753
- setMuted(muted) {
17754
- if (this.isMuted !== muted) {
17755
- this.isMuted = muted;
17756
- this._mediaStreamTrack.enabled = !muted;
17757
- this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
17758
- }
17607
+ const aspect = width > height ? width / height : height / width;
17608
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
17609
+ return presets169;
17759
17610
  }
17760
- /** @internal */
17761
- setMediaStream(stream) {
17762
- // this is needed to determine when the track is finished
17763
- // we send each track down in its own MediaStream, so we can assume the
17764
- // current track is the only one that can be removed.
17765
- this.mediaStream = stream;
17766
- stream.onremovetrack = () => {
17767
- this.receiver = undefined;
17768
- this._currentBitrate = 0;
17769
- this.emit(TrackEvent.Ended, this);
17770
- };
17611
+ return presets43;
17612
+ }
17613
+ /* @internal */
17614
+ function defaultSimulcastLayers(isScreenShare, original) {
17615
+ if (isScreenShare) {
17616
+ return computeDefaultScreenShareSimulcastPresets(original);
17771
17617
  }
17772
- start() {
17773
- this.startMonitor();
17774
- // use `enabled` of track to enable re-use of transceiver
17775
- super.enable();
17618
+ const {
17619
+ width,
17620
+ height
17621
+ } = original;
17622
+ const aspect = width > height ? width / height : height / width;
17623
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
17624
+ return defaultSimulcastPresets169;
17776
17625
  }
17777
- stop() {
17778
- this.stopMonitor();
17779
- // use `enabled` of track to enable re-use of transceiver
17780
- super.disable();
17626
+ return defaultSimulcastPresets43;
17627
+ }
17628
+ // presets should be ordered by low, medium, high
17629
+ function encodingsFromPresets(width, height, presets) {
17630
+ const encodings = [];
17631
+ presets.forEach((preset, idx) => {
17632
+ if (idx >= videoRids.length) {
17633
+ return;
17634
+ }
17635
+ const size = Math.min(width, height);
17636
+ const rid = videoRids[idx];
17637
+ const encoding = {
17638
+ rid,
17639
+ scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
17640
+ maxBitrate: preset.encoding.maxBitrate
17641
+ };
17642
+ if (preset.encoding.maxFramerate) {
17643
+ encoding.maxFramerate = preset.encoding.maxFramerate;
17644
+ }
17645
+ const canSetPriority = isFireFox() || idx === 0;
17646
+ if (preset.encoding.priority && canSetPriority) {
17647
+ encoding.priority = preset.encoding.priority;
17648
+ encoding.networkPriority = preset.encoding.priority;
17649
+ }
17650
+ encodings.push(encoding);
17651
+ });
17652
+ // RN ios simulcast requires all same framerates.
17653
+ if (isReactNative() && getReactNativeOs() === 'ios') {
17654
+ let topFramerate = undefined;
17655
+ encodings.forEach(encoding => {
17656
+ if (!topFramerate) {
17657
+ topFramerate = encoding.maxFramerate;
17658
+ } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
17659
+ topFramerate = encoding.maxFramerate;
17660
+ }
17661
+ });
17662
+ let notifyOnce = true;
17663
+ encodings.forEach(encoding => {
17664
+ var _a;
17665
+ if (encoding.maxFramerate != topFramerate) {
17666
+ if (notifyOnce) {
17667
+ notifyOnce = false;
17668
+ livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
17669
+ }
17670
+ livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
17671
+ encoding.maxFramerate = topFramerate;
17672
+ }
17673
+ });
17781
17674
  }
17782
- /* @internal */
17783
- startMonitor() {
17784
- if (!this.monitorInterval) {
17785
- this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
17675
+ return encodings;
17676
+ }
17677
+ /** @internal */
17678
+ function sortPresets(presets) {
17679
+ if (!presets) return;
17680
+ return presets.sort((a, b) => {
17681
+ const {
17682
+ encoding: aEnc
17683
+ } = a;
17684
+ const {
17685
+ encoding: bEnc
17686
+ } = b;
17687
+ if (aEnc.maxBitrate > bEnc.maxBitrate) {
17688
+ return 1;
17689
+ }
17690
+ if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
17691
+ if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
17692
+ return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
17786
17693
  }
17694
+ return 0;
17695
+ });
17696
+ }
17697
+ /** @internal */
17698
+ class ScalabilityMode {
17699
+ constructor(scalabilityMode) {
17700
+ const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
17701
+ if (!results) {
17702
+ throw new Error('invalid scalability mode');
17703
+ }
17704
+ this.spatial = parseInt(results[1]);
17705
+ this.temporal = parseInt(results[2]);
17706
+ if (results.length > 3) {
17707
+ switch (results[3]) {
17708
+ case 'h':
17709
+ case '_KEY':
17710
+ case '_KEY_SHIFT':
17711
+ this.suffix = results[3];
17712
+ }
17713
+ }
17714
+ }
17715
+ toString() {
17716
+ var _a;
17717
+ return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
17787
17718
  }
17788
17719
  }
17789
17720
 
17790
- class RemoteAudioTrack extends RemoteTrack {
17791
- constructor(mediaTrack, sid, receiver, audioContext, audioOutput) {
17792
- super(mediaTrack, sid, Track.Kind.Audio, receiver);
17793
- this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
17794
- if (!this.receiver) {
17721
+ const refreshSubscribedCodecAfterNewCodec = 5000;
17722
+ class LocalVideoTrack extends LocalTrack {
17723
+ /**
17724
+ *
17725
+ * @param mediaTrack
17726
+ * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
17727
+ * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
17728
+ */
17729
+ constructor(mediaTrack, constraints) {
17730
+ let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
17731
+ super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
17732
+ /* @internal */
17733
+ this.simulcastCodecs = new Map();
17734
+ this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
17735
+ if (!this.sender) {
17795
17736
  this._currentBitrate = 0;
17796
17737
  return;
17797
17738
  }
17798
- const stats = yield this.getReceiverStats();
17799
- if (stats && this.prevStats && this.receiver) {
17800
- this._currentBitrate = computeBitrate(stats, this.prevStats);
17739
+ let stats;
17740
+ try {
17741
+ stats = yield this.getSenderStats();
17742
+ } catch (e) {
17743
+ livekitLogger.error('could not get audio sender stats', {
17744
+ error: e
17745
+ });
17746
+ return;
17801
17747
  }
17802
- this.prevStats = stats;
17748
+ const statsMap = new Map(stats.map(s => [s.rid, s]));
17749
+ if (this.prevStats) {
17750
+ let totalBitrate = 0;
17751
+ statsMap.forEach((s, key) => {
17752
+ var _a;
17753
+ const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
17754
+ totalBitrate += computeBitrate(s, prev);
17755
+ });
17756
+ this._currentBitrate = totalBitrate;
17757
+ }
17758
+ this.prevStats = statsMap;
17803
17759
  });
17804
- this.audioContext = audioContext;
17805
- this.webAudioPluginNodes = [];
17806
- if (audioOutput) {
17807
- this.sinkId = audioOutput.deviceId;
17760
+ this.senderLock = new Mutex();
17761
+ }
17762
+ get isSimulcast() {
17763
+ if (this.sender && this.sender.getParameters().encodings.length > 1) {
17764
+ return true;
17808
17765
  }
17766
+ return false;
17809
17767
  }
17810
- /**
17811
- * sets the volume for all attached audio elements
17812
- */
17813
- setVolume(volume) {
17768
+ /* @internal */
17769
+ startMonitor(signalClient) {
17814
17770
  var _a;
17815
- for (const el of this.attachedElements) {
17816
- if (this.audioContext) {
17817
- (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setTargetAtTime(volume, 0, 0.1);
17818
- } else {
17819
- el.volume = volume;
17820
- }
17771
+ this.signalClient = signalClient;
17772
+ if (!isWeb()) {
17773
+ return;
17821
17774
  }
17822
- this.elementVolume = volume;
17823
- }
17824
- /**
17825
- * gets the volume of attached audio elements (loudest)
17826
- */
17827
- getVolume() {
17828
- if (this.elementVolume) {
17829
- return this.elementVolume;
17775
+ // save original encodings
17776
+ // TODO : merge simulcast tracks stats
17777
+ const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
17778
+ if (params) {
17779
+ this.encodings = params.encodings;
17830
17780
  }
17831
- let highestVolume = 0;
17832
- this.attachedElements.forEach(element => {
17833
- if (element.volume > highestVolume) {
17834
- highestVolume = element.volume;
17835
- }
17781
+ if (this.monitorInterval) {
17782
+ return;
17783
+ }
17784
+ this.monitorInterval = setInterval(() => {
17785
+ this.monitorSender();
17786
+ }, monitorFrequency);
17787
+ }
17788
+ stop() {
17789
+ this._mediaStreamTrack.getConstraints();
17790
+ this.simulcastCodecs.forEach(trackInfo => {
17791
+ trackInfo.mediaStreamTrack.stop();
17836
17792
  });
17837
- return highestVolume;
17793
+ super.stop();
17838
17794
  }
17839
- /**
17840
- * calls setSinkId on all attached elements, if supported
17841
- * @param deviceId audio output device
17842
- */
17843
- setSinkId(deviceId) {
17795
+ pauseUpstream() {
17796
+ const _super = Object.create(null, {
17797
+ pauseUpstream: {
17798
+ get: () => super.pauseUpstream
17799
+ }
17800
+ });
17801
+ var _a, e_1, _b, _c;
17802
+ var _d;
17844
17803
  return __awaiter(this, void 0, void 0, function* () {
17845
- this.sinkId = deviceId;
17846
- yield Promise.all(this.attachedElements.map(elm => {
17847
- if (!supportsSetSinkId(elm)) {
17848
- return;
17804
+ yield _super.pauseUpstream.call(this);
17805
+ try {
17806
+ for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
17807
+ _c = _g.value;
17808
+ _e = false;
17809
+ const sc = _c;
17810
+ yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null);
17849
17811
  }
17850
- /* @ts-ignore */
17851
- return elm.setSinkId(deviceId);
17852
- }));
17812
+ } catch (e_1_1) {
17813
+ e_1 = {
17814
+ error: e_1_1
17815
+ };
17816
+ } finally {
17817
+ try {
17818
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
17819
+ } finally {
17820
+ if (e_1) throw e_1.error;
17821
+ }
17822
+ }
17853
17823
  });
17854
17824
  }
17855
- attach(element) {
17856
- const needsNewWebAudioConnection = this.attachedElements.length === 0;
17857
- if (!element) {
17858
- element = super.attach();
17859
- } else {
17860
- super.attach(element);
17861
- }
17862
- if (this.elementVolume) {
17863
- element.volume = this.elementVolume;
17864
- }
17865
- if (this.sinkId && supportsSetSinkId(element)) {
17866
- /* @ts-ignore */
17867
- element.setSinkId(this.sinkId);
17868
- }
17869
- if (this.audioContext && needsNewWebAudioConnection) {
17870
- livekitLogger.debug('using audio context mapping');
17871
- this.connectWebAudio(this.audioContext, element);
17872
- element.volume = 0;
17873
- element.muted = true;
17874
- }
17875
- return element;
17876
- }
17877
- detach(element) {
17878
- let detached;
17879
- if (!element) {
17880
- detached = super.detach();
17881
- this.disconnectWebAudio();
17882
- } else {
17883
- detached = super.detach(element);
17884
- // if there are still any attached elements after detaching, connect webaudio to the first element that's left
17885
- // disconnect webaudio otherwise
17886
- if (this.audioContext) {
17887
- if (this.attachedElements.length > 0) {
17888
- this.connectWebAudio(this.audioContext, this.attachedElements[0]);
17889
- } else {
17890
- this.disconnectWebAudio();
17825
+ resumeUpstream() {
17826
+ const _super = Object.create(null, {
17827
+ resumeUpstream: {
17828
+ get: () => super.resumeUpstream
17829
+ }
17830
+ });
17831
+ var _a, e_2, _b, _c;
17832
+ var _d;
17833
+ return __awaiter(this, void 0, void 0, function* () {
17834
+ yield _super.resumeUpstream.call(this);
17835
+ try {
17836
+ for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
17837
+ _c = _g.value;
17838
+ _e = false;
17839
+ const sc = _c;
17840
+ yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
17841
+ }
17842
+ } catch (e_2_1) {
17843
+ e_2 = {
17844
+ error: e_2_1
17845
+ };
17846
+ } finally {
17847
+ try {
17848
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
17849
+ } finally {
17850
+ if (e_2) throw e_2.error;
17891
17851
  }
17892
17852
  }
17893
- }
17894
- return detached;
17895
- }
17896
- /**
17897
- * @internal
17898
- * @experimental
17899
- */
17900
- setAudioContext(audioContext) {
17901
- this.audioContext = audioContext;
17902
- if (audioContext && this.attachedElements.length > 0) {
17903
- this.connectWebAudio(audioContext, this.attachedElements[0]);
17904
- } else if (!audioContext) {
17905
- this.disconnectWebAudio();
17906
- }
17853
+ });
17907
17854
  }
17908
- /**
17909
- * @internal
17910
- * @experimental
17911
- * @param {AudioNode[]} nodes - An array of WebAudio nodes. These nodes should not be connected to each other when passed, as the sdk will take care of connecting them in the order of the array.
17912
- */
17913
- setWebAudioPlugins(nodes) {
17914
- this.webAudioPluginNodes = nodes;
17915
- if (this.attachedElements.length > 0 && this.audioContext) {
17916
- this.connectWebAudio(this.audioContext, this.attachedElements[0]);
17917
- }
17855
+ mute() {
17856
+ const _super = Object.create(null, {
17857
+ mute: {
17858
+ get: () => super.mute
17859
+ }
17860
+ });
17861
+ return __awaiter(this, void 0, void 0, function* () {
17862
+ const unlock = yield this.muteLock.lock();
17863
+ try {
17864
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
17865
+ livekitLogger.debug('stopping camera track');
17866
+ // also stop the track, so that camera indicator is turned off
17867
+ this._mediaStreamTrack.stop();
17868
+ }
17869
+ yield _super.mute.call(this);
17870
+ return this;
17871
+ } finally {
17872
+ unlock();
17873
+ }
17874
+ });
17918
17875
  }
17919
- connectWebAudio(context, element) {
17920
- this.disconnectWebAudio();
17921
- // @ts-ignore attached elements always have a srcObject set
17922
- this.sourceNode = context.createMediaStreamSource(element.srcObject);
17923
- let lastNode = this.sourceNode;
17924
- this.webAudioPluginNodes.forEach(node => {
17925
- lastNode.connect(node);
17926
- lastNode = node;
17876
+ unmute() {
17877
+ const _super = Object.create(null, {
17878
+ unmute: {
17879
+ get: () => super.unmute
17880
+ }
17927
17881
  });
17928
- this.gainNode = context.createGain();
17929
- lastNode.connect(this.gainNode);
17930
- this.gainNode.connect(context.destination);
17931
- if (this.elementVolume) {
17932
- this.gainNode.gain.setTargetAtTime(this.elementVolume, 0, 0.1);
17933
- }
17934
- // try to resume the context if it isn't running already
17935
- if (context.state !== 'running') {
17936
- context.resume().then(() => {
17937
- if (context.state !== 'running') {
17938
- this.emit(TrackEvent.AudioPlaybackFailed, new Error("Audio Context couldn't be started automatically"));
17882
+ return __awaiter(this, void 0, void 0, function* () {
17883
+ const unlock = yield this.muteLock.lock();
17884
+ try {
17885
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
17886
+ livekitLogger.debug('reacquiring camera track');
17887
+ yield this.restartTrack();
17939
17888
  }
17940
- }).catch(e => {
17941
- this.emit(TrackEvent.AudioPlaybackFailed, e);
17942
- });
17943
- }
17889
+ yield _super.unmute.call(this);
17890
+ return this;
17891
+ } finally {
17892
+ unlock();
17893
+ }
17894
+ });
17944
17895
  }
17945
- disconnectWebAudio() {
17946
- var _a, _b;
17947
- (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.disconnect();
17948
- (_b = this.sourceNode) === null || _b === void 0 ? void 0 : _b.disconnect();
17949
- this.gainNode = undefined;
17950
- this.sourceNode = undefined;
17896
+ setTrackMuted(muted) {
17897
+ super.setTrackMuted(muted);
17898
+ for (const sc of this.simulcastCodecs.values()) {
17899
+ sc.mediaStreamTrack.enabled = !muted;
17900
+ }
17951
17901
  }
17952
- getReceiverStats() {
17902
+ getSenderStats() {
17903
+ var _a;
17953
17904
  return __awaiter(this, void 0, void 0, function* () {
17954
- if (!this.receiver || !this.receiver.getStats) {
17955
- return;
17905
+ if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
17906
+ return [];
17956
17907
  }
17957
- const stats = yield this.receiver.getStats();
17958
- let receiverStats;
17908
+ const items = [];
17909
+ const stats = yield this.sender.getStats();
17959
17910
  stats.forEach(v => {
17960
- if (v.type === 'inbound-rtp') {
17961
- receiverStats = {
17962
- type: 'audio',
17911
+ var _a;
17912
+ if (v.type === 'outbound-rtp') {
17913
+ const vs = {
17914
+ type: 'video',
17915
+ streamId: v.id,
17916
+ frameHeight: v.frameHeight,
17917
+ frameWidth: v.frameWidth,
17918
+ firCount: v.firCount,
17919
+ pliCount: v.pliCount,
17920
+ nackCount: v.nackCount,
17921
+ packetsSent: v.packetsSent,
17922
+ bytesSent: v.bytesSent,
17923
+ framesSent: v.framesSent,
17963
17924
  timestamp: v.timestamp,
17964
- jitter: v.jitter,
17965
- bytesReceived: v.bytesReceived,
17966
- concealedSamples: v.concealedSamples,
17967
- concealmentEvents: v.concealmentEvents,
17968
- silentConcealedSamples: v.silentConcealedSamples,
17969
- silentConcealmentEvents: v.silentConcealmentEvents,
17970
- totalAudioEnergy: v.totalAudioEnergy,
17971
- totalSamplesDuration: v.totalSamplesDuration
17925
+ rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
17926
+ retransmittedPacketsSent: v.retransmittedPacketsSent,
17927
+ qualityLimitationReason: v.qualityLimitationReason,
17928
+ qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges
17972
17929
  };
17930
+ // locate the appropriate remote-inbound-rtp item
17931
+ const r = stats.get(v.remoteId);
17932
+ if (r) {
17933
+ vs.jitter = r.jitter;
17934
+ vs.packetsLost = r.packetsLost;
17935
+ vs.roundTripTime = r.roundTripTime;
17936
+ }
17937
+ items.push(vs);
17973
17938
  }
17974
17939
  });
17975
- return receiverStats;
17940
+ return items;
17976
17941
  });
17977
17942
  }
17978
- }
17979
-
17980
- const REACTION_DELAY = 100;
17981
- class RemoteVideoTrack extends RemoteTrack {
17982
- constructor(mediaTrack, sid, receiver, adaptiveStreamSettings) {
17983
- super(mediaTrack, sid, Track.Kind.Video, receiver);
17984
- this.elementInfos = [];
17985
- this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
17986
- if (!this.receiver) {
17987
- this._currentBitrate = 0;
17988
- return;
17943
+ setPublishingQuality(maxQuality) {
17944
+ const qualities = [];
17945
+ for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
17946
+ qualities.push(new SubscribedQuality({
17947
+ quality: q,
17948
+ enabled: q <= maxQuality
17949
+ }));
17950
+ }
17951
+ livekitLogger.debug("setting publishing quality. max quality ".concat(maxQuality));
17952
+ this.setPublishingLayers(qualities);
17953
+ }
17954
+ setDeviceId(deviceId) {
17955
+ return __awaiter(this, void 0, void 0, function* () {
17956
+ if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
17957
+ return true;
17989
17958
  }
17990
- const stats = yield this.getReceiverStats();
17991
- if (stats && this.prevStats && this.receiver) {
17992
- this._currentBitrate = computeBitrate(stats, this.prevStats);
17959
+ this._constraints.deviceId = deviceId;
17960
+ // when video is muted, underlying media stream track is stopped and
17961
+ // will be restarted later
17962
+ if (!this.isMuted) {
17963
+ yield this.restartTrack();
17993
17964
  }
17994
- this.prevStats = stats;
17965
+ return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
17995
17966
  });
17996
- this.debouncedHandleResize = r(() => {
17997
- this.updateDimensions();
17998
- }, REACTION_DELAY);
17999
- this.adaptiveStreamSettings = adaptiveStreamSettings;
18000
- }
18001
- get isAdaptiveStream() {
18002
- return this.adaptiveStreamSettings !== undefined;
18003
17967
  }
18004
- /**
18005
- * Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
18006
- */
18007
- get mediaStreamTrack() {
18008
- return this._mediaStreamTrack;
17968
+ restartTrack(options) {
17969
+ var _a, e_3, _b, _c;
17970
+ return __awaiter(this, void 0, void 0, function* () {
17971
+ let constraints;
17972
+ if (options) {
17973
+ const streamConstraints = constraintsForOptions({
17974
+ video: options
17975
+ });
17976
+ if (typeof streamConstraints.video !== 'boolean') {
17977
+ constraints = streamConstraints.video;
17978
+ }
17979
+ }
17980
+ yield this.restart(constraints);
17981
+ try {
17982
+ for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
17983
+ _c = _f.value;
17984
+ _d = false;
17985
+ const sc = _c;
17986
+ if (sc.sender) {
17987
+ sc.mediaStreamTrack = this.mediaStreamTrack.clone();
17988
+ yield sc.sender.replaceTrack(sc.mediaStreamTrack);
17989
+ }
17990
+ }
17991
+ } catch (e_3_1) {
17992
+ e_3 = {
17993
+ error: e_3_1
17994
+ };
17995
+ } finally {
17996
+ try {
17997
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
17998
+ } finally {
17999
+ if (e_3) throw e_3.error;
18000
+ }
18001
+ }
18002
+ });
18009
18003
  }
18010
- /** @internal */
18011
- setMuted(muted) {
18012
- super.setMuted(muted);
18013
- this.attachedElements.forEach(element => {
18014
- // detach or attach
18015
- if (muted) {
18016
- detachTrack(this._mediaStreamTrack, element);
18017
- } else {
18018
- attachToElement(this._mediaStreamTrack, element);
18004
+ setProcessor(processor) {
18005
+ let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
18006
+ const _super = Object.create(null, {
18007
+ setProcessor: {
18008
+ get: () => super.setProcessor
18009
+ }
18010
+ });
18011
+ var _a, e_4, _b, _c;
18012
+ var _d, _e;
18013
+ return __awaiter(this, void 0, void 0, function* () {
18014
+ yield _super.setProcessor.call(this, processor, showProcessedStreamLocally);
18015
+ if ((_d = this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
18016
+ try {
18017
+ for (var _f = true, _g = __asyncValues(this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
18018
+ _c = _h.value;
18019
+ _f = false;
18020
+ const sc = _c;
18021
+ yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(this.processor.processedTrack);
18022
+ }
18023
+ } catch (e_4_1) {
18024
+ e_4 = {
18025
+ error: e_4_1
18026
+ };
18027
+ } finally {
18028
+ try {
18029
+ if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
18030
+ } finally {
18031
+ if (e_4) throw e_4.error;
18032
+ }
18033
+ }
18019
18034
  }
18020
18035
  });
18021
18036
  }
18022
- attach(element) {
18023
- if (!element) {
18024
- element = super.attach();
18025
- } else {
18026
- super.attach(element);
18027
- }
18028
- // It's possible attach is called multiple times on an element. When that's
18029
- // the case, we'd want to avoid adding duplicate elementInfos
18030
- if (this.adaptiveStreamSettings && this.elementInfos.find(info => info.element === element) === undefined) {
18031
- const elementInfo = new HTMLElementInfo(element);
18032
- this.observeElementInfo(elementInfo);
18037
+ addSimulcastTrack(codec, encodings) {
18038
+ if (this.simulcastCodecs.has(codec)) {
18039
+ throw new Error("".concat(codec, " already added"));
18033
18040
  }
18034
- return element;
18041
+ const simulcastCodecInfo = {
18042
+ codec,
18043
+ mediaStreamTrack: this.mediaStreamTrack.clone(),
18044
+ sender: undefined,
18045
+ encodings
18046
+ };
18047
+ this.simulcastCodecs.set(codec, simulcastCodecInfo);
18048
+ return simulcastCodecInfo;
18035
18049
  }
18036
- /**
18037
- * Observe an ElementInfo for changes when adaptive streaming.
18038
- * @param elementInfo
18039
- * @internal
18040
- */
18041
- observeElementInfo(elementInfo) {
18042
- if (this.adaptiveStreamSettings && this.elementInfos.find(info => info === elementInfo) === undefined) {
18043
- elementInfo.handleResize = () => {
18044
- this.debouncedHandleResize();
18045
- };
18046
- elementInfo.handleVisibilityChanged = () => {
18047
- this.updateVisibility();
18048
- };
18049
- this.elementInfos.push(elementInfo);
18050
- elementInfo.observe();
18051
- // trigger the first resize update cycle
18052
- // if the tab is backgrounded, the initial resize event does not fire until
18053
- // the tab comes into focus for the first time.
18054
- this.debouncedHandleResize();
18055
- this.updateVisibility();
18056
- } else {
18057
- livekitLogger.warn('visibility resize observer not triggered');
18050
+ setSimulcastTrackSender(codec, sender) {
18051
+ const simulcastCodecInfo = this.simulcastCodecs.get(codec);
18052
+ if (!simulcastCodecInfo) {
18053
+ return;
18058
18054
  }
18055
+ simulcastCodecInfo.sender = sender;
18056
+ // browser will reenable disabled codec/layers after new codec has been published,
18057
+ // so refresh subscribedCodecs after publish a new codec
18058
+ setTimeout(() => {
18059
+ if (this.subscribedCodecs) {
18060
+ this.setPublishingCodecs(this.subscribedCodecs);
18061
+ }
18062
+ }, refreshSubscribedCodecAfterNewCodec);
18059
18063
  }
18060
18064
  /**
18061
- * Stop observing an ElementInfo for changes.
18062
- * @param elementInfo
18063
18065
  * @internal
18066
+ * Sets codecs that should be publishing
18064
18067
  */
18065
- stopObservingElementInfo(elementInfo) {
18066
- if (!this.isAdaptiveStream) {
18067
- livekitLogger.warn('stopObservingElementInfo ignored');
18068
- return;
18069
- }
18070
- const stopElementInfos = this.elementInfos.filter(info => info === elementInfo);
18071
- for (const info of stopElementInfos) {
18072
- info.stopObserving();
18073
- }
18074
- this.elementInfos = this.elementInfos.filter(info => info !== elementInfo);
18075
- this.updateVisibility();
18076
- this.debouncedHandleResize();
18077
- }
18078
- detach(element) {
18079
- let detachedElements = [];
18080
- if (element) {
18081
- this.stopObservingElement(element);
18082
- return super.detach(element);
18083
- }
18084
- detachedElements = super.detach();
18085
- for (const e of detachedElements) {
18086
- this.stopObservingElement(e);
18087
- }
18088
- return detachedElements;
18089
- }
18090
- /** @internal */
18091
- getDecoderImplementation() {
18092
- var _a;
18093
- return (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.decoderImplementation;
18068
+ setPublishingCodecs(codecs) {
18069
+ var _a, codecs_1, codecs_1_1;
18070
+ var _b, e_5, _c, _d;
18071
+ return __awaiter(this, void 0, void 0, function* () {
18072
+ livekitLogger.debug('setting publishing codecs', {
18073
+ codecs,
18074
+ currentCodec: this.codec
18075
+ });
18076
+ // only enable simulcast codec for preference codec setted
18077
+ if (!this.codec && codecs.length > 0) {
18078
+ yield this.setPublishingLayers(codecs[0].qualities);
18079
+ return [];
18080
+ }
18081
+ this.subscribedCodecs = codecs;
18082
+ const newCodecs = [];
18083
+ try {
18084
+ for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
18085
+ _d = codecs_1_1.value;
18086
+ _a = false;
18087
+ const codec = _d;
18088
+ if (!this.codec || this.codec === codec.codec) {
18089
+ yield this.setPublishingLayers(codec.qualities);
18090
+ } else {
18091
+ const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
18092
+ livekitLogger.debug("try setPublishingCodec for ".concat(codec.codec), simulcastCodecInfo);
18093
+ if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
18094
+ for (const q of codec.qualities) {
18095
+ if (q.enabled) {
18096
+ newCodecs.push(codec.codec);
18097
+ break;
18098
+ }
18099
+ }
18100
+ } else if (simulcastCodecInfo.encodings) {
18101
+ livekitLogger.debug("try setPublishingLayersForSender ".concat(codec.codec));
18102
+ yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock);
18103
+ }
18104
+ }
18105
+ }
18106
+ } catch (e_5_1) {
18107
+ e_5 = {
18108
+ error: e_5_1
18109
+ };
18110
+ } finally {
18111
+ try {
18112
+ if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
18113
+ } finally {
18114
+ if (e_5) throw e_5.error;
18115
+ }
18116
+ }
18117
+ return newCodecs;
18118
+ });
18094
18119
  }
18095
- getReceiverStats() {
18120
+ /**
18121
+ * @internal
18122
+ * Sets layers that should be publishing
18123
+ */
18124
+ setPublishingLayers(qualities) {
18096
18125
  return __awaiter(this, void 0, void 0, function* () {
18097
- if (!this.receiver || !this.receiver.getStats) {
18126
+ livekitLogger.debug('setting publishing layers', qualities);
18127
+ if (!this.sender || !this.encodings) {
18098
18128
  return;
18099
18129
  }
18100
- const stats = yield this.receiver.getStats();
18101
- let receiverStats;
18102
- stats.forEach(v => {
18103
- if (v.type === 'inbound-rtp') {
18104
- receiverStats = {
18105
- type: 'video',
18106
- framesDecoded: v.framesDecoded,
18107
- framesDropped: v.framesDropped,
18108
- framesReceived: v.framesReceived,
18109
- packetsReceived: v.packetsReceived,
18110
- packetsLost: v.packetsLost,
18111
- frameWidth: v.frameWidth,
18112
- frameHeight: v.frameHeight,
18113
- pliCount: v.pliCount,
18114
- firCount: v.firCount,
18115
- nackCount: v.nackCount,
18116
- jitter: v.jitter,
18117
- timestamp: v.timestamp,
18118
- bytesReceived: v.bytesReceived,
18119
- decoderImplementation: v.decoderImplementation
18120
- };
18121
- }
18122
- });
18123
- return receiverStats;
18130
+ yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock);
18124
18131
  });
18125
18132
  }
18126
- stopObservingElement(element) {
18127
- const stopElementInfos = this.elementInfos.filter(info => info.element === element);
18128
- for (const info of stopElementInfos) {
18129
- this.stopObservingElementInfo(info);
18130
- }
18131
- }
18132
18133
  handleAppVisibilityChanged() {
18133
18134
  const _super = Object.create(null, {
18134
18135
  handleAppVisibilityChanged: {
@@ -18137,1337 +18138,1574 @@ class RemoteVideoTrack extends RemoteTrack {
18137
18138
  });
18138
18139
  return __awaiter(this, void 0, void 0, function* () {
18139
18140
  yield _super.handleAppVisibilityChanged.call(this);
18140
- if (!this.isAdaptiveStream) return;
18141
- this.updateVisibility();
18141
+ if (!isMobile()) return;
18142
+ if (this.isInBackground && this.source === Track.Source.Camera) {
18143
+ this._mediaStreamTrack.enabled = false;
18144
+ }
18142
18145
  });
18143
18146
  }
18144
- updateVisibility() {
18145
- var _a, _b;
18146
- const lastVisibilityChange = this.elementInfos.reduce((prev, info) => Math.max(prev, info.visibilityChangedAt || 0), 0);
18147
- const backgroundPause = ((_b = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pauseVideoInBackground) !== null && _b !== void 0 ? _b : true // default to true
18148
- ) ? this.isInBackground : false;
18149
- const isPiPMode = this.elementInfos.some(info => info.pictureInPicture);
18150
- const isVisible = this.elementInfos.some(info => info.visible) && !backgroundPause || isPiPMode;
18151
- if (this.lastVisible === isVisible) {
18152
- return;
18153
- }
18154
- if (!isVisible && Date.now() - lastVisibilityChange < REACTION_DELAY) {
18155
- // delay hidden events
18156
- CriticalTimers.setTimeout(() => {
18157
- this.updateVisibility();
18158
- }, REACTION_DELAY);
18159
- return;
18160
- }
18161
- this.lastVisible = isVisible;
18162
- this.emit(TrackEvent.VisibilityChanged, isVisible, this);
18163
- }
18164
- updateDimensions() {
18165
- var _a, _b;
18166
- let maxWidth = 0;
18167
- let maxHeight = 0;
18168
- const pixelDensity = this.getPixelDensity();
18169
- for (const info of this.elementInfos) {
18170
- const currentElementWidth = info.width() * pixelDensity;
18171
- const currentElementHeight = info.height() * pixelDensity;
18172
- if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
18173
- maxWidth = currentElementWidth;
18174
- maxHeight = currentElementHeight;
18147
+ }
18148
+ function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock) {
18149
+ return __awaiter(this, void 0, void 0, function* () {
18150
+ const unlock = yield senderLock.lock();
18151
+ livekitLogger.debug('setPublishingLayersForSender', {
18152
+ sender,
18153
+ qualities,
18154
+ senderEncodings
18155
+ });
18156
+ try {
18157
+ const params = sender.getParameters();
18158
+ const {
18159
+ encodings
18160
+ } = params;
18161
+ if (!encodings) {
18162
+ return;
18175
18163
  }
18176
- }
18177
- if (((_a = this.lastDimensions) === null || _a === void 0 ? void 0 : _a.width) === maxWidth && ((_b = this.lastDimensions) === null || _b === void 0 ? void 0 : _b.height) === maxHeight) {
18178
- return;
18179
- }
18180
- this.lastDimensions = {
18181
- width: maxWidth,
18182
- height: maxHeight
18183
- };
18184
- this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
18185
- }
18186
- getPixelDensity() {
18187
- var _a;
18188
- const pixelDensity = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pixelDensity;
18189
- if (pixelDensity === 'screen') {
18190
- return getDevicePixelRatio();
18191
- } else if (!pixelDensity) {
18192
- // when unset, we'll pick a sane default here.
18193
- // for higher pixel density devices (mobile phones, etc), we'll use 2
18194
- // otherwise it defaults to 1
18195
- const devicePixelRatio = getDevicePixelRatio();
18196
- if (devicePixelRatio > 2) {
18197
- return 2;
18198
- } else {
18199
- return 1;
18164
+ if (encodings.length !== senderEncodings.length) {
18165
+ livekitLogger.warn('cannot set publishing layers, encodings mismatch');
18166
+ return;
18167
+ }
18168
+ let hasChanged = false;
18169
+ /* disable closable spatial layer as it has video blur / frozen issue with current server / client
18170
+ 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
18171
+ low resolution frame and recover very quickly, but noticable
18172
+ 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
18173
+ const closableSpatial = false;
18174
+ /* @ts-ignore */
18175
+ if (closableSpatial && encodings[0].scalabilityMode) ; else {
18176
+ // simulcast dynacast encodings
18177
+ encodings.forEach((encoding, idx) => {
18178
+ var _a;
18179
+ let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
18180
+ if (rid === '') {
18181
+ rid = 'q';
18182
+ }
18183
+ const quality = videoQualityForRid(rid);
18184
+ const subscribedQuality = qualities.find(q => q.quality === quality);
18185
+ if (!subscribedQuality) {
18186
+ return;
18187
+ }
18188
+ if (encoding.active !== subscribedQuality.enabled) {
18189
+ hasChanged = true;
18190
+ encoding.active = subscribedQuality.enabled;
18191
+ livekitLogger.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'));
18192
+ // FireFox does not support setting encoding.active to false, so we
18193
+ // have a workaround of lowering its bitrate and resolution to the min.
18194
+ if (isFireFox()) {
18195
+ if (subscribedQuality.enabled) {
18196
+ encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
18197
+ encoding.maxBitrate = senderEncodings[idx].maxBitrate;
18198
+ /* @ts-ignore */
18199
+ encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
18200
+ } else {
18201
+ encoding.scaleResolutionDownBy = 4;
18202
+ encoding.maxBitrate = 10;
18203
+ /* @ts-ignore */
18204
+ encoding.maxFrameRate = 2;
18205
+ }
18206
+ }
18207
+ }
18208
+ });
18209
+ }
18210
+ if (hasChanged) {
18211
+ params.encodings = encodings;
18212
+ livekitLogger.debug("setting encodings", params.encodings);
18213
+ yield sender.setParameters(params);
18200
18214
  }
18215
+ } finally {
18216
+ unlock();
18201
18217
  }
18202
- return pixelDensity;
18203
- }
18218
+ });
18204
18219
  }
18205
- class HTMLElementInfo {
18206
- get visible() {
18207
- return this.isPiP || this.isIntersecting;
18208
- }
18209
- get pictureInPicture() {
18210
- return this.isPiP;
18211
- }
18212
- constructor(element, visible) {
18213
- this.onVisibilityChanged = entry => {
18214
- var _a;
18215
- const {
18216
- target,
18217
- isIntersecting
18218
- } = entry;
18219
- if (target === this.element) {
18220
- this.isIntersecting = isIntersecting;
18221
- this.visibilityChangedAt = Date.now();
18222
- (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18223
- }
18224
- };
18225
- this.onEnterPiP = () => {
18226
- var _a;
18227
- this.isPiP = true;
18228
- (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18229
- };
18230
- this.onLeavePiP = () => {
18231
- var _a;
18232
- this.isPiP = false;
18233
- (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18234
- };
18235
- this.element = element;
18236
- this.isIntersecting = visible !== null && visible !== void 0 ? visible : isElementInViewport(element);
18237
- this.isPiP = isWeb() && document.pictureInPictureElement === element;
18238
- this.visibilityChangedAt = 0;
18239
- }
18240
- width() {
18241
- return this.element.clientWidth;
18242
- }
18243
- height() {
18244
- return this.element.clientHeight;
18245
- }
18246
- observe() {
18247
- // make sure we update the current visible state once we start to observe
18248
- this.isIntersecting = isElementInViewport(this.element);
18249
- this.isPiP = document.pictureInPictureElement === this.element;
18250
- this.element.handleResize = () => {
18251
- var _a;
18252
- (_a = this.handleResize) === null || _a === void 0 ? void 0 : _a.call(this);
18253
- };
18254
- this.element.handleVisibilityChanged = this.onVisibilityChanged;
18255
- getIntersectionObserver().observe(this.element);
18256
- getResizeObserver().observe(this.element);
18257
- this.element.addEventListener('enterpictureinpicture', this.onEnterPiP);
18258
- this.element.addEventListener('leavepictureinpicture', this.onLeavePiP);
18259
- }
18260
- stopObserving() {
18261
- var _a, _b;
18262
- (_a = getIntersectionObserver()) === null || _a === void 0 ? void 0 : _a.unobserve(this.element);
18263
- (_b = getResizeObserver()) === null || _b === void 0 ? void 0 : _b.unobserve(this.element);
18264
- this.element.removeEventListener('enterpictureinpicture', this.onEnterPiP);
18265
- this.element.removeEventListener('leavepictureinpicture', this.onLeavePiP);
18220
+ function videoQualityForRid(rid) {
18221
+ switch (rid) {
18222
+ case 'f':
18223
+ return VideoQuality.HIGH;
18224
+ case 'h':
18225
+ return VideoQuality.MEDIUM;
18226
+ case 'q':
18227
+ return VideoQuality.LOW;
18228
+ default:
18229
+ return VideoQuality.HIGH;
18266
18230
  }
18267
18231
  }
18268
- // does not account for occlusion by other elements
18269
- function isElementInViewport(el) {
18270
- let top = el.offsetTop;
18271
- let left = el.offsetLeft;
18272
- const width = el.offsetWidth;
18273
- const height = el.offsetHeight;
18274
- const {
18275
- hidden
18276
- } = el;
18277
- const {
18278
- opacity,
18279
- display
18280
- } = getComputedStyle(el);
18281
- while (el.offsetParent) {
18282
- el = el.offsetParent;
18283
- top += el.offsetTop;
18284
- left += el.offsetLeft;
18232
+ function videoLayersFromEncodings(width, height, encodings, svc) {
18233
+ // default to a single layer, HQ
18234
+ if (!encodings) {
18235
+ return [new VideoLayer({
18236
+ quality: VideoQuality.HIGH,
18237
+ width,
18238
+ height,
18239
+ bitrate: 0,
18240
+ ssrc: 0
18241
+ })];
18285
18242
  }
18286
- return top < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && top + height > window.pageYOffset && left + width > window.pageXOffset && !hidden && (opacity !== '' ? parseFloat(opacity) > 0 : true) && display !== 'none';
18243
+ if (svc) {
18244
+ // svc layers
18245
+ /* @ts-ignore */
18246
+ const encodingSM = encodings[0].scalabilityMode;
18247
+ const sm = new ScalabilityMode(encodingSM);
18248
+ const layers = [];
18249
+ for (let i = 0; i < sm.spatial; i += 1) {
18250
+ layers.push(new VideoLayer({
18251
+ quality: VideoQuality.HIGH - i,
18252
+ width: Math.ceil(width / Math.pow(2, i)),
18253
+ height: Math.ceil(height / Math.pow(2, i)),
18254
+ bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(3, i)) : 0,
18255
+ ssrc: 0
18256
+ }));
18257
+ }
18258
+ return layers;
18259
+ }
18260
+ return encodings.map(encoding => {
18261
+ var _a, _b, _c;
18262
+ const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
18263
+ let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
18264
+ return new VideoLayer({
18265
+ quality,
18266
+ width: Math.ceil(width / scale),
18267
+ height: Math.ceil(height / scale),
18268
+ bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
18269
+ ssrc: 0
18270
+ });
18271
+ });
18287
18272
  }
18288
18273
 
18289
- class TrackPublication extends eventsExports.EventEmitter {
18290
- constructor(kind, id, name) {
18291
- super();
18292
- this.metadataMuted = false;
18293
- this.encryption = Encryption_Type.NONE;
18294
- this.handleMuted = () => {
18295
- this.emit(TrackEvent.Muted);
18296
- };
18297
- this.handleUnmuted = () => {
18298
- this.emit(TrackEvent.Unmuted);
18299
- };
18300
- this.setMaxListeners(100);
18301
- this.kind = kind;
18302
- this.trackSid = id;
18303
- this.trackName = name;
18304
- this.source = Track.Source.Unknown;
18274
+ class RemoteTrack extends Track {
18275
+ constructor(mediaTrack, sid, kind, receiver) {
18276
+ super(mediaTrack, kind);
18277
+ this.sid = sid;
18278
+ this.receiver = receiver;
18305
18279
  }
18306
18280
  /** @internal */
18307
- setTrack(track) {
18308
- if (this.track) {
18309
- this.track.off(TrackEvent.Muted, this.handleMuted);
18310
- this.track.off(TrackEvent.Unmuted, this.handleUnmuted);
18311
- }
18312
- this.track = track;
18313
- if (track) {
18314
- // forward events
18315
- track.on(TrackEvent.Muted, this.handleMuted);
18316
- track.on(TrackEvent.Unmuted, this.handleUnmuted);
18281
+ setMuted(muted) {
18282
+ if (this.isMuted !== muted) {
18283
+ this.isMuted = muted;
18284
+ this._mediaStreamTrack.enabled = !muted;
18285
+ this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
18317
18286
  }
18318
18287
  }
18319
- get isMuted() {
18320
- return this.metadataMuted;
18288
+ /** @internal */
18289
+ setMediaStream(stream) {
18290
+ // this is needed to determine when the track is finished
18291
+ // we send each track down in its own MediaStream, so we can assume the
18292
+ // current track is the only one that can be removed.
18293
+ this.mediaStream = stream;
18294
+ stream.onremovetrack = () => {
18295
+ this.receiver = undefined;
18296
+ this._currentBitrate = 0;
18297
+ this.emit(TrackEvent.Ended, this);
18298
+ };
18321
18299
  }
18322
- get isEnabled() {
18323
- return true;
18300
+ start() {
18301
+ this.startMonitor();
18302
+ // use `enabled` of track to enable re-use of transceiver
18303
+ super.enable();
18324
18304
  }
18325
- get isSubscribed() {
18326
- return this.track !== undefined;
18305
+ stop() {
18306
+ this.stopMonitor();
18307
+ // use `enabled` of track to enable re-use of transceiver
18308
+ super.disable();
18327
18309
  }
18328
- get isEncrypted() {
18329
- return this.encryption !== Encryption_Type.NONE;
18310
+ /* @internal */
18311
+ startMonitor() {
18312
+ if (!this.monitorInterval) {
18313
+ this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
18314
+ }
18315
+ }
18316
+ }
18317
+
18318
+ class RemoteAudioTrack extends RemoteTrack {
18319
+ constructor(mediaTrack, sid, receiver, audioContext, audioOutput) {
18320
+ super(mediaTrack, sid, Track.Kind.Audio, receiver);
18321
+ this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
18322
+ if (!this.receiver) {
18323
+ this._currentBitrate = 0;
18324
+ return;
18325
+ }
18326
+ const stats = yield this.getReceiverStats();
18327
+ if (stats && this.prevStats && this.receiver) {
18328
+ this._currentBitrate = computeBitrate(stats, this.prevStats);
18329
+ }
18330
+ this.prevStats = stats;
18331
+ });
18332
+ this.audioContext = audioContext;
18333
+ this.webAudioPluginNodes = [];
18334
+ if (audioOutput) {
18335
+ this.sinkId = audioOutput.deviceId;
18336
+ }
18330
18337
  }
18331
18338
  /**
18332
- * an [AudioTrack] if this publication holds an audio track
18339
+ * sets the volume for all attached audio elements
18333
18340
  */
18334
- get audioTrack() {
18335
- if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
18336
- return this.track;
18341
+ setVolume(volume) {
18342
+ var _a;
18343
+ for (const el of this.attachedElements) {
18344
+ if (this.audioContext) {
18345
+ (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setTargetAtTime(volume, 0, 0.1);
18346
+ } else {
18347
+ el.volume = volume;
18348
+ }
18349
+ }
18350
+ if (isReactNative()) {
18351
+ // @ts-ignore
18352
+ this._mediaStreamTrack._setVolume(volume);
18337
18353
  }
18354
+ this.elementVolume = volume;
18338
18355
  }
18339
18356
  /**
18340
- * an [VideoTrack] if this publication holds a video track
18357
+ * gets the volume of attached audio elements (loudest)
18341
18358
  */
18342
- get videoTrack() {
18343
- if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
18344
- return this.track;
18359
+ getVolume() {
18360
+ if (this.elementVolume) {
18361
+ return this.elementVolume;
18345
18362
  }
18346
- }
18347
- /** @internal */
18348
- updateInfo(info) {
18349
- this.trackSid = info.sid;
18350
- this.trackName = info.name;
18351
- this.source = Track.sourceFromProto(info.source);
18352
- this.mimeType = info.mimeType;
18353
- if (this.kind === Track.Kind.Video && info.width > 0) {
18354
- this.dimensions = {
18355
- width: info.width,
18356
- height: info.height
18357
- };
18358
- this.simulcasted = info.simulcast;
18363
+ if (isReactNative()) {
18364
+ // RN volume value defaults to 1.0 if hasn't been changed.
18365
+ return 1.0;
18359
18366
  }
18360
- this.encryption = info.encryption;
18361
- this.trackInfo = info;
18362
- livekitLogger.debug('update publication info', {
18363
- info
18367
+ let highestVolume = 0;
18368
+ this.attachedElements.forEach(element => {
18369
+ if (element.volume > highestVolume) {
18370
+ highestVolume = element.volume;
18371
+ }
18364
18372
  });
18373
+ return highestVolume;
18365
18374
  }
18366
- }
18367
- (function (TrackPublication) {
18368
- (function (SubscriptionStatus) {
18369
- SubscriptionStatus["Desired"] = "desired";
18370
- SubscriptionStatus["Subscribed"] = "subscribed";
18371
- SubscriptionStatus["Unsubscribed"] = "unsubscribed";
18372
- })(TrackPublication.SubscriptionStatus || (TrackPublication.SubscriptionStatus = {}));
18373
- (function (PermissionStatus) {
18374
- PermissionStatus["Allowed"] = "allowed";
18375
- PermissionStatus["NotAllowed"] = "not_allowed";
18376
- })(TrackPublication.PermissionStatus || (TrackPublication.PermissionStatus = {}));
18377
- })(TrackPublication || (TrackPublication = {}));
18378
-
18379
- class LocalTrackPublication extends TrackPublication {
18380
- get isUpstreamPaused() {
18381
- var _a;
18382
- return (_a = this.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused;
18383
- }
18384
- constructor(kind, ti, track) {
18385
- super(kind, ti.sid, ti.name);
18386
- this.track = undefined;
18387
- this.handleTrackEnded = () => {
18388
- this.emit(TrackEvent.Ended);
18389
- };
18390
- this.updateInfo(ti);
18391
- this.setTrack(track);
18375
+ /**
18376
+ * calls setSinkId on all attached elements, if supported
18377
+ * @param deviceId audio output device
18378
+ */
18379
+ setSinkId(deviceId) {
18380
+ return __awaiter(this, void 0, void 0, function* () {
18381
+ this.sinkId = deviceId;
18382
+ yield Promise.all(this.attachedElements.map(elm => {
18383
+ if (!supportsSetSinkId(elm)) {
18384
+ return;
18385
+ }
18386
+ /* @ts-ignore */
18387
+ return elm.setSinkId(deviceId);
18388
+ }));
18389
+ });
18392
18390
  }
18393
- setTrack(track) {
18394
- if (this.track) {
18395
- this.track.off(TrackEvent.Ended, this.handleTrackEnded);
18391
+ attach(element) {
18392
+ const needsNewWebAudioConnection = this.attachedElements.length === 0;
18393
+ if (!element) {
18394
+ element = super.attach();
18395
+ } else {
18396
+ super.attach(element);
18396
18397
  }
18397
- super.setTrack(track);
18398
- if (track) {
18399
- track.on(TrackEvent.Ended, this.handleTrackEnded);
18398
+ if (this.elementVolume) {
18399
+ element.volume = this.elementVolume;
18400
+ }
18401
+ if (this.sinkId && supportsSetSinkId(element)) {
18402
+ /* @ts-ignore */
18403
+ element.setSinkId(this.sinkId);
18400
18404
  }
18401
- }
18402
- get isMuted() {
18403
- if (this.track) {
18404
- return this.track.isMuted;
18405
+ if (this.audioContext && needsNewWebAudioConnection) {
18406
+ livekitLogger.debug('using audio context mapping');
18407
+ this.connectWebAudio(this.audioContext, element);
18408
+ element.volume = 0;
18409
+ element.muted = true;
18405
18410
  }
18406
- return super.isMuted;
18407
- }
18408
- get audioTrack() {
18409
- return super.audioTrack;
18411
+ return element;
18410
18412
  }
18411
- get videoTrack() {
18412
- return super.videoTrack;
18413
+ detach(element) {
18414
+ let detached;
18415
+ if (!element) {
18416
+ detached = super.detach();
18417
+ this.disconnectWebAudio();
18418
+ } else {
18419
+ detached = super.detach(element);
18420
+ // if there are still any attached elements after detaching, connect webaudio to the first element that's left
18421
+ // disconnect webaudio otherwise
18422
+ if (this.audioContext) {
18423
+ if (this.attachedElements.length > 0) {
18424
+ this.connectWebAudio(this.audioContext, this.attachedElements[0]);
18425
+ } else {
18426
+ this.disconnectWebAudio();
18427
+ }
18428
+ }
18429
+ }
18430
+ return detached;
18413
18431
  }
18414
18432
  /**
18415
- * Mute the track associated with this publication
18433
+ * @internal
18434
+ * @experimental
18416
18435
  */
18417
- mute() {
18418
- var _a;
18419
- return __awaiter(this, void 0, void 0, function* () {
18420
- return (_a = this.track) === null || _a === void 0 ? void 0 : _a.mute();
18421
- });
18436
+ setAudioContext(audioContext) {
18437
+ this.audioContext = audioContext;
18438
+ if (audioContext && this.attachedElements.length > 0) {
18439
+ this.connectWebAudio(audioContext, this.attachedElements[0]);
18440
+ } else if (!audioContext) {
18441
+ this.disconnectWebAudio();
18442
+ }
18422
18443
  }
18423
18444
  /**
18424
- * Unmute track associated with this publication
18445
+ * @internal
18446
+ * @experimental
18447
+ * @param {AudioNode[]} nodes - An array of WebAudio nodes. These nodes should not be connected to each other when passed, as the sdk will take care of connecting them in the order of the array.
18425
18448
  */
18426
- unmute() {
18427
- var _a;
18428
- return __awaiter(this, void 0, void 0, function* () {
18429
- return (_a = this.track) === null || _a === void 0 ? void 0 : _a.unmute();
18430
- });
18449
+ setWebAudioPlugins(nodes) {
18450
+ this.webAudioPluginNodes = nodes;
18451
+ if (this.attachedElements.length > 0 && this.audioContext) {
18452
+ this.connectWebAudio(this.audioContext, this.attachedElements[0]);
18453
+ }
18431
18454
  }
18432
- /**
18433
- * Pauses the media stream track associated with this publication from being sent to the server
18434
- * and signals "muted" event to other participants
18435
- * Useful if you want to pause the stream without pausing the local media stream track
18436
- */
18437
- pauseUpstream() {
18438
- var _a;
18439
- return __awaiter(this, void 0, void 0, function* () {
18440
- yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.pauseUpstream();
18455
+ connectWebAudio(context, element) {
18456
+ this.disconnectWebAudio();
18457
+ // @ts-ignore attached elements always have a srcObject set
18458
+ this.sourceNode = context.createMediaStreamSource(element.srcObject);
18459
+ let lastNode = this.sourceNode;
18460
+ this.webAudioPluginNodes.forEach(node => {
18461
+ lastNode.connect(node);
18462
+ lastNode = node;
18441
18463
  });
18464
+ this.gainNode = context.createGain();
18465
+ lastNode.connect(this.gainNode);
18466
+ this.gainNode.connect(context.destination);
18467
+ if (this.elementVolume) {
18468
+ this.gainNode.gain.setTargetAtTime(this.elementVolume, 0, 0.1);
18469
+ }
18470
+ // try to resume the context if it isn't running already
18471
+ if (context.state !== 'running') {
18472
+ context.resume().then(() => {
18473
+ if (context.state !== 'running') {
18474
+ this.emit(TrackEvent.AudioPlaybackFailed, new Error("Audio Context couldn't be started automatically"));
18475
+ }
18476
+ }).catch(e => {
18477
+ this.emit(TrackEvent.AudioPlaybackFailed, e);
18478
+ });
18479
+ }
18442
18480
  }
18443
- /**
18444
- * Resumes sending the media stream track associated with this publication to the server after a call to [[pauseUpstream()]]
18445
- * and signals "unmuted" event to other participants (unless the track is explicitly muted)
18446
- */
18447
- resumeUpstream() {
18448
- var _a;
18481
+ disconnectWebAudio() {
18482
+ var _a, _b;
18483
+ (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.disconnect();
18484
+ (_b = this.sourceNode) === null || _b === void 0 ? void 0 : _b.disconnect();
18485
+ this.gainNode = undefined;
18486
+ this.sourceNode = undefined;
18487
+ }
18488
+ getReceiverStats() {
18449
18489
  return __awaiter(this, void 0, void 0, function* () {
18450
- yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.resumeUpstream();
18490
+ if (!this.receiver || !this.receiver.getStats) {
18491
+ return;
18492
+ }
18493
+ const stats = yield this.receiver.getStats();
18494
+ let receiverStats;
18495
+ stats.forEach(v => {
18496
+ if (v.type === 'inbound-rtp') {
18497
+ receiverStats = {
18498
+ type: 'audio',
18499
+ timestamp: v.timestamp,
18500
+ jitter: v.jitter,
18501
+ bytesReceived: v.bytesReceived,
18502
+ concealedSamples: v.concealedSamples,
18503
+ concealmentEvents: v.concealmentEvents,
18504
+ silentConcealedSamples: v.silentConcealedSamples,
18505
+ silentConcealmentEvents: v.silentConcealmentEvents,
18506
+ totalAudioEnergy: v.totalAudioEnergy,
18507
+ totalSamplesDuration: v.totalSamplesDuration
18508
+ };
18509
+ }
18510
+ });
18511
+ return receiverStats;
18451
18512
  });
18452
18513
  }
18453
18514
  }
18454
18515
 
18455
- var ConnectionQuality;
18456
- (function (ConnectionQuality) {
18457
- ConnectionQuality["Excellent"] = "excellent";
18458
- ConnectionQuality["Good"] = "good";
18459
- ConnectionQuality["Poor"] = "poor";
18460
- ConnectionQuality["Unknown"] = "unknown";
18461
- })(ConnectionQuality || (ConnectionQuality = {}));
18462
- function qualityFromProto(q) {
18463
- switch (q) {
18464
- case ConnectionQuality$1.EXCELLENT:
18465
- return ConnectionQuality.Excellent;
18466
- case ConnectionQuality$1.GOOD:
18467
- return ConnectionQuality.Good;
18468
- case ConnectionQuality$1.POOR:
18469
- return ConnectionQuality.Poor;
18470
- default:
18471
- return ConnectionQuality.Unknown;
18516
+ const REACTION_DELAY = 100;
18517
+ class RemoteVideoTrack extends RemoteTrack {
18518
+ constructor(mediaTrack, sid, receiver, adaptiveStreamSettings) {
18519
+ super(mediaTrack, sid, Track.Kind.Video, receiver);
18520
+ this.elementInfos = [];
18521
+ this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
18522
+ if (!this.receiver) {
18523
+ this._currentBitrate = 0;
18524
+ return;
18525
+ }
18526
+ const stats = yield this.getReceiverStats();
18527
+ if (stats && this.prevStats && this.receiver) {
18528
+ this._currentBitrate = computeBitrate(stats, this.prevStats);
18529
+ }
18530
+ this.prevStats = stats;
18531
+ });
18532
+ this.debouncedHandleResize = r(() => {
18533
+ this.updateDimensions();
18534
+ }, REACTION_DELAY);
18535
+ this.adaptiveStreamSettings = adaptiveStreamSettings;
18472
18536
  }
18473
- }
18474
- class Participant extends eventsExports.EventEmitter {
18475
- get isEncrypted() {
18476
- return this.tracks.size > 0 && Array.from(this.tracks.values()).every(tr => tr.isEncrypted);
18537
+ get isAdaptiveStream() {
18538
+ return this.adaptiveStreamSettings !== undefined;
18539
+ }
18540
+ /**
18541
+ * Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
18542
+ */
18543
+ get mediaStreamTrack() {
18544
+ return this._mediaStreamTrack;
18477
18545
  }
18478
18546
  /** @internal */
18479
- constructor(sid, identity, name, metadata) {
18480
- super();
18481
- /** audio level between 0-1.0, 1 being loudest, 0 being softest */
18482
- this.audioLevel = 0;
18483
- /** if participant is currently speaking */
18484
- this.isSpeaking = false;
18485
- this._connectionQuality = ConnectionQuality.Unknown;
18486
- this.setMaxListeners(100);
18487
- this.sid = sid;
18488
- this.identity = identity;
18489
- this.name = name;
18490
- this.metadata = metadata;
18491
- this.audioTracks = new Map();
18492
- this.videoTracks = new Map();
18493
- this.tracks = new Map();
18547
+ setMuted(muted) {
18548
+ super.setMuted(muted);
18549
+ this.attachedElements.forEach(element => {
18550
+ // detach or attach
18551
+ if (muted) {
18552
+ detachTrack(this._mediaStreamTrack, element);
18553
+ } else {
18554
+ attachToElement(this._mediaStreamTrack, element);
18555
+ }
18556
+ });
18494
18557
  }
18495
- getTracks() {
18496
- return Array.from(this.tracks.values());
18558
+ attach(element) {
18559
+ if (!element) {
18560
+ element = super.attach();
18561
+ } else {
18562
+ super.attach(element);
18563
+ }
18564
+ // It's possible attach is called multiple times on an element. When that's
18565
+ // the case, we'd want to avoid adding duplicate elementInfos
18566
+ if (this.adaptiveStreamSettings && this.elementInfos.find(info => info.element === element) === undefined) {
18567
+ const elementInfo = new HTMLElementInfo(element);
18568
+ this.observeElementInfo(elementInfo);
18569
+ }
18570
+ return element;
18497
18571
  }
18498
18572
  /**
18499
- * Finds the first track that matches the source filter, for example, getting
18500
- * the user's camera track with getTrackBySource(Track.Source.Camera).
18501
- * @param source
18502
- * @returns
18573
+ * Observe an ElementInfo for changes when adaptive streaming.
18574
+ * @param elementInfo
18575
+ * @internal
18503
18576
  */
18504
- getTrack(source) {
18505
- for (const [, pub] of this.tracks) {
18506
- if (pub.source === source) {
18507
- return pub;
18508
- }
18577
+ observeElementInfo(elementInfo) {
18578
+ if (this.adaptiveStreamSettings && this.elementInfos.find(info => info === elementInfo) === undefined) {
18579
+ elementInfo.handleResize = () => {
18580
+ this.debouncedHandleResize();
18581
+ };
18582
+ elementInfo.handleVisibilityChanged = () => {
18583
+ this.updateVisibility();
18584
+ };
18585
+ this.elementInfos.push(elementInfo);
18586
+ elementInfo.observe();
18587
+ // trigger the first resize update cycle
18588
+ // if the tab is backgrounded, the initial resize event does not fire until
18589
+ // the tab comes into focus for the first time.
18590
+ this.debouncedHandleResize();
18591
+ this.updateVisibility();
18592
+ } else {
18593
+ livekitLogger.warn('visibility resize observer not triggered');
18509
18594
  }
18510
18595
  }
18511
18596
  /**
18512
- * Finds the first track that matches the track's name.
18513
- * @param name
18514
- * @returns
18597
+ * Stop observing an ElementInfo for changes.
18598
+ * @param elementInfo
18599
+ * @internal
18515
18600
  */
18516
- getTrackByName(name) {
18517
- for (const [, pub] of this.tracks) {
18518
- if (pub.trackName === name) {
18519
- return pub;
18520
- }
18601
+ stopObservingElementInfo(elementInfo) {
18602
+ if (!this.isAdaptiveStream) {
18603
+ livekitLogger.warn('stopObservingElementInfo ignored');
18604
+ return;
18605
+ }
18606
+ const stopElementInfos = this.elementInfos.filter(info => info === elementInfo);
18607
+ for (const info of stopElementInfos) {
18608
+ info.stopObserving();
18521
18609
  }
18610
+ this.elementInfos = this.elementInfos.filter(info => info !== elementInfo);
18611
+ this.updateVisibility();
18612
+ this.debouncedHandleResize();
18522
18613
  }
18523
- get connectionQuality() {
18524
- return this._connectionQuality;
18614
+ detach(element) {
18615
+ let detachedElements = [];
18616
+ if (element) {
18617
+ this.stopObservingElement(element);
18618
+ return super.detach(element);
18619
+ }
18620
+ detachedElements = super.detach();
18621
+ for (const e of detachedElements) {
18622
+ this.stopObservingElement(e);
18623
+ }
18624
+ return detachedElements;
18525
18625
  }
18526
- get isCameraEnabled() {
18626
+ /** @internal */
18627
+ getDecoderImplementation() {
18527
18628
  var _a;
18528
- const track = this.getTrack(Track.Source.Camera);
18529
- return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
18629
+ return (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.decoderImplementation;
18530
18630
  }
18531
- get isMicrophoneEnabled() {
18532
- var _a;
18533
- const track = this.getTrack(Track.Source.Microphone);
18534
- return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
18631
+ getReceiverStats() {
18632
+ return __awaiter(this, void 0, void 0, function* () {
18633
+ if (!this.receiver || !this.receiver.getStats) {
18634
+ return;
18635
+ }
18636
+ const stats = yield this.receiver.getStats();
18637
+ let receiverStats;
18638
+ stats.forEach(v => {
18639
+ if (v.type === 'inbound-rtp') {
18640
+ receiverStats = {
18641
+ type: 'video',
18642
+ framesDecoded: v.framesDecoded,
18643
+ framesDropped: v.framesDropped,
18644
+ framesReceived: v.framesReceived,
18645
+ packetsReceived: v.packetsReceived,
18646
+ packetsLost: v.packetsLost,
18647
+ frameWidth: v.frameWidth,
18648
+ frameHeight: v.frameHeight,
18649
+ pliCount: v.pliCount,
18650
+ firCount: v.firCount,
18651
+ nackCount: v.nackCount,
18652
+ jitter: v.jitter,
18653
+ timestamp: v.timestamp,
18654
+ bytesReceived: v.bytesReceived,
18655
+ decoderImplementation: v.decoderImplementation
18656
+ };
18657
+ }
18658
+ });
18659
+ return receiverStats;
18660
+ });
18535
18661
  }
18536
- get isScreenShareEnabled() {
18537
- const track = this.getTrack(Track.Source.ScreenShare);
18538
- return !!track;
18662
+ stopObservingElement(element) {
18663
+ const stopElementInfos = this.elementInfos.filter(info => info.element === element);
18664
+ for (const info of stopElementInfos) {
18665
+ this.stopObservingElementInfo(info);
18666
+ }
18539
18667
  }
18540
- get isLocal() {
18541
- return false;
18668
+ handleAppVisibilityChanged() {
18669
+ const _super = Object.create(null, {
18670
+ handleAppVisibilityChanged: {
18671
+ get: () => super.handleAppVisibilityChanged
18672
+ }
18673
+ });
18674
+ return __awaiter(this, void 0, void 0, function* () {
18675
+ yield _super.handleAppVisibilityChanged.call(this);
18676
+ if (!this.isAdaptiveStream) return;
18677
+ this.updateVisibility();
18678
+ });
18542
18679
  }
18543
- /** when participant joined the room */
18544
- get joinedAt() {
18545
- if (this.participantInfo) {
18546
- return new Date(Number.parseInt(this.participantInfo.joinedAt.toString()) * 1000);
18680
+ updateVisibility() {
18681
+ var _a, _b;
18682
+ const lastVisibilityChange = this.elementInfos.reduce((prev, info) => Math.max(prev, info.visibilityChangedAt || 0), 0);
18683
+ const backgroundPause = ((_b = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pauseVideoInBackground) !== null && _b !== void 0 ? _b : true // default to true
18684
+ ) ? this.isInBackground : false;
18685
+ const isPiPMode = this.elementInfos.some(info => info.pictureInPicture);
18686
+ const isVisible = this.elementInfos.some(info => info.visible) && !backgroundPause || isPiPMode;
18687
+ if (this.lastVisible === isVisible) {
18688
+ return;
18547
18689
  }
18548
- return new Date();
18690
+ if (!isVisible && Date.now() - lastVisibilityChange < REACTION_DELAY) {
18691
+ // delay hidden events
18692
+ CriticalTimers.setTimeout(() => {
18693
+ this.updateVisibility();
18694
+ }, REACTION_DELAY);
18695
+ return;
18696
+ }
18697
+ this.lastVisible = isVisible;
18698
+ this.emit(TrackEvent.VisibilityChanged, isVisible, this);
18549
18699
  }
18550
- /** @internal */
18551
- updateInfo(info) {
18552
- // it's possible the update could be applied out of order due to await
18553
- // during reconnect sequences. when that happens, it's possible for server
18554
- // to have sent more recent version of participant info while JS is waiting
18555
- // to process the existing payload.
18556
- // when the participant sid remains the same, and we already have a later version
18557
- // of the payload, they can be safely skipped
18558
- if (this.participantInfo && this.participantInfo.sid === info.sid && this.participantInfo.version > info.version) {
18559
- return false;
18700
+ updateDimensions() {
18701
+ var _a, _b;
18702
+ let maxWidth = 0;
18703
+ let maxHeight = 0;
18704
+ const pixelDensity = this.getPixelDensity();
18705
+ for (const info of this.elementInfos) {
18706
+ const currentElementWidth = info.width() * pixelDensity;
18707
+ const currentElementHeight = info.height() * pixelDensity;
18708
+ if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
18709
+ maxWidth = currentElementWidth;
18710
+ maxHeight = currentElementHeight;
18711
+ }
18560
18712
  }
18561
- this.identity = info.identity;
18562
- this.sid = info.sid;
18563
- this.setName(info.name);
18564
- this.setMetadata(info.metadata);
18565
- if (info.permission) {
18566
- this.setPermissions(info.permission);
18713
+ if (((_a = this.lastDimensions) === null || _a === void 0 ? void 0 : _a.width) === maxWidth && ((_b = this.lastDimensions) === null || _b === void 0 ? void 0 : _b.height) === maxHeight) {
18714
+ return;
18567
18715
  }
18568
- // set this last so setMetadata can detect changes
18569
- this.participantInfo = info;
18570
- livekitLogger.trace('update participant info', {
18571
- info
18572
- });
18573
- return true;
18716
+ this.lastDimensions = {
18717
+ width: maxWidth,
18718
+ height: maxHeight
18719
+ };
18720
+ this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
18574
18721
  }
18575
- /** @internal */
18576
- setMetadata(md) {
18577
- const changed = this.metadata !== md;
18578
- const prevMetadata = this.metadata;
18579
- this.metadata = md;
18580
- if (changed) {
18581
- this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
18722
+ getPixelDensity() {
18723
+ var _a;
18724
+ const pixelDensity = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pixelDensity;
18725
+ if (pixelDensity === 'screen') {
18726
+ return getDevicePixelRatio();
18727
+ } else if (!pixelDensity) {
18728
+ // when unset, we'll pick a sane default here.
18729
+ // for higher pixel density devices (mobile phones, etc), we'll use 2
18730
+ // otherwise it defaults to 1
18731
+ const devicePixelRatio = getDevicePixelRatio();
18732
+ if (devicePixelRatio > 2) {
18733
+ return 2;
18734
+ } else {
18735
+ return 1;
18736
+ }
18582
18737
  }
18738
+ return pixelDensity;
18583
18739
  }
18584
- setName(name) {
18585
- const changed = this.name !== name;
18586
- this.name = name;
18587
- if (changed) {
18588
- this.emit(ParticipantEvent.ParticipantNameChanged, name);
18589
- }
18740
+ }
18741
+ class HTMLElementInfo {
18742
+ get visible() {
18743
+ return this.isPiP || this.isIntersecting;
18590
18744
  }
18591
- /** @internal */
18592
- setPermissions(permissions) {
18593
- var _a, _b, _c, _d, _e;
18594
- const prevPermissions = this.permissions;
18595
- const changed = permissions.canPublish !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublish) || permissions.canSubscribe !== ((_b = this.permissions) === null || _b === void 0 ? void 0 : _b.canSubscribe) || permissions.canPublishData !== ((_c = this.permissions) === null || _c === void 0 ? void 0 : _c.canPublishData) || permissions.hidden !== ((_d = this.permissions) === null || _d === void 0 ? void 0 : _d.hidden) || permissions.recorder !== ((_e = this.permissions) === null || _e === void 0 ? void 0 : _e.recorder) || permissions.canPublishSources.length !== this.permissions.canPublishSources.length || permissions.canPublishSources.some((value, index) => {
18745
+ get pictureInPicture() {
18746
+ return this.isPiP;
18747
+ }
18748
+ constructor(element, visible) {
18749
+ this.onVisibilityChanged = entry => {
18596
18750
  var _a;
18597
- return value !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublishSources[index]);
18598
- });
18599
- this.permissions = permissions;
18600
- if (changed) {
18601
- this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
18602
- }
18603
- return changed;
18751
+ const {
18752
+ target,
18753
+ isIntersecting
18754
+ } = entry;
18755
+ if (target === this.element) {
18756
+ this.isIntersecting = isIntersecting;
18757
+ this.visibilityChangedAt = Date.now();
18758
+ (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18759
+ }
18760
+ };
18761
+ this.onEnterPiP = () => {
18762
+ var _a;
18763
+ this.isPiP = true;
18764
+ (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18765
+ };
18766
+ this.onLeavePiP = () => {
18767
+ var _a;
18768
+ this.isPiP = false;
18769
+ (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
18770
+ };
18771
+ this.element = element;
18772
+ this.isIntersecting = visible !== null && visible !== void 0 ? visible : isElementInViewport(element);
18773
+ this.isPiP = isWeb() && document.pictureInPictureElement === element;
18774
+ this.visibilityChangedAt = 0;
18604
18775
  }
18605
- /** @internal */
18606
- setIsSpeaking(speaking) {
18607
- if (speaking === this.isSpeaking) {
18608
- return;
18609
- }
18610
- this.isSpeaking = speaking;
18611
- if (speaking) {
18612
- this.lastSpokeAt = new Date();
18613
- }
18614
- this.emit(ParticipantEvent.IsSpeakingChanged, speaking);
18776
+ width() {
18777
+ return this.element.clientWidth;
18615
18778
  }
18616
- /** @internal */
18617
- setConnectionQuality(q) {
18618
- const prevQuality = this._connectionQuality;
18619
- this._connectionQuality = qualityFromProto(q);
18620
- if (prevQuality !== this._connectionQuality) {
18621
- this.emit(ParticipantEvent.ConnectionQualityChanged, this._connectionQuality);
18622
- }
18779
+ height() {
18780
+ return this.element.clientHeight;
18781
+ }
18782
+ observe() {
18783
+ // make sure we update the current visible state once we start to observe
18784
+ this.isIntersecting = isElementInViewport(this.element);
18785
+ this.isPiP = document.pictureInPictureElement === this.element;
18786
+ this.element.handleResize = () => {
18787
+ var _a;
18788
+ (_a = this.handleResize) === null || _a === void 0 ? void 0 : _a.call(this);
18789
+ };
18790
+ this.element.handleVisibilityChanged = this.onVisibilityChanged;
18791
+ getIntersectionObserver().observe(this.element);
18792
+ getResizeObserver().observe(this.element);
18793
+ this.element.addEventListener('enterpictureinpicture', this.onEnterPiP);
18794
+ this.element.addEventListener('leavepictureinpicture', this.onLeavePiP);
18623
18795
  }
18624
- addTrackPublication(publication) {
18625
- // forward publication driven events
18626
- publication.on(TrackEvent.Muted, () => {
18627
- this.emit(ParticipantEvent.TrackMuted, publication);
18628
- });
18629
- publication.on(TrackEvent.Unmuted, () => {
18630
- this.emit(ParticipantEvent.TrackUnmuted, publication);
18631
- });
18632
- const pub = publication;
18633
- if (pub.track) {
18634
- pub.track.sid = publication.trackSid;
18635
- }
18636
- this.tracks.set(publication.trackSid, publication);
18637
- switch (publication.kind) {
18638
- case Track.Kind.Audio:
18639
- this.audioTracks.set(publication.trackSid, publication);
18640
- break;
18641
- case Track.Kind.Video:
18642
- this.videoTracks.set(publication.trackSid, publication);
18643
- break;
18644
- }
18796
+ stopObserving() {
18797
+ var _a, _b;
18798
+ (_a = getIntersectionObserver()) === null || _a === void 0 ? void 0 : _a.unobserve(this.element);
18799
+ (_b = getResizeObserver()) === null || _b === void 0 ? void 0 : _b.unobserve(this.element);
18800
+ this.element.removeEventListener('enterpictureinpicture', this.onEnterPiP);
18801
+ this.element.removeEventListener('leavepictureinpicture', this.onLeavePiP);
18645
18802
  }
18646
18803
  }
18647
-
18648
- function trackPermissionToProto(perms) {
18649
- var _a, _b, _c;
18650
- if (!perms.participantSid && !perms.participantIdentity) {
18651
- throw new Error('Invalid track permission, must provide at least one of participantIdentity and participantSid');
18804
+ // does not account for occlusion by other elements
18805
+ function isElementInViewport(el) {
18806
+ let top = el.offsetTop;
18807
+ let left = el.offsetLeft;
18808
+ const width = el.offsetWidth;
18809
+ const height = el.offsetHeight;
18810
+ const {
18811
+ hidden
18812
+ } = el;
18813
+ const {
18814
+ opacity,
18815
+ display
18816
+ } = getComputedStyle(el);
18817
+ while (el.offsetParent) {
18818
+ el = el.offsetParent;
18819
+ top += el.offsetTop;
18820
+ left += el.offsetLeft;
18652
18821
  }
18653
- return new TrackPermission({
18654
- participantIdentity: (_a = perms.participantIdentity) !== null && _a !== void 0 ? _a : '',
18655
- participantSid: (_b = perms.participantSid) !== null && _b !== void 0 ? _b : '',
18656
- allTracks: (_c = perms.allowAll) !== null && _c !== void 0 ? _c : false,
18657
- trackSids: perms.allowedTrackSids || []
18658
- });
18822
+ return top < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && top + height > window.pageYOffset && left + width > window.pageXOffset && !hidden && (opacity !== '' ? parseFloat(opacity) > 0 : true) && display !== 'none';
18659
18823
  }
18660
18824
 
18661
- class RemoteTrackPublication extends TrackPublication {
18662
- constructor(kind, ti, autoSubscribe) {
18663
- super(kind, ti.sid, ti.name);
18664
- this.track = undefined;
18665
- /** @internal */
18666
- this.allowed = true;
18667
- this.disabled = false;
18668
- this.currentVideoQuality = VideoQuality.HIGH;
18669
- this.handleEnded = track => {
18670
- this.setTrack(undefined);
18671
- this.emit(TrackEvent.Ended, track);
18672
- };
18673
- this.handleVisibilityChange = visible => {
18674
- livekitLogger.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), {
18675
- trackSid: this.trackSid
18676
- });
18677
- this.disabled = !visible;
18678
- this.emitTrackUpdate();
18825
+ class TrackPublication extends eventsExports.EventEmitter {
18826
+ constructor(kind, id, name) {
18827
+ super();
18828
+ this.metadataMuted = false;
18829
+ this.encryption = Encryption_Type.NONE;
18830
+ this.handleMuted = () => {
18831
+ this.emit(TrackEvent.Muted);
18679
18832
  };
18680
- this.handleVideoDimensionsChange = dimensions => {
18681
- livekitLogger.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), {
18682
- trackSid: this.trackSid
18683
- });
18684
- this.videoDimensions = dimensions;
18685
- this.emitTrackUpdate();
18833
+ this.handleUnmuted = () => {
18834
+ this.emit(TrackEvent.Unmuted);
18686
18835
  };
18687
- this.subscribed = autoSubscribe;
18688
- this.updateInfo(ti);
18689
- }
18690
- /**
18691
- * Subscribe or unsubscribe to this remote track
18692
- * @param subscribed true to subscribe to a track, false to unsubscribe
18693
- */
18694
- setSubscribed(subscribed) {
18695
- const prevStatus = this.subscriptionStatus;
18696
- const prevPermission = this.permissionStatus;
18697
- this.subscribed = subscribed;
18698
- // reset allowed status when desired subscription state changes
18699
- // server will notify client via signal message if it's not allowed
18700
- if (subscribed) {
18701
- this.allowed = true;
18702
- }
18703
- const sub = new UpdateSubscription({
18704
- trackSids: [this.trackSid],
18705
- subscribe: this.subscribed,
18706
- participantTracks: [new ParticipantTracks({
18707
- // sending an empty participant id since TrackPublication doesn't keep it
18708
- // this is filled in by the participant that receives this message
18709
- participantSid: '',
18710
- trackSids: [this.trackSid]
18711
- })]
18712
- });
18713
- this.emit(TrackEvent.UpdateSubscription, sub);
18714
- this.emitSubscriptionUpdateIfChanged(prevStatus);
18715
- this.emitPermissionUpdateIfChanged(prevPermission);
18836
+ this.setMaxListeners(100);
18837
+ this.kind = kind;
18838
+ this.trackSid = id;
18839
+ this.trackName = name;
18840
+ this.source = Track.Source.Unknown;
18716
18841
  }
18717
- get subscriptionStatus() {
18718
- if (this.subscribed === false) {
18719
- return TrackPublication.SubscriptionStatus.Unsubscribed;
18842
+ /** @internal */
18843
+ setTrack(track) {
18844
+ if (this.track) {
18845
+ this.track.off(TrackEvent.Muted, this.handleMuted);
18846
+ this.track.off(TrackEvent.Unmuted, this.handleUnmuted);
18720
18847
  }
18721
- if (!super.isSubscribed) {
18722
- return TrackPublication.SubscriptionStatus.Desired;
18848
+ this.track = track;
18849
+ if (track) {
18850
+ // forward events
18851
+ track.on(TrackEvent.Muted, this.handleMuted);
18852
+ track.on(TrackEvent.Unmuted, this.handleUnmuted);
18723
18853
  }
18724
- return TrackPublication.SubscriptionStatus.Subscribed;
18725
18854
  }
18726
- get permissionStatus() {
18727
- return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
18855
+ get isMuted() {
18856
+ return this.metadataMuted;
18728
18857
  }
18729
- /**
18730
- * Returns true if track is subscribed, and ready for playback
18731
- */
18732
- get isSubscribed() {
18733
- if (this.subscribed === false) {
18734
- return false;
18735
- }
18736
- return super.isSubscribed;
18858
+ get isEnabled() {
18859
+ return true;
18737
18860
  }
18738
- // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
18739
- get isDesired() {
18740
- return this.subscribed !== false;
18861
+ get isSubscribed() {
18862
+ return this.track !== undefined;
18741
18863
  }
18742
- get isEnabled() {
18743
- return !this.disabled;
18864
+ get isEncrypted() {
18865
+ return this.encryption !== Encryption_Type.NONE;
18744
18866
  }
18745
18867
  /**
18746
- * disable server from sending down data for this track. this is useful when
18747
- * the participant is off screen, you may disable streaming down their video
18748
- * to reduce bandwidth requirements
18749
- * @param enabled
18868
+ * an [AudioTrack] if this publication holds an audio track
18750
18869
  */
18751
- setEnabled(enabled) {
18752
- if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
18753
- return;
18870
+ get audioTrack() {
18871
+ if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
18872
+ return this.track;
18754
18873
  }
18755
- this.disabled = !enabled;
18756
- this.emitTrackUpdate();
18757
18874
  }
18758
18875
  /**
18759
- * for tracks that support simulcasting, adjust subscribed quality
18760
- *
18761
- * This indicates the highest quality the client can accept. if network
18762
- * bandwidth does not allow, server will automatically reduce quality to
18763
- * optimize for uninterrupted video
18876
+ * an [VideoTrack] if this publication holds a video track
18764
18877
  */
18765
- setVideoQuality(quality) {
18766
- if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
18767
- return;
18878
+ get videoTrack() {
18879
+ if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
18880
+ return this.track;
18768
18881
  }
18769
- this.currentVideoQuality = quality;
18770
- this.videoDimensions = undefined;
18771
- this.emitTrackUpdate();
18772
18882
  }
18773
- setVideoDimensions(dimensions) {
18774
- var _a, _b;
18775
- if (!this.isManualOperationAllowed()) {
18776
- return;
18777
- }
18778
- if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
18779
- return;
18780
- }
18781
- if (this.track instanceof RemoteVideoTrack) {
18782
- this.videoDimensions = dimensions;
18883
+ /** @internal */
18884
+ updateInfo(info) {
18885
+ this.trackSid = info.sid;
18886
+ this.trackName = info.name;
18887
+ this.source = Track.sourceFromProto(info.source);
18888
+ this.mimeType = info.mimeType;
18889
+ if (this.kind === Track.Kind.Video && info.width > 0) {
18890
+ this.dimensions = {
18891
+ width: info.width,
18892
+ height: info.height
18893
+ };
18894
+ this.simulcasted = info.simulcast;
18783
18895
  }
18784
- this.currentVideoQuality = undefined;
18785
- this.emitTrackUpdate();
18896
+ this.encryption = info.encryption;
18897
+ this.trackInfo = info;
18898
+ livekitLogger.debug('update publication info', {
18899
+ info
18900
+ });
18786
18901
  }
18787
- setVideoFPS(fps) {
18788
- if (!this.isManualOperationAllowed()) {
18789
- return;
18790
- }
18791
- if (!(this.track instanceof RemoteVideoTrack)) {
18792
- return;
18793
- }
18794
- if (this.fps === fps) {
18795
- return;
18796
- }
18797
- this.fps = fps;
18798
- this.emitTrackUpdate();
18902
+ }
18903
+ (function (TrackPublication) {
18904
+ (function (SubscriptionStatus) {
18905
+ SubscriptionStatus["Desired"] = "desired";
18906
+ SubscriptionStatus["Subscribed"] = "subscribed";
18907
+ SubscriptionStatus["Unsubscribed"] = "unsubscribed";
18908
+ })(TrackPublication.SubscriptionStatus || (TrackPublication.SubscriptionStatus = {}));
18909
+ (function (PermissionStatus) {
18910
+ PermissionStatus["Allowed"] = "allowed";
18911
+ PermissionStatus["NotAllowed"] = "not_allowed";
18912
+ })(TrackPublication.PermissionStatus || (TrackPublication.PermissionStatus = {}));
18913
+ })(TrackPublication || (TrackPublication = {}));
18914
+
18915
+ class LocalTrackPublication extends TrackPublication {
18916
+ get isUpstreamPaused() {
18917
+ var _a;
18918
+ return (_a = this.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused;
18799
18919
  }
18800
- get videoQuality() {
18801
- return this.currentVideoQuality;
18920
+ constructor(kind, ti, track) {
18921
+ super(kind, ti.sid, ti.name);
18922
+ this.track = undefined;
18923
+ this.handleTrackEnded = () => {
18924
+ this.emit(TrackEvent.Ended);
18925
+ };
18926
+ this.updateInfo(ti);
18927
+ this.setTrack(track);
18802
18928
  }
18803
- /** @internal */
18804
18929
  setTrack(track) {
18805
- const prevStatus = this.subscriptionStatus;
18806
- const prevPermission = this.permissionStatus;
18807
- const prevTrack = this.track;
18808
- if (prevTrack === track) {
18809
- return;
18810
- }
18811
- if (prevTrack) {
18812
- // unregister listener
18813
- prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
18814
- prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
18815
- prevTrack.off(TrackEvent.Ended, this.handleEnded);
18816
- prevTrack.detach();
18817
- prevTrack.stopMonitor();
18818
- this.emit(TrackEvent.Unsubscribed, prevTrack);
18930
+ if (this.track) {
18931
+ this.track.off(TrackEvent.Ended, this.handleTrackEnded);
18819
18932
  }
18820
18933
  super.setTrack(track);
18821
18934
  if (track) {
18822
- track.sid = this.trackSid;
18823
- track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
18824
- track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
18825
- track.on(TrackEvent.Ended, this.handleEnded);
18826
- this.emit(TrackEvent.Subscribed, track);
18935
+ track.on(TrackEvent.Ended, this.handleTrackEnded);
18827
18936
  }
18828
- this.emitPermissionUpdateIfChanged(prevPermission);
18829
- this.emitSubscriptionUpdateIfChanged(prevStatus);
18830
- }
18831
- /** @internal */
18832
- setAllowed(allowed) {
18833
- const prevStatus = this.subscriptionStatus;
18834
- const prevPermission = this.permissionStatus;
18835
- this.allowed = allowed;
18836
- this.emitPermissionUpdateIfChanged(prevPermission);
18837
- this.emitSubscriptionUpdateIfChanged(prevStatus);
18838
- }
18839
- /** @internal */
18840
- setSubscriptionError(error) {
18841
- this.emit(TrackEvent.SubscriptionFailed, error);
18842
18937
  }
18843
- /** @internal */
18844
- updateInfo(info) {
18845
- super.updateInfo(info);
18846
- const prevMetadataMuted = this.metadataMuted;
18847
- this.metadataMuted = info.muted;
18938
+ get isMuted() {
18848
18939
  if (this.track) {
18849
- this.track.setMuted(info.muted);
18850
- } else if (prevMetadataMuted !== info.muted) {
18851
- this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
18940
+ return this.track.isMuted;
18852
18941
  }
18942
+ return super.isMuted;
18853
18943
  }
18854
- emitSubscriptionUpdateIfChanged(previousStatus) {
18855
- const currentStatus = this.subscriptionStatus;
18856
- if (previousStatus === currentStatus) {
18857
- return;
18858
- }
18859
- this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
18944
+ get audioTrack() {
18945
+ return super.audioTrack;
18860
18946
  }
18861
- emitPermissionUpdateIfChanged(previousPermissionStatus) {
18862
- const currentPermissionStatus = this.permissionStatus;
18863
- if (currentPermissionStatus !== previousPermissionStatus) {
18864
- this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
18865
- }
18947
+ get videoTrack() {
18948
+ return super.videoTrack;
18866
18949
  }
18867
- isManualOperationAllowed() {
18868
- if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
18869
- livekitLogger.warn('adaptive stream is enabled, cannot change video track settings', {
18870
- trackSid: this.trackSid
18871
- });
18872
- return false;
18873
- }
18874
- if (!this.isDesired) {
18875
- livekitLogger.warn('cannot update track settings when not subscribed', {
18876
- trackSid: this.trackSid
18877
- });
18878
- return false;
18879
- }
18880
- return true;
18950
+ /**
18951
+ * Mute the track associated with this publication
18952
+ */
18953
+ mute() {
18954
+ var _a;
18955
+ return __awaiter(this, void 0, void 0, function* () {
18956
+ return (_a = this.track) === null || _a === void 0 ? void 0 : _a.mute();
18957
+ });
18881
18958
  }
18882
- get isAdaptiveStream() {
18883
- return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
18959
+ /**
18960
+ * Unmute track associated with this publication
18961
+ */
18962
+ unmute() {
18963
+ var _a;
18964
+ return __awaiter(this, void 0, void 0, function* () {
18965
+ return (_a = this.track) === null || _a === void 0 ? void 0 : _a.unmute();
18966
+ });
18884
18967
  }
18885
- /* @internal */
18886
- emitTrackUpdate() {
18887
- const settings = new UpdateTrackSettings({
18888
- trackSids: [this.trackSid],
18889
- disabled: this.disabled,
18890
- fps: this.fps
18968
+ /**
18969
+ * Pauses the media stream track associated with this publication from being sent to the server
18970
+ * and signals "muted" event to other participants
18971
+ * Useful if you want to pause the stream without pausing the local media stream track
18972
+ */
18973
+ pauseUpstream() {
18974
+ var _a;
18975
+ return __awaiter(this, void 0, void 0, function* () {
18976
+ yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.pauseUpstream();
18977
+ });
18978
+ }
18979
+ /**
18980
+ * Resumes sending the media stream track associated with this publication to the server after a call to [[pauseUpstream()]]
18981
+ * and signals "unmuted" event to other participants (unless the track is explicitly muted)
18982
+ */
18983
+ resumeUpstream() {
18984
+ var _a;
18985
+ return __awaiter(this, void 0, void 0, function* () {
18986
+ yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.resumeUpstream();
18891
18987
  });
18892
- if (this.videoDimensions) {
18893
- settings.width = this.videoDimensions.width;
18894
- settings.height = this.videoDimensions.height;
18895
- } else if (this.currentVideoQuality !== undefined) {
18896
- settings.quality = this.currentVideoQuality;
18897
- } else {
18898
- // defaults to high quality
18899
- settings.quality = VideoQuality.HIGH;
18900
- }
18901
- this.emit(TrackEvent.UpdateSettings, settings);
18902
18988
  }
18903
18989
  }
18904
18990
 
18905
- class RemoteParticipant extends Participant {
18906
- /** @internal */
18907
- static fromParticipantInfo(signalClient, pi) {
18908
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
18991
+ var ConnectionQuality;
18992
+ (function (ConnectionQuality) {
18993
+ ConnectionQuality["Excellent"] = "excellent";
18994
+ ConnectionQuality["Good"] = "good";
18995
+ ConnectionQuality["Poor"] = "poor";
18996
+ ConnectionQuality["Unknown"] = "unknown";
18997
+ })(ConnectionQuality || (ConnectionQuality = {}));
18998
+ function qualityFromProto(q) {
18999
+ switch (q) {
19000
+ case ConnectionQuality$1.EXCELLENT:
19001
+ return ConnectionQuality.Excellent;
19002
+ case ConnectionQuality$1.GOOD:
19003
+ return ConnectionQuality.Good;
19004
+ case ConnectionQuality$1.POOR:
19005
+ return ConnectionQuality.Poor;
19006
+ default:
19007
+ return ConnectionQuality.Unknown;
19008
+ }
19009
+ }
19010
+ class Participant extends eventsExports.EventEmitter {
19011
+ get isEncrypted() {
19012
+ return this.tracks.size > 0 && Array.from(this.tracks.values()).every(tr => tr.isEncrypted);
18909
19013
  }
18910
19014
  /** @internal */
18911
- constructor(signalClient, sid, identity, name, metadata) {
18912
- super(sid, identity || '', name, metadata);
18913
- this.signalClient = signalClient;
18914
- this.tracks = new Map();
19015
+ constructor(sid, identity, name, metadata) {
19016
+ super();
19017
+ /** audio level between 0-1.0, 1 being loudest, 0 being softest */
19018
+ this.audioLevel = 0;
19019
+ /** if participant is currently speaking */
19020
+ this.isSpeaking = false;
19021
+ this._connectionQuality = ConnectionQuality.Unknown;
19022
+ this.setMaxListeners(100);
19023
+ this.sid = sid;
19024
+ this.identity = identity;
19025
+ this.name = name;
19026
+ this.metadata = metadata;
18915
19027
  this.audioTracks = new Map();
18916
19028
  this.videoTracks = new Map();
18917
- this.volumeMap = new Map();
19029
+ this.tracks = new Map();
18918
19030
  }
18919
- addTrackPublication(publication) {
18920
- super.addTrackPublication(publication);
18921
- // register action events
18922
- publication.on(TrackEvent.UpdateSettings, settings => {
18923
- livekitLogger.debug('send update settings', settings);
18924
- this.signalClient.sendUpdateTrackSettings(settings);
18925
- });
18926
- publication.on(TrackEvent.UpdateSubscription, sub => {
18927
- sub.participantTracks.forEach(pt => {
18928
- pt.participantSid = this.sid;
18929
- });
18930
- this.signalClient.sendUpdateSubscription(sub);
18931
- });
18932
- publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
18933
- this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
18934
- });
18935
- publication.on(TrackEvent.SubscriptionStatusChanged, status => {
18936
- this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
18937
- });
18938
- publication.on(TrackEvent.Subscribed, track => {
18939
- this.emit(ParticipantEvent.TrackSubscribed, track, publication);
18940
- });
18941
- publication.on(TrackEvent.Unsubscribed, previousTrack => {
18942
- this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
18943
- });
18944
- publication.on(TrackEvent.SubscriptionFailed, error => {
18945
- this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
18946
- });
19031
+ getTracks() {
19032
+ return Array.from(this.tracks.values());
18947
19033
  }
19034
+ /**
19035
+ * Finds the first track that matches the source filter, for example, getting
19036
+ * the user's camera track with getTrackBySource(Track.Source.Camera).
19037
+ * @param source
19038
+ * @returns
19039
+ */
18948
19040
  getTrack(source) {
18949
- const track = super.getTrack(source);
18950
- if (track) {
18951
- return track;
19041
+ for (const [, pub] of this.tracks) {
19042
+ if (pub.source === source) {
19043
+ return pub;
19044
+ }
19045
+ }
19046
+ }
19047
+ /**
19048
+ * Finds the first track that matches the track's name.
19049
+ * @param name
19050
+ * @returns
19051
+ */
19052
+ getTrackByName(name) {
19053
+ for (const [, pub] of this.tracks) {
19054
+ if (pub.trackName === name) {
19055
+ return pub;
19056
+ }
19057
+ }
19058
+ }
19059
+ get connectionQuality() {
19060
+ return this._connectionQuality;
19061
+ }
19062
+ get isCameraEnabled() {
19063
+ var _a;
19064
+ const track = this.getTrack(Track.Source.Camera);
19065
+ return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
19066
+ }
19067
+ get isMicrophoneEnabled() {
19068
+ var _a;
19069
+ const track = this.getTrack(Track.Source.Microphone);
19070
+ return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
19071
+ }
19072
+ get isScreenShareEnabled() {
19073
+ const track = this.getTrack(Track.Source.ScreenShare);
19074
+ return !!track;
19075
+ }
19076
+ get isLocal() {
19077
+ return false;
19078
+ }
19079
+ /** when participant joined the room */
19080
+ get joinedAt() {
19081
+ if (this.participantInfo) {
19082
+ return new Date(Number.parseInt(this.participantInfo.joinedAt.toString()) * 1000);
18952
19083
  }
19084
+ return new Date();
18953
19085
  }
18954
- getTrackByName(name) {
18955
- const track = super.getTrackByName(name);
18956
- if (track) {
18957
- return track;
19086
+ /** @internal */
19087
+ updateInfo(info) {
19088
+ // it's possible the update could be applied out of order due to await
19089
+ // during reconnect sequences. when that happens, it's possible for server
19090
+ // to have sent more recent version of participant info while JS is waiting
19091
+ // to process the existing payload.
19092
+ // when the participant sid remains the same, and we already have a later version
19093
+ // of the payload, they can be safely skipped
19094
+ if (this.participantInfo && this.participantInfo.sid === info.sid && this.participantInfo.version > info.version) {
19095
+ return false;
19096
+ }
19097
+ this.identity = info.identity;
19098
+ this.sid = info.sid;
19099
+ this.setName(info.name);
19100
+ this.setMetadata(info.metadata);
19101
+ if (info.permission) {
19102
+ this.setPermissions(info.permission);
18958
19103
  }
19104
+ // set this last so setMetadata can detect changes
19105
+ this.participantInfo = info;
19106
+ livekitLogger.trace('update participant info', {
19107
+ info
19108
+ });
19109
+ return true;
18959
19110
  }
18960
- /**
18961
- * sets the volume on the participant's audio track
18962
- * by default, this affects the microphone publication
18963
- * a different source can be passed in as a second argument
18964
- * if no track exists the volume will be applied when the microphone track is added
18965
- */
18966
- setVolume(volume) {
18967
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
18968
- this.volumeMap.set(source, volume);
18969
- const audioPublication = this.getTrack(source);
18970
- if (audioPublication && audioPublication.track) {
18971
- audioPublication.track.setVolume(volume);
19111
+ /** @internal */
19112
+ setMetadata(md) {
19113
+ const changed = this.metadata !== md;
19114
+ const prevMetadata = this.metadata;
19115
+ this.metadata = md;
19116
+ if (changed) {
19117
+ this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
18972
19118
  }
18973
19119
  }
18974
- /**
18975
- * gets the volume on the participant's microphone track
18976
- */
18977
- getVolume() {
18978
- let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
18979
- const audioPublication = this.getTrack(source);
18980
- if (audioPublication && audioPublication.track) {
18981
- return audioPublication.track.getVolume();
19120
+ setName(name) {
19121
+ const changed = this.name !== name;
19122
+ this.name = name;
19123
+ if (changed) {
19124
+ this.emit(ParticipantEvent.ParticipantNameChanged, name);
18982
19125
  }
18983
- return this.volumeMap.get(source);
18984
19126
  }
18985
19127
  /** @internal */
18986
- addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
18987
- // find the track publication
18988
- // it's possible for the media track to arrive before participant info
18989
- let publication = this.getTrackPublication(sid);
18990
- // it's also possible that the browser didn't honor our original track id
18991
- // FireFox would use its own local uuid instead of server track id
18992
- if (!publication) {
18993
- if (!sid.startsWith('TR')) {
18994
- // find the first track that matches type
18995
- this.tracks.forEach(p => {
18996
- if (!publication && mediaTrack.kind === p.kind.toString()) {
18997
- publication = p;
18998
- }
18999
- });
19000
- }
19001
- }
19002
- // when we couldn't locate the track, it's possible that the metadata hasn't
19003
- // yet arrived. Wait a bit longer for it to arrive, or fire an error
19004
- if (!publication) {
19005
- if (triesLeft === 0) {
19006
- livekitLogger.error('could not find published track', {
19007
- participant: this.sid,
19008
- trackSid: sid
19009
- });
19010
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19011
- return;
19012
- }
19013
- if (triesLeft === undefined) triesLeft = 20;
19014
- setTimeout(() => {
19015
- this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
19016
- }, 150);
19017
- return;
19128
+ setPermissions(permissions) {
19129
+ var _a, _b, _c, _d, _e;
19130
+ const prevPermissions = this.permissions;
19131
+ const changed = permissions.canPublish !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublish) || permissions.canSubscribe !== ((_b = this.permissions) === null || _b === void 0 ? void 0 : _b.canSubscribe) || permissions.canPublishData !== ((_c = this.permissions) === null || _c === void 0 ? void 0 : _c.canPublishData) || permissions.hidden !== ((_d = this.permissions) === null || _d === void 0 ? void 0 : _d.hidden) || permissions.recorder !== ((_e = this.permissions) === null || _e === void 0 ? void 0 : _e.recorder) || permissions.canPublishSources.length !== this.permissions.canPublishSources.length || permissions.canPublishSources.some((value, index) => {
19132
+ var _a;
19133
+ return value !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublishSources[index]);
19134
+ });
19135
+ this.permissions = permissions;
19136
+ if (changed) {
19137
+ this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
19018
19138
  }
19019
- if (mediaTrack.readyState === 'ended') {
19020
- livekitLogger.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', {
19021
- participant: this.sid,
19022
- trackSid: sid
19023
- });
19024
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19139
+ return changed;
19140
+ }
19141
+ /** @internal */
19142
+ setIsSpeaking(speaking) {
19143
+ if (speaking === this.isSpeaking) {
19025
19144
  return;
19026
19145
  }
19027
- const isVideo = mediaTrack.kind === 'video';
19028
- let track;
19029
- if (isVideo) {
19030
- track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
19031
- } else {
19032
- track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
19033
- }
19034
- // set track info
19035
- track.source = publication.source;
19036
- // keep publication's muted status
19037
- track.isMuted = publication.isMuted;
19038
- track.setMediaStream(mediaStream);
19039
- track.start();
19040
- publication.setTrack(track);
19041
- // set participant volumes on new audio tracks
19042
- if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
19043
- track.setVolume(this.volumeMap.get(publication.source));
19146
+ this.isSpeaking = speaking;
19147
+ if (speaking) {
19148
+ this.lastSpokeAt = new Date();
19044
19149
  }
19045
- return publication;
19150
+ this.emit(ParticipantEvent.IsSpeakingChanged, speaking);
19046
19151
  }
19047
19152
  /** @internal */
19048
- get hasMetadata() {
19049
- return !!this.participantInfo;
19153
+ setConnectionQuality(q) {
19154
+ const prevQuality = this._connectionQuality;
19155
+ this._connectionQuality = qualityFromProto(q);
19156
+ if (prevQuality !== this._connectionQuality) {
19157
+ this.emit(ParticipantEvent.ConnectionQualityChanged, this._connectionQuality);
19158
+ }
19050
19159
  }
19051
- getTrackPublication(sid) {
19052
- return this.tracks.get(sid);
19160
+ /**
19161
+ * @internal
19162
+ */
19163
+ setAudioContext(ctx) {
19164
+ this.audioContext = ctx;
19165
+ this.audioTracks.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
19053
19166
  }
19054
- /** @internal */
19055
- updateInfo(info) {
19056
- if (!super.updateInfo(info)) {
19057
- return false;
19058
- }
19059
- // we are getting a list of all available tracks, reconcile in here
19060
- // and send out events for changes
19061
- // reconcile track publications, publish events only if metadata is already there
19062
- // i.e. changes since the local participant has joined
19063
- const validTracks = new Map();
19064
- const newTracks = new Map();
19065
- info.tracks.forEach(ti => {
19066
- var _a;
19067
- let publication = this.getTrackPublication(ti.sid);
19068
- if (!publication) {
19069
- // new publication
19070
- const kind = Track.kindFromProto(ti.type);
19071
- if (!kind) {
19072
- return;
19073
- }
19074
- publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe);
19075
- publication.updateInfo(ti);
19076
- newTracks.set(ti.sid, publication);
19077
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
19078
- if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
19079
- livekitLogger.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
19080
- oldTrack: existingTrackOfSource,
19081
- newTrack: publication,
19082
- participant: this,
19083
- participantInfo: info
19084
- });
19085
- }
19086
- this.addTrackPublication(publication);
19087
- } else {
19088
- publication.updateInfo(ti);
19089
- }
19090
- validTracks.set(ti.sid, publication);
19091
- });
19092
- // detect removed tracks
19093
- this.tracks.forEach(publication => {
19094
- if (!validTracks.has(publication.trackSid)) {
19095
- livekitLogger.trace('detected removed track on remote participant, unpublishing', {
19096
- publication,
19097
- participantSid: this.sid
19098
- });
19099
- this.unpublishTrack(publication.trackSid, true);
19100
- }
19167
+ addTrackPublication(publication) {
19168
+ // forward publication driven events
19169
+ publication.on(TrackEvent.Muted, () => {
19170
+ this.emit(ParticipantEvent.TrackMuted, publication);
19101
19171
  });
19102
- // always emit events for new publications, Room will not forward them unless it's ready
19103
- newTracks.forEach(publication => {
19104
- this.emit(ParticipantEvent.TrackPublished, publication);
19172
+ publication.on(TrackEvent.Unmuted, () => {
19173
+ this.emit(ParticipantEvent.TrackUnmuted, publication);
19105
19174
  });
19106
- return true;
19175
+ const pub = publication;
19176
+ if (pub.track) {
19177
+ pub.track.sid = publication.trackSid;
19178
+ }
19179
+ this.tracks.set(publication.trackSid, publication);
19180
+ switch (publication.kind) {
19181
+ case Track.Kind.Audio:
19182
+ this.audioTracks.set(publication.trackSid, publication);
19183
+ break;
19184
+ case Track.Kind.Video:
19185
+ this.videoTracks.set(publication.trackSid, publication);
19186
+ break;
19187
+ }
19188
+ }
19189
+ }
19190
+
19191
+ function trackPermissionToProto(perms) {
19192
+ var _a, _b, _c;
19193
+ if (!perms.participantSid && !perms.participantIdentity) {
19194
+ throw new Error('Invalid track permission, must provide at least one of participantIdentity and participantSid');
19195
+ }
19196
+ return new TrackPermission({
19197
+ participantIdentity: (_a = perms.participantIdentity) !== null && _a !== void 0 ? _a : '',
19198
+ participantSid: (_b = perms.participantSid) !== null && _b !== void 0 ? _b : '',
19199
+ allTracks: (_c = perms.allowAll) !== null && _c !== void 0 ? _c : false,
19200
+ trackSids: perms.allowedTrackSids || []
19201
+ });
19202
+ }
19203
+
19204
+ class RemoteTrackPublication extends TrackPublication {
19205
+ constructor(kind, ti, autoSubscribe) {
19206
+ super(kind, ti.sid, ti.name);
19207
+ this.track = undefined;
19208
+ /** @internal */
19209
+ this.allowed = true;
19210
+ this.disabled = false;
19211
+ this.currentVideoQuality = VideoQuality.HIGH;
19212
+ this.handleEnded = track => {
19213
+ this.setTrack(undefined);
19214
+ this.emit(TrackEvent.Ended, track);
19215
+ };
19216
+ this.handleVisibilityChange = visible => {
19217
+ livekitLogger.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), {
19218
+ trackSid: this.trackSid
19219
+ });
19220
+ this.disabled = !visible;
19221
+ this.emitTrackUpdate();
19222
+ };
19223
+ this.handleVideoDimensionsChange = dimensions => {
19224
+ livekitLogger.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), {
19225
+ trackSid: this.trackSid
19226
+ });
19227
+ this.videoDimensions = dimensions;
19228
+ this.emitTrackUpdate();
19229
+ };
19230
+ this.subscribed = autoSubscribe;
19231
+ this.updateInfo(ti);
19107
19232
  }
19108
- /** @internal */
19109
- unpublishTrack(sid, sendUnpublish) {
19110
- const publication = this.tracks.get(sid);
19111
- if (!publication) {
19112
- return;
19233
+ /**
19234
+ * Subscribe or unsubscribe to this remote track
19235
+ * @param subscribed true to subscribe to a track, false to unsubscribe
19236
+ */
19237
+ setSubscribed(subscribed) {
19238
+ const prevStatus = this.subscriptionStatus;
19239
+ const prevPermission = this.permissionStatus;
19240
+ this.subscribed = subscribed;
19241
+ // reset allowed status when desired subscription state changes
19242
+ // server will notify client via signal message if it's not allowed
19243
+ if (subscribed) {
19244
+ this.allowed = true;
19113
19245
  }
19114
- // also send unsubscribe, if track is actively subscribed
19115
- const {
19116
- track
19117
- } = publication;
19118
- if (track) {
19119
- track.stop();
19120
- publication.setTrack(undefined);
19246
+ const sub = new UpdateSubscription({
19247
+ trackSids: [this.trackSid],
19248
+ subscribe: this.subscribed,
19249
+ participantTracks: [new ParticipantTracks({
19250
+ // sending an empty participant id since TrackPublication doesn't keep it
19251
+ // this is filled in by the participant that receives this message
19252
+ participantSid: '',
19253
+ trackSids: [this.trackSid]
19254
+ })]
19255
+ });
19256
+ this.emit(TrackEvent.UpdateSubscription, sub);
19257
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
19258
+ this.emitPermissionUpdateIfChanged(prevPermission);
19259
+ }
19260
+ get subscriptionStatus() {
19261
+ if (this.subscribed === false) {
19262
+ return TrackPublication.SubscriptionStatus.Unsubscribed;
19121
19263
  }
19122
- // remove track from maps only after unsubscribed has been fired
19123
- this.tracks.delete(sid);
19124
- // remove from the right type map
19125
- switch (publication.kind) {
19126
- case Track.Kind.Audio:
19127
- this.audioTracks.delete(sid);
19128
- break;
19129
- case Track.Kind.Video:
19130
- this.videoTracks.delete(sid);
19131
- break;
19264
+ if (!super.isSubscribed) {
19265
+ return TrackPublication.SubscriptionStatus.Desired;
19132
19266
  }
19133
- if (sendUnpublish) {
19134
- this.emit(ParticipantEvent.TrackUnpublished, publication);
19267
+ return TrackPublication.SubscriptionStatus.Subscribed;
19268
+ }
19269
+ get permissionStatus() {
19270
+ return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
19271
+ }
19272
+ /**
19273
+ * Returns true if track is subscribed, and ready for playback
19274
+ */
19275
+ get isSubscribed() {
19276
+ if (this.subscribed === false) {
19277
+ return false;
19135
19278
  }
19279
+ return super.isSubscribed;
19280
+ }
19281
+ // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
19282
+ get isDesired() {
19283
+ return this.subscribed !== false;
19284
+ }
19285
+ get isEnabled() {
19286
+ return !this.disabled;
19136
19287
  }
19137
19288
  /**
19138
- * @internal
19289
+ * disable server from sending down data for this track. this is useful when
19290
+ * the participant is off screen, you may disable streaming down their video
19291
+ * to reduce bandwidth requirements
19292
+ * @param enabled
19139
19293
  */
19140
- setAudioContext(ctx) {
19141
- this.audioContext = ctx;
19142
- this.audioTracks.forEach(track => track.track instanceof RemoteAudioTrack && track.track.setAudioContext(ctx));
19294
+ setEnabled(enabled) {
19295
+ if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
19296
+ return;
19297
+ }
19298
+ this.disabled = !enabled;
19299
+ this.emitTrackUpdate();
19143
19300
  }
19144
19301
  /**
19145
- * @internal
19302
+ * for tracks that support simulcasting, adjust subscribed quality
19303
+ *
19304
+ * This indicates the highest quality the client can accept. if network
19305
+ * bandwidth does not allow, server will automatically reduce quality to
19306
+ * optimize for uninterrupted video
19146
19307
  */
19147
- setAudioOutput(output) {
19148
- return __awaiter(this, void 0, void 0, function* () {
19149
- this.audioOutput = output;
19150
- const promises = [];
19151
- this.audioTracks.forEach(pub => {
19152
- var _a;
19153
- if (pub.track instanceof RemoteAudioTrack) {
19154
- promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
19155
- }
19156
- });
19157
- yield Promise.all(promises);
19158
- });
19308
+ setVideoQuality(quality) {
19309
+ if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
19310
+ return;
19311
+ }
19312
+ this.currentVideoQuality = quality;
19313
+ this.videoDimensions = undefined;
19314
+ this.emitTrackUpdate();
19159
19315
  }
19160
- /** @internal */
19161
- emit(event) {
19162
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
19163
- args[_key - 1] = arguments[_key];
19316
+ setVideoDimensions(dimensions) {
19317
+ var _a, _b;
19318
+ if (!this.isManualOperationAllowed()) {
19319
+ return;
19164
19320
  }
19165
- livekitLogger.trace('participant event', {
19166
- participant: this.sid,
19167
- event,
19168
- args
19169
- });
19170
- return super.emit(event, ...args);
19321
+ if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
19322
+ return;
19323
+ }
19324
+ if (this.track instanceof RemoteVideoTrack) {
19325
+ this.videoDimensions = dimensions;
19326
+ }
19327
+ this.currentVideoQuality = undefined;
19328
+ this.emitTrackUpdate();
19171
19329
  }
19172
- }
19173
-
19174
- /** @internal */
19175
- function mediaTrackToLocalTrack(mediaStreamTrack, constraints) {
19176
- switch (mediaStreamTrack.kind) {
19177
- case 'audio':
19178
- return new LocalAudioTrack(mediaStreamTrack, constraints, false);
19179
- case 'video':
19180
- return new LocalVideoTrack(mediaStreamTrack, constraints, false);
19181
- default:
19182
- throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
19330
+ setVideoFPS(fps) {
19331
+ if (!this.isManualOperationAllowed()) {
19332
+ return;
19333
+ }
19334
+ if (!(this.track instanceof RemoteVideoTrack)) {
19335
+ return;
19336
+ }
19337
+ if (this.fps === fps) {
19338
+ return;
19339
+ }
19340
+ this.fps = fps;
19341
+ this.emitTrackUpdate();
19183
19342
  }
19184
- }
19185
- /* @internal */
19186
- const presets169 = Object.values(VideoPresets);
19187
- /* @internal */
19188
- const presets43 = Object.values(VideoPresets43);
19189
- /* @internal */
19190
- const presetsScreenShare = Object.values(ScreenSharePresets);
19191
- /* @internal */
19192
- const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
19193
- /* @internal */
19194
- const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
19195
- /* @internal */
19196
- const computeDefaultScreenShareSimulcastPresets = fromPreset => {
19197
- const layers = [{
19198
- scaleResolutionDownBy: 2,
19199
- fps: 3
19200
- }];
19201
- return layers.map(t => {
19202
- var _a;
19203
- return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / t.fps)))), t.fps, fromPreset.encoding.priority);
19204
- });
19205
- };
19206
- // /**
19207
- // *
19208
- // * @internal
19209
- // * @experimental
19210
- // */
19211
- // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
19212
- // // use vp8 as a default
19213
- // const vp8 = determineAppropriateEncoding(false, width, height);
19214
- // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
19215
- // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
19216
- // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
19217
- // return {
19218
- // vp8,
19219
- // vp9,
19220
- // h264,
19221
- // av1,
19222
- // };
19223
- // };
19224
- const videoRids = ['q', 'h', 'f'];
19225
- /* @internal */
19226
- function computeVideoEncodings(isScreenShare, width, height, options) {
19227
- var _a, _b;
19228
- let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
19229
- if (isScreenShare) {
19230
- videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
19343
+ get videoQuality() {
19344
+ return this.currentVideoQuality;
19231
19345
  }
19232
- const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
19233
- const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
19234
- const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
19235
- if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
19236
- // when we aren't simulcasting or svc, will need to return a single encoding without
19237
- // capping bandwidth. we always require a encoding for dynacast
19238
- return [{}];
19346
+ /** @internal */
19347
+ setTrack(track) {
19348
+ const prevStatus = this.subscriptionStatus;
19349
+ const prevPermission = this.permissionStatus;
19350
+ const prevTrack = this.track;
19351
+ if (prevTrack === track) {
19352
+ return;
19353
+ }
19354
+ if (prevTrack) {
19355
+ // unregister listener
19356
+ prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
19357
+ prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
19358
+ prevTrack.off(TrackEvent.Ended, this.handleEnded);
19359
+ prevTrack.detach();
19360
+ prevTrack.stopMonitor();
19361
+ this.emit(TrackEvent.Unsubscribed, prevTrack);
19362
+ }
19363
+ super.setTrack(track);
19364
+ if (track) {
19365
+ track.sid = this.trackSid;
19366
+ track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
19367
+ track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
19368
+ track.on(TrackEvent.Ended, this.handleEnded);
19369
+ this.emit(TrackEvent.Subscribed, track);
19370
+ }
19371
+ this.emitPermissionUpdateIfChanged(prevPermission);
19372
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
19239
19373
  }
19240
- if (!videoEncoding) {
19241
- // find the right encoding based on width/height
19242
- videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
19243
- livekitLogger.debug('using video encoding', videoEncoding);
19374
+ /** @internal */
19375
+ setAllowed(allowed) {
19376
+ const prevStatus = this.subscriptionStatus;
19377
+ const prevPermission = this.permissionStatus;
19378
+ this.allowed = allowed;
19379
+ this.emitPermissionUpdateIfChanged(prevPermission);
19380
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
19244
19381
  }
19245
- const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
19246
- if (scalabilityMode && isSVCCodec(videoCodec)) {
19247
- livekitLogger.debug("using svc with scalabilityMode ".concat(scalabilityMode));
19248
- const sm = new ScalabilityMode$1(scalabilityMode);
19249
- const encodings = [];
19250
- if (sm.spatial > 3) {
19251
- throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
19252
- }
19253
- for (let i = 0; i < sm.spatial; i += 1) {
19254
- encodings.push({
19255
- rid: videoRids[2 - i],
19256
- maxBitrate: videoEncoding.maxBitrate / Math.pow(3, i),
19257
- /* @ts-ignore */
19258
- maxFramerate: original.encoding.maxFramerate
19259
- });
19382
+ /** @internal */
19383
+ setSubscriptionError(error) {
19384
+ this.emit(TrackEvent.SubscriptionFailed, error);
19385
+ }
19386
+ /** @internal */
19387
+ updateInfo(info) {
19388
+ super.updateInfo(info);
19389
+ const prevMetadataMuted = this.metadataMuted;
19390
+ this.metadataMuted = info.muted;
19391
+ if (this.track) {
19392
+ this.track.setMuted(info.muted);
19393
+ } else if (prevMetadataMuted !== info.muted) {
19394
+ this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
19260
19395
  }
19261
- /* @ts-ignore */
19262
- encodings[0].scalabilityMode = scalabilityMode;
19263
- livekitLogger.debug('encodings', encodings);
19264
- return encodings;
19265
19396
  }
19266
- if (!useSimulcast) {
19267
- return [videoEncoding];
19397
+ emitSubscriptionUpdateIfChanged(previousStatus) {
19398
+ const currentStatus = this.subscriptionStatus;
19399
+ if (previousStatus === currentStatus) {
19400
+ return;
19401
+ }
19402
+ this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
19268
19403
  }
19269
- let presets = [];
19270
- if (isScreenShare) {
19271
- presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
19272
- } else {
19273
- presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
19404
+ emitPermissionUpdateIfChanged(previousPermissionStatus) {
19405
+ const currentPermissionStatus = this.permissionStatus;
19406
+ if (currentPermissionStatus !== previousPermissionStatus) {
19407
+ this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
19408
+ }
19274
19409
  }
19275
- let midPreset;
19276
- if (presets.length > 0) {
19277
- const lowPreset = presets[0];
19278
- if (presets.length > 1) {
19279
- [, midPreset] = presets;
19410
+ isManualOperationAllowed() {
19411
+ if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
19412
+ livekitLogger.warn('adaptive stream is enabled, cannot change video track settings', {
19413
+ trackSid: this.trackSid
19414
+ });
19415
+ return false;
19280
19416
  }
19281
- // NOTE:
19282
- // 1. Ordering of these encodings is important. Chrome seems
19283
- // to use the index into encodings to decide which layer
19284
- // to disable when CPU constrained.
19285
- // So encodings should be ordered in increasing spatial
19286
- // resolution order.
19287
- // 2. ion-sfu translates rids into layers. So, all encodings
19288
- // should have the base layer `q` and then more added
19289
- // based on other conditions.
19290
- const size = Math.max(width, height);
19291
- if (size >= 960 && midPreset) {
19292
- return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
19417
+ if (!this.isDesired) {
19418
+ livekitLogger.warn('cannot update track settings when not subscribed', {
19419
+ trackSid: this.trackSid
19420
+ });
19421
+ return false;
19293
19422
  }
19294
- if (size >= 480) {
19295
- return encodingsFromPresets(width, height, [lowPreset, original]);
19423
+ return true;
19424
+ }
19425
+ get isAdaptiveStream() {
19426
+ return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
19427
+ }
19428
+ /* @internal */
19429
+ emitTrackUpdate() {
19430
+ const settings = new UpdateTrackSettings({
19431
+ trackSids: [this.trackSid],
19432
+ disabled: this.disabled,
19433
+ fps: this.fps
19434
+ });
19435
+ if (this.videoDimensions) {
19436
+ settings.width = Math.ceil(this.videoDimensions.width);
19437
+ settings.height = Math.ceil(this.videoDimensions.height);
19438
+ } else if (this.currentVideoQuality !== undefined) {
19439
+ settings.quality = this.currentVideoQuality;
19440
+ } else {
19441
+ // defaults to high quality
19442
+ settings.quality = VideoQuality.HIGH;
19296
19443
  }
19444
+ this.emit(TrackEvent.UpdateSettings, settings);
19297
19445
  }
19298
- return encodingsFromPresets(width, height, [original]);
19299
19446
  }
19300
- function computeTrackBackupEncodings(track, videoCodec, opts) {
19301
- var _a, _b, _c, _d;
19302
- if (!opts.backupCodec || opts.backupCodec.codec === opts.videoCodec) {
19303
- // backup codec publishing is disabled
19304
- return;
19447
+
19448
+ class RemoteParticipant extends Participant {
19449
+ /** @internal */
19450
+ static fromParticipantInfo(signalClient, pi) {
19451
+ return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
19305
19452
  }
19306
- if (videoCodec !== opts.backupCodec.codec) {
19307
- livekitLogger.warn('requested a different codec than specified as backup', {
19308
- serverRequested: videoCodec,
19309
- backup: opts.backupCodec.codec
19453
+ /** @internal */
19454
+ constructor(signalClient, sid, identity, name, metadata) {
19455
+ super(sid, identity || '', name, metadata);
19456
+ this.signalClient = signalClient;
19457
+ this.tracks = new Map();
19458
+ this.audioTracks = new Map();
19459
+ this.videoTracks = new Map();
19460
+ this.volumeMap = new Map();
19461
+ }
19462
+ addTrackPublication(publication) {
19463
+ super.addTrackPublication(publication);
19464
+ // register action events
19465
+ publication.on(TrackEvent.UpdateSettings, settings => {
19466
+ livekitLogger.debug('send update settings', settings);
19467
+ this.signalClient.sendUpdateTrackSettings(settings);
19468
+ });
19469
+ publication.on(TrackEvent.UpdateSubscription, sub => {
19470
+ sub.participantTracks.forEach(pt => {
19471
+ pt.participantSid = this.sid;
19472
+ });
19473
+ this.signalClient.sendUpdateSubscription(sub);
19474
+ });
19475
+ publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
19476
+ this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
19477
+ });
19478
+ publication.on(TrackEvent.SubscriptionStatusChanged, status => {
19479
+ this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
19480
+ });
19481
+ publication.on(TrackEvent.Subscribed, track => {
19482
+ this.emit(ParticipantEvent.TrackSubscribed, track, publication);
19483
+ });
19484
+ publication.on(TrackEvent.Unsubscribed, previousTrack => {
19485
+ this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
19486
+ });
19487
+ publication.on(TrackEvent.SubscriptionFailed, error => {
19488
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
19310
19489
  });
19311
19490
  }
19312
- opts.videoCodec = videoCodec;
19313
- // use backup encoding setting as videoEncoding for backup codec publishing
19314
- opts.videoEncoding = opts.backupCodec.encoding;
19315
- const settings = track.mediaStreamTrack.getSettings();
19316
- const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
19317
- const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
19318
- const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
19319
- return encodings;
19320
- }
19321
- /* @internal */
19322
- function determineAppropriateEncoding(isScreenShare, width, height, codec) {
19323
- const presets = presetsForResolution(isScreenShare, width, height);
19324
- let {
19325
- encoding
19326
- } = presets[0];
19327
- // handle portrait by swapping dimensions
19328
- const size = Math.max(width, height);
19329
- for (let i = 0; i < presets.length; i += 1) {
19330
- const preset = presets[i];
19331
- encoding = preset.encoding;
19332
- if (preset.width >= size) {
19333
- break;
19491
+ getTrack(source) {
19492
+ const track = super.getTrack(source);
19493
+ if (track) {
19494
+ return track;
19495
+ }
19496
+ }
19497
+ getTrackByName(name) {
19498
+ const track = super.getTrackByName(name);
19499
+ if (track) {
19500
+ return track;
19501
+ }
19502
+ }
19503
+ /**
19504
+ * sets the volume on the participant's audio track
19505
+ * by default, this affects the microphone publication
19506
+ * a different source can be passed in as a second argument
19507
+ * if no track exists the volume will be applied when the microphone track is added
19508
+ */
19509
+ setVolume(volume) {
19510
+ let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
19511
+ this.volumeMap.set(source, volume);
19512
+ const audioPublication = this.getTrack(source);
19513
+ if (audioPublication && audioPublication.track) {
19514
+ audioPublication.track.setVolume(volume);
19515
+ }
19516
+ }
19517
+ /**
19518
+ * gets the volume on the participant's microphone track
19519
+ */
19520
+ getVolume() {
19521
+ let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
19522
+ const audioPublication = this.getTrack(source);
19523
+ if (audioPublication && audioPublication.track) {
19524
+ return audioPublication.track.getVolume();
19525
+ }
19526
+ return this.volumeMap.get(source);
19527
+ }
19528
+ /** @internal */
19529
+ addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
19530
+ // find the track publication
19531
+ // it's possible for the media track to arrive before participant info
19532
+ let publication = this.getTrackPublication(sid);
19533
+ // it's also possible that the browser didn't honor our original track id
19534
+ // FireFox would use its own local uuid instead of server track id
19535
+ if (!publication) {
19536
+ if (!sid.startsWith('TR')) {
19537
+ // find the first track that matches type
19538
+ this.tracks.forEach(p => {
19539
+ if (!publication && mediaTrack.kind === p.kind.toString()) {
19540
+ publication = p;
19541
+ }
19542
+ });
19543
+ }
19544
+ }
19545
+ // when we couldn't locate the track, it's possible that the metadata hasn't
19546
+ // yet arrived. Wait a bit longer for it to arrive, or fire an error
19547
+ if (!publication) {
19548
+ if (triesLeft === 0) {
19549
+ livekitLogger.error('could not find published track', {
19550
+ participant: this.sid,
19551
+ trackSid: sid
19552
+ });
19553
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19554
+ return;
19555
+ }
19556
+ if (triesLeft === undefined) triesLeft = 20;
19557
+ setTimeout(() => {
19558
+ this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
19559
+ }, 150);
19560
+ return;
19561
+ }
19562
+ if (mediaTrack.readyState === 'ended') {
19563
+ livekitLogger.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', {
19564
+ participant: this.sid,
19565
+ trackSid: sid
19566
+ });
19567
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19568
+ return;
19334
19569
  }
19335
- }
19336
- // presets are based on the assumption of vp8 as a codec
19337
- // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
19338
- // TODO make the bitrate multipliers configurable per codec
19339
- if (codec) {
19340
- switch (codec) {
19341
- case 'av1':
19342
- encoding = Object.assign({}, encoding);
19343
- encoding.maxBitrate = encoding.maxBitrate * 0.7;
19344
- break;
19345
- case 'vp9':
19346
- encoding = Object.assign({}, encoding);
19347
- encoding.maxBitrate = encoding.maxBitrate * 0.85;
19348
- break;
19570
+ const isVideo = mediaTrack.kind === 'video';
19571
+ let track;
19572
+ if (isVideo) {
19573
+ track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
19574
+ } else {
19575
+ track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
19349
19576
  }
19577
+ // set track info
19578
+ track.source = publication.source;
19579
+ // keep publication's muted status
19580
+ track.isMuted = publication.isMuted;
19581
+ track.setMediaStream(mediaStream);
19582
+ track.start();
19583
+ publication.setTrack(track);
19584
+ // set participant volumes on new audio tracks
19585
+ if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
19586
+ track.setVolume(this.volumeMap.get(publication.source));
19587
+ }
19588
+ return publication;
19350
19589
  }
19351
- return encoding;
19352
- }
19353
- /* @internal */
19354
- function presetsForResolution(isScreenShare, width, height) {
19355
- if (isScreenShare) {
19356
- return presetsScreenShare;
19357
- }
19358
- const aspect = width > height ? width / height : height / width;
19359
- if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
19360
- return presets169;
19361
- }
19362
- return presets43;
19363
- }
19364
- /* @internal */
19365
- function defaultSimulcastLayers(isScreenShare, original) {
19366
- if (isScreenShare) {
19367
- return computeDefaultScreenShareSimulcastPresets(original);
19590
+ /** @internal */
19591
+ get hasMetadata() {
19592
+ return !!this.participantInfo;
19368
19593
  }
19369
- const {
19370
- width,
19371
- height
19372
- } = original;
19373
- const aspect = width > height ? width / height : height / width;
19374
- if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
19375
- return defaultSimulcastPresets169;
19594
+ getTrackPublication(sid) {
19595
+ return this.tracks.get(sid);
19376
19596
  }
19377
- return defaultSimulcastPresets43;
19378
- }
19379
- // presets should be ordered by low, medium, high
19380
- function encodingsFromPresets(width, height, presets) {
19381
- const encodings = [];
19382
- presets.forEach((preset, idx) => {
19383
- if (idx >= videoRids.length) {
19384
- return;
19385
- }
19386
- const size = Math.min(width, height);
19387
- const rid = videoRids[idx];
19388
- const encoding = {
19389
- rid,
19390
- scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
19391
- maxBitrate: preset.encoding.maxBitrate
19392
- };
19393
- if (preset.encoding.maxFramerate) {
19394
- encoding.maxFramerate = preset.encoding.maxFramerate;
19395
- }
19396
- const canSetPriority = isFireFox() || idx === 0;
19397
- if (preset.encoding.priority && canSetPriority) {
19398
- encoding.priority = preset.encoding.priority;
19399
- encoding.networkPriority = preset.encoding.priority;
19597
+ /** @internal */
19598
+ updateInfo(info) {
19599
+ if (!super.updateInfo(info)) {
19600
+ return false;
19400
19601
  }
19401
- encodings.push(encoding);
19402
- });
19403
- // RN ios simulcast requires all same framerates.
19404
- if (isReactNative() && getReactNativeOs() === 'ios') {
19405
- let topFramerate = undefined;
19406
- encodings.forEach(encoding => {
19407
- if (!topFramerate) {
19408
- topFramerate = encoding.maxFramerate;
19409
- } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
19410
- topFramerate = encoding.maxFramerate;
19411
- }
19412
- });
19413
- let notifyOnce = true;
19414
- encodings.forEach(encoding => {
19602
+ // we are getting a list of all available tracks, reconcile in here
19603
+ // and send out events for changes
19604
+ // reconcile track publications, publish events only if metadata is already there
19605
+ // i.e. changes since the local participant has joined
19606
+ const validTracks = new Map();
19607
+ const newTracks = new Map();
19608
+ info.tracks.forEach(ti => {
19415
19609
  var _a;
19416
- if (encoding.maxFramerate != topFramerate) {
19417
- if (notifyOnce) {
19418
- notifyOnce = false;
19419
- livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
19610
+ let publication = this.getTrackPublication(ti.sid);
19611
+ if (!publication) {
19612
+ // new publication
19613
+ const kind = Track.kindFromProto(ti.type);
19614
+ if (!kind) {
19615
+ return;
19420
19616
  }
19421
- livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
19422
- encoding.maxFramerate = topFramerate;
19617
+ publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe);
19618
+ publication.updateInfo(ti);
19619
+ newTracks.set(ti.sid, publication);
19620
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
19621
+ if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
19622
+ livekitLogger.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
19623
+ oldTrack: existingTrackOfSource,
19624
+ newTrack: publication,
19625
+ participant: this,
19626
+ participantInfo: info
19627
+ });
19628
+ }
19629
+ this.addTrackPublication(publication);
19630
+ } else {
19631
+ publication.updateInfo(ti);
19632
+ }
19633
+ validTracks.set(ti.sid, publication);
19634
+ });
19635
+ // detect removed tracks
19636
+ this.tracks.forEach(publication => {
19637
+ if (!validTracks.has(publication.trackSid)) {
19638
+ livekitLogger.trace('detected removed track on remote participant, unpublishing', {
19639
+ publication,
19640
+ participantSid: this.sid
19641
+ });
19642
+ this.unpublishTrack(publication.trackSid, true);
19423
19643
  }
19424
19644
  });
19645
+ // always emit events for new publications, Room will not forward them unless it's ready
19646
+ newTracks.forEach(publication => {
19647
+ this.emit(ParticipantEvent.TrackPublished, publication);
19648
+ });
19649
+ return true;
19425
19650
  }
19426
- return encodings;
19427
- }
19428
- /** @internal */
19429
- function sortPresets(presets) {
19430
- if (!presets) return;
19431
- return presets.sort((a, b) => {
19432
- const {
19433
- encoding: aEnc
19434
- } = a;
19435
- const {
19436
- encoding: bEnc
19437
- } = b;
19438
- if (aEnc.maxBitrate > bEnc.maxBitrate) {
19439
- return 1;
19651
+ /** @internal */
19652
+ unpublishTrack(sid, sendUnpublish) {
19653
+ const publication = this.tracks.get(sid);
19654
+ if (!publication) {
19655
+ return;
19440
19656
  }
19441
- if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
19442
- if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
19443
- return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
19657
+ // also send unsubscribe, if track is actively subscribed
19658
+ const {
19659
+ track
19660
+ } = publication;
19661
+ if (track) {
19662
+ track.stop();
19663
+ publication.setTrack(undefined);
19444
19664
  }
19445
- return 0;
19446
- });
19447
- }
19448
- /** @internal */
19449
- let ScalabilityMode$1 = class ScalabilityMode {
19450
- constructor(scalabilityMode) {
19451
- const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
19452
- if (!results) {
19453
- throw new Error('invalid scalability mode');
19665
+ // remove track from maps only after unsubscribed has been fired
19666
+ this.tracks.delete(sid);
19667
+ // remove from the right type map
19668
+ switch (publication.kind) {
19669
+ case Track.Kind.Audio:
19670
+ this.audioTracks.delete(sid);
19671
+ break;
19672
+ case Track.Kind.Video:
19673
+ this.videoTracks.delete(sid);
19674
+ break;
19454
19675
  }
19455
- this.spatial = parseInt(results[1]);
19456
- this.temporal = parseInt(results[2]);
19457
- if (results.length > 3) {
19458
- switch (results[3]) {
19459
- case 'h':
19460
- case '_KEY':
19461
- case '_KEY_SHIFT':
19462
- this.suffix = results[3];
19463
- }
19676
+ if (sendUnpublish) {
19677
+ this.emit(ParticipantEvent.TrackUnpublished, publication);
19464
19678
  }
19465
19679
  }
19466
- toString() {
19467
- var _a;
19468
- return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
19680
+ /**
19681
+ * @internal
19682
+ */
19683
+ setAudioOutput(output) {
19684
+ return __awaiter(this, void 0, void 0, function* () {
19685
+ this.audioOutput = output;
19686
+ const promises = [];
19687
+ this.audioTracks.forEach(pub => {
19688
+ var _a;
19689
+ if (pub.track instanceof RemoteAudioTrack) {
19690
+ promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
19691
+ }
19692
+ });
19693
+ yield Promise.all(promises);
19694
+ });
19469
19695
  }
19470
- };
19696
+ /** @internal */
19697
+ emit(event) {
19698
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
19699
+ args[_key - 1] = arguments[_key];
19700
+ }
19701
+ livekitLogger.trace('participant event', {
19702
+ participant: this.sid,
19703
+ event,
19704
+ args
19705
+ });
19706
+ return super.emit(event, ...args);
19707
+ }
19708
+ }
19471
19709
 
19472
19710
  class LocalParticipant extends Participant {
19473
19711
  /** @internal */
@@ -19910,7 +20148,7 @@ class LocalParticipant extends Participant {
19910
20148
  screenVideo.source = Track.Source.ScreenShare;
19911
20149
  const localTracks = [screenVideo];
19912
20150
  if (stream.getAudioTracks().length > 0) {
19913
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false);
20151
+ const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext);
19914
20152
  screenAudio.source = Track.Source.ScreenShareAudio;
19915
20153
  localTracks.push(screenAudio);
19916
20154
  }
@@ -19954,7 +20192,7 @@ class LocalParticipant extends Participant {
19954
20192
  if (track instanceof MediaStreamTrack) {
19955
20193
  switch (track.kind) {
19956
20194
  case 'audio':
19957
- track = new LocalAudioTrack(track, defaultConstraints, true);
20195
+ track = new LocalAudioTrack(track, defaultConstraints, true, this.audioContext);
19958
20196
  break;
19959
20197
  case 'video':
19960
20198
  track = new LocalVideoTrack(track, defaultConstraints, true);
@@ -19963,6 +20201,9 @@ class LocalParticipant extends Participant {
19963
20201
  throw new TrackInvalidError("unsupported MediaStreamTrack kind ".concat(track.kind));
19964
20202
  }
19965
20203
  }
20204
+ if (track instanceof LocalAudioTrack) {
20205
+ track.setAudioContext(this.audioContext);
20206
+ }
19966
20207
  // is it already published? if so skip
19967
20208
  let existingPublication;
19968
20209
  this.tracks.forEach(publication => {
@@ -20100,6 +20341,9 @@ class LocalParticipant extends Participant {
20100
20341
  }
20101
20342
  // set up backup
20102
20343
  if (opts.videoCodec && opts.backupCodec && opts.videoCodec !== opts.backupCodec.codec) {
20344
+ if (!this.roomOptions.dynacast) {
20345
+ this.roomOptions.dynacast = true;
20346
+ }
20103
20347
  const simOpts = Object.assign({}, opts);
20104
20348
  simOpts.simulcast = true;
20105
20349
  simEncodings = computeTrackBackupEncodings(track, opts.backupCodec.codec, simOpts);
@@ -20598,11 +20842,13 @@ class Room extends eventsExports.EventEmitter {
20598
20842
  if (this.abortController) {
20599
20843
  this.abortController.abort();
20600
20844
  }
20601
- this.abortController = new AbortController();
20845
+ // explicit creation as local var needed to satisfy TS compiler when passing it to `attemptConnection` further down
20846
+ const abortController = new AbortController();
20847
+ this.abortController = abortController;
20602
20848
  // at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again
20603
20849
  unlockDisconnect === null || unlockDisconnect === void 0 ? void 0 : unlockDisconnect();
20604
20850
  try {
20605
- yield this.attemptConnection(regionUrl !== null && regionUrl !== void 0 ? regionUrl : url, token, opts, this.abortController);
20851
+ yield this.attemptConnection(regionUrl !== null && regionUrl !== void 0 ? regionUrl : url, token, opts, abortController);
20606
20852
  this.abortController = undefined;
20607
20853
  resolve();
20608
20854
  } catch (e) {
@@ -20644,7 +20890,8 @@ class Room extends eventsExports.EventEmitter {
20644
20890
  publishOnly: connectOptions.publishOnly,
20645
20891
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
20646
20892
  maxRetries: connectOptions.maxRetries,
20647
- e2eeEnabled: !!this.e2eeManager
20893
+ e2eeEnabled: !!this.e2eeManager,
20894
+ websocketTimeout: connectOptions.websocketTimeout
20648
20895
  }, abortController.signal);
20649
20896
  let serverInfo = joinResponse.serverInfo;
20650
20897
  if (!serverInfo) {
@@ -20676,6 +20923,9 @@ class Room extends eventsExports.EventEmitter {
20676
20923
  if (joinResponse.room) {
20677
20924
  this.handleRoomUpdate(joinResponse.room);
20678
20925
  }
20926
+ if (this.options.e2ee && this.e2eeManager) {
20927
+ this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
20928
+ }
20679
20929
  };
20680
20930
  this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
20681
20931
  var _d, _e;
@@ -21254,7 +21504,7 @@ class Room extends eventsExports.EventEmitter {
21254
21504
  /**
21255
21505
  * @internal for testing
21256
21506
  */
21257
- simulateScenario(scenario) {
21507
+ simulateScenario(scenario, arg) {
21258
21508
  return __awaiter(this, void 0, void 0, function* () {
21259
21509
  let postAction = () => {};
21260
21510
  let req;
@@ -21323,6 +21573,17 @@ class Room extends eventsExports.EventEmitter {
21323
21573
  }
21324
21574
  });
21325
21575
  break;
21576
+ case 'subscriber-bandwidth':
21577
+ if (arg === undefined || typeof arg !== 'number') {
21578
+ throw new Error('subscriber-bandwidth requires a number as argument');
21579
+ }
21580
+ req = new SimulateScenario({
21581
+ scenario: {
21582
+ case: 'subscriberBandwidth',
21583
+ value: BigInt(arg)
21584
+ }
21585
+ });
21586
+ break;
21326
21587
  }
21327
21588
  if (req) {
21328
21589
  this.engine.client.sendSimulateScenario(req);
@@ -21339,7 +21600,6 @@ class Room extends eventsExports.EventEmitter {
21339
21600
  */
21340
21601
  startAudio() {
21341
21602
  return __awaiter(this, void 0, void 0, function* () {
21342
- yield this.acquireAudioContext();
21343
21603
  const elements = [];
21344
21604
  const browser = getBrowser();
21345
21605
  if (browser && browser.os === 'iOS') {
@@ -21386,10 +21646,10 @@ class Room extends eventsExports.EventEmitter {
21386
21646
  });
21387
21647
  });
21388
21648
  try {
21389
- yield Promise.all(elements.map(e => {
21649
+ yield Promise.all([this.acquireAudioContext(), ...elements.map(e => {
21390
21650
  e.muted = false;
21391
21651
  return e.play();
21392
- }));
21652
+ })]);
21393
21653
  this.handleAudioPlaybackStarted();
21394
21654
  } catch (err) {
21395
21655
  this.handleAudioPlaybackFailed(err);
@@ -21630,6 +21890,7 @@ class Room extends eventsExports.EventEmitter {
21630
21890
  if (this.options.expWebAudioMix) {
21631
21891
  this.participants.forEach(participant => participant.setAudioContext(this.audioContext));
21632
21892
  }
21893
+ this.localParticipant.setAudioContext(this.audioContext);
21633
21894
  const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
21634
21895
  if (newContextIsRunning !== this.canPlaybackAudio) {
21635
21896
  this.audioEnabled = newContextIsRunning;
@@ -22258,7 +22519,8 @@ class TURNCheck extends Checker {
22258
22519
  const joinRes = yield signalClient.join(this.url, this.token, {
22259
22520
  autoSubscribe: true,
22260
22521
  maxRetries: 0,
22261
- e2eeEnabled: false
22522
+ e2eeEnabled: false,
22523
+ websocketTimeout: 15000
22262
22524
  });
22263
22525
  let hasTLS = false;
22264
22526
  let hasTURN = false;
@@ -22310,18 +22572,19 @@ class WebRTCCheck extends Checker {
22310
22572
  this.room.on(RoomEvent.SignalConnected, () => {
22311
22573
  const prevTrickle = this.room.engine.client.onTrickle;
22312
22574
  this.room.engine.client.onTrickle = (sd, target) => {
22313
- console.log('got candidate', sd);
22314
22575
  if (sd.candidate) {
22315
22576
  const candidate = new RTCIceCandidate(sd);
22316
22577
  let str = "".concat(candidate.protocol, " ").concat(candidate.address, ":").concat(candidate.port, " ").concat(candidate.type);
22317
- if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') {
22318
- hasTcp = true;
22319
- str += ' (active)';
22320
- } else if (candidate.protocol === 'udp' && candidate.address) {
22578
+ if (candidate.address) {
22321
22579
  if (isIPPrivate(candidate.address)) {
22322
22580
  str += ' (private)';
22323
22581
  } else {
22324
- hasIpv4Udp = true;
22582
+ if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') {
22583
+ hasTcp = true;
22584
+ str += ' (passive)';
22585
+ } else if (candidate.protocol === 'udp') {
22586
+ hasIpv4Udp = true;
22587
+ }
22325
22588
  }
22326
22589
  }
22327
22590
  this.appendMessage(str);
@@ -22340,7 +22603,7 @@ class WebRTCCheck extends Checker {
22340
22603
  });
22341
22604
  try {
22342
22605
  yield this.connect();
22343
- console.log('now the room is connected');
22606
+ livekitLogger.info('now the room is connected');
22344
22607
  } catch (err) {
22345
22608
  this.appendWarning('ports need to be open on firewall in order to connect.');
22346
22609
  throw err;
@@ -22385,7 +22648,8 @@ class WebSocketCheck extends Checker {
22385
22648
  const joinRes = yield signalClient.join(this.url, this.token, {
22386
22649
  autoSubscribe: true,
22387
22650
  maxRetries: 0,
22388
- e2eeEnabled: false
22651
+ e2eeEnabled: false,
22652
+ websocketTimeout: 15000
22389
22653
  });
22390
22654
  this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
22391
22655
  if (((_a = joinRes.serverInfo) === null || _a === void 0 ? void 0 : _a.edition) === ServerInfo_Edition.Cloud && ((_b = joinRes.serverInfo) === null || _b === void 0 ? void 0 : _b.region)) {
@@ -22549,5 +22813,5 @@ function isFacingModeValue(item) {
22549
22813
  return item === undefined || allowedValues.includes(item);
22550
22814
  }
22551
22815
 
22552
- export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, importKey, isBackupCodec, isBrowserSupported, isCodecEqual, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, mimeTypeToVideoCodecString, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs };
22816
+ export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, importKey, isBackupCodec, isBrowserSupported, isCodecEqual, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, mimeTypeToVideoCodecString, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs };
22553
22817
  //# sourceMappingURL=livekit-client.esm.mjs.map