livekit-client 2.22.1 → 2.22.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) 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 +663 -476
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +480 -157
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.fm.worker.js +1 -1
  8. package/dist/livekit-client.fm.worker.js.map +1 -1
  9. package/dist/livekit-client.fm.worker.mjs +8 -1
  10. package/dist/livekit-client.fm.worker.mjs.map +1 -1
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/api/SignalClient.d.ts.map +1 -1
  14. package/dist/src/api/WebSocketStream.d.ts.map +1 -1
  15. package/dist/src/api/utils.d.ts +1 -0
  16. package/dist/src/api/utils.d.ts.map +1 -1
  17. package/dist/src/e2ee/E2eeManager.d.ts +33 -0
  18. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  19. package/dist/src/e2ee/constants.d.ts +5 -0
  20. package/dist/src/e2ee/constants.d.ts.map +1 -1
  21. package/dist/src/e2ee/types.d.ts +24 -3
  22. package/dist/src/e2ee/types.d.ts.map +1 -1
  23. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  24. package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
  25. package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts.map +1 -0
  26. package/dist/src/e2ee/worker/FrameCryptor.d.ts +43 -19
  27. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  28. package/dist/src/logger.d.ts +4 -0
  29. package/dist/src/logger.d.ts.map +1 -1
  30. package/dist/src/room/PCTransportManager.d.ts +12 -0
  31. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  32. package/dist/src/room/RTCEngine.d.ts +1 -0
  33. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  34. package/dist/src/room/data-stream/incoming/StreamReader.d.ts +17 -17
  35. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
  36. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  37. package/dist/src/room/participant/publishUtils.d.ts +16 -0
  38. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  39. package/dist/src/room/track/LocalVideoTrack.d.ts +7 -0
  40. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  41. package/dist/src/room/track/options.d.ts +1 -1
  42. package/dist/src/room/utils.d.ts +34 -0
  43. package/dist/src/room/utils.d.ts.map +1 -1
  44. package/dist/ts4.2/api/utils.d.ts +1 -0
  45. package/dist/ts4.2/e2ee/E2eeManager.d.ts +33 -0
  46. package/dist/ts4.2/e2ee/constants.d.ts +5 -0
  47. package/dist/ts4.2/e2ee/types.d.ts +24 -3
  48. package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
  49. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +43 -19
  50. package/dist/ts4.2/logger.d.ts +4 -0
  51. package/dist/ts4.2/room/PCTransportManager.d.ts +12 -0
  52. package/dist/ts4.2/room/RTCEngine.d.ts +1 -0
  53. package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +17 -17
  54. package/dist/ts4.2/room/participant/publishUtils.d.ts +16 -0
  55. package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +7 -0
  56. package/dist/ts4.2/room/track/options.d.ts +1 -1
  57. package/dist/ts4.2/room/utils.d.ts +34 -0
  58. package/package.json +1 -1
  59. package/src/api/SignalClient.ts +2 -1
  60. package/src/api/WebSocketStream.ts +3 -8
  61. package/src/api/utils.ts +10 -0
  62. package/src/e2ee/E2eeManager.test.ts +196 -0
  63. package/src/e2ee/E2eeManager.ts +150 -31
  64. package/src/e2ee/constants.ts +6 -0
  65. package/src/e2ee/subscriberBlackScreen.test.ts +544 -0
  66. package/src/e2ee/types.ts +28 -3
  67. package/src/e2ee/worker/DataCryptor.ts +2 -1
  68. package/src/e2ee/worker/ErrorRateLimiter.test.ts +53 -0
  69. package/src/e2ee/worker/ErrorRateLimiter.ts +52 -0
  70. package/src/e2ee/worker/FrameCryptor.race.test.ts +9 -26
  71. package/src/e2ee/worker/FrameCryptor.test.ts +0 -1
  72. package/src/e2ee/worker/FrameCryptor.ts +202 -119
  73. package/src/e2ee/worker/e2ee.worker.ts +72 -17
  74. package/src/logger.ts +22 -0
  75. package/src/room/PCTransportManager.test.ts +35 -0
  76. package/src/room/PCTransportManager.ts +12 -4
  77. package/src/room/RTCEngine.ts +28 -7
  78. package/src/room/Room.ts +1 -1
  79. package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +171 -0
  80. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +17 -18
  81. package/src/room/data-stream/incoming/StreamReader.ts +20 -50
  82. package/src/room/participant/LocalParticipant.ts +30 -14
  83. package/src/room/participant/publishUtils.test.ts +133 -0
  84. package/src/room/participant/publishUtils.ts +54 -19
  85. package/src/room/track/LocalVideoTrack.ts +15 -5
  86. package/src/room/track/options.ts +1 -1
  87. package/src/room/utils.test.ts +87 -0
  88. package/src/room/utils.ts +59 -0
@@ -930,7 +930,10 @@ function isScalarZeroValue(type, value) {
930
930
  return value == 0;
931
931
  // Loose comparison matches 0n, 0 and "0"
932
932
  }
933
- }function _arrayLikeToArray(r, a) {
933
+ }function _OverloadYield(e, d) {
934
+ this.v = e, this.k = d;
935
+ }
936
+ function _arrayLikeToArray(r, a) {
934
937
  (null == a || a > r.length) && (a = r.length);
935
938
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
936
939
  return n;
@@ -1046,7 +1049,61 @@ function _unsupportedIterableToArray(r, a) {
1046
1049
  var t = {}.toString.call(r).slice(8, -1);
1047
1050
  return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
1048
1051
  }
1049
- }/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/restrict-plus-operands */
1052
+ }
1053
+ function _wrapAsyncGenerator(e) {
1054
+ return function () {
1055
+ return new AsyncGenerator(e.apply(this, arguments));
1056
+ };
1057
+ }
1058
+ function AsyncGenerator(e) {
1059
+ var t, n;
1060
+ function resume(t, n) {
1061
+ try {
1062
+ var r = e[t](n),
1063
+ o = r.value,
1064
+ u = o instanceof _OverloadYield;
1065
+ Promise.resolve(u ? o.v : o).then(function (n) {
1066
+ if (u) {
1067
+ var i = "return" === t && o.k ? t : "next";
1068
+ if (!o.k || n.done) return resume(i, n);
1069
+ n = e[i](n).value;
1070
+ }
1071
+ settle(!!r.done, n);
1072
+ }, function (e) {
1073
+ resume("throw", e);
1074
+ });
1075
+ } catch (e) {
1076
+ settle(2, e);
1077
+ }
1078
+ }
1079
+ function settle(e, r) {
1080
+ 2 === e ? t.reject(r) : t.resolve({
1081
+ value: r,
1082
+ done: e
1083
+ }), (t = t.next) ? resume(t.key, t.arg) : n = null;
1084
+ }
1085
+ this._invoke = function (e, r) {
1086
+ return new Promise(function (o, u) {
1087
+ var i = {
1088
+ key: e,
1089
+ arg: r,
1090
+ resolve: o,
1091
+ reject: u,
1092
+ next: null
1093
+ };
1094
+ n ? n = n.next = i : (t = n = i, resume(e, r));
1095
+ });
1096
+ }, "function" != typeof e.return && (this.return = void 0);
1097
+ }
1098
+ AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function () {
1099
+ return this;
1100
+ }, AsyncGenerator.prototype.next = function (e) {
1101
+ return this._invoke("next", e);
1102
+ }, AsyncGenerator.prototype.throw = function (e) {
1103
+ return this._invoke("throw", e);
1104
+ }, AsyncGenerator.prototype.return = function (e) {
1105
+ return this._invoke("return", e);
1106
+ };/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/restrict-plus-operands */
1050
1107
  /**
1051
1108
  * Protobuf binary format wire types.
1052
1109
  *
@@ -8625,7 +8682,21 @@ function setLogExtension(extension, logger) {
8625
8682
  logR.setLevel(logR.getLevel());
8626
8683
  });
8627
8684
  }
8628
- const workerLogger = loglevelExports.getLogger(LoggerNames.E2EE);const maxRetryDelay = 7000;
8685
+ const workerLogger = loglevelExports.getLogger(LoggerNames.E2EE);
8686
+ const workerLogLevelListeners = new Set();
8687
+ const originalWorkerSetLevel = workerLogger.setLevel.bind(workerLogger);
8688
+ workerLogger.setLevel = (level, persist) => {
8689
+ originalWorkerSetLevel(level, persist);
8690
+ const numeric = workerLogger.getLevel();
8691
+ workerLogLevelListeners.forEach(cb => cb(numeric));
8692
+ };
8693
+ /** @internal Subscribe to workerLogger level changes (so E2EE workers can be kept in sync). */
8694
+ function onWorkerLogLevelChanged(cb) {
8695
+ workerLogLevelListeners.add(cb);
8696
+ return () => {
8697
+ workerLogLevelListeners.delete(cb);
8698
+ };
8699
+ }const maxRetryDelay = 7000;
8629
8700
  const DEFAULT_RETRY_DELAYS_IN_MS = [0, 300, 2 * 2 * 300, 3 * 3 * 300, 4 * 4 * 300, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay];
8630
8701
  class DefaultReconnectPolicy {
8631
8702
  constructor(retryDelays) {
@@ -12208,7 +12279,7 @@ function getMatch(exp, ua) {
12208
12279
  }
12209
12280
  function getOSVersion(ua) {
12210
12281
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
12211
- }var version$1 = "2.22.1";const version = version$1;
12282
+ }var version$1 = "2.22.3";const version = version$1;
12212
12283
  const protocolVersion = 17;
12213
12284
  /** Initial client protocol. */
12214
12285
  const CLIENT_PROTOCOL_DEFAULT = 0;
@@ -14216,6 +14287,56 @@ function negotiateDependencyDescriptor(transceiver) {
14216
14287
  return false;
14217
14288
  }
14218
14289
  }
14290
+ /**
14291
+ * VP9 and AV1 are published as SVC (a single RTP stream carrying every spatial layer)
14292
+ * by default. They can instead be published as real, rid based simulcast — one
14293
+ * independent stream per rid, each carrying a single spatial layer — when the caller
14294
+ * opts in with `simulcast: true` and a single spatial layer scalability mode (`L1Tx`).
14295
+ *
14296
+ * The SFU has to be told about this: without an explicit
14297
+ * `SimulcastCodec.videoLayerMode` it assumes `MULTIPLE_SPATIAL_LAYERS_PER_STREAM` for
14298
+ * any SVC capable codec.
14299
+ */
14300
+ function isSVCSimulcast(codec, options) {
14301
+ var _a;
14302
+ return isSVCCodec(codec) && !!(options === null || options === void 0 ? void 0 : options.simulcast) && !!((_a = options.scalabilityMode) === null || _a === void 0 ? void 0 : _a.startsWith('L1T'));
14303
+ }
14304
+ /**
14305
+ * Whether the browser reads multiple encodings on an SVC capable codec as *legacy SVC*
14306
+ * rather than as real simulcast.
14307
+ *
14308
+ * Before Chrome M113, supplying more than one encoding for VP9/AV1 selected SVC mode;
14309
+ * only from M113 does libwebrtc treat such encodings as simulcast, and only when each
14310
+ * one carries its own scalabilityMode. Safari (and anything WebKit based, i. e. every
14311
+ * browser on iOS) still uses the old interpretation, as does React Native's libwebrtc.
14312
+ * Announced at https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw
14313
+ *
14314
+ * Where this is true the rids would not exist on the wire, so VP9/AV1 must be published
14315
+ * as SVC no matter what the caller asked for.
14316
+ */
14317
+ function usesLegacySVCEncodings() {
14318
+ const browser = getBrowser();
14319
+ return isSafariBased() ||
14320
+ // Even tho RN runs M114, it does not produce SVC layers when a single encoding
14321
+ // is provided. So we'll use the legacy SVC specification for now.
14322
+ // TODO: when we upstream libwebrtc, this will need additional verification
14323
+ isReactNative() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser.version, '113') < 0;
14324
+ }
14325
+ /**
14326
+ * Last server version that doesn't support vp9/av1 simulcast.
14327
+ */
14328
+ const svcSimulcastMinServerVersion = '1.13.6';
14329
+ /**
14330
+ * Whether the connected server honours `SimulcastCodec.videoLayerMode`, i. e. whether
14331
+ * VP9/AV1 can be published as rid based simulcast. An unknown version is treated as
14332
+ * unsupported so the publish falls back to SVC.
14333
+ */
14334
+ function isSVCSimulcastSupportedByServer(serverVersion) {
14335
+ if (!serverVersion) {
14336
+ return false;
14337
+ }
14338
+ return compareVersions(serverVersion, svcSimulcastMinServerVersion) > 0;
14339
+ }
14219
14340
  function supportsSetSinkId(elm) {
14220
14341
  if (!document || isSafariBased()) {
14221
14342
  return false;
@@ -14824,11 +14945,25 @@ function getAbortReasonAsString(signal) {
14824
14945
  default:
14825
14946
  return 'toString' in reason ? reason.toString() : defaultMessage;
14826
14947
  }
14948
+ }
14949
+ function getErrorDescription(error, errorCategory) {
14950
+ if (error instanceof Error) {
14951
+ if (error.name && error.message) {
14952
+ return "".concat(error.name, ": ").concat(error.message);
14953
+ }
14954
+ return error.name;
14955
+ }
14956
+ return "Encountered unknown ".concat(errorCategory, " error: ").concat(String(error));
14827
14957
  }const ENCRYPTION_ALGORITHM = 'AES-GCM';
14828
14958
  // How many consecutive frames can fail decrypting before a particular key gets marked as invalid
14829
14959
  const DECRYPTION_FAILURE_TOLERANCE = 10;
14830
14960
  // flag set to indicate that e2ee has been setup for sender/receiver;
14831
14961
  const E2EE_FLAG = 'lk_e2ee';
14962
+ /**
14963
+ * trackId a receiver's/sender's encoded stream pipeline was last set up for.
14964
+ * Needed to recover the right cryptor when a transceiver gets reused.
14965
+ */
14966
+ const E2EE_TRACK_ID = 'lk_e2ee_track_id';
14832
14967
  const SALT = 'LKFrameEncryptionKey';
14833
14968
  const KEY_PROVIDER_DEFAULTS = {
14834
14969
  sharedKey: false,
@@ -15770,10 +15905,18 @@ function isElementInViewport(el, win) {
15770
15905
  * @experimental
15771
15906
  */
15772
15907
  class E2EEManager extends eventsExports.EventEmitter {
15908
+ get logContext() {
15909
+ var _a, _b;
15910
+ return {
15911
+ room: (_a = this.room) === null || _a === void 0 ? void 0 : _a.name,
15912
+ participant: (_b = this.room) === null || _b === void 0 ? void 0 : _b.localParticipant.identity
15913
+ };
15914
+ }
15773
15915
  constructor(options, dcEncryptionEnabled) {
15774
15916
  super();
15775
15917
  this.decryptDataRequests = new Map();
15776
15918
  this.encryptDataRequests = new Map();
15919
+ this.log = getLogger(LoggerNames.E2EE, () => this.logContext);
15777
15920
  this.onWorkerMessage = ev => {
15778
15921
  var _a, _b;
15779
15922
  const _ev$data = ev.data,
@@ -15781,22 +15924,22 @@ class E2EEManager extends eventsExports.EventEmitter {
15781
15924
  data = _ev$data.data;
15782
15925
  switch (kind) {
15783
15926
  case 'error':
15784
- livekitLogger.error(data.error.message);
15785
- // If error has uuid, it's from an async operation (encrypt/decrypt)
15786
- // Reject the corresponding future
15927
+ // If error has uuid, it's from an async operation (encrypt/decrypt).
15928
+ // Reject the corresponding future and let the caller decide how to log/handle;
15929
+ // logging here would duplicate whatever the caller does.
15787
15930
  if (data.uuid) {
15788
15931
  const decryptFuture = this.decryptDataRequests.get(data.uuid);
15789
15932
  if (decryptFuture === null || decryptFuture === void 0 ? void 0 : decryptFuture.reject) {
15790
15933
  decryptFuture.reject(data.error);
15791
- break; // Don't emit general error if it's handled by future
15934
+ break;
15792
15935
  }
15793
15936
  const encryptFuture = this.encryptDataRequests.get(data.uuid);
15794
15937
  if (encryptFuture === null || encryptFuture === void 0 ? void 0 : encryptFuture.reject) {
15795
15938
  encryptFuture.reject(data.error);
15796
- break; // Don't emit general error if it's handled by future
15939
+ break;
15797
15940
  }
15798
15941
  }
15799
- // Emit general error event for unhandled errors
15942
+ this.log.error(data.error.message);
15800
15943
  this.emit(EncryptionEvent.EncryptionError, data.error, data.participantIdentity);
15801
15944
  break;
15802
15945
  case 'initAck':
@@ -15841,10 +15984,13 @@ class E2EEManager extends eventsExports.EventEmitter {
15841
15984
  case 'packetTrailerMetadata':
15842
15985
  this.handleFrameMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata);
15843
15986
  break;
15987
+ case 'log':
15988
+ workerLogger[data.level](data.msg, data.context);
15989
+ break;
15844
15990
  }
15845
15991
  };
15846
15992
  this.onWorkerError = ev => {
15847
- livekitLogger.error('e2ee worker encountered an error:', {
15993
+ this.log.error('e2ee worker encountered an error:', {
15848
15994
  error: ev.error
15849
15995
  });
15850
15996
  this.emit(EncryptionEvent.EncryptionError, ev.error, undefined);
@@ -15867,7 +16013,7 @@ class E2EEManager extends eventsExports.EventEmitter {
15867
16013
  if (!isE2EESupported()) {
15868
16014
  throw new DeviceUnsupportedError('tried to setup end-to-end encryption on an unsupported browser');
15869
16015
  }
15870
- livekitLogger.info('setting up e2ee');
16016
+ this.log.info('setting up e2ee');
15871
16017
  if (room !== this.room) {
15872
16018
  this.room = room;
15873
16019
  this.setupEventListeners(room, this.keyProvider);
@@ -15880,20 +16026,94 @@ class E2EEManager extends eventsExports.EventEmitter {
15880
16026
  }
15881
16027
  };
15882
16028
  if (this.worker) {
15883
- livekitLogger.info("initializing worker", {
16029
+ this.log.info("initializing worker", {
15884
16030
  worker: this.worker
15885
16031
  });
15886
16032
  this.worker.onmessage = this.onWorkerMessage;
15887
16033
  this.worker.onerror = this.onWorkerError;
15888
16034
  this.worker.postMessage(msg);
16035
+ this.subscribeToLogLevelChanges();
15889
16036
  }
15890
16037
  }
15891
16038
  }
16039
+ /**
16040
+ * Subscribe the current worker to main-thread `workerLogger` level changes,
16041
+ * without strongly retaining `this` or `this.worker` from the module-global
16042
+ * listener Set on the logger. See {@link disposeRegistry}.
16043
+ */
16044
+ subscribeToLogLevelChanges() {
16045
+ var _a;
16046
+ // Guard against duplicate registration on re-setup.
16047
+ (_a = this.unsubscribeLogLevel) === null || _a === void 0 ? void 0 : _a.call(this);
16048
+ let unsub;
16049
+ if (E2EEManager.disposeRegistry) {
16050
+ // Modern engines: hold the worker weakly so the module-global listener Set
16051
+ // on the logger can't retain this manager, and clean up the entry on GC.
16052
+ const workerRef = new WeakRef(this.worker);
16053
+ unsub = onWorkerLogLevelChanged(level => {
16054
+ const worker = workerRef.deref();
16055
+ if (!worker) {
16056
+ unsub === null || unsub === void 0 ? void 0 : unsub();
16057
+ return;
16058
+ }
16059
+ worker.postMessage({
16060
+ kind: 'setLogLevel',
16061
+ data: {
16062
+ level
16063
+ }
16064
+ });
16065
+ });
16066
+ E2EEManager.disposeRegistry.register(this, unsub, this);
16067
+ } else {
16068
+ // Safari <14.1 and similar: no WeakRef. Fall back to a strong reference;
16069
+ // the leak lives until the consumer calls `dispose()`.
16070
+ const worker = this.worker;
16071
+ unsub = onWorkerLogLevelChanged(level => {
16072
+ worker.postMessage({
16073
+ kind: 'setLogLevel',
16074
+ data: {
16075
+ level
16076
+ }
16077
+ });
16078
+ });
16079
+ }
16080
+ this.unsubscribeLogLevel = unsub;
16081
+ }
16082
+ /**
16083
+ * @internal
16084
+ * Release the log-level subscription, reject any pending encrypt/decrypt
16085
+ * futures, and detach the worker message handlers. The worker itself is
16086
+ * caller-owned and is not terminated. Idempotent.
16087
+ */
16088
+ dispose() {
16089
+ var _a, _b, _c;
16090
+ (_a = this.unsubscribeLogLevel) === null || _a === void 0 ? void 0 : _a.call(this);
16091
+ this.unsubscribeLogLevel = undefined;
16092
+ if (E2EEManager.disposeRegistry) {
16093
+ E2EEManager.disposeRegistry.unregister(this);
16094
+ }
16095
+ // Reject pending futures BEFORE detaching worker handlers, so any late
16096
+ // response can't resolve one after we've cut the pipe. Each future's
16097
+ // `onFinally` deletes its own map entry, so both maps drain themselves.
16098
+ // Snapshot before iterating in case a rejection handler mutates the map.
16099
+ const disposalError = new CryptorError('E2EEManager disposed', CryptorErrorReason.InternalError);
16100
+ for (const future of [...this.encryptDataRequests.values()]) {
16101
+ (_b = future.reject) === null || _b === void 0 ? void 0 : _b.call(future, disposalError);
16102
+ }
16103
+ for (const future of [...this.decryptDataRequests.values()]) {
16104
+ (_c = future.reject) === null || _c === void 0 ? void 0 : _c.call(future, disposalError);
16105
+ }
16106
+ if (this.worker) {
16107
+ this.worker.onmessage = null;
16108
+ this.worker.onerror = null;
16109
+ }
16110
+ this.removeAllListeners();
16111
+ }
15892
16112
  /**
15893
16113
  * @internal
15894
16114
  */
15895
16115
  setParticipantCryptorEnabled(enabled, participantIdentity) {
15896
- livekitLogger.debug("set e2ee to ".concat(enabled, " for participant ").concat(participantIdentity));
16116
+ this.log.debug("set e2ee to ".concat(enabled, " for participant ").concat(participantIdentity));
15897
16117
  this.postEnable(enabled, participantIdentity);
15898
16118
  }
15899
16119
  /**
@@ -15901,7 +16121,7 @@ class E2EEManager extends eventsExports.EventEmitter {
15901
16121
  */
15902
16122
  setSifTrailer(trailer) {
15903
16123
  if (!trailer || trailer.length === 0) {
15904
- livekitLogger.warn("ignoring server sent trailer as it's empty");
16124
+ this.log.warn("ignoring server sent trailer as it's empty");
15905
16125
  } else {
15906
16126
  this.postSifTrailer(trailer);
15907
16127
  }
@@ -15925,12 +16145,12 @@ class E2EEManager extends eventsExports.EventEmitter {
15925
16145
  });
15926
16146
  }
15927
16147
  setupEventListeners(room, keyProvider) {
15928
- room.on(RoomEvent.TrackPublished, (pub, participant) => this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity));
16148
+ room.on(RoomEvent.TrackPublished, (pub, participant) => this.setParticipantCryptorEnabledForPublication(pub, participant.identity));
15929
16149
  room.on(RoomEvent.ConnectionStateChanged, state => {
15930
16150
  if (state === ConnectionState.Connected) {
15931
16151
  room.remoteParticipants.forEach(participant => {
15932
16152
  participant.trackPublications.forEach(pub => {
15933
- this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity);
16153
+ this.setParticipantCryptorEnabledForPublication(pub, participant.identity);
15934
16154
  });
15935
16155
  });
15936
16156
  }
@@ -16100,6 +16320,21 @@ class E2EEManager extends eventsExports.EventEmitter {
16100
16320
  };
16101
16321
  this.worker.postMessage(msg);
16102
16322
  }
16323
+ /**
16324
+ * Derive the participant's encryption state from one of its publications.
16325
+ * Publications without trackInfo are skipped rather than thrown on: throwing
16326
+ * inside the forEach above would leave every remaining participant without an
16327
+ * encryption state, and a cryptor with unknown state can no longer decrypt.
16328
+ */
16329
+ setParticipantCryptorEnabledForPublication(pub, participantIdentity) {
16330
+ if (!pub.trackInfo) {
16331
+ this.log.warn('skipping e2ee enabled update for publication without trackInfo', {
16332
+ trackSid: pub.trackSid
16333
+ });
16334
+ return;
16335
+ }
16336
+ this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participantIdentity);
16337
+ }
16103
16338
  setupE2EEReceiver(track, remoteId, trackInfo) {
16104
16339
  if (!track.receiver) {
16105
16340
  return;
@@ -16113,7 +16348,7 @@ class E2EEManager extends eventsExports.EventEmitter {
16113
16348
  setupE2EESender(track, sender) {
16114
16349
  var _a, _b, _c;
16115
16350
  if (!isLocalTrack(track) || !sender) {
16116
- if (!sender) livekitLogger.warn('early return because sender is not ready');
16351
+ if (!sender) this.log.warn('early return because sender is not ready');
16117
16352
  return;
16118
16353
  }
16119
16354
  this.handleSender(sender, track.mediaStreamID, undefined, isVideoTrack(track) ? (_b = (_a = track.publishOptions) === null || _a === void 0 ? void 0 : _a.frameMetadata) !== null && _b !== void 0 ? _b : (_c = track.publishOptions) === null || _c === void 0 ? void 0 : _c.packetTrailer : undefined);
@@ -16136,21 +16371,27 @@ class E2EEManager extends eventsExports.EventEmitter {
16136
16371
  codec,
16137
16372
  hasPacketTrailer
16138
16373
  };
16139
- // @ts-ignore
16140
16374
  receiver.transform = new RTCRtpScriptTransform(this.worker, options);
16141
16375
  } else {
16142
- if (E2EE_FLAG in receiver && codec) {
16143
- // update track-specific state when the transceiver is reused
16376
+ if (E2EE_FLAG in receiver && E2EE_TRACK_ID in receiver) {
16377
+ // The transceiver is being reused for a new track. We cannot set up a new
16378
+ // pipeline from here: this receiver's encoded streams were transferred to
16379
+ // the worker on first setup and a transferred stream can't be transferred
16380
+ // again, while createEncodedStreams() may only be called once per receiver.
16381
+ // So hand the worker the new track/participant and let it re-point (and if
16382
+ // necessary rebuild) the cryptor that still owns those streams.
16144
16383
  const msg = {
16145
16384
  kind: 'updateCodec',
16146
16385
  data: {
16147
16386
  trackId,
16387
+ previousTrackId: receiver[E2EE_TRACK_ID],
16148
16388
  codec,
16149
16389
  participantIdentity,
16150
16390
  hasPacketTrailer
16151
16391
  }
16152
16392
  };
16153
16393
  this.worker.postMessage(msg);
16394
+ receiver[E2EE_TRACK_ID] = trackId;
16154
16395
  return;
16155
16396
  }
16156
16397
  // @ts-ignore
@@ -16175,7 +16416,6 @@ class E2EEManager extends eventsExports.EventEmitter {
16175
16416
  trackId: trackId,
16176
16417
  codec,
16177
16418
  participantIdentity: participantIdentity,
16178
- isReuse: E2EE_FLAG in receiver,
16179
16419
  hasPacketTrailer
16180
16420
  }
16181
16421
  };
@@ -16183,6 +16423,8 @@ class E2EEManager extends eventsExports.EventEmitter {
16183
16423
  }
16184
16424
  // @ts-ignore
16185
16425
  receiver[E2EE_FLAG] = true;
16426
+ // @ts-ignore
16427
+ receiver[E2EE_TRACK_ID] = trackId;
16186
16428
  });
16187
16429
  }
16188
16430
  /**
@@ -16199,7 +16441,7 @@ class E2EEManager extends eventsExports.EventEmitter {
16199
16441
  throw TypeError('local identity needs to be known in order to set up encrypted sender');
16200
16442
  }
16201
16443
  if (isScriptTransformSupportedForWorker()) {
16202
- livekitLogger.info('initialize script transform');
16444
+ this.log.info('initialize script transform');
16203
16445
  const options = {
16204
16446
  kind: 'encode',
16205
16447
  participantIdentity: this.room.localParticipant.identity,
@@ -16211,7 +16453,7 @@ class E2EEManager extends eventsExports.EventEmitter {
16211
16453
  // @ts-ignore
16212
16454
  sender.transform = new RTCRtpScriptTransform(this.worker, options);
16213
16455
  } else {
16214
- livekitLogger.info('initialize encoded streams');
16456
+ this.log.info('initialize encoded streams');
16215
16457
  // @ts-ignore
16216
16458
  const senderStreams = sender.createEncodedStreams();
16217
16459
  const msg = {
@@ -16222,7 +16464,6 @@ class E2EEManager extends eventsExports.EventEmitter {
16222
16464
  codec,
16223
16465
  trackId,
16224
16466
  participantIdentity: this.room.localParticipant.identity,
16225
- isReuse: false,
16226
16467
  hasPacketTrailer: hasFrameMetadataPublishOptions(frameMetadata),
16227
16468
  packetTrailer: frameMetadata
16228
16469
  }
@@ -16232,7 +16473,15 @@ class E2EEManager extends eventsExports.EventEmitter {
16232
16473
  // @ts-ignore
16233
16474
  sender[E2EE_FLAG] = true;
16234
16475
  }
16235
- }const MAX_ENTRIES = 300;
16476
+ }
16477
+ /**
16478
+ * Runs a cleanup callback once this manager is garbage collected. Lets the
16479
+ * log-level listener (held in a module-global Set on the main-thread logger)
16480
+ * fall out of scope even when the consumer forgets to call `dispose()`.
16481
+ */
16482
+ E2EEManager.disposeRegistry = typeof FinalizationRegistry !== 'undefined' && typeof WeakRef !== 'undefined' && new FinalizationRegistry(cleanup => {
16483
+ cleanup();
16484
+ });const MAX_ENTRIES = 300;
16236
16485
  /**
16237
16486
  * Caches frame metadata extracted from received video frames,
16238
16487
  * keyed by RTP timestamp so it can be looked up when the frame is displayed.
@@ -18043,9 +18292,9 @@ class WebSocketStream {
18043
18292
  let data = _ref2.data;
18044
18293
  return controller.enqueue(data);
18045
18294
  };
18046
- ws.onerror = e => controller.error(ConnectionError.websocket(e instanceof Error ? "".concat(e.name, ": ").concat(e.message) : "Encountered unknown websocket error: ".concat(String(e))));
18295
+ ws.onerror = e => controller.error(ConnectionError.websocket(getErrorDescription(e, 'websocket')));
18047
18296
  ws.onclose = ev => {
18048
- if (ev.wasClean) {
18297
+ if (ev.wasClean || ev.code === 1000) {
18049
18298
  controller.close();
18050
18299
  } else {
18051
18300
  controller.error(ConnectionError.websocket("WS closed unexpectedly with code ".concat(ev.code)));
@@ -18500,6 +18749,7 @@ class SignalClient {
18500
18749
  if (firstMessage) {
18501
18750
  this.handleSignalResponse(firstMessage);
18502
18751
  }
18752
+ const attemptId = this.attemptId;
18503
18753
  while (true) {
18504
18754
  if (this.signalLatency) {
18505
18755
  yield sleep(this.signalLatency);
@@ -18517,7 +18767,7 @@ class SignalClient {
18517
18767
  this.log.error("error reading from signal stream", {
18518
18768
  error: e
18519
18769
  });
18520
- yield this.close(false, 'error in reading loop');
18770
+ yield this.handleOnClose('error in reading loop', attemptId);
18521
18771
  break;
18522
18772
  }
18523
18773
  }
@@ -21074,13 +21324,21 @@ class PCTransportManager {
21074
21324
  this.updateState();
21075
21325
  });
21076
21326
  }
21327
+ /**
21328
+ * Restarts ICE on the transports that need it. Only the publisher: the server restarts the
21329
+ * subscriber's ICE itself and follows with a fresh offer.
21330
+ *
21331
+ * The subscriber deliberately does NOT enter `restartingIce` here. Queueing its remote
21332
+ * candidates would guard against candidates for a new generation arriving before the offer
21333
+ * that introduces it, but the server does not send them in that order -- on a same-node
21334
+ * resume it buffers them until the offer has gone out, and on a reconnect that lands on
21335
+ * another node it withholds subscriber candidates until immediately before creating the
21336
+ * offer. Setting the flag only risks withholding candidates during the window that decides
21337
+ * whether the reconnect succeeded.
21338
+ */
21077
21339
  triggerIceRestart() {
21078
21340
  return __awaiter(this, void 0, void 0, function* () {
21079
21341
  this.iceLog.warn('triggering ICE restart');
21080
- if (this.subscriber) {
21081
- this.subscriber.restartingIce = true;
21082
- }
21083
- // only restart publisher if it's needed
21084
21342
  if (this.needsPublisher) {
21085
21343
  yield this.createAndSendPublisherOffer({
21086
21344
  iceRestart: true
@@ -22926,6 +23184,8 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
22926
23184
  const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
22927
23185
  const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
22928
23186
  const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
23187
+ // VP9/AV1 published as rid based simulcast rather than SVC, see isSVCSimulcast
23188
+ const useSVCSimulcast = isSVCSimulcast(videoCodec, options);
22929
23189
  if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
22930
23190
  // when we aren't simulcasting or svc, will need to return a single encoding without
22931
23191
  // capping bandwidth. we always require a encoding for dynacast
@@ -22938,23 +23198,16 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
22938
23198
  }
22939
23199
  const sourceFramerate = videoEncoding.maxFramerate;
22940
23200
  const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
22941
- if (scalabilityMode && isSVCCodec(videoCodec)) {
23201
+ if (scalabilityMode && isSVCCodec(videoCodec) && !useSVCSimulcast) {
22942
23202
  const sm = new ScalabilityMode(scalabilityMode);
22943
23203
  const encodings = [];
22944
23204
  if (sm.spatial > 3) {
22945
23205
  throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
22946
23206
  }
22947
- // Before M113 in Chrome, defining multiple encodings with an SVC codec indicated
22948
- // that SVC mode should be used. Safari still works this way.
22949
- // This is a bit confusing but is due to how libwebrtc interpreted the encodings field
22950
- // before M113.
22951
- // Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
23207
+ // Browsers that read multiple encodings on an SVC codec as SVC rather than as
23208
+ // simulcast, see usesLegacySVCEncodings
22952
23209
  const browser = getBrowser();
22953
- if (isSafariBased() ||
22954
- // Even tho RN runs M114, it does not produce SVC layers when a single encoding
22955
- // is provided. So we'll use the legacy SVC specification for now.
22956
- // TODO: when we upstream libwebrtc, this will need additional verification
22957
- isReactNative() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) {
23210
+ if (usesLegacySVCEncodings()) {
22958
23211
  const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
22959
23212
  // safari 18.4 uses a different svc API that requires scaleResolutionDownBy to be set.
22960
23213
  const requireScale = isSafariSvcApi(browser);
@@ -22990,6 +23243,18 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
22990
23243
  if (!useSimulcast) {
22991
23244
  return [videoEncoding];
22992
23245
  }
23246
+ // Chrome M113+ only treats multiple encodings on an SVC capable codec as real
23247
+ // simulcast when every encoding carries an explicit scalabilityMode next to its
23248
+ // scaleResolutionDownBy. Without it the encodings are interpreted as legacy SVC.
23249
+ const applySVCSimulcastMode = encodings => {
23250
+ if (useSVCSimulcast) {
23251
+ encodings.forEach(encoding => {
23252
+ /* @ts-ignore */
23253
+ encoding.scalabilityMode = scalabilityMode;
23254
+ });
23255
+ }
23256
+ return encodings;
23257
+ };
22993
23258
  let presets;
22994
23259
  if (isScreenShare) {
22995
23260
  presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
@@ -23015,13 +23280,38 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
23015
23280
  // based on other conditions.
23016
23281
  const size = Math.max(width, height);
23017
23282
  if (size >= 960 && midPreset) {
23018
- return encodingsFromPresets(width, height, [lowPreset, midPreset, original], sourceFramerate);
23283
+ return applySVCSimulcastMode(encodingsFromPresets(width, height, [lowPreset, midPreset, original], sourceFramerate));
23019
23284
  }
23020
23285
  if (size >= 480) {
23021
- return encodingsFromPresets(width, height, [lowPreset, original], sourceFramerate);
23286
+ return applySVCSimulcastMode(encodingsFromPresets(width, height, [lowPreset, original], sourceFramerate));
23022
23287
  }
23023
23288
  }
23024
- return encodingsFromPresets(width, height, [original]);
23289
+ return applySVCSimulcastMode(encodingsFromPresets(width, height, [original]));
23290
+ }
23291
+ /**
23292
+ * Bitrate to hint to the bandwidth estimator through `x-google-start-bitrate`, so that
23293
+ * a publish does not spend its first seconds ramping up from a very low rate.
23294
+ *
23295
+ * It has to be the total the encoder will put on the wire, which means picking the
23296
+ * encoding that carries the inclusive bitrate:
23297
+ * - SVC publishes a single stream with the layers built in. `encodings[0]` holds the
23298
+ * full bitrate — the legacy SVC shape orders its encodings `f`..`q`, so that holds
23299
+ * for both SVC shapes.
23300
+ * - Simulcast publishes independent streams ordered `q`..`f`, so the total is the sum.
23301
+ * This includes VP9/AV1 published as rid based simulcast, where `encodings[0]` is
23302
+ * the *smallest* layer even though the codec is SVC capable.
23303
+ *
23304
+ * @internal
23305
+ */
23306
+ function computeStartTargetBitrate(codec, options, encodings) {
23307
+ var _a, _b;
23308
+ if (isSVCCodec(codec) && !isSVCSimulcast(codec, options)) {
23309
+ return (_b = (_a = encodings[0]) === null || _a === void 0 ? void 0 : _a.maxBitrate) !== null && _b !== void 0 ? _b : 0;
23310
+ }
23311
+ return encodings.reduce((sum, enc) => {
23312
+ var _a;
23313
+ return sum + ((_a = enc.maxBitrate) !== null && _a !== void 0 ? _a : 0);
23314
+ }, 0);
23025
23315
  }
23026
23316
  function computeTrackBackupEncodings(track, videoCodec, opts) {
23027
23317
  var _a, _b, _c, _d;
@@ -23475,6 +23765,15 @@ class LocalVideoTrack extends LocalTrack {
23475
23765
  return items;
23476
23766
  });
23477
23767
  }
23768
+ /**
23769
+ * Whether `codec` is being published as SVC (a single stream carrying all spatial
23770
+ * layers) as opposed to rid based simulcast. VP9/AV1 are SVC unless the publisher
23771
+ * opted into simulcast, in which case each rid is an independent stream and the
23772
+ * layers can be enabled/disabled individually.
23773
+ */
23774
+ isSvcPublish(codec) {
23775
+ return isSVCCodec(codec) && !isSVCSimulcast(codec, this.publishOptions);
23776
+ }
23478
23777
  setPublishingQuality(maxQuality) {
23479
23778
  const qualities = [];
23480
23779
  for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
@@ -23484,7 +23783,7 @@ class LocalVideoTrack extends LocalTrack {
23484
23783
  }));
23485
23784
  }
23486
23785
  this.log.debug("setting publishing quality. max quality ".concat(maxQuality), this.logContext);
23487
- this.setPublishingLayers(isSVCCodec(this.codec), qualities);
23786
+ this.setPublishingLayers(this.isSvcPublish(this.codec), qualities);
23488
23787
  }
23489
23788
  restartTrack(options) {
23490
23789
  return __awaiter(this, void 0, void 0, function* () {
@@ -23749,7 +24048,7 @@ class LocalVideoTrack extends LocalTrack {
23749
24048
  }));
23750
24049
  // only enable simulcast codec for preference codec setted
23751
24050
  if (!this.codec && codecs.length > 0) {
23752
- yield this.setPublishingLayers(isSVCCodec(codecs[0].codec), codecs[0].qualities);
24051
+ yield this.setPublishingLayers(this.isSvcPublish(codecs[0].codec), codecs[0].qualities);
23753
24052
  return [];
23754
24053
  }
23755
24054
  this.subscribedCodecs = codecs;
@@ -23760,7 +24059,7 @@ class LocalVideoTrack extends LocalTrack {
23760
24059
  _a = false;
23761
24060
  const codec = _d;
23762
24061
  if (!this.codec || this.codec === codec.codec) {
23763
- yield this.setPublishingLayers(isSVCCodec(codec.codec), codec.qualities);
24062
+ yield this.setPublishingLayers(this.isSvcPublish(codec.codec), codec.qualities);
23764
24063
  } else {
23765
24064
  const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
23766
24065
  this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), {
@@ -23775,7 +24074,7 @@ class LocalVideoTrack extends LocalTrack {
23775
24074
  }
23776
24075
  } else if (simulcastCodecInfo.encodings) {
23777
24076
  this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext);
23778
- yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, isSVCCodec(codec.codec), this.log, this.logContext);
24077
+ yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, this.isSvcPublish(codec.codec), this.log, this.logContext);
23779
24078
  }
23780
24079
  }
23781
24080
  }
@@ -24035,6 +24334,10 @@ class RTCEngine extends eventsExports.EventEmitter {
24035
24334
  get pendingReconnect() {
24036
24335
  return !!this.reconnectTimeout;
24037
24336
  }
24337
+ get serverVersion() {
24338
+ var _a, _b, _c;
24339
+ return ((_b = (_a = this.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.serverInfo) === null || _b === void 0 ? void 0 : _b.version) || ((_c = this.latestJoinResponse) === null || _c === void 0 ? void 0 : _c.serverVersion) || undefined;
24340
+ }
24038
24341
  get reliableChannel() {
24039
24342
  return this.dataChannels.reliable;
24040
24343
  }
@@ -24087,7 +24390,7 @@ class RTCEngine extends eventsExports.EventEmitter {
24087
24390
  }();
24088
24391
  });
24089
24392
  this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () {
24090
- var _a, _b, _c, _d, _e;
24393
+ var _a, _b, _c, _d;
24091
24394
  // make sure to respect incoming data message order by processing message events one after the other
24092
24395
  const unlock = yield this.dataProcessLock.lock();
24093
24396
  try {
@@ -24112,20 +24415,29 @@ class RTCEngine extends eventsExports.EventEmitter {
24112
24415
  this.log.error('Received encrypted packet but E2EE not set up');
24113
24416
  return;
24114
24417
  }
24115
- const decryptedData = yield (_c = this.e2eeManager) === null || _c === void 0 ? void 0 : _c.handleEncryptedData(dp.value.value.encryptedValue, dp.value.value.iv, dp.participantIdentity, dp.value.value.keyIndex);
24418
+ let decryptedData;
24419
+ try {
24420
+ decryptedData = yield this.e2eeManager.handleEncryptedData(dp.value.value.encryptedValue, dp.value.value.iv, dp.participantIdentity, dp.value.value.keyIndex);
24421
+ } catch (err) {
24422
+ this.log.debug('failed to decrypt data packet', {
24423
+ error: err,
24424
+ participantIdentity: dp.participantIdentity
24425
+ });
24426
+ return;
24427
+ }
24116
24428
  const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload);
24117
24429
  const newDp = new DataPacket({
24118
24430
  value: decryptedPacket.value,
24119
24431
  participantIdentity: dp.participantIdentity,
24120
24432
  participantSid: dp.participantSid
24121
24433
  });
24122
- if (((_d = newDp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
24434
+ if (((_c = newDp.value) === null || _c === void 0 ? void 0 : _c.case) === 'user') {
24123
24435
  // compatibility
24124
24436
  applyUserDataCompat(newDp, newDp.value.value);
24125
24437
  }
24126
24438
  this.emit(EngineEvent.DataPacketReceived, newDp, dp.value.value.encryptionType);
24127
24439
  } else {
24128
- if (((_e = dp.value) === null || _e === void 0 ? void 0 : _e.case) === 'user') {
24440
+ if (((_d = dp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
24129
24441
  // compatibility
24130
24442
  applyUserDataCompat(dp, dp.value.value);
24131
24443
  }
@@ -24626,7 +24938,11 @@ class RTCEngine extends eventsExports.EventEmitter {
24626
24938
  sdp: sd.sdp,
24627
24939
  midToTrackId
24628
24940
  });
24629
- this.midToTrackId = midToTrackId;
24941
+ // in dual PC mode the publisher answer carries no mapping (the server's publisher
24942
+ // transport has no sending tracks) and must not clobber the subscriber offer mapping
24943
+ if (this.pcManager.mode === 'publisher-only') {
24944
+ this.midToTrackId = midToTrackId;
24945
+ }
24630
24946
  yield this.pcManager.setPublisherAnswer(sd, offerId);
24631
24947
  });
24632
24948
  // add candidate on trickle
@@ -25911,8 +26227,11 @@ const DEFAULT_MAX_PAYLOAD_BYTE_LENGTH = 5000000000;class BaseStreamReader {
25911
26227
  this._info = info;
25912
26228
  this.bytesReceived = 0;
25913
26229
  }
25914
- }
25915
- class ByteStreamReader extends BaseStreamReader {
26230
+ /**
26231
+ * Counts a chunk's bytes against `totalByteSize` and reports progress. Chunk ordering and
26232
+ * de-duplication happen upstream in the manager's `ensureOrderedChunks`, so every chunk reaching
26233
+ * here is new and in order.
26234
+ */
25916
26235
  handleChunkReceived(chunk) {
25917
26236
  var _a;
25918
26237
  this.bytesReceived += chunk.content.byteLength;
@@ -25920,6 +26239,8 @@ class ByteStreamReader extends BaseStreamReader {
25920
26239
  const currentProgress = this.totalByteSize ? this.bytesReceived / this.totalByteSize : undefined;
25921
26240
  (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, currentProgress);
25922
26241
  }
26242
+ }
26243
+ class ByteStreamReader extends BaseStreamReader {
25923
26244
  [Symbol.asyncIterator]() {
25924
26245
  const reader = this.reader.getReader();
25925
26246
  // Suppress unhandled rejection on reader.closed — errors are
@@ -26027,35 +26348,18 @@ class ByteStreamReader extends BaseStreamReader {
26027
26348
  }
26028
26349
  }
26029
26350
  /**
26030
- * A class to read chunks from a ReadableStream and provide them in a structured format.
26351
+ * A class to read chunks from a ReadableStream and decode them as UTF-8 text.
26352
+ *
26353
+ * NOTE: chunk-level `version` (resending a chunk at an already-received `chunkIndex` to supersede
26354
+ * it) is not supported. The reader used to rebuild the whole string from a per-index chunk map and
26355
+ * yield it as `TextStreamChunk.collected`, which made superseding work; 5d4a6346 (#1410, text auto
26356
+ * chunking) changed the iterator to yield each chunk's text as it arrives, and a streaming reader
26357
+ * cannot retract text it has already handed to the consumer. No sender emits a versioned chunk.
26031
26358
  */
26032
26359
  class TextStreamReader extends BaseStreamReader {
26033
- /**
26034
- * A TextStreamReader instance can be used as an AsyncIterator that returns the entire string
26035
- * that has been received up to the current point in time.
26036
- */
26037
- constructor(info, stream, totalChunkCount) {
26038
- super(info, stream, totalChunkCount);
26039
- this.receivedChunks = new Map();
26040
- }
26041
- handleChunkReceived(chunk) {
26042
- var _a;
26043
- const index = bigIntToNumber(chunk.chunkIndex);
26044
- const previousChunkAtIndex = this.receivedChunks.get(index);
26045
- if (previousChunkAtIndex && previousChunkAtIndex.version > chunk.version) {
26046
- // we have a newer version already, dropping the old one
26047
- return;
26048
- }
26049
- this.receivedChunks.set(index, chunk);
26050
- this.bytesReceived += chunk.content.byteLength;
26051
- this.validateBytesReceived();
26052
- const currentProgress = this.totalByteSize ? this.bytesReceived / this.totalByteSize : undefined;
26053
- (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, currentProgress);
26054
- }
26055
26360
  /**
26056
26361
  * Async iterator implementation to allow usage of `for await...of` syntax.
26057
- * Yields structured chunks from the stream.
26058
- *
26362
+ * Yields each chunk's decoded text as it arrives - a delta, not the string accumulated so far.
26059
26363
  */
26060
26364
  [Symbol.asyncIterator]() {
26061
26365
  const reader = this.reader.getReader();
@@ -26326,7 +26630,7 @@ class TextStreamReader extends BaseStreamReader {
26326
26630
  });
26327
26631
  }
26328
26632
  });
26329
- streamHandlerCallback(new ByteStreamReader(info, compressed ? inflateRawByteChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream,
26633
+ streamHandlerCallback(new ByteStreamReader(info, compressed ? inflateRawByteChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream.pipeThrough(ensureOrderedChunks(streamHeader.streamId)),
26330
26634
  // `totalLength` is the pre-compression size, and the reader counts decompressed bytes,
26331
26635
  // so it applies to both paths (mirrors text).
26332
26636
  bigIntToNumber(streamHeader.totalLength)), {
@@ -26398,7 +26702,7 @@ class TextStreamReader extends BaseStreamReader {
26398
26702
  });
26399
26703
  }
26400
26704
  });
26401
- streamHandlerCallback(new TextStreamReader(info, compressed ? inflateRawChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream,
26705
+ streamHandlerCallback(new TextStreamReader(info, compressed ? inflateRawChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream.pipeThrough(ensureOrderedChunks(streamHeader.streamId)),
26402
26706
  // `totalLength` is the pre-compression size, and the reader sees decompressed bytes, so
26403
26707
  // it applies to both paths.
26404
26708
  bigIntToNumber(streamHeader.totalLength)), {
@@ -26414,7 +26718,7 @@ class TextStreamReader extends BaseStreamReader {
26414
26718
  if (fileBuffer.info.encryptionType !== encryptionType) {
26415
26719
  fileBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(chunk.streamId, ". Expected ").concat(encryptionType, ", got ").concat(fileBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch));
26416
26720
  this.byteStreamControllers.delete(chunk.streamId);
26417
- } else if (chunk.content.length > 0) {
26721
+ } else {
26418
26722
  fileBuffer.controller.enqueue(chunk);
26419
26723
  }
26420
26724
  }
@@ -26423,7 +26727,7 @@ class TextStreamReader extends BaseStreamReader {
26423
26727
  if (textBuffer.info.encryptionType !== encryptionType) {
26424
26728
  textBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(chunk.streamId, ". Expected ").concat(encryptionType, ", got ").concat(textBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch));
26425
26729
  this.textStreamControllers.delete(chunk.streamId);
26426
- } else if (chunk.content.length > 0) {
26730
+ } else {
26427
26731
  textBuffer.controller.enqueue(chunk);
26428
26732
  }
26429
26733
  }
@@ -26471,26 +26775,24 @@ class TextStreamReader extends BaseStreamReader {
26471
26775
  function createInlineStream(streamId, content) {
26472
26776
  return new ReadableStream({
26473
26777
  start: controller => __awaiter(this, void 0, void 0, function* () {
26474
- try {
26475
- const bytes = yield content;
26476
- controller.enqueue(new DataStream_Chunk({
26477
- streamId,
26478
- chunkIndex: BigInt(0),
26479
- content: bytes
26480
- }));
26481
- controller.close();
26482
- } catch (err) {
26483
- controller.error(err);
26484
- }
26778
+ const bytes = yield content;
26779
+ controller.enqueue(new DataStream_Chunk({
26780
+ streamId,
26781
+ chunkIndex: BigInt(0),
26782
+ content: bytes
26783
+ }));
26784
+ controller.close();
26485
26785
  })
26486
26786
  });
26487
26787
  }
26488
26788
  /**
26489
26789
  * Validates that chunks are received in order, dropping duplicates and throwing if gaps are found.
26490
26790
  *
26491
- * A stateful decompressor silently corrupts on duplicated or out-of-order input, so duplicates are
26492
- * dropped (with a warning - in-order delivery is expected on the reliable channel, but reconnect
26493
- * handling may replay) and a gap is a hard error. Shared by the text and byte deflate-raw decoders.
26791
+ * Reassembly (and, for compressed streams, a stateful decompressor) silently corrupts on duplicated
26792
+ * or out-of-order input, so duplicates are dropped (with a warning - in-order delivery is expected
26793
+ * on the reliable channel, but reconnect handling may replay) and a gap is a hard error. Empty
26794
+ * chunks consume their index and are then dropped, so they never reach the reader. Applied to every
26795
+ * chunked stream, compressed or not.
26494
26796
  */
26495
26797
  function ensureOrderedChunks(streamId) {
26496
26798
  let lastChunkIndex = -1;
@@ -26498,13 +26800,16 @@ function ensureOrderedChunks(streamId) {
26498
26800
  transform: (value, controller) => {
26499
26801
  const index = bigIntToNumber(value.chunkIndex);
26500
26802
  if (index <= lastChunkIndex) {
26501
- livekitLogger.warn("ignoring duplicate chunk ".concat(index, " for compressed data stream ").concat(streamId, " (last processed: ").concat(lastChunkIndex, ")"));
26803
+ livekitLogger.warn("ignoring duplicate chunk ".concat(index, " ").concat(value.version > 0 ? "(version ".concat(value.version, ")") : '', " for data stream ").concat(streamId, " (last processed: ").concat(lastChunkIndex, ")"));
26502
26804
  return;
26503
26805
  }
26504
26806
  if (index > lastChunkIndex + 1) {
26505
- throw new DataStreamError("Missing chunk(s) ".concat(lastChunkIndex + 1, "..").concat(index - 1, " for compressed data stream ").concat(streamId, " - cannot continue decompressing"), DataStreamErrorReason.Incomplete);
26807
+ throw new DataStreamError("Missing chunk(s) ".concat(lastChunkIndex + 1, "..").concat(index - 1, " for data stream ").concat(streamId, " - cannot reassemble payload"), DataStreamErrorReason.Incomplete);
26506
26808
  }
26507
26809
  lastChunkIndex = index;
26810
+ if (value.content.length === 0) {
26811
+ return;
26812
+ }
26508
26813
  controller.enqueue(value);
26509
26814
  }
26510
26815
  });
@@ -31748,7 +32053,7 @@ class Participant extends eventsExports.EventEmitter {
31748
32053
  }
31749
32054
  publish(track, opts, isStereo) {
31750
32055
  return __awaiter(this, void 0, void 0, function* () {
31751
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
32056
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
31752
32057
  if (!this.hasPermissionsToPublish(track)) {
31753
32058
  throw new PublishTrackError('failed to publish track, insufficient permissions', 403);
31754
32059
  }
@@ -31851,7 +32156,12 @@ class Participant extends eventsExports.EventEmitter {
31851
32156
  req.height = dims.height;
31852
32157
  // for svc codecs, disable simulcast and use vp8 for backup codec
31853
32158
  if (isLocalVideoTrack(track)) {
31854
- if (isSVCCodec(videoCodec)) {
32159
+ if (isSVCSimulcast(videoCodec, opts) && (usesLegacySVCEncodings() || !isSVCSimulcastSupportedByServer((_e = this.engine) === null || _e === void 0 ? void 0 : _e.serverVersion))) {
32160
+ opts.simulcast = false;
32161
+ this.log.info('SVC simulcast is not supported, disabling simulcast.', getLogContextFromTrack(track));
32162
+ }
32163
+ const svcSimulcast = isSVCSimulcast(videoCodec, opts);
32164
+ if (isSVCCodec(videoCodec) && !svcSimulcast) {
31855
32165
  if (track.source === Track.Source.ScreenShare) {
31856
32166
  // vp9 svc with screenshare cannot encode multiple spatial layers
31857
32167
  // doing so reduces publish resolution to minimal resolution
@@ -31867,12 +32177,16 @@ class Participant extends eventsExports.EventEmitter {
31867
32177
  }
31868
32178
  }
31869
32179
  // set scalabilityMode to 'L3T3_KEY' by default
31870
- opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
32180
+ opts.scalabilityMode = (_f = opts.scalabilityMode) !== null && _f !== void 0 ? _f : 'L3T3_KEY';
31871
32181
  }
31872
- req.simulcastCodecs = [new SimulcastCodec({
32182
+ const primaryCodec = new SimulcastCodec({
31873
32183
  codec: videoCodec,
31874
32184
  cid: track.mediaStreamTrack.id
31875
- })];
32185
+ });
32186
+ if (svcSimulcast) {
32187
+ primaryCodec.videoLayerMode = VideoLayer_Mode.ONE_SPATIAL_LAYER_PER_STREAM;
32188
+ }
32189
+ req.simulcastCodecs = [primaryCodec];
31876
32190
  // set up backup
31877
32191
  if (opts.backupCodec === true) {
31878
32192
  opts.backupCodec = {
@@ -31893,19 +32207,19 @@ class Participant extends eventsExports.EventEmitter {
31893
32207
  }
31894
32208
  }
31895
32209
  encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
31896
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
32210
+ req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec) && !isSVCSimulcast(opts.videoCodec, opts));
31897
32211
  } else if (track.kind === Track.Kind.Audio) {
31898
32212
  encodings = [{
31899
- maxBitrate: (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate,
31900
- priority: (_h = (_g = opts.audioPreset) === null || _g === void 0 ? void 0 : _g.priority) !== null && _h !== void 0 ? _h : 'high',
31901
- networkPriority: (_k = (_j = opts.audioPreset) === null || _j === void 0 ? void 0 : _j.priority) !== null && _k !== void 0 ? _k : 'high'
32213
+ maxBitrate: (_g = opts.audioPreset) === null || _g === void 0 ? void 0 : _g.maxBitrate,
32214
+ priority: (_j = (_h = opts.audioPreset) === null || _h === void 0 ? void 0 : _h.priority) !== null && _j !== void 0 ? _j : 'high',
32215
+ networkPriority: (_l = (_k = opts.audioPreset) === null || _k === void 0 ? void 0 : _k.priority) !== null && _l !== void 0 ? _l : 'high'
31902
32216
  }];
31903
32217
  }
31904
32218
  if (!this.engine || this.engine.isClosed) {
31905
32219
  throw new UnexpectedConnectionState('cannot publish track when not connected');
31906
32220
  }
31907
32221
  const negotiate = () => __awaiter(this, void 0, void 0, function* () {
31908
- var _a, _b, _c, _d;
32222
+ var _a, _b;
31909
32223
  if (!this.engine.pcManager) {
31910
32224
  throw new UnexpectedConnectionState('pcManager is not ready');
31911
32225
  }
@@ -31942,13 +32256,9 @@ class Participant extends eventsExports.EventEmitter {
31942
32256
  });
31943
32257
  }
31944
32258
  } else if (track.codec && isVideoCodec(track.codec)) {
31945
- // Apply start bitrate for all video codecs to prevent initial blurriness.
31946
- // - SVC codecs: use first encoding's bitrate (single stream with built-in layers)
31947
- // - Simulcast: sum all encoding bitrates (independent streams, BWE needs total)
31948
- const targetBitrate = isSVCCodec(track.codec) ? (_d = (_c = encodings[0]) === null || _c === void 0 ? void 0 : _c.maxBitrate) !== null && _d !== void 0 ? _d : 0 : encodings.reduce((sum, enc) => {
31949
- var _a;
31950
- return sum + ((_a = enc.maxBitrate) !== null && _a !== void 0 ? _a : 0);
31951
- }, 0);
32259
+ // Apply start bitrate for all video codecs to prevent initial blurriness,
32260
+ // see computeStartTargetBitrate
32261
+ const targetBitrate = computeStartTargetBitrate(track.codec, opts, encodings);
31952
32262
  if (targetBitrate > 0) {
31953
32263
  this.engine.pcManager.publisher.setTrackCodecBitrate({
31954
32264
  cid: req.cid,
@@ -34187,8 +34497,8 @@ class Room extends eventsExports.EventEmitter {
34187
34497
  });
34188
34498
  this.registerRpcDataStreamHandler();
34189
34499
  this.rpcClientManager = new RpcClientManager(this.log, this.outgoingDataStreamManager, this.getRemoteParticipantClientProtocol, () => {
34190
- var _a, _b;
34191
- return (_b = (_a = this.engine.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.serverInfo) === null || _b === void 0 ? void 0 : _b.version;
34500
+ var _a;
34501
+ return (_a = this.engine) === null || _a === void 0 ? void 0 : _a.serverVersion;
34192
34502
  });
34193
34503
  this.rpcClientManager.on('sendDataPacket', _ref2 => {
34194
34504
  let packet = _ref2.packet;
@@ -36343,33 +36653,10 @@ class TokenSourceFixed {}
36343
36653
  * and {@link TokenSourceCustom}.
36344
36654
  */
36345
36655
  class TokenSourceConfigurable {}new TextEncoder();
36346
- const decoder = new TextDecoder();function decodeBase64(encoded) {
36347
- if (Uint8Array.fromBase64) {
36348
- return Uint8Array.fromBase64(encoded);
36349
- }
36350
- const binary = atob(encoded);
36351
- const bytes = new Uint8Array(binary.length);
36352
- for (let i = 0; i < binary.length; i++) {
36353
- bytes[i] = binary.charCodeAt(i);
36354
- }
36355
- return bytes;
36356
- }function decode(input) {
36357
- if (Uint8Array.fromBase64) {
36358
- return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
36359
- alphabet: 'base64url'
36360
- });
36361
- }
36362
- let encoded = input;
36363
- if (encoded instanceof Uint8Array) {
36364
- encoded = decoder.decode(encoded);
36365
- }
36366
- encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
36367
- try {
36368
- return decodeBase64(encoded);
36369
- } catch (_unused) {
36370
- throw new TypeError('The input to be decoded is not correctly encoded.');
36371
- }
36372
- }class JOSEError extends Error {
36656
+ const decoder = new TextDecoder();
36657
+ const strictDecoder = new TextDecoder('utf-8', {
36658
+ fatal: true
36659
+ });class JOSEError extends Error {
36373
36660
  constructor(message, options) {
36374
36661
  var _Error$captureStackTr;
36375
36662
  super(message, options);
@@ -36493,7 +36780,7 @@ class JWKSMultipleMatchingKeys extends JOSEError {
36493
36780
  let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'multiple matching keys found in the JSON Web Key Set';
36494
36781
  let options = arguments.length > 1 ? arguments[1] : undefined;
36495
36782
  super(message, options);
36496
- _defineProperty(this, Symbol.asyncIterator, void 0);
36783
+ _defineProperty(this, Symbol.asyncIterator, _wrapAsyncGenerator(function* () {}));
36497
36784
  _defineProperty(this, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS');
36498
36785
  }
36499
36786
  }
@@ -36515,19 +36802,55 @@ class JWSSignatureVerificationFailed extends JOSEError {
36515
36802
  _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
36516
36803
  }
36517
36804
  }
36518
- _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');const isObjectLike = value => typeof value === 'object' && value !== null;
36519
- function isObject(input) {
36520
- if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
36805
+ _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');function decodeBase64(encoded) {
36806
+ if (Uint8Array.fromBase64) {
36807
+ return Uint8Array.fromBase64(encoded);
36808
+ }
36809
+ const binary = atob(encoded);
36810
+ const bytes = new Uint8Array(binary.length);
36811
+ for (let i = 0; i < binary.length; i++) {
36812
+ bytes[i] = binary.charCodeAt(i);
36813
+ }
36814
+ return bytes;
36815
+ }const invalid = 'The input to be decoded is not correctly encoded.';
36816
+ function decode(input) {
36817
+ if (Uint8Array.fromBase64) {
36818
+ try {
36819
+ return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
36820
+ alphabet: 'base64url'
36821
+ });
36822
+ } catch (cause) {
36823
+ throw new TypeError(invalid, {
36824
+ cause
36825
+ });
36826
+ }
36827
+ }
36828
+ let encoded = input;
36829
+ if (encoded instanceof Uint8Array) {
36830
+ encoded = decoder.decode(encoded);
36831
+ }
36832
+ if (encoded.includes('+') || encoded.includes('/')) {
36833
+ throw new TypeError(invalid);
36834
+ }
36835
+ encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
36836
+ try {
36837
+ return decodeBase64(encoded);
36838
+ } catch (_unused) {
36839
+ throw new TypeError(invalid);
36840
+ }
36841
+ }function isObject(input) {
36842
+ if (typeof input !== 'object' || input === null || Object.prototype.toString.call(input) !== '[object Object]') {
36521
36843
  return false;
36522
36844
  }
36523
- if (Object.getPrototypeOf(input) === null) {
36845
+ const prototype = Object.getPrototypeOf(input);
36846
+ if (prototype === null) {
36524
36847
  return true;
36525
36848
  }
36526
- let proto = input;
36849
+ let proto = prototype;
36527
36850
  while (Object.getPrototypeOf(proto) !== null) {
36528
36851
  proto = Object.getPrototypeOf(proto);
36529
36852
  }
36530
- return Object.getPrototypeOf(input) === proto;
36853
+ return prototype === proto;
36531
36854
  }function decodeJwt(jwt) {
36532
36855
  if (typeof jwt !== 'string') throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
36533
36856
  const _jwt$split = jwt.split('.'),
@@ -36544,7 +36867,7 @@ function isObject(input) {
36544
36867
  }
36545
36868
  let result;
36546
36869
  try {
36547
- result = JSON.parse(decoder.decode(decoded));
36870
+ result = JSON.parse(strictDecoder.decode(decoded));
36548
36871
  } catch (_unused2) {
36549
36872
  throw new JWTInvalid('Failed to parse the decoded payload as JSON');
36550
36873
  }