livekit-client 2.18.4 → 2.18.6
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.
- package/dist/livekit-client.esm.mjs +451 -227
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +10 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +3 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +12 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +10 -4
- package/dist/ts4.2/room/Room.d.ts +1 -0
- package/dist/ts4.2/room/events.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -0
- package/dist/ts4.2/room/track/LocalTrack.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +12 -1
- package/package.json +2 -2
- package/src/api/SignalClient.ts +4 -0
- package/src/room/PCTransport.ts +6 -5
- package/src/room/RTCEngine.ts +41 -29
- package/src/room/RegionUrlProvider.ts +7 -0
- package/src/room/Room.ts +21 -3
- package/src/room/data-track/packet/index.test.ts +16 -21
- package/src/room/data-track/packet/index.ts +3 -3
- package/src/room/events.ts +2 -0
- package/src/room/participant/LocalParticipant.ts +70 -5
- package/src/room/token-source/TokenSource.test.ts +337 -0
- package/src/room/token-source/test-tokens.ts +28 -0
- package/src/room/token-source/utils.test.ts +12 -20
- package/src/room/track/LocalTrack.ts +15 -1
- package/src/room/track/LocalVideoTrack.ts +126 -2
- 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.
|
|
11747
|
+
}var version$1 = "2.18.6";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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
20876
|
-
this.
|
|
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
|
-
|
|
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
|
|
20985
|
+
if (leave.regions) {
|
|
21036
20986
|
this.log.debug('updating regions', this.logContext);
|
|
21037
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
21605
|
-
|
|
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;
|
|
@@ -23048,8 +23180,8 @@ class DataTrackPacketHeader extends Serializable {
|
|
|
23048
23180
|
}
|
|
23049
23181
|
extensionsMetrics() {
|
|
23050
23182
|
const lengthBytes = this.extensions.toBinaryLengthBytes();
|
|
23051
|
-
const lengthWords = Math.ceil(lengthBytes / 4);
|
|
23052
|
-
const paddingLengthBytes = lengthWords * 4 - lengthBytes;
|
|
23183
|
+
const lengthWords = Math.ceil((EXT_WORDS_INDICATOR_SIZE + lengthBytes) / 4);
|
|
23184
|
+
const paddingLengthBytes = lengthWords * 4 - EXT_WORDS_INDICATOR_SIZE - lengthBytes;
|
|
23053
23185
|
return {
|
|
23054
23186
|
lengthBytes,
|
|
23055
23187
|
lengthWords,
|
|
@@ -23190,7 +23322,7 @@ class DataTrackPacketHeader extends Serializable {
|
|
|
23190
23322
|
// field represents the "number of additional bytes" long the extensions section is. This is
|
|
23191
23323
|
// potentially unintuitive so I wanted to call it out.
|
|
23192
23324
|
const extensionWords = rtpOrientedExtensionWords + 1;
|
|
23193
|
-
let extensionLengthBytes = 4 * extensionWords;
|
|
23325
|
+
let extensionLengthBytes = 4 * extensionWords - EXT_WORDS_INDICATOR_SIZE;
|
|
23194
23326
|
if (byteIndex + extensionLengthBytes > dataView.byteLength) {
|
|
23195
23327
|
throw DataTrackDeserializeError.headerOverrun();
|
|
23196
23328
|
}
|
|
@@ -25270,10 +25402,17 @@ class HTMLElementInfo {
|
|
|
25270
25402
|
}
|
|
25271
25403
|
};
|
|
25272
25404
|
this.onEnterPiP = () => {
|
|
25273
|
-
var _a, _b
|
|
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
|
-
|
|
25276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|