livekit-client 2.18.3 → 2.18.5

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 (53) hide show
  1. package/dist/livekit-client.esm.mjs +703 -334
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/api/SignalClient.d.ts.map +1 -1
  6. package/dist/src/room/PCTransport.d.ts.map +1 -1
  7. package/dist/src/room/RTCEngine.d.ts +12 -4
  8. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  9. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
  10. package/dist/src/room/Room.d.ts +3 -0
  11. package/dist/src/room/Room.d.ts.map +1 -1
  12. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -0
  13. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  14. package/dist/src/room/events.d.ts +3 -1
  15. package/dist/src/room/events.d.ts.map +1 -1
  16. package/dist/src/room/participant/LocalParticipant.d.ts +8 -0
  17. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  18. package/dist/src/room/participant/RemoteParticipant.d.ts +4 -3
  19. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  20. package/dist/src/room/track/LocalTrack.d.ts +7 -0
  21. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  22. package/dist/src/room/track/LocalVideoTrack.d.ts +12 -1
  23. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  24. package/dist/src/room/types.d.ts +1 -1
  25. package/dist/src/room/types.d.ts.map +1 -1
  26. package/dist/ts4.2/room/RTCEngine.d.ts +12 -4
  27. package/dist/ts4.2/room/Room.d.ts +3 -0
  28. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -0
  29. package/dist/ts4.2/room/events.d.ts +3 -1
  30. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -0
  31. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +4 -3
  32. package/dist/ts4.2/room/track/LocalTrack.d.ts +7 -0
  33. package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +12 -1
  34. package/dist/ts4.2/room/types.d.ts +1 -1
  35. package/package.json +3 -3
  36. package/src/api/SignalClient.ts +4 -0
  37. package/src/room/PCTransport.ts +10 -8
  38. package/src/room/RTCEngine.ts +59 -28
  39. package/src/room/RegionUrlProvider.ts +7 -0
  40. package/src/room/Room.ts +93 -23
  41. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +331 -16
  42. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +92 -41
  43. package/src/room/events.ts +2 -0
  44. package/src/room/participant/LocalParticipant.ts +70 -5
  45. package/src/room/participant/RemoteParticipant.ts +14 -2
  46. package/src/room/token-source/TokenSource.test.ts +337 -0
  47. package/src/room/token-source/test-tokens.ts +28 -0
  48. package/src/room/token-source/utils.test.ts +12 -20
  49. package/src/room/track/LocalTrack.ts +15 -1
  50. package/src/room/track/LocalVideoTrack.ts +126 -2
  51. package/src/room/track/RemoteVideoTrack.ts +8 -2
  52. package/src/room/types.ts +2 -1
  53. package/src/utils/deferrable-map.ts +2 -2
@@ -8704,6 +8704,11 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
8704
8704
  if (!window.RTCPeerConnection) {
8705
8705
  return;
8706
8706
  }
8707
+ const addEventListener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener');
8708
+ if (!addEventListener.writable) {
8709
+ log$4('Unable to polyfill events');
8710
+ return;
8711
+ }
8707
8712
  const proto = window.RTCPeerConnection.prototype;
8708
8713
  const nativeAddEventListener = proto.addEventListener;
8709
8714
  proto.addEventListener = function (nativeEventName, cb) {
@@ -8846,7 +8851,7 @@ function detectBrowser(window) {
8846
8851
  // Chrome 74 removed webkitGetUserMedia on http as well so we need the
8847
8852
  // more complicated fallback to webkitRTCPeerConnection.
8848
8853
  result.browser = 'chrome';
8849
- result.version = parseInt(extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2));
8854
+ result.version = parseInt(extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2)) || null;
8850
8855
  } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
8851
8856
  // Safari.
8852
8857
  result.browser = 'safari';
@@ -9120,7 +9125,11 @@ function shimGetUserMedia$2(window, browserDetails) {
9120
9125
  function shimMediaStream(window) {
9121
9126
  window.MediaStream = window.MediaStream || window.webkitMediaStream;
9122
9127
  }
9123
- function shimOnTrack$1(window) {
9128
+ function shimOnTrack$1(window, browserDetails) {
9129
+ if (browserDetails.version > 102) {
9130
+ // Unified plan is supported so no need to do anything.
9131
+ return;
9132
+ }
9124
9133
  if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) {
9125
9134
  Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
9126
9135
  get() {
@@ -9283,7 +9292,10 @@ function shimGetSendersWithDtmf(window) {
9283
9292
  });
9284
9293
  }
9285
9294
  }
9286
- function shimSenderReceiverGetStats(window) {
9295
+ function shimSenderReceiverGetStats(window, browserDetails) {
9296
+ if (browserDetails.version >= 67) {
9297
+ return;
9298
+ }
9287
9299
  if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) {
9288
9300
  return;
9289
9301
  }
@@ -9650,6 +9662,10 @@ function shimPeerConnection$1(window, browserDetails) {
9650
9662
 
9651
9663
  // Attempt to fix ONN in plan-b mode.
9652
9664
  function fixNegotiationNeeded(window, browserDetails) {
9665
+ if (browserDetails.version > 102) {
9666
+ // Plan-B is no longer supported.
9667
+ return;
9668
+ }
9653
9669
  wrapPeerConnectionEvent(window, 'negotiationneeded', e => {
9654
9670
  const pc = e.target;
9655
9671
  if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') {
@@ -9785,6 +9801,15 @@ function shimPeerConnection(window, browserDetails) {
9785
9801
  window.RTCPeerConnection.prototype[method] = methodObj[method];
9786
9802
  });
9787
9803
  }
9804
+ }
9805
+ function shimGetStats(window, browserDetails) {
9806
+ if (typeof window !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
9807
+ return; // probably media.peerconnection.enabled=false in about:config
9808
+ }
9809
+ if (browserDetails.version >= 151) {
9810
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1056433
9811
+ return;
9812
+ }
9788
9813
  const modernStatsTypes = {
9789
9814
  inboundrtp: 'inbound-rtp',
9790
9815
  outboundrtp: 'outbound-rtp',
@@ -9795,6 +9820,10 @@ function shimPeerConnection(window, browserDetails) {
9795
9820
  const nativeGetStats = window.RTCPeerConnection.prototype.getStats;
9796
9821
  window.RTCPeerConnection.prototype.getStats = function getStats() {
9797
9822
  const [selector, onSucc, onErr] = arguments;
9823
+ if (this.signalingState === 'closed') {
9824
+ // No longer required in FF151+
9825
+ return Promise.resolve(new Map());
9826
+ }
9798
9827
  return nativeGetStats.apply(this, [selector || null]).then(stats => {
9799
9828
  if (browserDetails.version < 53 && !onSucc) {
9800
9829
  // Shim only promise getStats with spec-hyphens in type names
@@ -10009,7 +10038,7 @@ function shimCreateAnswer(window) {
10009
10038
  }
10010
10039
  return origCreateAnswer.apply(this, arguments);
10011
10040
  };
10012
- }var firefoxShim=/*#__PURE__*/Object.freeze({__proto__:null,shimAddTransceiver:shimAddTransceiver,shimCreateAnswer:shimCreateAnswer,shimCreateOffer:shimCreateOffer,shimGetDisplayMedia:shimGetDisplayMedia,shimGetParameters:shimGetParameters,shimGetUserMedia:shimGetUserMedia$1,shimOnTrack:shimOnTrack,shimPeerConnection:shimPeerConnection,shimRTCDataChannel:shimRTCDataChannel,shimReceiverGetStats:shimReceiverGetStats,shimRemoveStream:shimRemoveStream,shimSenderGetStats:shimSenderGetStats});/*
10041
+ }var firefoxShim=/*#__PURE__*/Object.freeze({__proto__:null,shimAddTransceiver:shimAddTransceiver,shimCreateAnswer:shimCreateAnswer,shimCreateOffer:shimCreateOffer,shimGetDisplayMedia:shimGetDisplayMedia,shimGetParameters:shimGetParameters,shimGetStats:shimGetStats,shimGetUserMedia:shimGetUserMedia$1,shimOnTrack:shimOnTrack,shimPeerConnection:shimPeerConnection,shimRTCDataChannel:shimRTCDataChannel,shimReceiverGetStats:shimReceiverGetStats,shimRemoveStream:shimRemoveStream,shimSenderGetStats:shimSenderGetStats});/*
10013
10042
  * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
10014
10043
  *
10015
10044
  * Use of this source code is governed by a BSD-style license
@@ -10444,7 +10473,7 @@ function requireSdp() {
10444
10473
  const type = candidate.type;
10445
10474
  sdp.push('typ');
10446
10475
  sdp.push(type);
10447
- if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) {
10476
+ if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort !== undefined) {
10448
10477
  sdp.push('raddr');
10449
10478
  sdp.push(candidate.relatedAddress);
10450
10479
  sdp.push('rport');
@@ -10517,6 +10546,8 @@ function requireSdp() {
10517
10546
  // Parses a fmtp line, returns dictionary. Sample input:
10518
10547
  // a=fmtp:96 vbr=on;cng=on
10519
10548
  // Also deals with vbr=on; cng=on
10549
+ // Non-key-value such as telephone-events `0-15` get parsed as
10550
+ // {`0-15`:undefined}
10520
10551
  SDPUtils.parseFmtp = function (line) {
10521
10552
  const parsed = {};
10522
10553
  let kv;
@@ -11290,15 +11321,21 @@ function shimMaxMessageSize(window, browserDetails) {
11290
11321
  return origSetRemoteDescription.apply(this, arguments);
11291
11322
  };
11292
11323
  }
11293
- function shimSendThrowTypeError(window) {
11324
+ function shimSendThrowTypeError(window, browserDetails) {
11294
11325
  if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) {
11295
11326
  return;
11296
11327
  }
11328
+ if (browserDetails.browser === 'chrome' && browserDetails.version > 149) {
11329
+ // Fixed by https://issues.chromium.org/issues/490588131
11330
+ return;
11331
+ }
11297
11332
 
11298
11333
  // Note: Although Firefox >= 57 has a native implementation, the maximum
11299
11334
  // message size can be reset for all data channels at a later stage.
11300
11335
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
11301
-
11336
+ if (browserDetails.browser === 'firefox' && browserDetails.version > 60) {
11337
+ return;
11338
+ }
11302
11339
  function wrapDcSend(dc, pc) {
11303
11340
  const origDataChannelSend = dc.send;
11304
11341
  dc.send = function send() {
@@ -11536,16 +11573,16 @@ function adapterFactory() {
11536
11573
  shimGetUserMedia$2(window, browserDetails);
11537
11574
  shimMediaStream(window);
11538
11575
  shimPeerConnection$1(window, browserDetails);
11539
- shimOnTrack$1(window);
11576
+ shimOnTrack$1(window, browserDetails);
11540
11577
  shimAddTrackRemoveTrack(window, browserDetails);
11541
11578
  shimGetSendersWithDtmf(window);
11542
- shimSenderReceiverGetStats(window);
11579
+ shimSenderReceiverGetStats(window, browserDetails);
11543
11580
  fixNegotiationNeeded(window, browserDetails);
11544
11581
  shimRTCIceCandidate(window);
11545
11582
  shimRTCIceCandidateRelayProtocol(window);
11546
11583
  shimConnectionState(window);
11547
11584
  shimMaxMessageSize(window, browserDetails);
11548
- shimSendThrowTypeError(window);
11585
+ shimSendThrowTypeError(window, browserDetails);
11549
11586
  removeExtmapAllowMixed(window, browserDetails);
11550
11587
  break;
11551
11588
  case 'firefox':
@@ -11562,6 +11599,7 @@ function adapterFactory() {
11562
11599
  shimParameterlessSetLocalDescription(window);
11563
11600
  shimGetUserMedia$1(window, browserDetails);
11564
11601
  shimPeerConnection(window, browserDetails);
11602
+ shimGetStats(window, browserDetails);
11565
11603
  shimOnTrack(window);
11566
11604
  shimRemoveStream(window);
11567
11605
  shimSenderGetStats(window);
@@ -11574,7 +11612,7 @@ function adapterFactory() {
11574
11612
  shimRTCIceCandidate(window);
11575
11613
  shimConnectionState(window);
11576
11614
  shimMaxMessageSize(window, browserDetails);
11577
- shimSendThrowTypeError(window);
11615
+ shimSendThrowTypeError(window, browserDetails);
11578
11616
  break;
11579
11617
  case 'safari':
11580
11618
  if (!safariShim || !options.shimSafari) {
@@ -11599,7 +11637,7 @@ function adapterFactory() {
11599
11637
  shimRTCIceCandidate(window);
11600
11638
  shimRTCIceCandidateRelayProtocol(window);
11601
11639
  shimMaxMessageSize(window, browserDetails);
11602
- shimSendThrowTypeError(window);
11640
+ shimSendThrowTypeError(window, browserDetails);
11603
11641
  removeExtmapAllowMixed(window, browserDetails);
11604
11642
  break;
11605
11643
  default:
@@ -11706,7 +11744,7 @@ function getMatch(exp, ua) {
11706
11744
  }
11707
11745
  function getOSVersion(ua) {
11708
11746
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11709
- }var version$1 = "2.18.3";const version = version$1;
11747
+ }var version$1 = "2.18.5";const version = version$1;
11710
11748
  const protocolVersion = 16;/** Base error that all LiveKit specific custom errors inherit from. */
11711
11749
  class LivekitError extends Error {
11712
11750
  constructor(code, message, options) {
@@ -12460,6 +12498,8 @@ var EngineEvent;
12460
12498
  EngineEvent["DataTrackSubscriberHandles"] = "dataTrackSubscriberHandles";
12461
12499
  EngineEvent["DataTrackPacketReceived"] = "dataTrackPacketReceived";
12462
12500
  EngineEvent["Joined"] = "joined";
12501
+ EngineEvent["TokenRefreshed"] = "tokenRefreshed";
12502
+ EngineEvent["ServerRegionsReported"] = "serverRegionsReported";
12463
12503
  })(EngineEvent || (EngineEvent = {}));
12464
12504
  var TrackEvent;
12465
12505
  (function (TrackEvent) {
@@ -16015,6 +16055,10 @@ class SignalClient {
16015
16055
  const resp = yield fetch(validateUrl);
16016
16056
  switch (resp.status) {
16017
16057
  case 404:
16058
+ const errorMsg = yield resp.text();
16059
+ if (errorMsg.includes('requested room does not exist')) {
16060
+ return ConnectionError.notAllowed(errorMsg, resp.status);
16061
+ }
16018
16062
  return ConnectionError.serviceNotFound('v1 RTC path not found. Consider upgrading your LiveKit server version', 'v0-rtc');
16019
16063
  case 401:
16020
16064
  case 403:
@@ -17120,6 +17164,7 @@ class PCTransport extends eventsExports.EventEmitter {
17120
17164
  if (!this._pc) {
17121
17165
  return;
17122
17166
  }
17167
+ this.pendingInitialOffer = undefined;
17123
17168
  this._pc.close();
17124
17169
  this._pc.onconnectionstatechange = null;
17125
17170
  this._pc.oniceconnectionstatechange = null;
@@ -17207,7 +17252,7 @@ class PCTransport extends eventsExports.EventEmitter {
17207
17252
  this.remoteStereoMids = stereoMids;
17208
17253
  this.remoteNackMids = nackMids;
17209
17254
  } else if (sd.type === 'answer') {
17210
- if (this.pendingInitialOffer) {
17255
+ if (this.pendingInitialOffer && this._pc) {
17211
17256
  const initialOffer = this.pendingInitialOffer;
17212
17257
  this.pendingInitialOffer = undefined;
17213
17258
  const sdpParsed = libExports.parse((_a = initialOffer.sdp) !== null && _a !== void 0 ? _a : '');
@@ -17529,8 +17574,9 @@ class PCTransport extends eventsExports.EventEmitter {
17529
17574
  }
17530
17575
  setMungedSDP(sd, munged, remote) {
17531
17576
  return __awaiter(this, void 0, void 0, function* () {
17577
+ var _a, _b;
17578
+ const originalSdp = sd.sdp;
17532
17579
  if (munged) {
17533
- const originalSdp = sd.sdp;
17534
17580
  sd.sdp = munged;
17535
17581
  try {
17536
17582
  this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description"), this.logContext);
@@ -17543,16 +17589,17 @@ class PCTransport extends eventsExports.EventEmitter {
17543
17589
  } catch (e) {
17544
17590
  this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"), Object.assign(Object.assign({}, this.logContext), {
17545
17591
  error: e,
17546
- sdp: munged
17592
+ mungedSdp: munged,
17593
+ originalSdp
17547
17594
  }));
17548
17595
  sd.sdp = originalSdp;
17549
17596
  }
17550
17597
  }
17551
17598
  try {
17552
17599
  if (remote) {
17553
- yield this.pc.setRemoteDescription(sd);
17600
+ yield (_a = this._pc) === null || _a === void 0 ? void 0 : _a.setRemoteDescription(sd);
17554
17601
  } else {
17555
- yield this.pc.setLocalDescription(sd);
17602
+ yield (_b = this._pc) === null || _b === void 0 ? void 0 : _b.setLocalDescription(sd);
17556
17603
  }
17557
17604
  } catch (e) {
17558
17605
  let msg = 'unknown error';
@@ -17565,6 +17612,9 @@ class PCTransport extends eventsExports.EventEmitter {
17565
17612
  error: msg,
17566
17613
  sdp: sd.sdp
17567
17614
  };
17615
+ if (munged && munged !== originalSdp) {
17616
+ fields.mungedSdp = munged;
17617
+ }
17568
17618
  if (!remote && this.pc.remoteDescription) {
17569
17619
  fields.remoteSdp = this.pc.remoteDescription;
17570
17620
  }
@@ -17588,9 +17638,6 @@ class PCTransport extends eventsExports.EventEmitter {
17588
17638
  let maxID = 0;
17589
17639
  sdp.media.forEach(m => {
17590
17640
  var _a;
17591
- if (m.type !== 'video') {
17592
- return;
17593
- }
17594
17641
  (_a = m.ext) === null || _a === void 0 ? void 0 : _a.forEach(ext => {
17595
17642
  if (ext.value > maxID) {
17596
17643
  maxID = ext.value;
@@ -18057,192 +18104,6 @@ class PCTransportManager {
18057
18104
  }();
18058
18105
  });
18059
18106
  }
18060
- }const DEFAULT_MAX_AGE_MS = 5000;
18061
- const STOP_REFETCH_DELAY_MS = 30000;
18062
- class RegionUrlProvider {
18063
- static fetchRegionSettings(serverUrl, token, signal) {
18064
- return __awaiter(this, void 0, void 0, function* () {
18065
- const unlock = yield RegionUrlProvider.fetchLock.lock();
18066
- try {
18067
- const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(serverUrl), "/regions"), {
18068
- headers: {
18069
- authorization: "Bearer ".concat(token)
18070
- },
18071
- signal
18072
- });
18073
- if (regionSettingsResponse.ok) {
18074
- const maxAge = extractMaxAgeFromRequestHeaders(regionSettingsResponse.headers);
18075
- const maxAgeInMs = maxAge ? maxAge * 1000 : DEFAULT_MAX_AGE_MS;
18076
- const regionSettings = yield regionSettingsResponse.json();
18077
- return {
18078
- regionSettings,
18079
- updatedAtInMs: Date.now(),
18080
- maxAgeInMs
18081
- };
18082
- } else {
18083
- if (regionSettingsResponse.status === 401) {
18084
- throw ConnectionError.notAllowed("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status);
18085
- } else {
18086
- throw ConnectionError.internal("Could not fetch region settings: ".concat(regionSettingsResponse.statusText));
18087
- }
18088
- }
18089
- } catch (e) {
18090
- if (e instanceof ConnectionError) {
18091
- // rethrow connection errors
18092
- throw e;
18093
- } else if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
18094
- throw ConnectionError.cancelled("Region fetching was aborted");
18095
- } else {
18096
- // wrap other errors as connection errors
18097
- throw ConnectionError.serverUnreachable("Could not fetch region settings, ".concat(e instanceof Error ? "".concat(e.name, ": ").concat(e.message) : e));
18098
- }
18099
- } finally {
18100
- unlock();
18101
- }
18102
- });
18103
- }
18104
- static scheduleRefetch(url, token, maxAgeInMs) {
18105
- return __awaiter(this, void 0, void 0, function* () {
18106
- const timeout = RegionUrlProvider.settingsTimeouts.get(url.hostname);
18107
- clearTimeout(timeout);
18108
- RegionUrlProvider.settingsTimeouts.set(url.hostname, setTimeout(() => __awaiter(this, void 0, void 0, function* () {
18109
- try {
18110
- const newSettings = yield RegionUrlProvider.fetchRegionSettings(url, token);
18111
- RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings);
18112
- } catch (error) {
18113
- if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) {
18114
- livekitLogger.debug('token is not valid, cancelling auto region refresh');
18115
- return;
18116
- }
18117
- livekitLogger.debug('auto refetching of region settings failed', {
18118
- error
18119
- });
18120
- // continue retrying with the same max age
18121
- RegionUrlProvider.scheduleRefetch(url, token, maxAgeInMs);
18122
- }
18123
- }), maxAgeInMs));
18124
- });
18125
- }
18126
- static updateCachedRegionSettings(url, token, settings) {
18127
- RegionUrlProvider.cache.set(url.hostname, settings);
18128
- RegionUrlProvider.scheduleRefetch(url, token, settings.maxAgeInMs);
18129
- }
18130
- static stopRefetch(hostname) {
18131
- const timeout = RegionUrlProvider.settingsTimeouts.get(hostname);
18132
- if (timeout) {
18133
- clearTimeout(timeout);
18134
- RegionUrlProvider.settingsTimeouts.delete(hostname);
18135
- }
18136
- }
18137
- static scheduleCleanup(hostname) {
18138
- let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18139
- if (!tracker) {
18140
- return;
18141
- }
18142
- // Cancel any existing cleanup timeout
18143
- if (tracker.cleanupTimeout) {
18144
- clearTimeout(tracker.cleanupTimeout);
18145
- }
18146
- // Schedule cleanup to stop refetch after delay
18147
- tracker.cleanupTimeout = setTimeout(() => {
18148
- const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname);
18149
- if (currentTracker && currentTracker.connectionCount === 0) {
18150
- livekitLogger.debug('stopping region refetch after disconnect delay', {
18151
- hostname
18152
- });
18153
- RegionUrlProvider.stopRefetch(hostname);
18154
- }
18155
- if (currentTracker) {
18156
- currentTracker.cleanupTimeout = undefined;
18157
- }
18158
- }, STOP_REFETCH_DELAY_MS);
18159
- }
18160
- static cancelCleanup(hostname) {
18161
- const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18162
- if (tracker === null || tracker === void 0 ? void 0 : tracker.cleanupTimeout) {
18163
- clearTimeout(tracker.cleanupTimeout);
18164
- tracker.cleanupTimeout = undefined;
18165
- }
18166
- }
18167
- notifyConnected() {
18168
- const hostname = this.serverUrl.hostname;
18169
- let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18170
- if (!tracker) {
18171
- tracker = {
18172
- connectionCount: 0
18173
- };
18174
- RegionUrlProvider.connectionTrackers.set(hostname, tracker);
18175
- }
18176
- tracker.connectionCount++;
18177
- // Cancel any scheduled cleanup since we have an active connection
18178
- RegionUrlProvider.cancelCleanup(hostname);
18179
- }
18180
- notifyDisconnected() {
18181
- const hostname = this.serverUrl.hostname;
18182
- const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18183
- if (!tracker) {
18184
- return;
18185
- }
18186
- tracker.connectionCount = Math.max(0, tracker.connectionCount - 1);
18187
- // If no more connections, schedule cleanup
18188
- if (tracker.connectionCount === 0) {
18189
- RegionUrlProvider.scheduleCleanup(hostname);
18190
- }
18191
- }
18192
- constructor(url, token) {
18193
- this.attemptedRegions = [];
18194
- this.serverUrl = new URL(url);
18195
- this.token = token;
18196
- }
18197
- updateToken(token) {
18198
- this.token = token;
18199
- }
18200
- isCloud() {
18201
- return isCloud(this.serverUrl);
18202
- }
18203
- getServerUrl() {
18204
- return this.serverUrl;
18205
- }
18206
- /** @internal */
18207
- fetchRegionSettings(abortSignal) {
18208
- return __awaiter(this, void 0, void 0, function* () {
18209
- return RegionUrlProvider.fetchRegionSettings(this.serverUrl, this.token, abortSignal);
18210
- });
18211
- }
18212
- getNextBestRegionUrl(abortSignal) {
18213
- return __awaiter(this, void 0, void 0, function* () {
18214
- if (!this.isCloud()) {
18215
- throw Error('region availability is only supported for LiveKit Cloud domains');
18216
- }
18217
- let cachedSettings = RegionUrlProvider.cache.get(this.serverUrl.hostname);
18218
- if (!cachedSettings || Date.now() - cachedSettings.updatedAtInMs > cachedSettings.maxAgeInMs) {
18219
- cachedSettings = yield this.fetchRegionSettings(abortSignal);
18220
- RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, cachedSettings);
18221
- }
18222
- const regionsLeft = cachedSettings.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
18223
- if (regionsLeft.length > 0) {
18224
- const nextRegion = regionsLeft[0];
18225
- this.attemptedRegions.push(nextRegion);
18226
- livekitLogger.debug("next region: ".concat(nextRegion.region));
18227
- return nextRegion.url;
18228
- } else {
18229
- return null;
18230
- }
18231
- });
18232
- }
18233
- resetAttempts() {
18234
- this.attemptedRegions = [];
18235
- }
18236
- setServerReportedRegions(settings) {
18237
- RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, settings);
18238
- }
18239
- }
18240
- RegionUrlProvider.cache = new Map();
18241
- RegionUrlProvider.settingsTimeouts = new Map();
18242
- RegionUrlProvider.connectionTrackers = new Map();
18243
- RegionUrlProvider.fetchLock = new _();
18244
- function getCloudConfigUrl(serverUrl) {
18245
- return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
18246
18107
  }// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
18247
18108
  //
18248
18109
  // SPDX-License-Identifier: Apache-2.0
@@ -18708,10 +18569,23 @@ class LocalTrack extends Track {
18708
18569
  if (stopProcessor && this.processor) {
18709
18570
  yield this.internalStopProcessor();
18710
18571
  }
18711
- return this;
18712
18572
  } finally {
18713
18573
  unlock();
18714
18574
  }
18575
+ yield this.onSenderTrackSwapped();
18576
+ return this;
18577
+ });
18578
+ }
18579
+ /**
18580
+ * Hook invoked after the MediaStreamTrack on the sender has been swapped
18581
+ * (via replaceTrack, setProcessor, or stopProcessor). Fires outside the
18582
+ * trackChangeLock so subclasses can do asynchronous work such as polling
18583
+ * for new dimensions without blocking other track operations.
18584
+ */
18585
+ onSenderTrackSwapped() {
18586
+ return __awaiter(this, void 0, void 0, function* () {
18587
+ // base implementation is a no-op; LocalVideoTrack overrides this to
18588
+ // recompute sender encoding parameters.
18715
18589
  });
18716
18590
  }
18717
18591
  restart(constraints, isUnmuting) {
@@ -18956,6 +18830,7 @@ class LocalTrack extends Track {
18956
18830
  } finally {
18957
18831
  unlock();
18958
18832
  }
18833
+ yield _this3.onSenderTrackSwapped();
18959
18834
  }();
18960
18835
  });
18961
18836
  }
@@ -18978,6 +18853,7 @@ class LocalTrack extends Track {
18978
18853
  } finally {
18979
18854
  unlock();
18980
18855
  }
18856
+ yield _this4.onSenderTrackSwapped();
18981
18857
  }();
18982
18858
  });
18983
18859
  }
@@ -19921,46 +19797,146 @@ class LocalVideoTrack extends LocalTrack {
19921
19797
  if (e_3) throw e_3.error;
19922
19798
  }
19923
19799
  }
19800
+ // The new MediaStreamTrack may have different dimensions than the previous one
19801
+ // (e.g. switching between cameras with different native resolutions), which would
19802
+ // leave the sender's encoding parameters (scaleResolutionDownBy, maxBitrate, etc.)
19803
+ // based on the old dimensions. Recompute them so the encoded output matches the
19804
+ // new source.
19805
+ yield this.onSenderTrackSwapped();
19924
19806
  });
19925
19807
  }
19926
- setProcessor(processor_1) {
19927
- const _super = Object.create(null, {
19928
- setProcessor: {
19929
- get: () => super.setProcessor
19930
- }
19931
- });
19932
- return __awaiter(this, arguments, void 0, function (processor) {
19933
- var _this = this;
19934
- let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
19935
- return function* () {
19936
- var _a, e_4, _b, _c;
19937
- var _d, _e;
19938
- yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally);
19939
- if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
19940
- try {
19941
- for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
19942
- _c = _h.value;
19943
- _f = false;
19944
- const sc = _c;
19945
- yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
19946
- }
19947
- } catch (e_4_1) {
19948
- e_4 = {
19949
- error: e_4_1
19950
- };
19951
- } finally {
19952
- try {
19953
- if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
19954
- } finally {
19955
- if (e_4) throw e_4.error;
19956
- }
19957
- }
19958
- }
19959
- }();
19808
+ onSenderTrackSwapped() {
19809
+ return __awaiter(this, void 0, void 0, function* () {
19810
+ yield this.refreshSenderEncodings();
19960
19811
  });
19961
19812
  }
19962
- setDegradationPreference(preference) {
19963
- return __awaiter(this, void 0, void 0, function* () {
19813
+ /**
19814
+ * Recomputes encoding parameters for this track's senders based on the current
19815
+ * MediaStreamTrack dimensions and reapplies them via setParameters. This is a no-op
19816
+ * if the track hasn't been published yet or if the track is in performance-optimized
19817
+ * mode (which manages its own encodings).
19818
+ */
19819
+ refreshSenderEncodings() {
19820
+ return __awaiter(this, void 0, void 0, function* () {
19821
+ var _a;
19822
+ if (!this.sender || !this.publishOptions || this.optimizeForPerformance) {
19823
+ return;
19824
+ }
19825
+ const unlock = yield this.senderLock.lock();
19826
+ try {
19827
+ let dims;
19828
+ try {
19829
+ dims = yield this.waitForDimensions();
19830
+ } catch (e) {
19831
+ this.log.warn('could not determine new track dimensions, skipping encoding recompute', Object.assign(Object.assign({}, this.logContext), {
19832
+ error: e
19833
+ }));
19834
+ return;
19835
+ }
19836
+ if (this.lastEncodedDimensions && this.lastEncodedDimensions.width === dims.width && this.lastEncodedDimensions.height === dims.height) {
19837
+ return;
19838
+ }
19839
+ const isScreenShare = this.source === Track.Source.ScreenShare;
19840
+ const newEncodings = computeVideoEncodings(isScreenShare, dims.width, dims.height, Object.assign({}, this.publishOptions));
19841
+ yield this.applyEncodingsToSender(this.sender, newEncodings);
19842
+ this.encodings = newEncodings;
19843
+ this.lastEncodedDimensions = dims;
19844
+ for (const [codec, sc] of this.simulcastCodecs) {
19845
+ if (!sc.sender || ((_a = sc.sender.transport) === null || _a === void 0 ? void 0 : _a.state) === 'closed') {
19846
+ continue;
19847
+ }
19848
+ if (!isBackupVideoCodec(codec)) {
19849
+ continue;
19850
+ }
19851
+ const backupOpts = Object.assign({}, this.publishOptions);
19852
+ const backupEncodings = computeTrackBackupEncodings(this, codec, backupOpts);
19853
+ if (!backupEncodings) {
19854
+ continue;
19855
+ }
19856
+ yield this.applyEncodingsToSender(sc.sender, backupEncodings);
19857
+ sc.encodings = backupEncodings;
19858
+ }
19859
+ } catch (e) {
19860
+ this.log.warn('failed to apply recomputed encodings', Object.assign(Object.assign({}, this.logContext), {
19861
+ error: e
19862
+ }));
19863
+ } finally {
19864
+ unlock();
19865
+ }
19866
+ });
19867
+ }
19868
+ applyEncodingsToSender(sender, encodings) {
19869
+ return __awaiter(this, void 0, void 0, function* () {
19870
+ const params = sender.getParameters();
19871
+ if (!params.encodings || params.encodings.length !== encodings.length) {
19872
+ return;
19873
+ }
19874
+ params.encodings.forEach((existing, idx) => {
19875
+ // preserve disabled layers (dynacast / Firefox workaround in
19876
+ // setPublishingLayersForSender set scaleResolutionDownBy/maxBitrate to sentinel
19877
+ // values for disabled layers — don't clobber those).
19878
+ if (existing.active === false) {
19879
+ return;
19880
+ }
19881
+ const next = encodings[idx];
19882
+ if (next.scaleResolutionDownBy !== undefined) {
19883
+ existing.scaleResolutionDownBy = next.scaleResolutionDownBy;
19884
+ }
19885
+ if (next.maxBitrate !== undefined) {
19886
+ existing.maxBitrate = next.maxBitrate;
19887
+ }
19888
+ if (next.maxFramerate !== undefined) {
19889
+ existing.maxFramerate = next.maxFramerate;
19890
+ }
19891
+ if (next.priority !== undefined) {
19892
+ existing.priority = next.priority;
19893
+ existing.networkPriority = next.priority;
19894
+ }
19895
+ });
19896
+ this.log.debug('updating sender encodings after track restart', Object.assign(Object.assign({}, this.logContext), {
19897
+ encodings: params.encodings
19898
+ }));
19899
+ yield sender.setParameters(params);
19900
+ });
19901
+ }
19902
+ setProcessor(processor_1) {
19903
+ const _super = Object.create(null, {
19904
+ setProcessor: {
19905
+ get: () => super.setProcessor
19906
+ }
19907
+ });
19908
+ return __awaiter(this, arguments, void 0, function (processor) {
19909
+ var _this = this;
19910
+ let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
19911
+ return function* () {
19912
+ var _a, e_4, _b, _c;
19913
+ var _d, _e;
19914
+ yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally);
19915
+ if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
19916
+ try {
19917
+ for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
19918
+ _c = _h.value;
19919
+ _f = false;
19920
+ const sc = _c;
19921
+ yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
19922
+ }
19923
+ } catch (e_4_1) {
19924
+ e_4 = {
19925
+ error: e_4_1
19926
+ };
19927
+ } finally {
19928
+ try {
19929
+ if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
19930
+ } finally {
19931
+ if (e_4) throw e_4.error;
19932
+ }
19933
+ }
19934
+ }
19935
+ }();
19936
+ });
19937
+ }
19938
+ setDegradationPreference(preference) {
19939
+ return __awaiter(this, void 0, void 0, function* () {
19964
19940
  this.degradationPreference = preference;
19965
19941
  if (this.sender) {
19966
19942
  try {
@@ -20333,6 +20309,7 @@ class RTCEngine extends eventsExports.EventEmitter {
20333
20309
  /** specifies how often an initial join connection is allowed to retry */
20334
20310
  this.maxJoinAttempts = 1;
20335
20311
  this.shouldFailNext = false;
20312
+ this.shouldFailOnV1Path = false;
20336
20313
  this.log = livekitLogger;
20337
20314
  this.reliableDataSequence = 1;
20338
20315
  this.reliableMessageBuffer = new DataPacketBuffer();
@@ -20343,6 +20320,7 @@ class RTCEngine extends eventsExports.EventEmitter {
20343
20320
  this.midToTrackId = {};
20344
20321
  /** used to indicate whether the browser is currently waiting to reconnect */
20345
20322
  this.isWaitingForNetworkReconnect = false;
20323
+ this.bufferStatusLowClosingFuture = new Future();
20346
20324
  this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) {
20347
20325
  var _this = this;
20348
20326
  let {
@@ -20491,10 +20469,10 @@ class RTCEngine extends eventsExports.EventEmitter {
20491
20469
  }
20492
20470
  this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
20493
20471
  this.clearReconnectTimeout();
20494
- if (this.token && this.regionUrlProvider) {
20472
+ if (this.token) {
20495
20473
  // token may have been refreshed, we do not want to recreate the regionUrlProvider
20496
20474
  // since the current engine may have inherited a regional url
20497
- this.regionUrlProvider.updateToken(this.token);
20475
+ this.emit(EngineEvent.TokenRefreshed, this.token);
20498
20476
  }
20499
20477
  this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
20500
20478
  };
@@ -20594,6 +20572,13 @@ class RTCEngine extends eventsExports.EventEmitter {
20594
20572
  this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response);
20595
20573
  this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
20596
20574
  this.client.onJoined = joinResponse => this.emit(EngineEvent.Joined, joinResponse);
20575
+ this.on(EngineEvent.Closing, () => {
20576
+ var _a, _b;
20577
+ (_b = (_a = this.bufferStatusLowClosingFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, new UnexpectedConnectionState('engine closed'));
20578
+ });
20579
+ // Swallow the rejection at the source so it doesn't surface as an unhandled promise rejection
20580
+ // when no waitForBufferStatusLow callers are attached.
20581
+ this.bufferStatusLowClosingFuture.promise.catch(() => {});
20597
20582
  }
20598
20583
  /** @internal */
20599
20584
  get logContext() {
@@ -20634,6 +20619,10 @@ class RTCEngine extends eventsExports.EventEmitter {
20634
20619
  if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
20635
20620
  throw ConnectionError.cancelled('Connection aborted');
20636
20621
  }
20622
+ if (!useV0Path && _this2.shouldFailOnV1Path) {
20623
+ _this2.shouldFailOnV1Path = false;
20624
+ throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc');
20625
+ }
20637
20626
  const joinResponse = yield _this2.client.join(url, token, opts, abortSignal, useV0Path, offerProto);
20638
20627
  _this2._isClosed = false;
20639
20628
  _this2.latestJoinResponse = joinResponse;
@@ -20683,6 +20672,10 @@ class RTCEngine extends eventsExports.EventEmitter {
20683
20672
  }
20684
20673
  } else if (e.reason === ConnectionErrorReason.ServiceNotFound) {
20685
20674
  _this2.log.warn("Initial connection failed: ".concat(e.message, " \u2013 Retrying"));
20675
+ if (_this2.pcManager) {
20676
+ _this2.pcManager.onStateChange = undefined;
20677
+ yield _this2.cleanupPeerConnections();
20678
+ }
20686
20679
  return _this2.join(url, token, opts, abortSignal, true);
20687
20680
  }
20688
20681
  }
@@ -20758,6 +20751,14 @@ class RTCEngine extends eventsExports.EventEmitter {
20758
20751
  return __awaiter(this, void 0, void 0, function* () {
20759
20752
  yield this.client.close();
20760
20753
  this.client.resetCallbacks();
20754
+ // Any in-flight addTrack requests are orphaned by the signal reconnect — the new session
20755
+ // won't deliver `trackPublishedResponse` for them, so reject the pending resolvers and
20756
+ // clear the map. Otherwise a subsequent `addTrack` call with the same client id (e.g. a
20757
+ // publish retry after a `NegotiationError`) throws `TrackInvalidError`.
20758
+ for (const cid of Object.keys(this.pendingTrackResolvers)) {
20759
+ this.pendingTrackResolvers[cid].reject();
20760
+ }
20761
+ this.pendingTrackResolvers = {};
20761
20762
  });
20762
20763
  }
20763
20764
  addTrack(req) {
@@ -20822,8 +20823,8 @@ class RTCEngine extends eventsExports.EventEmitter {
20822
20823
  });
20823
20824
  }
20824
20825
  /* @internal */
20825
- setRegionUrlProvider(provider) {
20826
- this.regionUrlProvider = provider;
20826
+ setRegionStrategy(strategy) {
20827
+ this.regionStrategy = strategy;
20827
20828
  }
20828
20829
  configure(joinResponse, useSinglePeerConnection) {
20829
20830
  return __awaiter(this, void 0, void 0, function* () {
@@ -20944,9 +20945,8 @@ class RTCEngine extends eventsExports.EventEmitter {
20944
20945
  this.emit(EngineEvent.LocalTrackSubscribed, trackSid);
20945
20946
  };
20946
20947
  this.client.onTokenRefresh = token => {
20947
- var _a;
20948
20948
  this.token = token;
20949
- (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.updateToken(token);
20949
+ this.emit(EngineEvent.TokenRefreshed, token);
20950
20950
  };
20951
20951
  this.client.onRemoteMuteChanged = (trackSid, muted) => {
20952
20952
  this.emit(EngineEvent.RemoteMute, trackSid, muted);
@@ -20982,13 +20982,9 @@ class RTCEngine extends eventsExports.EventEmitter {
20982
20982
  this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
20983
20983
  reason: leave === null || leave === void 0 ? void 0 : leave.reason
20984
20984
  }));
20985
- if (leave.regions && this.regionUrlProvider) {
20985
+ if (leave.regions) {
20986
20986
  this.log.debug('updating regions', this.logContext);
20987
- this.regionUrlProvider.setServerReportedRegions({
20988
- updatedAtInMs: Date.now(),
20989
- maxAgeInMs: DEFAULT_MAX_AGE_MS,
20990
- regionSettings: leave.regions
20991
- });
20987
+ this.emit(EngineEvent.ServerRegionsReported, leave.regions);
20992
20988
  }
20993
20989
  switch (leave.action) {
20994
20990
  case LeaveRequest_Action.DISCONNECT:
@@ -21292,17 +21288,17 @@ class RTCEngine extends eventsExports.EventEmitter {
21292
21288
  if (this.client.currentState !== SignalConnectionState.CONNECTED) {
21293
21289
  throw new SignalReconnectError('Signal connection got severed during reconnect');
21294
21290
  }
21295
- (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
21291
+ (_a = this.regionStrategy) === null || _a === void 0 ? void 0 : _a.resetAttempts();
21296
21292
  // reconnect success
21297
21293
  this.emit(EngineEvent.Restarted);
21298
21294
  } catch (error) {
21299
- const nextRegionUrl = yield (_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.getNextBestRegionUrl();
21295
+ const nextRegionUrl = yield (_b = this.regionStrategy) === null || _b === void 0 ? void 0 : _b.getNextUrl();
21300
21296
  if (nextRegionUrl) {
21301
21297
  yield this.restartConnection(nextRegionUrl);
21302
21298
  return;
21303
21299
  } else {
21304
21300
  // no more regions to try (or we're not on cloud)
21305
- (_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
21301
+ (_c = this.regionStrategy) === null || _c === void 0 ? void 0 : _c.resetAttempts();
21306
21302
  throw error;
21307
21303
  }
21308
21304
  }
@@ -21544,17 +21540,13 @@ class RTCEngine extends eventsExports.EventEmitter {
21544
21540
  if (this.isBufferStatusLow(kind)) {
21545
21541
  resolve();
21546
21542
  } else {
21547
- const onClosing = () => reject(new UnexpectedConnectionState('engine closed'));
21548
- this.once(EngineEvent.Closing, onClosing);
21549
21543
  const dc = this.dataChannelForKind(kind);
21550
21544
  if (!dc) {
21551
21545
  reject(new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind)));
21552
21546
  return;
21553
21547
  }
21554
- dc.addEventListener('bufferedamountlow', () => {
21555
- this.off(EngineEvent.Closing, onClosing);
21556
- resolve();
21557
- }, {
21548
+ this.bufferStatusLowClosingFuture.promise.catch(e => reject(e));
21549
+ dc.addEventListener('bufferedamountlow', () => resolve(), {
21558
21550
  once: true
21559
21551
  });
21560
21552
  }
@@ -21783,6 +21775,11 @@ class RTCEngine extends eventsExports.EventEmitter {
21783
21775
  // debugging method to fail the next reconnect/resume attempt
21784
21776
  this.shouldFailNext = true;
21785
21777
  }
21778
+ /* @internal */
21779
+ failNextV1Path() {
21780
+ // debugging method to fail the next connection attempt for /rtc/v1 to trigger the fallback version
21781
+ this.shouldFailOnV1Path = true;
21782
+ }
21786
21783
  dataChannelsInfo() {
21787
21784
  const infos = [];
21788
21785
  const getInfo = (dc, target) => {
@@ -21842,6 +21839,196 @@ function applyUserDataCompat(newObj, oldObj) {
21842
21839
  const destinationIdentities = newObj.destinationIdentities.length !== 0 ? newObj.destinationIdentities : oldObj.destinationIdentities;
21843
21840
  newObj.destinationIdentities = destinationIdentities;
21844
21841
  oldObj.destinationIdentities = destinationIdentities;
21842
+ }const DEFAULT_MAX_AGE_MS = 5000;
21843
+ const STOP_REFETCH_DELAY_MS = 30000;
21844
+ class RegionUrlProvider {
21845
+ static fetchRegionSettings(serverUrl, token, signal) {
21846
+ return __awaiter(this, void 0, void 0, function* () {
21847
+ const unlock = yield RegionUrlProvider.fetchLock.lock();
21848
+ try {
21849
+ const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(serverUrl), "/regions"), {
21850
+ headers: {
21851
+ authorization: "Bearer ".concat(token)
21852
+ },
21853
+ signal
21854
+ });
21855
+ if (regionSettingsResponse.ok) {
21856
+ const maxAge = extractMaxAgeFromRequestHeaders(regionSettingsResponse.headers);
21857
+ const maxAgeInMs = maxAge ? maxAge * 1000 : DEFAULT_MAX_AGE_MS;
21858
+ const regionSettings = yield regionSettingsResponse.json();
21859
+ return {
21860
+ regionSettings,
21861
+ updatedAtInMs: Date.now(),
21862
+ maxAgeInMs
21863
+ };
21864
+ } else {
21865
+ if (regionSettingsResponse.status === 401) {
21866
+ throw ConnectionError.notAllowed("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status);
21867
+ } else {
21868
+ throw ConnectionError.internal("Could not fetch region settings: ".concat(regionSettingsResponse.statusText));
21869
+ }
21870
+ }
21871
+ } catch (e) {
21872
+ if (e instanceof ConnectionError) {
21873
+ // rethrow connection errors
21874
+ throw e;
21875
+ } else if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
21876
+ throw ConnectionError.cancelled("Region fetching was aborted");
21877
+ } else {
21878
+ // wrap other errors as connection errors
21879
+ throw ConnectionError.serverUnreachable("Could not fetch region settings, ".concat(e instanceof Error ? "".concat(e.name, ": ").concat(e.message) : e));
21880
+ }
21881
+ } finally {
21882
+ unlock();
21883
+ }
21884
+ });
21885
+ }
21886
+ static scheduleRefetch(url, token, maxAgeInMs) {
21887
+ return __awaiter(this, void 0, void 0, function* () {
21888
+ const timeout = RegionUrlProvider.settingsTimeouts.get(url.hostname);
21889
+ clearTimeout(timeout);
21890
+ RegionUrlProvider.settingsTimeouts.set(url.hostname, setTimeout(() => __awaiter(this, void 0, void 0, function* () {
21891
+ try {
21892
+ const newSettings = yield RegionUrlProvider.fetchRegionSettings(url, token);
21893
+ RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings);
21894
+ } catch (error) {
21895
+ if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) {
21896
+ livekitLogger.debug('token is not valid, cancelling auto region refresh');
21897
+ return;
21898
+ }
21899
+ livekitLogger.debug('auto refetching of region settings failed', {
21900
+ error
21901
+ });
21902
+ // continue retrying with the same max age
21903
+ RegionUrlProvider.scheduleRefetch(url, token, maxAgeInMs);
21904
+ }
21905
+ }), maxAgeInMs));
21906
+ });
21907
+ }
21908
+ static updateCachedRegionSettings(url, token, settings) {
21909
+ RegionUrlProvider.cache.set(url.hostname, settings);
21910
+ RegionUrlProvider.scheduleRefetch(url, token, settings.maxAgeInMs);
21911
+ }
21912
+ static stopRefetch(hostname) {
21913
+ const timeout = RegionUrlProvider.settingsTimeouts.get(hostname);
21914
+ if (timeout) {
21915
+ clearTimeout(timeout);
21916
+ RegionUrlProvider.settingsTimeouts.delete(hostname);
21917
+ }
21918
+ }
21919
+ static scheduleCleanup(hostname) {
21920
+ let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
21921
+ if (!tracker) {
21922
+ return;
21923
+ }
21924
+ // Cancel any existing cleanup timeout
21925
+ if (tracker.cleanupTimeout) {
21926
+ clearTimeout(tracker.cleanupTimeout);
21927
+ }
21928
+ // Schedule cleanup to stop refetch after delay
21929
+ tracker.cleanupTimeout = setTimeout(() => {
21930
+ const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname);
21931
+ if (currentTracker && currentTracker.connectionCount === 0) {
21932
+ livekitLogger.debug('stopping region refetch after disconnect delay', {
21933
+ hostname
21934
+ });
21935
+ RegionUrlProvider.stopRefetch(hostname);
21936
+ }
21937
+ if (currentTracker) {
21938
+ currentTracker.cleanupTimeout = undefined;
21939
+ }
21940
+ }, STOP_REFETCH_DELAY_MS);
21941
+ }
21942
+ static cancelCleanup(hostname) {
21943
+ const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
21944
+ if (tracker === null || tracker === void 0 ? void 0 : tracker.cleanupTimeout) {
21945
+ clearTimeout(tracker.cleanupTimeout);
21946
+ tracker.cleanupTimeout = undefined;
21947
+ }
21948
+ }
21949
+ notifyConnected() {
21950
+ const hostname = this.serverUrl.hostname;
21951
+ let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
21952
+ if (!tracker) {
21953
+ tracker = {
21954
+ connectionCount: 0
21955
+ };
21956
+ RegionUrlProvider.connectionTrackers.set(hostname, tracker);
21957
+ }
21958
+ tracker.connectionCount++;
21959
+ // Cancel any scheduled cleanup since we have an active connection
21960
+ RegionUrlProvider.cancelCleanup(hostname);
21961
+ }
21962
+ notifyDisconnected() {
21963
+ const hostname = this.serverUrl.hostname;
21964
+ const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
21965
+ if (!tracker) {
21966
+ return;
21967
+ }
21968
+ tracker.connectionCount = Math.max(0, tracker.connectionCount - 1);
21969
+ // If no more connections, schedule cleanup
21970
+ if (tracker.connectionCount === 0) {
21971
+ RegionUrlProvider.scheduleCleanup(hostname);
21972
+ }
21973
+ }
21974
+ constructor(url, token) {
21975
+ this.attemptedRegions = [];
21976
+ this.serverUrl = new URL(url);
21977
+ this.token = token;
21978
+ }
21979
+ updateToken(token) {
21980
+ var _a;
21981
+ this.token = token;
21982
+ const url = this.getServerUrl();
21983
+ const settings = RegionUrlProvider.cache.get(url.hostname);
21984
+ RegionUrlProvider.scheduleRefetch(this.serverUrl, this.token, (_a = settings === null || settings === void 0 ? void 0 : settings.maxAgeInMs) !== null && _a !== void 0 ? _a : DEFAULT_MAX_AGE_MS);
21985
+ }
21986
+ isCloud() {
21987
+ return isCloud(this.serverUrl);
21988
+ }
21989
+ getServerUrl() {
21990
+ return this.serverUrl;
21991
+ }
21992
+ /** @internal */
21993
+ fetchRegionSettings(abortSignal) {
21994
+ return __awaiter(this, void 0, void 0, function* () {
21995
+ return RegionUrlProvider.fetchRegionSettings(this.serverUrl, this.token, abortSignal);
21996
+ });
21997
+ }
21998
+ getNextBestRegionUrl(abortSignal) {
21999
+ return __awaiter(this, void 0, void 0, function* () {
22000
+ if (!this.isCloud()) {
22001
+ throw Error('region availability is only supported for LiveKit Cloud domains');
22002
+ }
22003
+ let cachedSettings = RegionUrlProvider.cache.get(this.serverUrl.hostname);
22004
+ if (!cachedSettings || Date.now() - cachedSettings.updatedAtInMs > cachedSettings.maxAgeInMs) {
22005
+ cachedSettings = yield this.fetchRegionSettings(abortSignal);
22006
+ RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, cachedSettings);
22007
+ }
22008
+ const regionsLeft = cachedSettings.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
22009
+ if (regionsLeft.length > 0) {
22010
+ const nextRegion = regionsLeft[0];
22011
+ this.attemptedRegions.push(nextRegion);
22012
+ livekitLogger.debug("next region: ".concat(nextRegion.region));
22013
+ return nextRegion.url;
22014
+ } else {
22015
+ return null;
22016
+ }
22017
+ });
22018
+ }
22019
+ resetAttempts() {
22020
+ this.attemptedRegions = [];
22021
+ }
22022
+ setServerReportedRegions(settings) {
22023
+ RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, settings);
22024
+ }
22025
+ }
22026
+ RegionUrlProvider.cache = new Map();
22027
+ RegionUrlProvider.settingsTimeouts = new Map();
22028
+ RegionUrlProvider.connectionTrackers = new Map();
22029
+ RegionUrlProvider.fetchLock = new _();
22030
+ function getCloudConfigUrl(serverUrl) {
22031
+ return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
21845
22032
  }class BaseStreamReader {
21846
22033
  get info() {
21847
22034
  return this._info;
@@ -23540,55 +23727,82 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23540
23727
  let bufferSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : READABLE_STREAM_DEFAULT_BUFFER_SIZE;
23541
23728
  let streamController = null;
23542
23729
  const sfuSubscriptionComplete = new Future();
23730
+ const detachSignal = () => {
23731
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
23732
+ };
23733
+ const cleanup = () => {
23734
+ detachSignal();
23735
+ if (!streamController) {
23736
+ log$1.warn("ReadableStream subscribed to ".concat(sid, " was not started."));
23737
+ return;
23738
+ }
23739
+ const descriptor = this.descriptors.get(sid);
23740
+ if (!descriptor) {
23741
+ log$1.warn("Unknown track ".concat(sid, ", skipping cancel..."));
23742
+ return;
23743
+ }
23744
+ if (descriptor.subscription.type !== 'active') {
23745
+ log$1.warn("Subscription for track ".concat(sid, " is not active, skipping cancel..."));
23746
+ return;
23747
+ }
23748
+ descriptor.subscription.streamControllers.delete(streamController);
23749
+ // If no active stream controllers are left, also unsubscribe on the SFU end.
23750
+ if (descriptor.subscription.streamControllers.size === 0) {
23751
+ this.unSubscribeRequest(descriptor.info.sid);
23752
+ }
23753
+ };
23754
+ const onAbort = () => {
23755
+ var _a;
23756
+ if (!streamController) {
23757
+ return;
23758
+ }
23759
+ const currentDescriptor = this.descriptors.get(sid);
23760
+ if ((currentDescriptor === null || currentDescriptor === void 0 ? void 0 : currentDescriptor.subscription.type) === 'active') {
23761
+ currentDescriptor.subscription.streamControllers.delete(streamController);
23762
+ }
23763
+ streamController.error(DataTrackSubscribeError.cancelled());
23764
+ (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, DataTrackSubscribeError.cancelled());
23765
+ cleanup();
23766
+ };
23543
23767
  const stream = new ReadableStream({
23544
23768
  start: controller => {
23545
23769
  streamController = controller;
23546
- const onAbort = () => {
23547
- var _a;
23548
- controller.error(DataTrackSubscribeError.cancelled());
23549
- (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, DataTrackSubscribeError.cancelled());
23550
- };
23551
23770
  this.subscribeRequest(sid, signal).then(() => __awaiter(this, void 0, void 0, function* () {
23552
- var _a;
23553
- signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort);
23771
+ var _a, _b, _c;
23554
23772
  const descriptor = this.descriptors.get(sid);
23555
23773
  if (!descriptor) {
23556
23774
  log$1.error("Unknown track ".concat(sid));
23775
+ const err = DataTrackSubscribeError.disconnected();
23776
+ controller.error(err);
23777
+ (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, err);
23557
23778
  return;
23558
23779
  }
23559
23780
  if (descriptor.subscription.type !== 'active') {
23560
23781
  log$1.error("Subscription for track ".concat(sid, " is not active"));
23782
+ const err = DataTrackSubscribeError.disconnected();
23783
+ controller.error(err);
23784
+ (_b = sfuSubscriptionComplete.reject) === null || _b === void 0 ? void 0 : _b.call(sfuSubscriptionComplete, err);
23561
23785
  return;
23562
23786
  }
23563
- descriptor.subscription.streamControllers.add(controller);
23564
- (_a = sfuSubscriptionComplete.resolve) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete);
23787
+ // Attach the abort signal, aborting immediately if the abort signal was fired while
23788
+ // subscribeRequest was in flight.
23789
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
23790
+ onAbort();
23791
+ return;
23792
+ }
23793
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort);
23794
+ descriptor.subscription.streamControllers.set(controller, detachSignal);
23795
+ (_c = sfuSubscriptionComplete.resolve) === null || _c === void 0 ? void 0 : _c.call(sfuSubscriptionComplete);
23565
23796
  })).catch(err => {
23566
23797
  var _a;
23798
+ // subscribeRequest rejected (cancelled, timed out, disconnected). The signal
23799
+ // listener was never attached in this path, so nothing to detach.
23567
23800
  controller.error(err);
23568
23801
  (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, err);
23569
- }).finally(() => {
23570
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
23571
23802
  });
23572
23803
  },
23573
23804
  cancel: () => {
23574
- if (!streamController) {
23575
- log$1.warn("ReadableStream subscribed to ".concat(sid, " was not started."));
23576
- return;
23577
- }
23578
- const descriptor = this.descriptors.get(sid);
23579
- if (!descriptor) {
23580
- log$1.warn("Unknown track ".concat(sid, ", skipping cancel..."));
23581
- return;
23582
- }
23583
- if (descriptor.subscription.type !== 'active') {
23584
- log$1.warn("Subscription for track ".concat(sid, " is not active, skipping cancel..."));
23585
- return;
23586
- }
23587
- descriptor.subscription.streamControllers.delete(streamController);
23588
- // If no active stream controllers are left, also unsubscribe on the SFU end.
23589
- if (descriptor.subscription.streamControllers.size === 0) {
23590
- this.unSubscribeRequest(descriptor.info.sid);
23591
- }
23805
+ cleanup();
23592
23806
  }
23593
23807
  }, new CountQueuingStrategy({
23594
23808
  highWaterMark: bufferSize
@@ -23726,9 +23940,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23726
23940
  log$1.warn("Unexpected descriptor state in unSubscribeRequest, expected active, found ".concat((_a = descriptor.subscription) === null || _a === void 0 ? void 0 : _a.type));
23727
23941
  return;
23728
23942
  }
23729
- for (const controller of descriptor.subscription.streamControllers) {
23730
- controller.close();
23731
- }
23943
+ this.closeStreamControllers(descriptor.subscription.streamControllers, sid);
23732
23944
  // FIXME: this might be wrong? Shouldn't this only occur if it is the last subscription to
23733
23945
  // terminate?
23734
23946
  const previousDescriptorSubscription = descriptor.subscription;
@@ -23741,6 +23953,23 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23741
23953
  subscribe: false
23742
23954
  });
23743
23955
  }
23956
+ /** Detach abort-signal listeners and close all downstream stream controllers for an active
23957
+ * subscription. Used when the subscription is being torn down by the manager (unsubscribe,
23958
+ * unpublish, or shutdown). */
23959
+ closeStreamControllers(streamControllers, sid) {
23960
+ for (const [controller, detachSignal] of streamControllers) {
23961
+ // Detach before close so we don't leak a listener on the user's AbortSignal.
23962
+ detachSignal();
23963
+ try {
23964
+ controller.close();
23965
+ } catch (err) {
23966
+ // Defensive: if the controller has already been errored (e.g. by a racing abort whose
23967
+ // listener removed itself before we got here), close() throws. There's nothing
23968
+ // meaningful to do other than log — the stream is already terminal.
23969
+ log$1.warn("Failed to close readable stream for track ".concat(sid, ": ").concat(err));
23970
+ }
23971
+ }
23972
+ }
23744
23973
  /** SFU notification that track publications have changed.
23745
23974
  *
23746
23975
  * This event is produced from both {@link JoinResponse} and {@link ParticipantUpdate}
@@ -23822,9 +24051,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23822
24051
  }
23823
24052
  this.descriptors.delete(sid);
23824
24053
  if (descriptor.subscription.type === 'active') {
23825
- descriptor.subscription.streamControllers.forEach(controller => {
23826
- controller.close();
23827
- });
24054
+ this.closeStreamControllers(descriptor.subscription.streamControllers, sid);
23828
24055
  this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle);
23829
24056
  }
23830
24057
  this.emit('trackUnpublished', {
@@ -23874,7 +24101,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23874
24101
  type: 'active',
23875
24102
  subcriptionHandle: assignedHandle,
23876
24103
  pipeline,
23877
- streamControllers: new Set()
24104
+ streamControllers: new Map()
23878
24105
  };
23879
24106
  this.subscriptionHandles.set(assignedHandle, sid);
23880
24107
  (_b = (_a = previousDescriptorSubscription.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
@@ -23911,7 +24138,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23911
24138
  return;
23912
24139
  }
23913
24140
  // Broadcast to all downstream subscribers
23914
- for (const controller of descriptor.subscription.streamControllers) {
24141
+ for (const controller of descriptor.subscription.streamControllers.keys()) {
23915
24142
  if (controller.desiredSize !== null && controller.desiredSize <= 0) {
23916
24143
  log$1.warn("Cannot send frame to subscribers: readable stream is full (desiredSize is ".concat(controller.desiredSize, "). To increase this threshold, set a higher 'options.highWaterMark' when calling .subscribe()."));
23917
24144
  continue;
@@ -23969,7 +24196,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
23969
24196
  (_b = (_a = descriptor.subscription.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, DataTrackSubscribeError.disconnected());
23970
24197
  }
23971
24198
  if (descriptor.subscription.type === 'active') {
23972
- descriptor.subscription.streamControllers.forEach(controller => controller.close());
24199
+ this.closeStreamControllers(descriptor.subscription.streamControllers, descriptor.info.sid);
23973
24200
  }
23974
24201
  }
23975
24202
  this.descriptors.clear();
@@ -25175,10 +25402,17 @@ class HTMLElementInfo {
25175
25402
  }
25176
25403
  };
25177
25404
  this.onEnterPiP = () => {
25178
- var _a, _b, _c;
25405
+ var _a, _b;
25179
25406
  (_b = (_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.window) === null || _b === void 0 ? void 0 : _b.addEventListener('pagehide', this.onLeavePiP);
25180
- this.isPiP = isElementInPiP(this.element);
25181
- (_c = this.handleVisibilityChanged) === null || _c === void 0 ? void 0 : _c.call(this);
25407
+ // Document PiP: the browser may fire 'enter' before the app has appended its subtree into
25408
+ // documentPictureInPicture.window. Defer so pipWin.document.contains(video) is reliable.
25409
+ queueMicrotask(() => {
25410
+ requestAnimationFrame(() => {
25411
+ var _a;
25412
+ this.isPiP = isElementInPiP(this.element);
25413
+ (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
25414
+ });
25415
+ });
25182
25416
  };
25183
25417
  this.onLeavePiP = () => {
25184
25418
  var _a;
@@ -26451,11 +26685,12 @@ class Participant extends eventsExports.EventEmitter {
26451
26685
  if (track && track.track) {
26452
26686
  // screenshare cannot be muted, unpublish instead
26453
26687
  if (source === Track.Source.ScreenShare) {
26454
- track = yield this.unpublishTrack(track.track);
26688
+ const unpublishPromises = [this.unpublishTrack(track.track)];
26455
26689
  const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio);
26456
26690
  if (screenAudioTrack && screenAudioTrack.track) {
26457
- this.unpublishTrack(screenAudioTrack.track);
26691
+ unpublishPromises.push(this.unpublishTrack(screenAudioTrack.track));
26458
26692
  }
26693
+ [track] = yield Promise.all(unpublishPromises);
26459
26694
  } else {
26460
26695
  yield track.mute();
26461
26696
  }
@@ -26586,10 +26821,42 @@ class Participant extends eventsExports.EventEmitter {
26586
26821
  return this.publishOrRepublishTrack(track, options);
26587
26822
  });
26588
26823
  }
26824
+ /**
26825
+ * Waits for the engine's next `Restarted` event. Unlike `engine.waitForRestarted`, this does
26826
+ * not short-circuit when `pcState === Connected` — at the point this is called (right after a
26827
+ * `NegotiationError`) the PC transport is still connected, but `fullReconnectOnNext` has been
26828
+ * set and `attemptReconnect` is queued via setTimeout. We need to wait for that restart to
26829
+ * actually complete (which clears `pendingTrackResolvers` via `cleanupClient`) before retrying.
26830
+ */
26831
+ waitForNextEngineRestart() {
26832
+ let timeoutMs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 15000;
26833
+ return new Promise((resolve, reject) => {
26834
+ const cleanup = () => {
26835
+ clearTimeout(timeout);
26836
+ this.engine.off(EngineEvent.Restarted, onRestarted);
26837
+ this.engine.off(EngineEvent.Closing, onClosing);
26838
+ };
26839
+ const onRestarted = () => {
26840
+ cleanup();
26841
+ resolve();
26842
+ };
26843
+ const onClosing = () => {
26844
+ cleanup();
26845
+ reject(new Error('engine closed before restart completed'));
26846
+ };
26847
+ const timeout = setTimeout(() => {
26848
+ cleanup();
26849
+ reject(new Error('timed out waiting for engine restart'));
26850
+ }, timeoutMs);
26851
+ this.engine.once(EngineEvent.Restarted, onRestarted);
26852
+ this.engine.once(EngineEvent.Closing, onClosing);
26853
+ });
26854
+ }
26589
26855
  publishOrRepublishTrack(track_1, options_1) {
26590
26856
  return __awaiter(this, arguments, void 0, function (track, options) {
26591
26857
  var _this2 = this;
26592
26858
  let isRepublish = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
26859
+ let hasRetriedAfterNegotiationError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
26593
26860
  return function* () {
26594
26861
  var _a, _b, _c, _d;
26595
26862
  if (isLocalAudioTrack(track)) {
@@ -26720,6 +26987,14 @@ class Participant extends eventsExports.EventEmitter {
26720
26987
  const publication = yield publishPromise;
26721
26988
  return publication;
26722
26989
  } catch (e) {
26990
+ if (!hasRetriedAfterNegotiationError && e instanceof NegotiationError) {
26991
+ _this2.log.warn('negotiation due to track publish failed, retrying after reconnect', Object.assign(Object.assign({}, _this2.logContext), {
26992
+ error: e
26993
+ }));
26994
+ _this2.pendingPublishPromises.delete(track);
26995
+ yield _this2.waitForNextEngineRestart();
26996
+ return yield _this2.publishOrRepublishTrack(track, options, isRepublish, true);
26997
+ }
26723
26998
  throw e;
26724
26999
  } finally {
26725
27000
  _this2.pendingPublishPromises.delete(track);
@@ -26959,7 +27234,11 @@ class Participant extends eventsExports.EventEmitter {
26959
27234
  resolve(ti);
26960
27235
  } catch (err) {
26961
27236
  if (track.sender && ((_a = this.engine.pcManager) === null || _a === void 0 ? void 0 : _a.publisher)) {
26962
- this.engine.pcManager.publisher.removeTrack(track.sender);
27237
+ try {
27238
+ this.engine.pcManager.publisher.removeTrack(track.sender);
27239
+ } catch (e) {
27240
+ this.log.error(e, this.logContext);
27241
+ }
26963
27242
  yield this.engine.negotiate().catch(negotiateErr => {
26964
27243
  this.log.error('failed to negotiate after removing track due to failed add track request', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
26965
27244
  error: negotiateErr
@@ -27003,6 +27282,19 @@ class Participant extends eventsExports.EventEmitter {
27003
27282
  // save options for when it needs to be republished again
27004
27283
  publication.options = opts;
27005
27284
  track.sid = ti.sid;
27285
+ // keep publish options on the video track so that it can recompute encoding
27286
+ // parameters when the MediaStreamTrack is restarted (e.g. after switching cameras).
27287
+ // Seed the dimensions we encoded at publish time so the first no-op restart
27288
+ // (e.g. unmute with unchanged constraints) can skip the recompute.
27289
+ if (isLocalVideoTrack(track)) {
27290
+ track.publishOptions = opts;
27291
+ if (req.width && req.height) {
27292
+ track.lastEncodedDimensions = {
27293
+ width: req.width,
27294
+ height: req.height
27295
+ };
27296
+ }
27297
+ }
27006
27298
  this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
27007
27299
  encodings,
27008
27300
  trackInfo: ti
@@ -27202,13 +27494,19 @@ class Participant extends eventsExports.EventEmitter {
27202
27494
  negotiationNeeded = true;
27203
27495
  }
27204
27496
  }
27205
- if (this.engine.removeTrack(trackSender)) {
27497
+ try {
27498
+ negotiationNeeded = this.engine.removeTrack(trackSender);
27499
+ } catch (e) {
27500
+ this.log.warn(e, this.logContext);
27206
27501
  negotiationNeeded = true;
27207
27502
  }
27208
27503
  if (isLocalVideoTrack(track)) {
27209
27504
  for (const [, trackInfo] of track.simulcastCodecs) {
27210
27505
  if (trackInfo.sender) {
27211
- if (this.engine.removeTrack(trackInfo.sender)) {
27506
+ try {
27507
+ negotiationNeeded = this.engine.removeTrack(trackInfo.sender);
27508
+ } catch (e) {
27509
+ this.log.warn(e, this.logContext);
27212
27510
  negotiationNeeded = true;
27213
27511
  }
27214
27512
  trackInfo.sender = undefined;
@@ -27718,14 +28016,14 @@ class DeferrableMap extends Map {
27718
28016
  this.pending = new Map();
27719
28017
  }
27720
28018
  set(key, value) {
27721
- var _a;
28019
+ var _a, _b;
27722
28020
  super.set(key, value);
27723
28021
  // Resolve any futures waiting on this key.
27724
- const futures = this.pending.get(key);
28022
+ const futures = (_a = this.pending) === null || _a === void 0 ? void 0 : _a.get(key);
27725
28023
  if (futures) {
27726
28024
  for (const future of futures) {
27727
28025
  if (!future.isResolved) {
27728
- (_a = future.resolve) === null || _a === void 0 ? void 0 : _a.call(future, value);
28026
+ (_b = future.resolve) === null || _b === void 0 ? void 0 : _b.call(future, value);
27729
28027
  }
27730
28028
  }
27731
28029
  this.pending.delete(key);
@@ -27733,7 +28031,7 @@ class DeferrableMap extends Map {
27733
28031
  return this;
27734
28032
  }
27735
28033
  get [Symbol.toStringTag]() {
27736
- return 'WaitableMap';
28034
+ return 'DeferrableMap';
27737
28035
  }
27738
28036
  getDeferred(key, signal) {
27739
28037
  return __awaiter(this, void 0, void 0, function* () {
@@ -28056,8 +28354,13 @@ class DeferrableMap extends Map {
28056
28354
  }
28057
28355
  }class RemoteParticipant extends Participant {
28058
28356
  /** @internal */
28059
- static fromParticipantInfo(signalClient, pi, loggerOptions) {
28060
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata, pi.attributes, loggerOptions, pi.kind);
28357
+ static fromParticipantInfo(signalClient, pi, loggerOptions, manager) {
28358
+ return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata, pi.attributes, loggerOptions, pi.kind, pi.dataTracks.map(dti => {
28359
+ const info = DataTrackInfo.from(dti);
28360
+ return new RemoteDataTrack(info, manager, {
28361
+ publisherIdentity: pi.identity
28362
+ });
28363
+ }));
28061
28364
  }
28062
28365
  get logContext() {
28063
28366
  return Object.assign(Object.assign({}, super.logContext), {
@@ -28068,12 +28371,15 @@ class DeferrableMap extends Map {
28068
28371
  /** @internal */
28069
28372
  constructor(signalClient, sid, identity, name, metadata, attributes, loggerOptions) {
28070
28373
  let kind = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : ParticipantInfo_Kind.STANDARD;
28374
+ let remoteDataTracks = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : [];
28071
28375
  super(sid, identity || '', name, metadata, attributes, loggerOptions, kind);
28072
28376
  this.signalClient = signalClient;
28073
28377
  this.trackPublications = new Map();
28074
28378
  this.audioTrackPublications = new Map();
28075
28379
  this.videoTrackPublications = new Map();
28076
- this.dataTracks = new DeferrableMap();
28380
+ this.dataTracks = new DeferrableMap(remoteDataTracks.map(remoteDataTrack => {
28381
+ return [remoteDataTrack.info.name, remoteDataTrack];
28382
+ }));
28077
28383
  this.volumeMap = new Map();
28078
28384
  }
28079
28385
  addTrackPublication(publication) {
@@ -28538,7 +28844,7 @@ class Room extends eventsExports.EventEmitter {
28538
28844
  this.maybeCreateEngine();
28539
28845
  }
28540
28846
  if ((_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.isCloud()) {
28541
- this.engine.setRegionUrlProvider(this.regionUrlProvider);
28847
+ this.engine.setRegionStrategy(this.createRegionStrategy());
28542
28848
  }
28543
28849
  this.acquireAudioContext();
28544
28850
  this.connOptions = Object.assign(Object.assign({}, roomConnectOptionDefaults), opts);
@@ -29367,18 +29673,7 @@ class Room extends eventsExports.EventEmitter {
29367
29673
  }).on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
29368
29674
  this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
29369
29675
  }).on(EngineEvent.LocalTrackSubscribed, subscribedSid => {
29370
- const trackPublication = this.localParticipant.getTrackPublications().find(_ref2 => {
29371
- let {
29372
- trackSid
29373
- } = _ref2;
29374
- return trackSid === subscribedSid;
29375
- });
29376
- if (!trackPublication) {
29377
- this.log.warn('could not find local track subscription for subscribed event', this.logContext);
29378
- return;
29379
- }
29380
- this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication);
29381
- this.emitWhenConnected(RoomEvent.LocalTrackSubscribed, trackPublication, this.localParticipant);
29676
+ this.handleLocalTrackSubscribed(subscribedSid);
29382
29677
  }).on(EngineEvent.RoomMoved, roomMoved => {
29383
29678
  this.log.debug('room moved', roomMoved);
29384
29679
  if (roomMoved.room) {
@@ -29414,8 +29709,8 @@ class Room extends eventsExports.EventEmitter {
29414
29709
  }
29415
29710
  this.outgoingDataTrackManager.receivedSfuUnpublishResponse(event.info.pubHandle);
29416
29711
  }).on(EngineEvent.DataTrackSubscriberHandles, event => {
29417
- const handleToSidMapping = new Map(Object.entries(event.subHandles).map(_ref3 => {
29418
- let [key, value] = _ref3;
29712
+ const handleToSidMapping = new Map(Object.entries(event.subHandles).map(_ref2 => {
29713
+ let [key, value] = _ref2;
29419
29714
  return [parseInt(key, 10), value.trackSid];
29420
29715
  }));
29421
29716
  this.incomingDataTrackManager.receivedSfuSubscriberHandles(handleToSidMapping);
@@ -29433,6 +29728,16 @@ class Room extends eventsExports.EventEmitter {
29433
29728
  return [participant.identity, participant.dataTracks.map(info => DataTrackInfo.from(info))];
29434
29729
  }));
29435
29730
  this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
29731
+ }).on(EngineEvent.TokenRefreshed, token => {
29732
+ var _a;
29733
+ (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.updateToken(token);
29734
+ }).on(EngineEvent.ServerRegionsReported, regions => {
29735
+ var _a;
29736
+ (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.setServerReportedRegions({
29737
+ regionSettings: regions,
29738
+ updatedAtInMs: Date.now(),
29739
+ maxAgeInMs: DEFAULT_MAX_AGE_MS
29740
+ });
29436
29741
  });
29437
29742
  if (this.localParticipant) {
29438
29743
  this.localParticipant.setupEngine(this.engine);
@@ -29444,6 +29749,17 @@ class Room extends eventsExports.EventEmitter {
29444
29749
  this.outgoingDataStreamManager.setupEngine(this.engine);
29445
29750
  }
29446
29751
  }
29752
+ createRegionStrategy() {
29753
+ return {
29754
+ getNextUrl: signal => __awaiter(this, void 0, void 0, function* () {
29755
+ return this.regionUrlProvider ? this.regionUrlProvider.getNextBestRegionUrl(signal) : null;
29756
+ }),
29757
+ resetAttempts: () => {
29758
+ var _a;
29759
+ return (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
29760
+ }
29761
+ };
29762
+ }
29447
29763
  /**
29448
29764
  * getLocalDevices abstracts navigator.mediaDevices.enumerateDevices.
29449
29765
  * In particular, it requests device permissions by default if needed
@@ -29521,6 +29837,9 @@ class Room extends eventsExports.EventEmitter {
29521
29837
  // @ts-expect-error function is private
29522
29838
  yield this.engine.client.handleOnClose('simulate disconnect');
29523
29839
  break;
29840
+ case 'fail-on-v1-path':
29841
+ this.engine.failNextV1Path();
29842
+ break;
29524
29843
  case 'speaker':
29525
29844
  req = new SimulateScenario({
29526
29845
  scenario: {
@@ -29844,6 +30163,56 @@ class Room extends eventsExports.EventEmitter {
29844
30163
  this.emit(RoomEvent.EncryptionError, new Error("Encrypted ".concat(publication.source, " track received from participant ").concat(participant.sid, ", but room does not have encryption enabled!")));
29845
30164
  }
29846
30165
  }
30166
+ handleLocalTrackSubscribed(subscribedSid) {
30167
+ const findPublication = () => this.localParticipant.getTrackPublications().find(_ref3 => {
30168
+ let {
30169
+ trackSid
30170
+ } = _ref3;
30171
+ return trackSid === subscribedSid;
30172
+ });
30173
+ const trackPublication = findPublication();
30174
+ if (trackPublication) {
30175
+ this.emitLocalTrackSubscribed(trackPublication);
30176
+ return;
30177
+ }
30178
+ // the track publication may not be registered yet if the server signals
30179
+ // the subscription before publishTrack has finished adding the publication.
30180
+ // defer with a timeout until LocalTrackPublished fires for the matching trackSid
30181
+ this.log.debug('deferring LocalTrackSubscribed, publication not yet available', Object.assign(Object.assign({}, this.logContext), {
30182
+ subscribedSid
30183
+ }));
30184
+ const TIMEOUT_MS = 10000;
30185
+ let timer;
30186
+ const onPublished = pub => {
30187
+ if (pub.trackSid === subscribedSid) {
30188
+ cleanup();
30189
+ this.emitLocalTrackSubscribed(pub);
30190
+ }
30191
+ };
30192
+ const cleanup = () => {
30193
+ clearTimeout(timer);
30194
+ this.localParticipant.off(ParticipantEvent.LocalTrackPublished, onPublished);
30195
+ this.off(RoomEvent.Disconnected, cleanup);
30196
+ };
30197
+ this.localParticipant.on(ParticipantEvent.LocalTrackPublished, onPublished);
30198
+ this.once(RoomEvent.Disconnected, cleanup);
30199
+ timer = setTimeout(() => {
30200
+ cleanup();
30201
+ // final attempt in case the publication was added without emitting the event
30202
+ const pub = findPublication();
30203
+ if (pub) {
30204
+ this.emitLocalTrackSubscribed(pub);
30205
+ } else {
30206
+ this.log.warn('could not find local track publication for LocalTrackSubscribed event after timeout', Object.assign(Object.assign({}, this.logContext), {
30207
+ subscribedSid
30208
+ }));
30209
+ }
30210
+ }, TIMEOUT_MS);
30211
+ }
30212
+ emitLocalTrackSubscribed(trackPublication) {
30213
+ this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication);
30214
+ this.emitWhenConnected(RoomEvent.LocalTrackSubscribed, trackPublication, this.localParticipant);
30215
+ }
29847
30216
  handleDisconnect() {
29848
30217
  let shouldStopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
29849
30218
  let reason = arguments.length > 1 ? arguments[1] : undefined;
@@ -30048,7 +30417,7 @@ class Room extends eventsExports.EventEmitter {
30048
30417
  participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info, {
30049
30418
  loggerContextCb: () => this.logContext,
30050
30419
  loggerName: this.options.loggerName
30051
- });
30420
+ }, this.incomingDataTrackManager);
30052
30421
  } else {
30053
30422
  participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, undefined, {
30054
30423
  loggerContextCb: () => this.logContext,