livekit-client 2.18.4 → 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 (39) hide show
  1. package/dist/livekit-client.esm.mjs +448 -224
  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 +10 -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 +1 -0
  11. package/dist/src/room/Room.d.ts.map +1 -1
  12. package/dist/src/room/events.d.ts +3 -1
  13. package/dist/src/room/events.d.ts.map +1 -1
  14. package/dist/src/room/participant/LocalParticipant.d.ts +8 -0
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/track/LocalTrack.d.ts +7 -0
  17. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  18. package/dist/src/room/track/LocalVideoTrack.d.ts +12 -1
  19. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  20. package/dist/ts4.2/room/RTCEngine.d.ts +10 -4
  21. package/dist/ts4.2/room/Room.d.ts +1 -0
  22. package/dist/ts4.2/room/events.d.ts +3 -1
  23. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -0
  24. package/dist/ts4.2/room/track/LocalTrack.d.ts +7 -0
  25. package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +12 -1
  26. package/package.json +2 -2
  27. package/src/api/SignalClient.ts +4 -0
  28. package/src/room/PCTransport.ts +6 -5
  29. package/src/room/RTCEngine.ts +41 -29
  30. package/src/room/RegionUrlProvider.ts +7 -0
  31. package/src/room/Room.ts +21 -3
  32. package/src/room/events.ts +2 -0
  33. package/src/room/participant/LocalParticipant.ts +70 -5
  34. package/src/room/token-source/TokenSource.test.ts +337 -0
  35. package/src/room/token-source/test-tokens.ts +28 -0
  36. package/src/room/token-source/utils.test.ts +12 -20
  37. package/src/room/track/LocalTrack.ts +15 -1
  38. package/src/room/track/LocalVideoTrack.ts +126 -2
  39. package/src/room/track/RemoteVideoTrack.ts +8 -2
@@ -11744,7 +11744,7 @@ function getMatch(exp, ua) {
11744
11744
  }
11745
11745
  function getOSVersion(ua) {
11746
11746
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11747
- }var version$1 = "2.18.4";const version = version$1;
11747
+ }var version$1 = "2.18.5";const version = version$1;
11748
11748
  const protocolVersion = 16;/** Base error that all LiveKit specific custom errors inherit from. */
11749
11749
  class LivekitError extends Error {
11750
11750
  constructor(code, message, options) {
@@ -12498,6 +12498,8 @@ var EngineEvent;
12498
12498
  EngineEvent["DataTrackSubscriberHandles"] = "dataTrackSubscriberHandles";
12499
12499
  EngineEvent["DataTrackPacketReceived"] = "dataTrackPacketReceived";
12500
12500
  EngineEvent["Joined"] = "joined";
12501
+ EngineEvent["TokenRefreshed"] = "tokenRefreshed";
12502
+ EngineEvent["ServerRegionsReported"] = "serverRegionsReported";
12501
12503
  })(EngineEvent || (EngineEvent = {}));
12502
12504
  var TrackEvent;
12503
12505
  (function (TrackEvent) {
@@ -16053,6 +16055,10 @@ class SignalClient {
16053
16055
  const resp = yield fetch(validateUrl);
16054
16056
  switch (resp.status) {
16055
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
+ }
16056
16062
  return ConnectionError.serviceNotFound('v1 RTC path not found. Consider upgrading your LiveKit server version', 'v0-rtc');
16057
16063
  case 401:
16058
16064
  case 403:
@@ -17569,8 +17575,8 @@ class PCTransport extends eventsExports.EventEmitter {
17569
17575
  setMungedSDP(sd, munged, remote) {
17570
17576
  return __awaiter(this, void 0, void 0, function* () {
17571
17577
  var _a, _b;
17578
+ const originalSdp = sd.sdp;
17572
17579
  if (munged) {
17573
- const originalSdp = sd.sdp;
17574
17580
  sd.sdp = munged;
17575
17581
  try {
17576
17582
  this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description"), this.logContext);
@@ -17583,7 +17589,8 @@ class PCTransport extends eventsExports.EventEmitter {
17583
17589
  } catch (e) {
17584
17590
  this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"), Object.assign(Object.assign({}, this.logContext), {
17585
17591
  error: e,
17586
- sdp: munged
17592
+ mungedSdp: munged,
17593
+ originalSdp
17587
17594
  }));
17588
17595
  sd.sdp = originalSdp;
17589
17596
  }
@@ -17605,6 +17612,9 @@ class PCTransport extends eventsExports.EventEmitter {
17605
17612
  error: msg,
17606
17613
  sdp: sd.sdp
17607
17614
  };
17615
+ if (munged && munged !== originalSdp) {
17616
+ fields.mungedSdp = munged;
17617
+ }
17608
17618
  if (!remote && this.pc.remoteDescription) {
17609
17619
  fields.remoteSdp = this.pc.remoteDescription;
17610
17620
  }
@@ -17628,9 +17638,6 @@ class PCTransport extends eventsExports.EventEmitter {
17628
17638
  let maxID = 0;
17629
17639
  sdp.media.forEach(m => {
17630
17640
  var _a;
17631
- if (m.type !== 'video') {
17632
- return;
17633
- }
17634
17641
  (_a = m.ext) === null || _a === void 0 ? void 0 : _a.forEach(ext => {
17635
17642
  if (ext.value > maxID) {
17636
17643
  maxID = ext.value;
@@ -18097,192 +18104,6 @@ class PCTransportManager {
18097
18104
  }();
18098
18105
  });
18099
18106
  }
18100
- }const DEFAULT_MAX_AGE_MS = 5000;
18101
- const STOP_REFETCH_DELAY_MS = 30000;
18102
- class RegionUrlProvider {
18103
- static fetchRegionSettings(serverUrl, token, signal) {
18104
- return __awaiter(this, void 0, void 0, function* () {
18105
- const unlock = yield RegionUrlProvider.fetchLock.lock();
18106
- try {
18107
- const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(serverUrl), "/regions"), {
18108
- headers: {
18109
- authorization: "Bearer ".concat(token)
18110
- },
18111
- signal
18112
- });
18113
- if (regionSettingsResponse.ok) {
18114
- const maxAge = extractMaxAgeFromRequestHeaders(regionSettingsResponse.headers);
18115
- const maxAgeInMs = maxAge ? maxAge * 1000 : DEFAULT_MAX_AGE_MS;
18116
- const regionSettings = yield regionSettingsResponse.json();
18117
- return {
18118
- regionSettings,
18119
- updatedAtInMs: Date.now(),
18120
- maxAgeInMs
18121
- };
18122
- } else {
18123
- if (regionSettingsResponse.status === 401) {
18124
- throw ConnectionError.notAllowed("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status);
18125
- } else {
18126
- throw ConnectionError.internal("Could not fetch region settings: ".concat(regionSettingsResponse.statusText));
18127
- }
18128
- }
18129
- } catch (e) {
18130
- if (e instanceof ConnectionError) {
18131
- // rethrow connection errors
18132
- throw e;
18133
- } else if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
18134
- throw ConnectionError.cancelled("Region fetching was aborted");
18135
- } else {
18136
- // wrap other errors as connection errors
18137
- throw ConnectionError.serverUnreachable("Could not fetch region settings, ".concat(e instanceof Error ? "".concat(e.name, ": ").concat(e.message) : e));
18138
- }
18139
- } finally {
18140
- unlock();
18141
- }
18142
- });
18143
- }
18144
- static scheduleRefetch(url, token, maxAgeInMs) {
18145
- return __awaiter(this, void 0, void 0, function* () {
18146
- const timeout = RegionUrlProvider.settingsTimeouts.get(url.hostname);
18147
- clearTimeout(timeout);
18148
- RegionUrlProvider.settingsTimeouts.set(url.hostname, setTimeout(() => __awaiter(this, void 0, void 0, function* () {
18149
- try {
18150
- const newSettings = yield RegionUrlProvider.fetchRegionSettings(url, token);
18151
- RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings);
18152
- } catch (error) {
18153
- if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) {
18154
- livekitLogger.debug('token is not valid, cancelling auto region refresh');
18155
- return;
18156
- }
18157
- livekitLogger.debug('auto refetching of region settings failed', {
18158
- error
18159
- });
18160
- // continue retrying with the same max age
18161
- RegionUrlProvider.scheduleRefetch(url, token, maxAgeInMs);
18162
- }
18163
- }), maxAgeInMs));
18164
- });
18165
- }
18166
- static updateCachedRegionSettings(url, token, settings) {
18167
- RegionUrlProvider.cache.set(url.hostname, settings);
18168
- RegionUrlProvider.scheduleRefetch(url, token, settings.maxAgeInMs);
18169
- }
18170
- static stopRefetch(hostname) {
18171
- const timeout = RegionUrlProvider.settingsTimeouts.get(hostname);
18172
- if (timeout) {
18173
- clearTimeout(timeout);
18174
- RegionUrlProvider.settingsTimeouts.delete(hostname);
18175
- }
18176
- }
18177
- static scheduleCleanup(hostname) {
18178
- let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18179
- if (!tracker) {
18180
- return;
18181
- }
18182
- // Cancel any existing cleanup timeout
18183
- if (tracker.cleanupTimeout) {
18184
- clearTimeout(tracker.cleanupTimeout);
18185
- }
18186
- // Schedule cleanup to stop refetch after delay
18187
- tracker.cleanupTimeout = setTimeout(() => {
18188
- const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname);
18189
- if (currentTracker && currentTracker.connectionCount === 0) {
18190
- livekitLogger.debug('stopping region refetch after disconnect delay', {
18191
- hostname
18192
- });
18193
- RegionUrlProvider.stopRefetch(hostname);
18194
- }
18195
- if (currentTracker) {
18196
- currentTracker.cleanupTimeout = undefined;
18197
- }
18198
- }, STOP_REFETCH_DELAY_MS);
18199
- }
18200
- static cancelCleanup(hostname) {
18201
- const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18202
- if (tracker === null || tracker === void 0 ? void 0 : tracker.cleanupTimeout) {
18203
- clearTimeout(tracker.cleanupTimeout);
18204
- tracker.cleanupTimeout = undefined;
18205
- }
18206
- }
18207
- notifyConnected() {
18208
- const hostname = this.serverUrl.hostname;
18209
- let tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18210
- if (!tracker) {
18211
- tracker = {
18212
- connectionCount: 0
18213
- };
18214
- RegionUrlProvider.connectionTrackers.set(hostname, tracker);
18215
- }
18216
- tracker.connectionCount++;
18217
- // Cancel any scheduled cleanup since we have an active connection
18218
- RegionUrlProvider.cancelCleanup(hostname);
18219
- }
18220
- notifyDisconnected() {
18221
- const hostname = this.serverUrl.hostname;
18222
- const tracker = RegionUrlProvider.connectionTrackers.get(hostname);
18223
- if (!tracker) {
18224
- return;
18225
- }
18226
- tracker.connectionCount = Math.max(0, tracker.connectionCount - 1);
18227
- // If no more connections, schedule cleanup
18228
- if (tracker.connectionCount === 0) {
18229
- RegionUrlProvider.scheduleCleanup(hostname);
18230
- }
18231
- }
18232
- constructor(url, token) {
18233
- this.attemptedRegions = [];
18234
- this.serverUrl = new URL(url);
18235
- this.token = token;
18236
- }
18237
- updateToken(token) {
18238
- this.token = token;
18239
- }
18240
- isCloud() {
18241
- return isCloud(this.serverUrl);
18242
- }
18243
- getServerUrl() {
18244
- return this.serverUrl;
18245
- }
18246
- /** @internal */
18247
- fetchRegionSettings(abortSignal) {
18248
- return __awaiter(this, void 0, void 0, function* () {
18249
- return RegionUrlProvider.fetchRegionSettings(this.serverUrl, this.token, abortSignal);
18250
- });
18251
- }
18252
- getNextBestRegionUrl(abortSignal) {
18253
- return __awaiter(this, void 0, void 0, function* () {
18254
- if (!this.isCloud()) {
18255
- throw Error('region availability is only supported for LiveKit Cloud domains');
18256
- }
18257
- let cachedSettings = RegionUrlProvider.cache.get(this.serverUrl.hostname);
18258
- if (!cachedSettings || Date.now() - cachedSettings.updatedAtInMs > cachedSettings.maxAgeInMs) {
18259
- cachedSettings = yield this.fetchRegionSettings(abortSignal);
18260
- RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, cachedSettings);
18261
- }
18262
- const regionsLeft = cachedSettings.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
18263
- if (regionsLeft.length > 0) {
18264
- const nextRegion = regionsLeft[0];
18265
- this.attemptedRegions.push(nextRegion);
18266
- livekitLogger.debug("next region: ".concat(nextRegion.region));
18267
- return nextRegion.url;
18268
- } else {
18269
- return null;
18270
- }
18271
- });
18272
- }
18273
- resetAttempts() {
18274
- this.attemptedRegions = [];
18275
- }
18276
- setServerReportedRegions(settings) {
18277
- RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, settings);
18278
- }
18279
- }
18280
- RegionUrlProvider.cache = new Map();
18281
- RegionUrlProvider.settingsTimeouts = new Map();
18282
- RegionUrlProvider.connectionTrackers = new Map();
18283
- RegionUrlProvider.fetchLock = new _();
18284
- function getCloudConfigUrl(serverUrl) {
18285
- return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
18286
18107
  }// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
18287
18108
  //
18288
18109
  // SPDX-License-Identifier: Apache-2.0
@@ -18748,10 +18569,23 @@ class LocalTrack extends Track {
18748
18569
  if (stopProcessor && this.processor) {
18749
18570
  yield this.internalStopProcessor();
18750
18571
  }
18751
- return this;
18752
18572
  } finally {
18753
18573
  unlock();
18754
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.
18755
18589
  });
18756
18590
  }
18757
18591
  restart(constraints, isUnmuting) {
@@ -18996,6 +18830,7 @@ class LocalTrack extends Track {
18996
18830
  } finally {
18997
18831
  unlock();
18998
18832
  }
18833
+ yield _this3.onSenderTrackSwapped();
18999
18834
  }();
19000
18835
  });
19001
18836
  }
@@ -19018,6 +18853,7 @@ class LocalTrack extends Track {
19018
18853
  } finally {
19019
18854
  unlock();
19020
18855
  }
18856
+ yield _this4.onSenderTrackSwapped();
19021
18857
  }();
19022
18858
  });
19023
18859
  }
@@ -19961,6 +19797,106 @@ class LocalVideoTrack extends LocalTrack {
19961
19797
  if (e_3) throw e_3.error;
19962
19798
  }
19963
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();
19806
+ });
19807
+ }
19808
+ onSenderTrackSwapped() {
19809
+ return __awaiter(this, void 0, void 0, function* () {
19810
+ yield this.refreshSenderEncodings();
19811
+ });
19812
+ }
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);
19964
19900
  });
19965
19901
  }
19966
19902
  setProcessor(processor_1) {
@@ -20384,6 +20320,7 @@ class RTCEngine extends eventsExports.EventEmitter {
20384
20320
  this.midToTrackId = {};
20385
20321
  /** used to indicate whether the browser is currently waiting to reconnect */
20386
20322
  this.isWaitingForNetworkReconnect = false;
20323
+ this.bufferStatusLowClosingFuture = new Future();
20387
20324
  this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) {
20388
20325
  var _this = this;
20389
20326
  let {
@@ -20532,10 +20469,10 @@ class RTCEngine extends eventsExports.EventEmitter {
20532
20469
  }
20533
20470
  this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
20534
20471
  this.clearReconnectTimeout();
20535
- if (this.token && this.regionUrlProvider) {
20472
+ if (this.token) {
20536
20473
  // token may have been refreshed, we do not want to recreate the regionUrlProvider
20537
20474
  // since the current engine may have inherited a regional url
20538
- this.regionUrlProvider.updateToken(this.token);
20475
+ this.emit(EngineEvent.TokenRefreshed, this.token);
20539
20476
  }
20540
20477
  this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
20541
20478
  };
@@ -20635,6 +20572,13 @@ class RTCEngine extends eventsExports.EventEmitter {
20635
20572
  this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response);
20636
20573
  this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
20637
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(() => {});
20638
20582
  }
20639
20583
  /** @internal */
20640
20584
  get logContext() {
@@ -20679,7 +20623,6 @@ class RTCEngine extends eventsExports.EventEmitter {
20679
20623
  _this2.shouldFailOnV1Path = false;
20680
20624
  throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc');
20681
20625
  }
20682
- livekitLogger.warn('joining signal with ', url);
20683
20626
  const joinResponse = yield _this2.client.join(url, token, opts, abortSignal, useV0Path, offerProto);
20684
20627
  _this2._isClosed = false;
20685
20628
  _this2.latestJoinResponse = joinResponse;
@@ -20808,6 +20751,14 @@ class RTCEngine extends eventsExports.EventEmitter {
20808
20751
  return __awaiter(this, void 0, void 0, function* () {
20809
20752
  yield this.client.close();
20810
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 = {};
20811
20762
  });
20812
20763
  }
20813
20764
  addTrack(req) {
@@ -20872,8 +20823,8 @@ class RTCEngine extends eventsExports.EventEmitter {
20872
20823
  });
20873
20824
  }
20874
20825
  /* @internal */
20875
- setRegionUrlProvider(provider) {
20876
- this.regionUrlProvider = provider;
20826
+ setRegionStrategy(strategy) {
20827
+ this.regionStrategy = strategy;
20877
20828
  }
20878
20829
  configure(joinResponse, useSinglePeerConnection) {
20879
20830
  return __awaiter(this, void 0, void 0, function* () {
@@ -20994,9 +20945,8 @@ class RTCEngine extends eventsExports.EventEmitter {
20994
20945
  this.emit(EngineEvent.LocalTrackSubscribed, trackSid);
20995
20946
  };
20996
20947
  this.client.onTokenRefresh = token => {
20997
- var _a;
20998
20948
  this.token = token;
20999
- (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.updateToken(token);
20949
+ this.emit(EngineEvent.TokenRefreshed, token);
21000
20950
  };
21001
20951
  this.client.onRemoteMuteChanged = (trackSid, muted) => {
21002
20952
  this.emit(EngineEvent.RemoteMute, trackSid, muted);
@@ -21032,13 +20982,9 @@ class RTCEngine extends eventsExports.EventEmitter {
21032
20982
  this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
21033
20983
  reason: leave === null || leave === void 0 ? void 0 : leave.reason
21034
20984
  }));
21035
- if (leave.regions && this.regionUrlProvider) {
20985
+ if (leave.regions) {
21036
20986
  this.log.debug('updating regions', this.logContext);
21037
- this.regionUrlProvider.setServerReportedRegions({
21038
- updatedAtInMs: Date.now(),
21039
- maxAgeInMs: DEFAULT_MAX_AGE_MS,
21040
- regionSettings: leave.regions
21041
- });
20987
+ this.emit(EngineEvent.ServerRegionsReported, leave.regions);
21042
20988
  }
21043
20989
  switch (leave.action) {
21044
20990
  case LeaveRequest_Action.DISCONNECT:
@@ -21342,17 +21288,17 @@ class RTCEngine extends eventsExports.EventEmitter {
21342
21288
  if (this.client.currentState !== SignalConnectionState.CONNECTED) {
21343
21289
  throw new SignalReconnectError('Signal connection got severed during reconnect');
21344
21290
  }
21345
- (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
21291
+ (_a = this.regionStrategy) === null || _a === void 0 ? void 0 : _a.resetAttempts();
21346
21292
  // reconnect success
21347
21293
  this.emit(EngineEvent.Restarted);
21348
21294
  } catch (error) {
21349
- 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();
21350
21296
  if (nextRegionUrl) {
21351
21297
  yield this.restartConnection(nextRegionUrl);
21352
21298
  return;
21353
21299
  } else {
21354
21300
  // no more regions to try (or we're not on cloud)
21355
- (_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
21301
+ (_c = this.regionStrategy) === null || _c === void 0 ? void 0 : _c.resetAttempts();
21356
21302
  throw error;
21357
21303
  }
21358
21304
  }
@@ -21594,17 +21540,13 @@ class RTCEngine extends eventsExports.EventEmitter {
21594
21540
  if (this.isBufferStatusLow(kind)) {
21595
21541
  resolve();
21596
21542
  } else {
21597
- const onClosing = () => reject(new UnexpectedConnectionState('engine closed'));
21598
- this.once(EngineEvent.Closing, onClosing);
21599
21543
  const dc = this.dataChannelForKind(kind);
21600
21544
  if (!dc) {
21601
21545
  reject(new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind)));
21602
21546
  return;
21603
21547
  }
21604
- dc.addEventListener('bufferedamountlow', () => {
21605
- this.off(EngineEvent.Closing, onClosing);
21606
- resolve();
21607
- }, {
21548
+ this.bufferStatusLowClosingFuture.promise.catch(e => reject(e));
21549
+ dc.addEventListener('bufferedamountlow', () => resolve(), {
21608
21550
  once: true
21609
21551
  });
21610
21552
  }
@@ -21897,6 +21839,196 @@ function applyUserDataCompat(newObj, oldObj) {
21897
21839
  const destinationIdentities = newObj.destinationIdentities.length !== 0 ? newObj.destinationIdentities : oldObj.destinationIdentities;
21898
21840
  newObj.destinationIdentities = destinationIdentities;
21899
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");
21900
22032
  }class BaseStreamReader {
21901
22033
  get info() {
21902
22034
  return this._info;
@@ -25270,10 +25402,17 @@ class HTMLElementInfo {
25270
25402
  }
25271
25403
  };
25272
25404
  this.onEnterPiP = () => {
25273
- var _a, _b, _c;
25405
+ var _a, _b;
25274
25406
  (_b = (_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.window) === null || _b === void 0 ? void 0 : _b.addEventListener('pagehide', this.onLeavePiP);
25275
- this.isPiP = isElementInPiP(this.element);
25276
- (_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
+ });
25277
25416
  };
25278
25417
  this.onLeavePiP = () => {
25279
25418
  var _a;
@@ -26546,11 +26685,12 @@ class Participant extends eventsExports.EventEmitter {
26546
26685
  if (track && track.track) {
26547
26686
  // screenshare cannot be muted, unpublish instead
26548
26687
  if (source === Track.Source.ScreenShare) {
26549
- track = yield this.unpublishTrack(track.track);
26688
+ const unpublishPromises = [this.unpublishTrack(track.track)];
26550
26689
  const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio);
26551
26690
  if (screenAudioTrack && screenAudioTrack.track) {
26552
- this.unpublishTrack(screenAudioTrack.track);
26691
+ unpublishPromises.push(this.unpublishTrack(screenAudioTrack.track));
26553
26692
  }
26693
+ [track] = yield Promise.all(unpublishPromises);
26554
26694
  } else {
26555
26695
  yield track.mute();
26556
26696
  }
@@ -26681,10 +26821,42 @@ class Participant extends eventsExports.EventEmitter {
26681
26821
  return this.publishOrRepublishTrack(track, options);
26682
26822
  });
26683
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
+ }
26684
26855
  publishOrRepublishTrack(track_1, options_1) {
26685
26856
  return __awaiter(this, arguments, void 0, function (track, options) {
26686
26857
  var _this2 = this;
26687
26858
  let isRepublish = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
26859
+ let hasRetriedAfterNegotiationError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
26688
26860
  return function* () {
26689
26861
  var _a, _b, _c, _d;
26690
26862
  if (isLocalAudioTrack(track)) {
@@ -26815,6 +26987,14 @@ class Participant extends eventsExports.EventEmitter {
26815
26987
  const publication = yield publishPromise;
26816
26988
  return publication;
26817
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
+ }
26818
26998
  throw e;
26819
26999
  } finally {
26820
27000
  _this2.pendingPublishPromises.delete(track);
@@ -27054,7 +27234,11 @@ class Participant extends eventsExports.EventEmitter {
27054
27234
  resolve(ti);
27055
27235
  } catch (err) {
27056
27236
  if (track.sender && ((_a = this.engine.pcManager) === null || _a === void 0 ? void 0 : _a.publisher)) {
27057
- 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
+ }
27058
27242
  yield this.engine.negotiate().catch(negotiateErr => {
27059
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)), {
27060
27244
  error: negotiateErr
@@ -27098,6 +27282,19 @@ class Participant extends eventsExports.EventEmitter {
27098
27282
  // save options for when it needs to be republished again
27099
27283
  publication.options = opts;
27100
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
+ }
27101
27298
  this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
27102
27299
  encodings,
27103
27300
  trackInfo: ti
@@ -27297,13 +27494,19 @@ class Participant extends eventsExports.EventEmitter {
27297
27494
  negotiationNeeded = true;
27298
27495
  }
27299
27496
  }
27300
- if (this.engine.removeTrack(trackSender)) {
27497
+ try {
27498
+ negotiationNeeded = this.engine.removeTrack(trackSender);
27499
+ } catch (e) {
27500
+ this.log.warn(e, this.logContext);
27301
27501
  negotiationNeeded = true;
27302
27502
  }
27303
27503
  if (isLocalVideoTrack(track)) {
27304
27504
  for (const [, trackInfo] of track.simulcastCodecs) {
27305
27505
  if (trackInfo.sender) {
27306
- 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);
27307
27510
  negotiationNeeded = true;
27308
27511
  }
27309
27512
  trackInfo.sender = undefined;
@@ -28641,7 +28844,7 @@ class Room extends eventsExports.EventEmitter {
28641
28844
  this.maybeCreateEngine();
28642
28845
  }
28643
28846
  if ((_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.isCloud()) {
28644
- this.engine.setRegionUrlProvider(this.regionUrlProvider);
28847
+ this.engine.setRegionStrategy(this.createRegionStrategy());
28645
28848
  }
28646
28849
  this.acquireAudioContext();
28647
28850
  this.connOptions = Object.assign(Object.assign({}, roomConnectOptionDefaults), opts);
@@ -29525,6 +29728,16 @@ class Room extends eventsExports.EventEmitter {
29525
29728
  return [participant.identity, participant.dataTracks.map(info => DataTrackInfo.from(info))];
29526
29729
  }));
29527
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
+ });
29528
29741
  });
29529
29742
  if (this.localParticipant) {
29530
29743
  this.localParticipant.setupEngine(this.engine);
@@ -29536,6 +29749,17 @@ class Room extends eventsExports.EventEmitter {
29536
29749
  this.outgoingDataStreamManager.setupEngine(this.engine);
29537
29750
  }
29538
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
+ }
29539
29763
  /**
29540
29764
  * getLocalDevices abstracts navigator.mediaDevices.enumerateDevices.
29541
29765
  * In particular, it requests device permissions by default if needed