livekit-client 2.1.0 → 2.1.1
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 +2062 -2102
- 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/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +2 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/browserParser.d.ts +1 -0
- package/dist/src/utils/browserParser.d.ts.map +1 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -3
- package/dist/ts4.2/src/room/utils.d.ts +1 -1
- package/dist/ts4.2/src/utils/browserParser.d.ts +1 -0
- package/package.json +1 -1
- package/src/room/PCTransport.ts +0 -2
- package/src/room/RTCEngine.ts +7 -61
- package/src/room/participant/LocalParticipant.ts +3 -5
- package/src/room/utils.ts +29 -27
- package/src/utils/browserParser.test.ts +4 -0
- package/src/utils/browserParser.ts +11 -1
@@ -175,6 +175,7 @@ function normalizeEnumValue(value) {
|
|
175
175
|
class Message {
|
176
176
|
/**
|
177
177
|
* Compare with a message of the same type.
|
178
|
+
* Note that this function disregards extensions and unknown fields.
|
178
179
|
*/
|
179
180
|
equals(other) {
|
180
181
|
return this.getType().runtime.util.equals(this.getType(), this, other);
|
@@ -2185,7 +2186,7 @@ function readScalar$1(type, json, longType, nullAsZeroValue) {
|
|
2185
2186
|
if (json.trim().length === json.length) int32 = Number(json);
|
2186
2187
|
}
|
2187
2188
|
if (int32 === undefined) break;
|
2188
|
-
if (type == ScalarType.UINT32) assertUInt32(int32);else assertInt32(int32);
|
2189
|
+
if (type == ScalarType.UINT32 || type == ScalarType.FIXED32) assertUInt32(int32);else assertInt32(int32);
|
2189
2190
|
return int32;
|
2190
2191
|
// int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted.
|
2191
2192
|
case ScalarType.INT64:
|
@@ -2997,6 +2998,9 @@ function makeUtilCommon() {
|
|
2997
2998
|
}
|
2998
2999
|
any[member.localName] = copy;
|
2999
3000
|
}
|
3001
|
+
for (const uf of type.runtime.bin.listUnknownFields(message)) {
|
3002
|
+
type.runtime.bin.onUnknownField(any, uf.no, uf.wireType, uf.data);
|
3003
|
+
}
|
3000
3004
|
return target;
|
3001
3005
|
}
|
3002
3006
|
};
|
@@ -10353,7 +10357,8 @@ const browsersList = [{
|
|
10353
10357
|
const browser = {
|
10354
10358
|
name: 'Firefox',
|
10355
10359
|
version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
|
10356
|
-
os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined
|
10360
|
+
os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined,
|
10361
|
+
osVersion: getOSVersion(ua)
|
10357
10362
|
};
|
10358
10363
|
return browser;
|
10359
10364
|
}
|
@@ -10363,7 +10368,8 @@ const browsersList = [{
|
|
10363
10368
|
const browser = {
|
10364
10369
|
name: 'Chrome',
|
10365
10370
|
version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
|
10366
|
-
os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined
|
10371
|
+
os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined,
|
10372
|
+
osVersion: getOSVersion(ua)
|
10367
10373
|
};
|
10368
10374
|
return browser;
|
10369
10375
|
}
|
@@ -10374,7 +10380,8 @@ const browsersList = [{
|
|
10374
10380
|
const browser = {
|
10375
10381
|
name: 'Safari',
|
10376
10382
|
version: getMatch(commonVersionIdentifier, ua),
|
10377
|
-
os: ua.includes('mobile/') ? 'iOS' : 'macOS'
|
10383
|
+
os: ua.includes('mobile/') ? 'iOS' : 'macOS',
|
10384
|
+
osVersion: getOSVersion(ua)
|
10378
10385
|
};
|
10379
10386
|
return browser;
|
10380
10387
|
}
|
@@ -10384,8 +10391,11 @@ function getMatch(exp, ua) {
|
|
10384
10391
|
const match = ua.match(exp);
|
10385
10392
|
return match && match.length >= id && match[id] || '';
|
10386
10393
|
}
|
10394
|
+
function getOSVersion(ua) {
|
10395
|
+
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
10396
|
+
}
|
10387
10397
|
|
10388
|
-
var version$1 = "2.1.
|
10398
|
+
var version$1 = "2.1.1";
|
10389
10399
|
|
10390
10400
|
const version = version$1;
|
10391
10401
|
const protocolVersion = 12;
|
@@ -11175,29 +11185,6 @@ function supportsSetSinkId(elm) {
|
|
11175
11185
|
}
|
11176
11186
|
return 'setSinkId' in elm;
|
11177
11187
|
}
|
11178
|
-
const setCodecPreferencesVersions = {
|
11179
|
-
Chrome: '100',
|
11180
|
-
Safari: '15',
|
11181
|
-
Firefox: '100'
|
11182
|
-
};
|
11183
|
-
function supportsSetCodecPreferences(transceiver) {
|
11184
|
-
if (!isWeb()) {
|
11185
|
-
return false;
|
11186
|
-
}
|
11187
|
-
if (!('setCodecPreferences' in transceiver)) {
|
11188
|
-
return false;
|
11189
|
-
}
|
11190
|
-
const browser = getBrowser();
|
11191
|
-
if (!(browser === null || browser === void 0 ? void 0 : browser.name) || !browser.version) {
|
11192
|
-
// version is required
|
11193
|
-
return false;
|
11194
|
-
}
|
11195
|
-
const v = setCodecPreferencesVersions[browser.name];
|
11196
|
-
if (v) {
|
11197
|
-
return compareVersions(browser.version, v) >= 0;
|
11198
|
-
}
|
11199
|
-
return false;
|
11200
|
-
}
|
11201
11188
|
function isBrowserSupported() {
|
11202
11189
|
if (typeof RTCPeerConnection === 'undefined') {
|
11203
11190
|
return false;
|
@@ -11217,8 +11204,27 @@ function isSafari17() {
|
|
11217
11204
|
return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.');
|
11218
11205
|
}
|
11219
11206
|
function isMobile() {
|
11207
|
+
var _a, _b;
|
11220
11208
|
if (!isWeb()) return false;
|
11221
|
-
return
|
11209
|
+
return (
|
11210
|
+
// @ts-expect-error `userAgentData` is not yet part of typescript
|
11211
|
+
(_b = (_a = navigator.userAgentData) === null || _a === void 0 ? void 0 : _a.mobile) !== null && _b !== void 0 ? _b : /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent)
|
11212
|
+
);
|
11213
|
+
}
|
11214
|
+
function isE2EESimulcastSupported() {
|
11215
|
+
const browser = getBrowser();
|
11216
|
+
const supportedSafariVersion = '17.2'; // see https://bugs.webkit.org/show_bug.cgi?id=257803
|
11217
|
+
if (browser) {
|
11218
|
+
if (browser.name !== 'Safari' && browser.os !== 'iOS') {
|
11219
|
+
return true;
|
11220
|
+
} else if (browser.os === 'iOS' && browser.osVersion && compareVersions(supportedSafariVersion, browser.osVersion) >= 0) {
|
11221
|
+
return true;
|
11222
|
+
} else if (browser.name === 'Safari' && compareVersions(supportedSafariVersion, browser.version) >= 0) {
|
11223
|
+
return true;
|
11224
|
+
} else {
|
11225
|
+
return false;
|
11226
|
+
}
|
11227
|
+
}
|
11222
11228
|
}
|
11223
11229
|
function isWeb() {
|
11224
11230
|
return typeof document !== 'undefined';
|
@@ -14141,8 +14147,6 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
14141
14147
|
yield this.pc.setLocalDescription(sd);
|
14142
14148
|
}
|
14143
14149
|
} catch (e) {
|
14144
|
-
// this error cannot always be caught.
|
14145
|
-
// If the local description has a setCodecPreferences error, this error will be uncaught
|
14146
14150
|
let msg = 'unknown error';
|
14147
14151
|
if (e instanceof Error) {
|
14148
14152
|
msg = e.message;
|
@@ -14568,2330 +14572,2288 @@ class PCTransportManager {
|
|
14568
14572
|
}
|
14569
14573
|
}
|
14570
14574
|
|
14571
|
-
const
|
14572
|
-
|
14573
|
-
|
14574
|
-
|
14575
|
-
var PCState;
|
14576
|
-
(function (PCState) {
|
14577
|
-
PCState[PCState["New"] = 0] = "New";
|
14578
|
-
PCState[PCState["Connected"] = 1] = "Connected";
|
14579
|
-
PCState[PCState["Disconnected"] = 2] = "Disconnected";
|
14580
|
-
PCState[PCState["Reconnecting"] = 3] = "Reconnecting";
|
14581
|
-
PCState[PCState["Closed"] = 4] = "Closed";
|
14582
|
-
})(PCState || (PCState = {}));
|
14583
|
-
/** @internal */
|
14584
|
-
class RTCEngine extends eventsExports.EventEmitter {
|
14585
|
-
get isClosed() {
|
14586
|
-
return this._isClosed;
|
14575
|
+
const monitorFrequency = 2000;
|
14576
|
+
function computeBitrate(currentStats, prevStats) {
|
14577
|
+
if (!prevStats) {
|
14578
|
+
return 0;
|
14587
14579
|
}
|
14588
|
-
|
14589
|
-
|
14580
|
+
let bytesNow;
|
14581
|
+
let bytesPrev;
|
14582
|
+
if ('bytesReceived' in currentStats) {
|
14583
|
+
bytesNow = currentStats.bytesReceived;
|
14584
|
+
bytesPrev = prevStats.bytesReceived;
|
14585
|
+
} else if ('bytesSent' in currentStats) {
|
14586
|
+
bytesNow = currentStats.bytesSent;
|
14587
|
+
bytesPrev = prevStats.bytesSent;
|
14590
14588
|
}
|
14591
|
-
|
14592
|
-
|
14593
|
-
|
14594
|
-
|
14595
|
-
|
14596
|
-
|
14597
|
-
|
14598
|
-
|
14599
|
-
|
14600
|
-
|
14601
|
-
|
14602
|
-
this.
|
14603
|
-
|
14604
|
-
|
14605
|
-
|
14606
|
-
|
14607
|
-
|
14608
|
-
|
14609
|
-
|
14610
|
-
|
14611
|
-
|
14612
|
-
|
14613
|
-
|
14614
|
-
|
14615
|
-
|
14616
|
-
|
14617
|
-
|
14618
|
-
|
14589
|
+
if (bytesNow === undefined || bytesPrev === undefined || currentStats.timestamp === undefined || prevStats.timestamp === undefined) {
|
14590
|
+
return 0;
|
14591
|
+
}
|
14592
|
+
return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
|
14593
|
+
}
|
14594
|
+
|
14595
|
+
class LocalAudioTrack extends LocalTrack {
|
14596
|
+
/**
|
14597
|
+
* boolean indicating whether enhanced noise cancellation is currently being used on this track
|
14598
|
+
*/
|
14599
|
+
get enhancedNoiseCancellation() {
|
14600
|
+
return this.isKrispNoiseFilterEnabled;
|
14601
|
+
}
|
14602
|
+
/**
|
14603
|
+
*
|
14604
|
+
* @param mediaTrack
|
14605
|
+
* @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
|
14606
|
+
* @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
|
14607
|
+
*/
|
14608
|
+
constructor(mediaTrack, constraints) {
|
14609
|
+
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
14610
|
+
let audioContext = arguments.length > 3 ? arguments[3] : undefined;
|
14611
|
+
let loggerOptions = arguments.length > 4 ? arguments[4] : undefined;
|
14612
|
+
super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack, loggerOptions);
|
14613
|
+
/** @internal */
|
14614
|
+
this.stopOnMute = false;
|
14615
|
+
this.isKrispNoiseFilterEnabled = false;
|
14616
|
+
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
14617
|
+
if (!this.sender) {
|
14618
|
+
this._currentBitrate = 0;
|
14619
|
+
return;
|
14620
|
+
}
|
14621
|
+
let stats;
|
14622
|
+
try {
|
14623
|
+
stats = yield this.getSenderStats();
|
14624
|
+
} catch (e) {
|
14625
|
+
this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
|
14626
|
+
error: e
|
14627
|
+
}));
|
14628
|
+
return;
|
14629
|
+
}
|
14630
|
+
if (stats && this.prevStats) {
|
14631
|
+
this._currentBitrate = computeBitrate(stats, this.prevStats);
|
14632
|
+
}
|
14633
|
+
this.prevStats = stats;
|
14634
|
+
});
|
14635
|
+
this.handleKrispNoiseFilterEnable = () => {
|
14636
|
+
this.isKrispNoiseFilterEnabled = true;
|
14637
|
+
this.log.debug("Krisp noise filter enabled", this.logContext);
|
14638
|
+
this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, true);
|
14639
|
+
};
|
14640
|
+
this.handleKrispNoiseFilterDisable = () => {
|
14641
|
+
this.isKrispNoiseFilterEnabled = false;
|
14642
|
+
this.log.debug("Krisp noise filter disabled", this.logContext);
|
14643
|
+
this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, false);
|
14644
|
+
};
|
14645
|
+
this.audioContext = audioContext;
|
14646
|
+
this.checkForSilence();
|
14647
|
+
}
|
14648
|
+
setDeviceId(deviceId) {
|
14649
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14650
|
+
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
|
14651
|
+
return true;
|
14652
|
+
}
|
14653
|
+
this._constraints.deviceId = deviceId;
|
14654
|
+
if (!this.isMuted) {
|
14655
|
+
yield this.restartTrack();
|
14656
|
+
}
|
14657
|
+
return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
14658
|
+
});
|
14659
|
+
}
|
14660
|
+
mute() {
|
14661
|
+
const _super = Object.create(null, {
|
14662
|
+
mute: {
|
14663
|
+
get: () => super.mute
|
14664
|
+
}
|
14665
|
+
});
|
14666
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14667
|
+
const unlock = yield this.muteLock.lock();
|
14668
|
+
try {
|
14669
|
+
if (this.isMuted) {
|
14670
|
+
this.log.debug('Track already muted', this.logContext);
|
14671
|
+
return this;
|
14619
14672
|
}
|
14620
|
-
|
14621
|
-
|
14622
|
-
|
14623
|
-
|
14624
|
-
|
14625
|
-
return;
|
14673
|
+
// disabled special handling as it will cause BT headsets to switch communication modes
|
14674
|
+
if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
|
14675
|
+
this.log.debug('stopping mic track', this.logContext);
|
14676
|
+
// also stop the track, so that microphone indicator is turned off
|
14677
|
+
this._mediaStreamTrack.stop();
|
14626
14678
|
}
|
14627
|
-
|
14628
|
-
|
14629
|
-
}
|
14679
|
+
yield _super.mute.call(this);
|
14680
|
+
return this;
|
14681
|
+
} finally {
|
14682
|
+
unlock();
|
14683
|
+
}
|
14630
14684
|
});
|
14631
|
-
|
14632
|
-
|
14633
|
-
|
14634
|
-
|
14685
|
+
}
|
14686
|
+
unmute() {
|
14687
|
+
const _super = Object.create(null, {
|
14688
|
+
unmute: {
|
14689
|
+
get: () => super.unmute
|
14690
|
+
}
|
14691
|
+
});
|
14692
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14693
|
+
const unlock = yield this.muteLock.lock();
|
14635
14694
|
try {
|
14636
|
-
|
14637
|
-
|
14638
|
-
|
14639
|
-
buffer = message.data;
|
14640
|
-
} else if (message.data instanceof Blob) {
|
14641
|
-
buffer = yield message.data.arrayBuffer();
|
14642
|
-
} else {
|
14643
|
-
this.log.error('unsupported data type', Object.assign(Object.assign({}, this.logContext), {
|
14644
|
-
data: message.data
|
14645
|
-
}));
|
14646
|
-
return;
|
14695
|
+
if (!this.isMuted) {
|
14696
|
+
this.log.debug('Track already unmuted', this.logContext);
|
14697
|
+
return this;
|
14647
14698
|
}
|
14648
|
-
const
|
14649
|
-
if (
|
14650
|
-
|
14651
|
-
this.
|
14652
|
-
} else if (((_d = dp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
|
14653
|
-
this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
|
14699
|
+
const deviceHasChanged = this._constraints.deviceId && this._mediaStreamTrack.getSettings().deviceId !== unwrapConstraint(this._constraints.deviceId);
|
14700
|
+
if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || deviceHasChanged) && !this.isUserProvided) {
|
14701
|
+
this.log.debug('reacquiring mic track', this.logContext);
|
14702
|
+
yield this.restartTrack();
|
14654
14703
|
}
|
14704
|
+
yield _super.unmute.call(this);
|
14705
|
+
return this;
|
14655
14706
|
} finally {
|
14656
14707
|
unlock();
|
14657
14708
|
}
|
14658
14709
|
});
|
14659
|
-
|
14660
|
-
|
14661
|
-
|
14662
|
-
|
14663
|
-
|
14664
|
-
|
14665
|
-
|
14666
|
-
|
14667
|
-
|
14668
|
-
|
14669
|
-
|
14670
|
-
this.log.error("Unknown DataChannel error on ".concat(channelKind), Object.assign(Object.assign({}, this.logContext), {
|
14671
|
-
event
|
14672
|
-
}));
|
14710
|
+
}
|
14711
|
+
restartTrack(options) {
|
14712
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14713
|
+
let constraints;
|
14714
|
+
if (options) {
|
14715
|
+
const streamConstraints = constraintsForOptions({
|
14716
|
+
audio: options
|
14717
|
+
});
|
14718
|
+
if (typeof streamConstraints.audio !== 'boolean') {
|
14719
|
+
constraints = streamConstraints.audio;
|
14720
|
+
}
|
14673
14721
|
}
|
14674
|
-
|
14675
|
-
|
14676
|
-
|
14677
|
-
|
14678
|
-
|
14679
|
-
|
14680
|
-
|
14681
|
-
// continues to work, we can reconnect to websocket to continue the session
|
14682
|
-
// after a number of retries, we'll close and give up permanently
|
14683
|
-
this.handleDisconnect = (connection, disconnectReason) => {
|
14684
|
-
if (this._isClosed) {
|
14685
|
-
return;
|
14686
|
-
}
|
14687
|
-
this.log.warn("".concat(connection, " disconnected"), this.logContext);
|
14688
|
-
if (this.reconnectAttempts === 0) {
|
14689
|
-
// only reset start time on the first try
|
14690
|
-
this.reconnectStart = Date.now();
|
14691
|
-
}
|
14692
|
-
const disconnect = duration => {
|
14693
|
-
this.log.warn("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"), this.logContext);
|
14694
|
-
this.emit(EngineEvent.Disconnected);
|
14695
|
-
this.close();
|
14696
|
-
};
|
14697
|
-
const duration = Date.now() - this.reconnectStart;
|
14698
|
-
let delay = this.getNextRetryDelay({
|
14699
|
-
elapsedMs: duration,
|
14700
|
-
retryCount: this.reconnectAttempts
|
14701
|
-
});
|
14702
|
-
if (delay === null) {
|
14703
|
-
disconnect(duration);
|
14704
|
-
return;
|
14705
|
-
}
|
14706
|
-
if (connection === leaveReconnect) {
|
14707
|
-
delay = 0;
|
14708
|
-
}
|
14709
|
-
this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
|
14710
|
-
this.clearReconnectTimeout();
|
14711
|
-
if (this.token && this.regionUrlProvider) {
|
14712
|
-
// token may have been refreshed, we do not want to recreate the regionUrlProvider
|
14713
|
-
// since the current engine may have inherited a regional url
|
14714
|
-
this.regionUrlProvider.updateToken(this.token);
|
14715
|
-
}
|
14716
|
-
this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
|
14717
|
-
};
|
14718
|
-
this.waitForRestarted = () => {
|
14719
|
-
return new Promise((resolve, reject) => {
|
14720
|
-
if (this.pcState === PCState.Connected) {
|
14721
|
-
resolve();
|
14722
|
-
}
|
14723
|
-
const onRestarted = () => {
|
14724
|
-
this.off(EngineEvent.Disconnected, onDisconnected);
|
14725
|
-
resolve();
|
14726
|
-
};
|
14727
|
-
const onDisconnected = () => {
|
14728
|
-
this.off(EngineEvent.Restarted, onRestarted);
|
14729
|
-
reject();
|
14730
|
-
};
|
14731
|
-
this.once(EngineEvent.Restarted, onRestarted);
|
14732
|
-
this.once(EngineEvent.Disconnected, onDisconnected);
|
14733
|
-
});
|
14734
|
-
};
|
14735
|
-
this.updateAndEmitDCBufferStatus = kind => {
|
14736
|
-
const status = this.isBufferStatusLow(kind);
|
14737
|
-
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
14738
|
-
this.dcBufferStatus.set(kind, status);
|
14739
|
-
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
14740
|
-
}
|
14741
|
-
};
|
14742
|
-
this.isBufferStatusLow = kind => {
|
14743
|
-
const dc = this.dataChannelForKind(kind);
|
14744
|
-
if (dc) {
|
14745
|
-
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
14746
|
-
}
|
14747
|
-
};
|
14748
|
-
this.handleBrowserOnLine = () => {
|
14749
|
-
// in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
|
14750
|
-
if (this.client.currentState === SignalConnectionState.RECONNECTING) {
|
14751
|
-
this.clearReconnectTimeout();
|
14752
|
-
this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
14722
|
+
yield this.restart(constraints);
|
14723
|
+
});
|
14724
|
+
}
|
14725
|
+
restart(constraints) {
|
14726
|
+
const _super = Object.create(null, {
|
14727
|
+
restart: {
|
14728
|
+
get: () => super.restart
|
14753
14729
|
}
|
14754
|
-
};
|
14755
|
-
|
14756
|
-
|
14757
|
-
|
14758
|
-
|
14759
|
-
};
|
14760
|
-
this.client = new SignalClient(undefined, this.loggerOptions);
|
14761
|
-
this.client.signalLatency = this.options.expSignalLatency;
|
14762
|
-
this.reconnectPolicy = this.options.reconnectPolicy;
|
14763
|
-
this.registerOnLineListener();
|
14764
|
-
this.closingLock = new Mutex();
|
14765
|
-
this.dataProcessLock = new Mutex();
|
14766
|
-
this.dcBufferStatus = new Map([[DataPacket_Kind.LOSSY, true], [DataPacket_Kind.RELIABLE, true]]);
|
14767
|
-
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
14768
|
-
this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update);
|
14769
|
-
this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update);
|
14770
|
-
this.client.onSubscriptionError = resp => this.emit(EngineEvent.SubscriptionError, resp);
|
14771
|
-
this.client.onSubscriptionPermissionUpdate = update => this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
|
14772
|
-
this.client.onSpeakersChanged = update => this.emit(EngineEvent.SpeakersChanged, update);
|
14773
|
-
this.client.onStreamStateUpdate = update => this.emit(EngineEvent.StreamStateChanged, update);
|
14730
|
+
});
|
14731
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14732
|
+
const track = yield _super.restart.call(this, constraints);
|
14733
|
+
this.checkForSilence();
|
14734
|
+
return track;
|
14735
|
+
});
|
14774
14736
|
}
|
14775
|
-
|
14776
|
-
|
14777
|
-
|
14778
|
-
|
14779
|
-
|
14780
|
-
|
14781
|
-
|
14782
|
-
|
14783
|
-
|
14737
|
+
/* @internal */
|
14738
|
+
startMonitor() {
|
14739
|
+
if (!isWeb()) {
|
14740
|
+
return;
|
14741
|
+
}
|
14742
|
+
if (this.monitorInterval) {
|
14743
|
+
return;
|
14744
|
+
}
|
14745
|
+
this.monitorInterval = setInterval(() => {
|
14746
|
+
this.monitorSender();
|
14747
|
+
}, monitorFrequency);
|
14784
14748
|
}
|
14785
|
-
|
14749
|
+
setProcessor(processor) {
|
14786
14750
|
return __awaiter(this, void 0, void 0, function* () {
|
14787
|
-
|
14788
|
-
|
14789
|
-
this.signalOpts = opts;
|
14790
|
-
this.maxJoinAttempts = opts.maxRetries;
|
14751
|
+
var _a;
|
14752
|
+
const unlock = yield this.processorLock.lock();
|
14791
14753
|
try {
|
14792
|
-
this.
|
14793
|
-
|
14794
|
-
const joinResponse = yield this.client.join(url, token, opts, abortSignal);
|
14795
|
-
this._isClosed = false;
|
14796
|
-
this.latestJoinResponse = joinResponse;
|
14797
|
-
this.subscriberPrimary = joinResponse.subscriberPrimary;
|
14798
|
-
if (!this.pcManager) {
|
14799
|
-
yield this.configure(joinResponse);
|
14754
|
+
if (!this.audioContext) {
|
14755
|
+
throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors');
|
14800
14756
|
}
|
14801
|
-
|
14802
|
-
|
14803
|
-
this.negotiate();
|
14757
|
+
if (this.processor) {
|
14758
|
+
yield this.stopProcessor();
|
14804
14759
|
}
|
14805
|
-
|
14806
|
-
|
14807
|
-
|
14808
|
-
|
14809
|
-
|
14810
|
-
|
14811
|
-
|
14812
|
-
|
14813
|
-
|
14814
|
-
|
14760
|
+
const processorOptions = {
|
14761
|
+
kind: this.kind,
|
14762
|
+
track: this._mediaStreamTrack,
|
14763
|
+
audioContext: this.audioContext
|
14764
|
+
};
|
14765
|
+
this.log.debug("setting up audio processor ".concat(processor.name), this.logContext);
|
14766
|
+
yield processor.init(processorOptions);
|
14767
|
+
this.processor = processor;
|
14768
|
+
if (this.processor.processedTrack) {
|
14769
|
+
yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
|
14770
|
+
this.processor.processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
|
14771
|
+
this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
|
14815
14772
|
}
|
14816
|
-
|
14817
|
-
}
|
14818
|
-
});
|
14819
|
-
}
|
14820
|
-
close() {
|
14821
|
-
return __awaiter(this, void 0, void 0, function* () {
|
14822
|
-
const unlock = yield this.closingLock.lock();
|
14823
|
-
if (this.isClosed) {
|
14824
|
-
unlock();
|
14825
|
-
return;
|
14826
|
-
}
|
14827
|
-
try {
|
14828
|
-
this._isClosed = true;
|
14829
|
-
this.emit(EngineEvent.Closing);
|
14830
|
-
this.removeAllListeners();
|
14831
|
-
this.deregisterOnLineListener();
|
14832
|
-
this.clearPendingReconnect();
|
14833
|
-
yield this.cleanupPeerConnections();
|
14834
|
-
yield this.cleanupClient();
|
14773
|
+
this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
|
14835
14774
|
} finally {
|
14836
14775
|
unlock();
|
14837
14776
|
}
|
14838
14777
|
});
|
14839
14778
|
}
|
14840
|
-
|
14779
|
+
/**
|
14780
|
+
* @internal
|
14781
|
+
* @experimental
|
14782
|
+
*/
|
14783
|
+
setAudioContext(audioContext) {
|
14784
|
+
this.audioContext = audioContext;
|
14785
|
+
}
|
14786
|
+
getSenderStats() {
|
14841
14787
|
return __awaiter(this, void 0, void 0, function* () {
|
14842
14788
|
var _a;
|
14843
|
-
|
14844
|
-
|
14845
|
-
|
14846
|
-
|
14847
|
-
|
14848
|
-
|
14849
|
-
|
14850
|
-
|
14851
|
-
|
14852
|
-
|
14853
|
-
|
14854
|
-
|
14855
|
-
|
14856
|
-
|
14857
|
-
|
14858
|
-
|
14859
|
-
|
14860
|
-
|
14861
|
-
|
14862
|
-
|
14789
|
+
if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
|
14790
|
+
return undefined;
|
14791
|
+
}
|
14792
|
+
const stats = yield this.sender.getStats();
|
14793
|
+
let audioStats;
|
14794
|
+
stats.forEach(v => {
|
14795
|
+
if (v.type === 'outbound-rtp') {
|
14796
|
+
audioStats = {
|
14797
|
+
type: 'audio',
|
14798
|
+
streamId: v.id,
|
14799
|
+
packetsSent: v.packetsSent,
|
14800
|
+
packetsLost: v.packetsLost,
|
14801
|
+
bytesSent: v.bytesSent,
|
14802
|
+
timestamp: v.timestamp,
|
14803
|
+
roundTripTime: v.roundTripTime,
|
14804
|
+
jitter: v.jitter
|
14805
|
+
};
|
14806
|
+
}
|
14807
|
+
});
|
14808
|
+
return audioStats;
|
14863
14809
|
});
|
14864
14810
|
}
|
14865
|
-
|
14811
|
+
checkForSilence() {
|
14866
14812
|
return __awaiter(this, void 0, void 0, function* () {
|
14867
|
-
yield this
|
14868
|
-
|
14813
|
+
const trackIsSilent = yield detectSilence(this);
|
14814
|
+
if (trackIsSilent) {
|
14815
|
+
if (!this.isMuted) {
|
14816
|
+
this.log.warn('silence detected on local audio track', this.logContext);
|
14817
|
+
}
|
14818
|
+
this.emit(TrackEvent.AudioSilenceDetected);
|
14819
|
+
}
|
14820
|
+
return trackIsSilent;
|
14869
14821
|
});
|
14870
14822
|
}
|
14871
|
-
|
14872
|
-
|
14873
|
-
|
14874
|
-
|
14875
|
-
|
14876
|
-
|
14877
|
-
|
14878
|
-
|
14879
|
-
|
14880
|
-
|
14881
|
-
|
14882
|
-
clearTimeout(publicationTimeout);
|
14883
|
-
resolve(info);
|
14884
|
-
},
|
14885
|
-
reject: () => {
|
14886
|
-
clearTimeout(publicationTimeout);
|
14887
|
-
reject(new Error('Cancelled publication by calling unpublish'));
|
14888
|
-
}
|
14889
|
-
};
|
14890
|
-
this.client.sendAddTrack(req);
|
14891
|
-
});
|
14823
|
+
}
|
14824
|
+
|
14825
|
+
/** @internal */
|
14826
|
+
function mediaTrackToLocalTrack(mediaStreamTrack, constraints, loggerOptions) {
|
14827
|
+
switch (mediaStreamTrack.kind) {
|
14828
|
+
case 'audio':
|
14829
|
+
return new LocalAudioTrack(mediaStreamTrack, constraints, false, undefined, loggerOptions);
|
14830
|
+
case 'video':
|
14831
|
+
return new LocalVideoTrack(mediaStreamTrack, constraints, false, loggerOptions);
|
14832
|
+
default:
|
14833
|
+
throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
|
14892
14834
|
}
|
14893
|
-
|
14894
|
-
|
14895
|
-
|
14896
|
-
|
14897
|
-
|
14898
|
-
|
14899
|
-
|
14900
|
-
|
14901
|
-
|
14902
|
-
|
14903
|
-
|
14904
|
-
|
14905
|
-
|
14906
|
-
|
14907
|
-
|
14835
|
+
}
|
14836
|
+
/* @internal */
|
14837
|
+
const presets169 = Object.values(VideoPresets);
|
14838
|
+
/* @internal */
|
14839
|
+
const presets43 = Object.values(VideoPresets43);
|
14840
|
+
/* @internal */
|
14841
|
+
const presetsScreenShare = Object.values(ScreenSharePresets);
|
14842
|
+
/* @internal */
|
14843
|
+
const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
|
14844
|
+
/* @internal */
|
14845
|
+
const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
|
14846
|
+
/* @internal */
|
14847
|
+
const computeDefaultScreenShareSimulcastPresets = fromPreset => {
|
14848
|
+
const layers = [{
|
14849
|
+
scaleResolutionDownBy: 2,
|
14850
|
+
fps: fromPreset.encoding.maxFramerate
|
14851
|
+
}];
|
14852
|
+
return layers.map(t => {
|
14853
|
+
var _a, _b;
|
14854
|
+
return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / ((_b = t.fps) !== null && _b !== void 0 ? _b : 30))))), t.fps, fromPreset.encoding.priority);
|
14855
|
+
});
|
14856
|
+
};
|
14857
|
+
// /**
|
14858
|
+
// *
|
14859
|
+
// * @internal
|
14860
|
+
// * @experimental
|
14861
|
+
// */
|
14862
|
+
// const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
|
14863
|
+
// // use vp8 as a default
|
14864
|
+
// const vp8 = determineAppropriateEncoding(false, width, height);
|
14865
|
+
// const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
|
14866
|
+
// const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
|
14867
|
+
// const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
|
14868
|
+
// return {
|
14869
|
+
// vp8,
|
14870
|
+
// vp9,
|
14871
|
+
// h264,
|
14872
|
+
// av1,
|
14873
|
+
// };
|
14874
|
+
// };
|
14875
|
+
const videoRids = ['q', 'h', 'f'];
|
14876
|
+
/* @internal */
|
14877
|
+
function computeVideoEncodings(isScreenShare, width, height, options) {
|
14878
|
+
var _a, _b;
|
14879
|
+
let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
|
14880
|
+
if (isScreenShare) {
|
14881
|
+
videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
|
14882
|
+
}
|
14883
|
+
const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
|
14884
|
+
const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
|
14885
|
+
const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
|
14886
|
+
if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
|
14887
|
+
// when we aren't simulcasting or svc, will need to return a single encoding without
|
14888
|
+
// capping bandwidth. we always require a encoding for dynacast
|
14889
|
+
return [{}];
|
14890
|
+
}
|
14891
|
+
if (!videoEncoding) {
|
14892
|
+
// find the right encoding based on width/height
|
14893
|
+
videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
|
14894
|
+
livekitLogger.debug('using video encoding', videoEncoding);
|
14895
|
+
}
|
14896
|
+
const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
|
14897
|
+
if (scalabilityMode && isSVCCodec(videoCodec)) {
|
14898
|
+
const sm = new ScalabilityMode(scalabilityMode);
|
14899
|
+
const encodings = [];
|
14900
|
+
if (sm.spatial > 3) {
|
14901
|
+
throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
|
14908
14902
|
}
|
14909
|
-
|
14910
|
-
|
14911
|
-
|
14912
|
-
|
14913
|
-
|
14914
|
-
|
14915
|
-
|
14903
|
+
// Before M113 in Chrome, defining multiple encodings with an SVC codec indicated
|
14904
|
+
// that SVC mode should be used. Safari still works this way.
|
14905
|
+
// This is a bit confusing but is due to how libwebrtc interpreted the encodings field
|
14906
|
+
// before M113.
|
14907
|
+
// Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
|
14908
|
+
const browser = getBrowser();
|
14909
|
+
if (isSafari() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) {
|
14910
|
+
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
14911
|
+
for (let i = 0; i < sm.spatial; i += 1) {
|
14912
|
+
// in legacy SVC, scaleResolutionDownBy cannot be set
|
14913
|
+
encodings.push({
|
14914
|
+
rid: videoRids[2 - i],
|
14915
|
+
maxBitrate: videoEncoding.maxBitrate / Math.pow(bitratesRatio, i),
|
14916
|
+
maxFramerate: original.encoding.maxFramerate
|
14917
|
+
});
|
14918
|
+
}
|
14919
|
+
// legacy SVC, scalabilityMode is set only on the first encoding
|
14920
|
+
/* @ts-ignore */
|
14921
|
+
encodings[0].scalabilityMode = scalabilityMode;
|
14922
|
+
} else {
|
14923
|
+
encodings.push({
|
14924
|
+
maxBitrate: videoEncoding.maxBitrate,
|
14925
|
+
maxFramerate: original.encoding.maxFramerate,
|
14926
|
+
/* @ts-ignore */
|
14927
|
+
scalabilityMode: scalabilityMode
|
14928
|
+
});
|
14916
14929
|
}
|
14917
|
-
|
14930
|
+
livekitLogger.debug("using svc encoding", {
|
14931
|
+
encodings
|
14932
|
+
});
|
14933
|
+
return encodings;
|
14918
14934
|
}
|
14919
|
-
|
14920
|
-
|
14935
|
+
if (!useSimulcast) {
|
14936
|
+
return [videoEncoding];
|
14921
14937
|
}
|
14922
|
-
|
14923
|
-
|
14924
|
-
|
14938
|
+
let presets = [];
|
14939
|
+
if (isScreenShare) {
|
14940
|
+
presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
|
14941
|
+
} else {
|
14942
|
+
presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
|
14925
14943
|
}
|
14926
|
-
|
14927
|
-
|
14928
|
-
|
14929
|
-
|
14944
|
+
let midPreset;
|
14945
|
+
if (presets.length > 0) {
|
14946
|
+
const lowPreset = presets[0];
|
14947
|
+
if (presets.length > 1) {
|
14948
|
+
[, midPreset] = presets;
|
14949
|
+
}
|
14950
|
+
// NOTE:
|
14951
|
+
// 1. Ordering of these encodings is important. Chrome seems
|
14952
|
+
// to use the index into encodings to decide which layer
|
14953
|
+
// to disable when CPU constrained.
|
14954
|
+
// So encodings should be ordered in increasing spatial
|
14955
|
+
// resolution order.
|
14956
|
+
// 2. livekit-server translates rids into layers. So, all encodings
|
14957
|
+
// should have the base layer `q` and then more added
|
14958
|
+
// based on other conditions.
|
14959
|
+
const size = Math.max(width, height);
|
14960
|
+
if (size >= 960 && midPreset) {
|
14961
|
+
return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
|
14962
|
+
}
|
14963
|
+
if (size >= 480) {
|
14964
|
+
return encodingsFromPresets(width, height, [lowPreset, original]);
|
14965
|
+
}
|
14966
|
+
}
|
14967
|
+
return encodingsFromPresets(width, height, [original]);
|
14968
|
+
}
|
14969
|
+
function computeTrackBackupEncodings(track, videoCodec, opts) {
|
14970
|
+
var _a, _b, _c, _d;
|
14971
|
+
// backupCodec should not be true anymore, default codec is set in LocalParticipant.publish
|
14972
|
+
if (!opts.backupCodec || opts.backupCodec === true || opts.backupCodec.codec === opts.videoCodec) {
|
14973
|
+
// backup codec publishing is disabled
|
14974
|
+
return;
|
14975
|
+
}
|
14976
|
+
if (videoCodec !== opts.backupCodec.codec) {
|
14977
|
+
livekitLogger.warn('requested a different codec than specified as backup', {
|
14978
|
+
serverRequested: videoCodec,
|
14979
|
+
backup: opts.backupCodec.codec
|
14930
14980
|
});
|
14931
14981
|
}
|
14932
|
-
|
14933
|
-
|
14934
|
-
|
14982
|
+
opts.videoCodec = videoCodec;
|
14983
|
+
// use backup encoding setting as videoEncoding for backup codec publishing
|
14984
|
+
opts.videoEncoding = opts.backupCodec.encoding;
|
14985
|
+
const settings = track.mediaStreamTrack.getSettings();
|
14986
|
+
const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
|
14987
|
+
const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
|
14988
|
+
const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
|
14989
|
+
return encodings;
|
14990
|
+
}
|
14991
|
+
/* @internal */
|
14992
|
+
function determineAppropriateEncoding(isScreenShare, width, height, codec) {
|
14993
|
+
const presets = presetsForResolution(isScreenShare, width, height);
|
14994
|
+
let {
|
14995
|
+
encoding
|
14996
|
+
} = presets[0];
|
14997
|
+
// handle portrait by swapping dimensions
|
14998
|
+
const size = Math.max(width, height);
|
14999
|
+
for (let i = 0; i < presets.length; i += 1) {
|
15000
|
+
const preset = presets[i];
|
15001
|
+
encoding = preset.encoding;
|
15002
|
+
if (preset.width >= size) {
|
15003
|
+
break;
|
15004
|
+
}
|
14935
15005
|
}
|
14936
|
-
|
14937
|
-
|
14938
|
-
|
14939
|
-
|
14940
|
-
|
14941
|
-
|
15006
|
+
// presets are based on the assumption of vp8 as a codec
|
15007
|
+
// for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
|
15008
|
+
// users should override these with ones that are optimized for their use case
|
15009
|
+
// NOTE: SVC codec bitrates are inclusive of all scalability layers. while
|
15010
|
+
// bitrate for non-SVC codecs does not include other simulcast layers.
|
15011
|
+
if (codec) {
|
15012
|
+
switch (codec) {
|
15013
|
+
case 'av1':
|
15014
|
+
encoding = Object.assign({}, encoding);
|
15015
|
+
encoding.maxBitrate = encoding.maxBitrate * 0.7;
|
15016
|
+
break;
|
15017
|
+
case 'vp9':
|
15018
|
+
encoding = Object.assign({}, encoding);
|
15019
|
+
encoding.maxBitrate = encoding.maxBitrate * 0.85;
|
15020
|
+
break;
|
15021
|
+
}
|
15022
|
+
}
|
15023
|
+
return encoding;
|
15024
|
+
}
|
15025
|
+
/* @internal */
|
15026
|
+
function presetsForResolution(isScreenShare, width, height) {
|
15027
|
+
if (isScreenShare) {
|
15028
|
+
return presetsScreenShare;
|
15029
|
+
}
|
15030
|
+
const aspect = width > height ? width / height : height / width;
|
15031
|
+
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
15032
|
+
return presets169;
|
15033
|
+
}
|
15034
|
+
return presets43;
|
15035
|
+
}
|
15036
|
+
/* @internal */
|
15037
|
+
function defaultSimulcastLayers(isScreenShare, original) {
|
15038
|
+
if (isScreenShare) {
|
15039
|
+
return computeDefaultScreenShareSimulcastPresets(original);
|
15040
|
+
}
|
15041
|
+
const {
|
15042
|
+
width,
|
15043
|
+
height
|
15044
|
+
} = original;
|
15045
|
+
const aspect = width > height ? width / height : height / width;
|
15046
|
+
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
15047
|
+
return defaultSimulcastPresets169;
|
15048
|
+
}
|
15049
|
+
return defaultSimulcastPresets43;
|
15050
|
+
}
|
15051
|
+
// presets should be ordered by low, medium, high
|
15052
|
+
function encodingsFromPresets(width, height, presets) {
|
15053
|
+
const encodings = [];
|
15054
|
+
presets.forEach((preset, idx) => {
|
15055
|
+
if (idx >= videoRids.length) {
|
15056
|
+
return;
|
15057
|
+
}
|
15058
|
+
const size = Math.min(width, height);
|
15059
|
+
const rid = videoRids[idx];
|
15060
|
+
const encoding = {
|
15061
|
+
rid,
|
15062
|
+
scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
|
15063
|
+
maxBitrate: preset.encoding.maxBitrate
|
15064
|
+
};
|
15065
|
+
if (preset.encoding.maxFramerate) {
|
15066
|
+
encoding.maxFramerate = preset.encoding.maxFramerate;
|
15067
|
+
}
|
15068
|
+
const canSetPriority = isFireFox() || idx === 0;
|
15069
|
+
if (preset.encoding.priority && canSetPriority) {
|
15070
|
+
encoding.priority = preset.encoding.priority;
|
15071
|
+
encoding.networkPriority = preset.encoding.priority;
|
15072
|
+
}
|
15073
|
+
encodings.push(encoding);
|
15074
|
+
});
|
15075
|
+
// RN ios simulcast requires all same framerates.
|
15076
|
+
if (isReactNative() && getReactNativeOs() === 'ios') {
|
15077
|
+
let topFramerate = undefined;
|
15078
|
+
encodings.forEach(encoding => {
|
15079
|
+
if (!topFramerate) {
|
15080
|
+
topFramerate = encoding.maxFramerate;
|
15081
|
+
} else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
|
15082
|
+
topFramerate = encoding.maxFramerate;
|
14942
15083
|
}
|
14943
|
-
this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
|
14944
|
-
const rtcConfig = this.makeRTCConfiguration(joinResponse);
|
14945
|
-
this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
|
14946
|
-
this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
|
14947
|
-
this.pcManager.onIceCandidate = (candidate, target) => {
|
14948
|
-
this.client.sendIceCandidate(candidate, target);
|
14949
|
-
};
|
14950
|
-
this.pcManager.onPublisherOffer = offer => {
|
14951
|
-
this.client.sendOffer(offer);
|
14952
|
-
};
|
14953
|
-
this.pcManager.onDataChannel = this.handleDataChannel;
|
14954
|
-
this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () {
|
14955
|
-
this.log.debug("primary PC state changed ".concat(connectionState), this.logContext);
|
14956
|
-
if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
|
14957
|
-
// reset publisher connection promise
|
14958
|
-
this.publisherConnectionPromise = undefined;
|
14959
|
-
}
|
14960
|
-
if (connectionState === PCTransportState.CONNECTED) {
|
14961
|
-
const shouldEmit = this.pcState === PCState.New;
|
14962
|
-
this.pcState = PCState.Connected;
|
14963
|
-
if (shouldEmit) {
|
14964
|
-
this.emit(EngineEvent.Connected, joinResponse);
|
14965
|
-
}
|
14966
|
-
} else if (connectionState === PCTransportState.FAILED) {
|
14967
|
-
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
14968
|
-
if (this.pcState === PCState.Connected) {
|
14969
|
-
this.pcState = PCState.Disconnected;
|
14970
|
-
this.handleDisconnect('peerconnection failed', subscriberState === 'failed' ? ReconnectReason.RR_SUBSCRIBER_FAILED : ReconnectReason.RR_PUBLISHER_FAILED);
|
14971
|
-
}
|
14972
|
-
}
|
14973
|
-
// detect cases where both signal client and peer connection are severed and assume that user has lost network connection
|
14974
|
-
const isSignalSevered = this.client.isDisconnected || this.client.currentState === SignalConnectionState.RECONNECTING;
|
14975
|
-
const isPCSevered = [PCTransportState.FAILED, PCTransportState.CLOSING, PCTransportState.CLOSED].includes(connectionState);
|
14976
|
-
if (isSignalSevered && isPCSevered && !this._isClosed) {
|
14977
|
-
this.emit(EngineEvent.Offline);
|
14978
|
-
}
|
14979
|
-
});
|
14980
|
-
this.pcManager.onTrack = ev => {
|
14981
|
-
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
14982
|
-
};
|
14983
|
-
this.createDataChannels();
|
14984
15084
|
});
|
14985
|
-
|
14986
|
-
|
14987
|
-
|
14988
|
-
|
14989
|
-
|
14990
|
-
|
15085
|
+
let notifyOnce = true;
|
15086
|
+
encodings.forEach(encoding => {
|
15087
|
+
var _a;
|
15088
|
+
if (encoding.maxFramerate != topFramerate) {
|
15089
|
+
if (notifyOnce) {
|
15090
|
+
notifyOnce = false;
|
15091
|
+
livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
|
15092
|
+
}
|
15093
|
+
livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
|
15094
|
+
encoding.maxFramerate = topFramerate;
|
14991
15095
|
}
|
14992
|
-
this.log.debug('received server answer', Object.assign(Object.assign({}, this.logContext), {
|
14993
|
-
RTCSdpType: sd.type
|
14994
|
-
}));
|
14995
|
-
yield this.pcManager.setPublisherAnswer(sd);
|
14996
15096
|
});
|
14997
|
-
|
14998
|
-
|
14999
|
-
|
15000
|
-
|
15097
|
+
}
|
15098
|
+
return encodings;
|
15099
|
+
}
|
15100
|
+
/** @internal */
|
15101
|
+
function sortPresets(presets) {
|
15102
|
+
if (!presets) return;
|
15103
|
+
return presets.sort((a, b) => {
|
15104
|
+
const {
|
15105
|
+
encoding: aEnc
|
15106
|
+
} = a;
|
15107
|
+
const {
|
15108
|
+
encoding: bEnc
|
15109
|
+
} = b;
|
15110
|
+
if (aEnc.maxBitrate > bEnc.maxBitrate) {
|
15111
|
+
return 1;
|
15112
|
+
}
|
15113
|
+
if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
|
15114
|
+
if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
|
15115
|
+
return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
|
15116
|
+
}
|
15117
|
+
return 0;
|
15118
|
+
});
|
15119
|
+
}
|
15120
|
+
/** @internal */
|
15121
|
+
class ScalabilityMode {
|
15122
|
+
constructor(scalabilityMode) {
|
15123
|
+
const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
|
15124
|
+
if (!results) {
|
15125
|
+
throw new Error('invalid scalability mode');
|
15126
|
+
}
|
15127
|
+
this.spatial = parseInt(results[1]);
|
15128
|
+
this.temporal = parseInt(results[2]);
|
15129
|
+
if (results.length > 3) {
|
15130
|
+
switch (results[3]) {
|
15131
|
+
case 'h':
|
15132
|
+
case '_KEY':
|
15133
|
+
case '_KEY_SHIFT':
|
15134
|
+
this.suffix = results[3];
|
15001
15135
|
}
|
15002
|
-
|
15003
|
-
|
15004
|
-
|
15005
|
-
|
15006
|
-
|
15007
|
-
|
15008
|
-
|
15009
|
-
|
15010
|
-
|
15136
|
+
}
|
15137
|
+
}
|
15138
|
+
toString() {
|
15139
|
+
var _a;
|
15140
|
+
return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
|
15141
|
+
}
|
15142
|
+
}
|
15143
|
+
|
15144
|
+
const refreshSubscribedCodecAfterNewCodec = 5000;
|
15145
|
+
class LocalVideoTrack extends LocalTrack {
|
15146
|
+
/**
|
15147
|
+
*
|
15148
|
+
* @param mediaTrack
|
15149
|
+
* @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
|
15150
|
+
* @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
|
15151
|
+
*/
|
15152
|
+
constructor(mediaTrack, constraints) {
|
15153
|
+
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
15154
|
+
let loggerOptions = arguments.length > 3 ? arguments[3] : undefined;
|
15155
|
+
super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack, loggerOptions);
|
15156
|
+
/* @internal */
|
15157
|
+
this.simulcastCodecs = new Map();
|
15158
|
+
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
15159
|
+
if (!this.sender) {
|
15160
|
+
this._currentBitrate = 0;
|
15011
15161
|
return;
|
15012
15162
|
}
|
15013
|
-
|
15014
|
-
|
15015
|
-
|
15016
|
-
|
15017
|
-
|
15018
|
-
|
15019
|
-
cid: res.cid,
|
15020
|
-
track: (_a = res.track) === null || _a === void 0 ? void 0 : _a.sid
|
15021
|
-
}));
|
15022
|
-
if (!this.pendingTrackResolvers[res.cid]) {
|
15023
|
-
this.log.error("missing track resolver for ".concat(res.cid), Object.assign(Object.assign({}, this.logContext), {
|
15024
|
-
cid: res.cid
|
15163
|
+
let stats;
|
15164
|
+
try {
|
15165
|
+
stats = yield this.getSenderStats();
|
15166
|
+
} catch (e) {
|
15167
|
+
this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
|
15168
|
+
error: e
|
15025
15169
|
}));
|
15026
15170
|
return;
|
15027
15171
|
}
|
15028
|
-
const
|
15029
|
-
|
15030
|
-
|
15031
|
-
|
15032
|
-
|
15033
|
-
|
15034
|
-
|
15035
|
-
|
15036
|
-
|
15037
|
-
|
15038
|
-
this.
|
15039
|
-
};
|
15040
|
-
this.
|
15041
|
-
this.emit(EngineEvent.RemoteMute, trackSid, muted);
|
15042
|
-
};
|
15043
|
-
this.client.onSubscribedQualityUpdate = update => {
|
15044
|
-
this.emit(EngineEvent.SubscribedQualityUpdate, update);
|
15045
|
-
};
|
15046
|
-
this.client.onClose = () => {
|
15047
|
-
this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
15048
|
-
};
|
15049
|
-
this.client.onLeave = leave => {
|
15050
|
-
if (leave === null || leave === void 0 ? void 0 : leave.canReconnect) {
|
15051
|
-
this.fullReconnectOnNext = true;
|
15052
|
-
// reconnect immediately instead of waiting for next attempt
|
15053
|
-
this.handleDisconnect(leaveReconnect);
|
15054
|
-
} else {
|
15055
|
-
this.emit(EngineEvent.Disconnected, leave === null || leave === void 0 ? void 0 : leave.reason);
|
15056
|
-
this.close();
|
15057
|
-
}
|
15058
|
-
this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
|
15059
|
-
reason: leave === null || leave === void 0 ? void 0 : leave.reason
|
15060
|
-
}));
|
15061
|
-
};
|
15172
|
+
const statsMap = new Map(stats.map(s => [s.rid, s]));
|
15173
|
+
if (this.prevStats) {
|
15174
|
+
let totalBitrate = 0;
|
15175
|
+
statsMap.forEach((s, key) => {
|
15176
|
+
var _a;
|
15177
|
+
const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
|
15178
|
+
totalBitrate += computeBitrate(s, prev);
|
15179
|
+
});
|
15180
|
+
this._currentBitrate = totalBitrate;
|
15181
|
+
}
|
15182
|
+
this.prevStats = statsMap;
|
15183
|
+
});
|
15184
|
+
this.senderLock = new Mutex();
|
15062
15185
|
}
|
15063
|
-
|
15064
|
-
|
15065
|
-
|
15066
|
-
if ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled) {
|
15067
|
-
this.log.debug('E2EE - setting up transports with insertable streams', this.logContext);
|
15068
|
-
// this makes sure that no data is sent before the transforms are ready
|
15069
|
-
// @ts-ignore
|
15070
|
-
rtcConfig.encodedInsertableStreams = true;
|
15071
|
-
}
|
15072
|
-
// update ICE servers before creating PeerConnection
|
15073
|
-
if (serverResponse.iceServers && !rtcConfig.iceServers) {
|
15074
|
-
const rtcIceServers = [];
|
15075
|
-
serverResponse.iceServers.forEach(iceServer => {
|
15076
|
-
const rtcIceServer = {
|
15077
|
-
urls: iceServer.urls
|
15078
|
-
};
|
15079
|
-
if (iceServer.username) rtcIceServer.username = iceServer.username;
|
15080
|
-
if (iceServer.credential) {
|
15081
|
-
rtcIceServer.credential = iceServer.credential;
|
15082
|
-
}
|
15083
|
-
rtcIceServers.push(rtcIceServer);
|
15084
|
-
});
|
15085
|
-
rtcConfig.iceServers = rtcIceServers;
|
15086
|
-
}
|
15087
|
-
if (serverResponse.clientConfiguration && serverResponse.clientConfiguration.forceRelay === ClientConfigSetting.ENABLED) {
|
15088
|
-
rtcConfig.iceTransportPolicy = 'relay';
|
15186
|
+
get isSimulcast() {
|
15187
|
+
if (this.sender && this.sender.getParameters().encodings.length > 1) {
|
15188
|
+
return true;
|
15089
15189
|
}
|
15090
|
-
|
15091
|
-
rtcConfig.sdpSemantics = 'unified-plan';
|
15092
|
-
// @ts-ignore
|
15093
|
-
rtcConfig.continualGatheringPolicy = 'gather_continually';
|
15094
|
-
return rtcConfig;
|
15190
|
+
return false;
|
15095
15191
|
}
|
15096
|
-
|
15097
|
-
|
15192
|
+
/* @internal */
|
15193
|
+
startMonitor(signalClient) {
|
15194
|
+
var _a;
|
15195
|
+
this.signalClient = signalClient;
|
15196
|
+
if (!isWeb()) {
|
15098
15197
|
return;
|
15099
15198
|
}
|
15100
|
-
//
|
15101
|
-
|
15102
|
-
|
15103
|
-
|
15199
|
+
// save original encodings
|
15200
|
+
// TODO : merge simulcast tracks stats
|
15201
|
+
const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
|
15202
|
+
if (params) {
|
15203
|
+
this.encodings = params.encodings;
|
15104
15204
|
}
|
15105
|
-
if (this.
|
15106
|
-
|
15107
|
-
this.reliableDC.onerror = null;
|
15205
|
+
if (this.monitorInterval) {
|
15206
|
+
return;
|
15108
15207
|
}
|
15109
|
-
|
15110
|
-
|
15111
|
-
|
15112
|
-
|
15113
|
-
|
15208
|
+
this.monitorInterval = setInterval(() => {
|
15209
|
+
this.monitorSender();
|
15210
|
+
}, monitorFrequency);
|
15211
|
+
}
|
15212
|
+
stop() {
|
15213
|
+
this._mediaStreamTrack.getConstraints();
|
15214
|
+
this.simulcastCodecs.forEach(trackInfo => {
|
15215
|
+
trackInfo.mediaStreamTrack.stop();
|
15114
15216
|
});
|
15115
|
-
|
15116
|
-
|
15217
|
+
super.stop();
|
15218
|
+
}
|
15219
|
+
pauseUpstream() {
|
15220
|
+
const _super = Object.create(null, {
|
15221
|
+
pauseUpstream: {
|
15222
|
+
get: () => super.pauseUpstream
|
15223
|
+
}
|
15224
|
+
});
|
15225
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15226
|
+
var _a, e_1, _b, _c;
|
15227
|
+
var _d;
|
15228
|
+
yield _super.pauseUpstream.call(this);
|
15229
|
+
try {
|
15230
|
+
for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
|
15231
|
+
_c = _g.value;
|
15232
|
+
_e = false;
|
15233
|
+
const sc = _c;
|
15234
|
+
yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null);
|
15235
|
+
}
|
15236
|
+
} catch (e_1_1) {
|
15237
|
+
e_1 = {
|
15238
|
+
error: e_1_1
|
15239
|
+
};
|
15240
|
+
} finally {
|
15241
|
+
try {
|
15242
|
+
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
|
15243
|
+
} finally {
|
15244
|
+
if (e_1) throw e_1.error;
|
15245
|
+
}
|
15246
|
+
}
|
15117
15247
|
});
|
15118
|
-
// also handle messages over the pub channel, for backwards compatibility
|
15119
|
-
this.lossyDC.onmessage = this.handleDataMessage;
|
15120
|
-
this.reliableDC.onmessage = this.handleDataMessage;
|
15121
|
-
// handle datachannel errors
|
15122
|
-
this.lossyDC.onerror = this.handleDataError;
|
15123
|
-
this.reliableDC.onerror = this.handleDataError;
|
15124
|
-
// set up dc buffer threshold, set to 64kB (otherwise 0 by default)
|
15125
|
-
this.lossyDC.bufferedAmountLowThreshold = 65535;
|
15126
|
-
this.reliableDC.bufferedAmountLowThreshold = 65535;
|
15127
|
-
// handle buffer amount low events
|
15128
|
-
this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
15129
|
-
this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
15130
15248
|
}
|
15131
|
-
|
15132
|
-
|
15133
|
-
|
15134
|
-
|
15135
|
-
// when setting codec preferences, the capabilites need to be read from the RTCRtpReceiver
|
15136
|
-
const cap = RTCRtpReceiver.getCapabilities(kind);
|
15137
|
-
if (!cap) return;
|
15138
|
-
this.log.debug('get receiver capabilities', Object.assign(Object.assign({}, this.logContext), {
|
15139
|
-
cap
|
15140
|
-
}));
|
15141
|
-
const matched = [];
|
15142
|
-
const partialMatched = [];
|
15143
|
-
const unmatched = [];
|
15144
|
-
cap.codecs.forEach(c => {
|
15145
|
-
const codec = c.mimeType.toLowerCase();
|
15146
|
-
if (codec === 'audio/opus') {
|
15147
|
-
matched.push(c);
|
15148
|
-
return;
|
15249
|
+
resumeUpstream() {
|
15250
|
+
const _super = Object.create(null, {
|
15251
|
+
resumeUpstream: {
|
15252
|
+
get: () => super.resumeUpstream
|
15149
15253
|
}
|
15150
|
-
|
15151
|
-
|
15152
|
-
|
15153
|
-
|
15254
|
+
});
|
15255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15256
|
+
var _a, e_2, _b, _c;
|
15257
|
+
var _d;
|
15258
|
+
yield _super.resumeUpstream.call(this);
|
15259
|
+
try {
|
15260
|
+
for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
|
15261
|
+
_c = _g.value;
|
15262
|
+
_e = false;
|
15263
|
+
const sc = _c;
|
15264
|
+
yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
|
15265
|
+
}
|
15266
|
+
} catch (e_2_1) {
|
15267
|
+
e_2 = {
|
15268
|
+
error: e_2_1
|
15269
|
+
};
|
15270
|
+
} finally {
|
15271
|
+
try {
|
15272
|
+
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
|
15273
|
+
} finally {
|
15274
|
+
if (e_2) throw e_2.error;
|
15275
|
+
}
|
15154
15276
|
}
|
15155
|
-
|
15156
|
-
|
15157
|
-
|
15158
|
-
|
15159
|
-
|
15160
|
-
|
15161
|
-
|
15277
|
+
});
|
15278
|
+
}
|
15279
|
+
mute() {
|
15280
|
+
const _super = Object.create(null, {
|
15281
|
+
mute: {
|
15282
|
+
get: () => super.mute
|
15283
|
+
}
|
15284
|
+
});
|
15285
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15286
|
+
const unlock = yield this.muteLock.lock();
|
15287
|
+
try {
|
15288
|
+
if (this.isMuted) {
|
15289
|
+
this.log.debug('Track already muted', this.logContext);
|
15290
|
+
return this;
|
15162
15291
|
}
|
15163
|
-
|
15292
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
15293
|
+
this.log.debug('stopping camera track', this.logContext);
|
15294
|
+
// also stop the track, so that camera indicator is turned off
|
15295
|
+
this._mediaStreamTrack.stop();
|
15296
|
+
}
|
15297
|
+
yield _super.mute.call(this);
|
15298
|
+
return this;
|
15299
|
+
} finally {
|
15300
|
+
unlock();
|
15164
15301
|
}
|
15165
|
-
matched.push(c);
|
15166
15302
|
});
|
15167
|
-
if (supportsSetCodecPreferences(transceiver)) {
|
15168
|
-
transceiver.setCodecPreferences(matched.concat(partialMatched, unmatched));
|
15169
|
-
}
|
15170
15303
|
}
|
15171
|
-
|
15304
|
+
unmute() {
|
15305
|
+
const _super = Object.create(null, {
|
15306
|
+
unmute: {
|
15307
|
+
get: () => super.unmute
|
15308
|
+
}
|
15309
|
+
});
|
15172
15310
|
return __awaiter(this, void 0, void 0, function* () {
|
15173
|
-
|
15174
|
-
|
15175
|
-
|
15311
|
+
const unlock = yield this.muteLock.lock();
|
15312
|
+
try {
|
15313
|
+
if (!this.isMuted) {
|
15314
|
+
this.log.debug('Track already unmuted', this.logContext);
|
15315
|
+
return this;
|
15316
|
+
}
|
15317
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
15318
|
+
this.log.debug('reacquiring camera track', this.logContext);
|
15319
|
+
yield this.restartTrack();
|
15320
|
+
}
|
15321
|
+
yield _super.unmute.call(this);
|
15322
|
+
return this;
|
15323
|
+
} finally {
|
15324
|
+
unlock();
|
15176
15325
|
}
|
15177
|
-
|
15178
|
-
|
15179
|
-
|
15180
|
-
|
15326
|
+
});
|
15327
|
+
}
|
15328
|
+
setTrackMuted(muted) {
|
15329
|
+
super.setTrackMuted(muted);
|
15330
|
+
for (const sc of this.simulcastCodecs.values()) {
|
15331
|
+
sc.mediaStreamTrack.enabled = !muted;
|
15332
|
+
}
|
15333
|
+
}
|
15334
|
+
getSenderStats() {
|
15335
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15336
|
+
var _a;
|
15337
|
+
if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
|
15338
|
+
return [];
|
15181
15339
|
}
|
15182
|
-
|
15340
|
+
const items = [];
|
15341
|
+
const stats = yield this.sender.getStats();
|
15342
|
+
stats.forEach(v => {
|
15343
|
+
var _a;
|
15344
|
+
if (v.type === 'outbound-rtp') {
|
15345
|
+
const vs = {
|
15346
|
+
type: 'video',
|
15347
|
+
streamId: v.id,
|
15348
|
+
frameHeight: v.frameHeight,
|
15349
|
+
frameWidth: v.frameWidth,
|
15350
|
+
framesPerSecond: v.framesPerSecond,
|
15351
|
+
framesSent: v.framesSent,
|
15352
|
+
firCount: v.firCount,
|
15353
|
+
pliCount: v.pliCount,
|
15354
|
+
nackCount: v.nackCount,
|
15355
|
+
packetsSent: v.packetsSent,
|
15356
|
+
bytesSent: v.bytesSent,
|
15357
|
+
qualityLimitationReason: v.qualityLimitationReason,
|
15358
|
+
qualityLimitationDurations: v.qualityLimitationDurations,
|
15359
|
+
qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
|
15360
|
+
rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
|
15361
|
+
retransmittedPacketsSent: v.retransmittedPacketsSent,
|
15362
|
+
targetBitrate: v.targetBitrate,
|
15363
|
+
timestamp: v.timestamp
|
15364
|
+
};
|
15365
|
+
// locate the appropriate remote-inbound-rtp item
|
15366
|
+
const r = stats.get(v.remoteId);
|
15367
|
+
if (r) {
|
15368
|
+
vs.jitter = r.jitter;
|
15369
|
+
vs.packetsLost = r.packetsLost;
|
15370
|
+
vs.roundTripTime = r.roundTripTime;
|
15371
|
+
}
|
15372
|
+
items.push(vs);
|
15373
|
+
}
|
15374
|
+
});
|
15375
|
+
// make sure highest res layer is always first
|
15376
|
+
items.sort((a, b) => {
|
15377
|
+
var _a, _b;
|
15378
|
+
return ((_a = b.frameWidth) !== null && _a !== void 0 ? _a : 0) - ((_b = a.frameWidth) !== null && _b !== void 0 ? _b : 0);
|
15379
|
+
});
|
15380
|
+
return items;
|
15183
15381
|
});
|
15184
15382
|
}
|
15185
|
-
|
15186
|
-
|
15187
|
-
|
15188
|
-
|
15189
|
-
|
15190
|
-
|
15191
|
-
|
15192
|
-
|
15193
|
-
|
15194
|
-
|
15195
|
-
throw new UnexpectedConnectionState('Cannot stream on this device');
|
15196
|
-
});
|
15383
|
+
setPublishingQuality(maxQuality) {
|
15384
|
+
const qualities = [];
|
15385
|
+
for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
|
15386
|
+
qualities.push(new SubscribedQuality({
|
15387
|
+
quality: q,
|
15388
|
+
enabled: q <= maxQuality
|
15389
|
+
}));
|
15390
|
+
}
|
15391
|
+
this.log.debug("setting publishing quality. max quality ".concat(maxQuality), this.logContext);
|
15392
|
+
this.setPublishingLayers(qualities);
|
15197
15393
|
}
|
15198
|
-
|
15394
|
+
setDeviceId(deviceId) {
|
15199
15395
|
return __awaiter(this, void 0, void 0, function* () {
|
15200
|
-
if (
|
15201
|
-
|
15202
|
-
}
|
15203
|
-
const streams = [];
|
15204
|
-
if (track.mediaStream) {
|
15205
|
-
streams.push(track.mediaStream);
|
15206
|
-
}
|
15207
|
-
const transceiverInit = {
|
15208
|
-
direction: 'sendonly',
|
15209
|
-
streams
|
15210
|
-
};
|
15211
|
-
if (encodings) {
|
15212
|
-
transceiverInit.sendEncodings = encodings;
|
15396
|
+
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
|
15397
|
+
return true;
|
15213
15398
|
}
|
15214
|
-
|
15215
|
-
|
15216
|
-
|
15217
|
-
|
15218
|
-
|
15399
|
+
this._constraints.deviceId = deviceId;
|
15400
|
+
// when video is muted, underlying media stream track is stopped and
|
15401
|
+
// will be restarted later
|
15402
|
+
if (!this.isMuted) {
|
15403
|
+
yield this.restartTrack();
|
15219
15404
|
}
|
15220
|
-
return
|
15405
|
+
return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
15221
15406
|
});
|
15222
15407
|
}
|
15223
|
-
|
15408
|
+
restartTrack(options) {
|
15224
15409
|
return __awaiter(this, void 0, void 0, function* () {
|
15225
|
-
|
15226
|
-
|
15227
|
-
|
15228
|
-
|
15229
|
-
|
15230
|
-
|
15231
|
-
|
15232
|
-
|
15410
|
+
var _a, e_3, _b, _c;
|
15411
|
+
let constraints;
|
15412
|
+
if (options) {
|
15413
|
+
const streamConstraints = constraintsForOptions({
|
15414
|
+
video: options
|
15415
|
+
});
|
15416
|
+
if (typeof streamConstraints.video !== 'boolean') {
|
15417
|
+
constraints = streamConstraints.video;
|
15418
|
+
}
|
15233
15419
|
}
|
15234
|
-
|
15235
|
-
|
15236
|
-
|
15237
|
-
|
15420
|
+
yield this.restart(constraints);
|
15421
|
+
try {
|
15422
|
+
for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
15423
|
+
_c = _f.value;
|
15424
|
+
_d = false;
|
15425
|
+
const sc = _c;
|
15426
|
+
if (sc.sender) {
|
15427
|
+
sc.mediaStreamTrack = this.mediaStreamTrack.clone();
|
15428
|
+
yield sc.sender.replaceTrack(sc.mediaStreamTrack);
|
15429
|
+
}
|
15430
|
+
}
|
15431
|
+
} catch (e_3_1) {
|
15432
|
+
e_3 = {
|
15433
|
+
error: e_3_1
|
15434
|
+
};
|
15435
|
+
} finally {
|
15436
|
+
try {
|
15437
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
15438
|
+
} finally {
|
15439
|
+
if (e_3) throw e_3.error;
|
15440
|
+
}
|
15238
15441
|
}
|
15239
|
-
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
15240
|
-
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
15241
|
-
return transceiver.sender;
|
15242
15442
|
});
|
15243
15443
|
}
|
15244
|
-
|
15245
|
-
|
15246
|
-
|
15247
|
-
|
15444
|
+
setProcessor(processor_1) {
|
15445
|
+
const _super = Object.create(null, {
|
15446
|
+
setProcessor: {
|
15447
|
+
get: () => super.setProcessor
|
15248
15448
|
}
|
15249
|
-
return this.pcManager.addPublisherTrack(track);
|
15250
15449
|
});
|
15251
|
-
|
15252
|
-
|
15253
|
-
|
15254
|
-
|
15255
|
-
|
15256
|
-
|
15257
|
-
|
15258
|
-
|
15259
|
-
|
15260
|
-
|
15261
|
-
|
15262
|
-
|
15263
|
-
|
15264
|
-
|
15265
|
-
|
15266
|
-
|
15267
|
-
|
15268
|
-
|
15269
|
-
|
15270
|
-
|
15271
|
-
|
15272
|
-
|
15273
|
-
|
15274
|
-
|
15275
|
-
|
15276
|
-
|
15277
|
-
this.fullReconnectOnNext = false;
|
15278
|
-
} catch (e) {
|
15279
|
-
this.reconnectAttempts += 1;
|
15280
|
-
let recoverable = true;
|
15281
|
-
if (e instanceof UnexpectedConnectionState) {
|
15282
|
-
this.log.debug('received unrecoverable error', Object.assign(Object.assign({}, this.logContext), {
|
15283
|
-
error: e
|
15284
|
-
}));
|
15285
|
-
// unrecoverable
|
15286
|
-
recoverable = false;
|
15287
|
-
} else if (!(e instanceof SignalReconnectError)) {
|
15288
|
-
// cannot resume
|
15289
|
-
this.fullReconnectOnNext = true;
|
15290
|
-
}
|
15291
|
-
if (recoverable) {
|
15292
|
-
this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN);
|
15293
|
-
} else {
|
15294
|
-
this.log.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up"), this.logContext);
|
15295
|
-
this.emit(EngineEvent.Disconnected);
|
15296
|
-
yield this.close();
|
15450
|
+
return __awaiter(this, arguments, void 0, function (processor) {
|
15451
|
+
var _this = this;
|
15452
|
+
let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
15453
|
+
return function* () {
|
15454
|
+
var _a, e_4, _b, _c;
|
15455
|
+
var _d, _e;
|
15456
|
+
yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally);
|
15457
|
+
if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
|
15458
|
+
try {
|
15459
|
+
for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
|
15460
|
+
_c = _h.value;
|
15461
|
+
_f = false;
|
15462
|
+
const sc = _c;
|
15463
|
+
yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
|
15464
|
+
}
|
15465
|
+
} catch (e_4_1) {
|
15466
|
+
e_4 = {
|
15467
|
+
error: e_4_1
|
15468
|
+
};
|
15469
|
+
} finally {
|
15470
|
+
try {
|
15471
|
+
if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
|
15472
|
+
} finally {
|
15473
|
+
if (e_4) throw e_4.error;
|
15474
|
+
}
|
15475
|
+
}
|
15297
15476
|
}
|
15298
|
-
}
|
15299
|
-
this.attemptingReconnect = false;
|
15300
|
-
}
|
15477
|
+
}();
|
15301
15478
|
});
|
15302
15479
|
}
|
15303
|
-
|
15304
|
-
|
15305
|
-
|
15306
|
-
|
15307
|
-
this.log.warn('encountered error in reconnect policy', Object.assign(Object.assign({}, this.logContext), {
|
15308
|
-
error: e
|
15309
|
-
}));
|
15480
|
+
addSimulcastTrack(codec, encodings) {
|
15481
|
+
if (this.simulcastCodecs.has(codec)) {
|
15482
|
+
this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
|
15483
|
+
return;
|
15310
15484
|
}
|
15311
|
-
|
15312
|
-
|
15485
|
+
const simulcastCodecInfo = {
|
15486
|
+
codec,
|
15487
|
+
mediaStreamTrack: this.mediaStreamTrack.clone(),
|
15488
|
+
sender: undefined,
|
15489
|
+
encodings
|
15490
|
+
};
|
15491
|
+
this.simulcastCodecs.set(codec, simulcastCodecInfo);
|
15492
|
+
return simulcastCodecInfo;
|
15313
15493
|
}
|
15314
|
-
|
15494
|
+
setSimulcastTrackSender(codec, sender) {
|
15495
|
+
const simulcastCodecInfo = this.simulcastCodecs.get(codec);
|
15496
|
+
if (!simulcastCodecInfo) {
|
15497
|
+
return;
|
15498
|
+
}
|
15499
|
+
simulcastCodecInfo.sender = sender;
|
15500
|
+
// browser will reenable disabled codec/layers after new codec has been published,
|
15501
|
+
// so refresh subscribedCodecs after publish a new codec
|
15502
|
+
setTimeout(() => {
|
15503
|
+
if (this.subscribedCodecs) {
|
15504
|
+
this.setPublishingCodecs(this.subscribedCodecs);
|
15505
|
+
}
|
15506
|
+
}, refreshSubscribedCodecAfterNewCodec);
|
15507
|
+
}
|
15508
|
+
/**
|
15509
|
+
* @internal
|
15510
|
+
* Sets codecs that should be publishing, returns new codecs that have not yet
|
15511
|
+
* been published
|
15512
|
+
*/
|
15513
|
+
setPublishingCodecs(codecs) {
|
15315
15514
|
return __awaiter(this, void 0, void 0, function* () {
|
15316
|
-
var _a,
|
15317
|
-
|
15318
|
-
|
15319
|
-
|
15320
|
-
|
15321
|
-
|
15322
|
-
|
15323
|
-
|
15324
|
-
|
15325
|
-
|
15326
|
-
|
15327
|
-
|
15328
|
-
|
15329
|
-
|
15330
|
-
|
15331
|
-
|
15332
|
-
|
15333
|
-
|
15334
|
-
|
15335
|
-
|
15336
|
-
|
15337
|
-
|
15338
|
-
|
15339
|
-
|
15515
|
+
var _a, codecs_1, codecs_1_1;
|
15516
|
+
var _b, e_5, _c, _d;
|
15517
|
+
this.log.debug('setting publishing codecs', Object.assign(Object.assign({}, this.logContext), {
|
15518
|
+
codecs,
|
15519
|
+
currentCodec: this.codec
|
15520
|
+
}));
|
15521
|
+
// only enable simulcast codec for preference codec setted
|
15522
|
+
if (!this.codec && codecs.length > 0) {
|
15523
|
+
yield this.setPublishingLayers(codecs[0].qualities);
|
15524
|
+
return [];
|
15525
|
+
}
|
15526
|
+
this.subscribedCodecs = codecs;
|
15527
|
+
const newCodecs = [];
|
15528
|
+
try {
|
15529
|
+
for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
|
15530
|
+
_d = codecs_1_1.value;
|
15531
|
+
_a = false;
|
15532
|
+
const codec = _d;
|
15533
|
+
if (!this.codec || this.codec === codec.codec) {
|
15534
|
+
yield this.setPublishingLayers(codec.qualities);
|
15535
|
+
} else {
|
15536
|
+
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
|
15537
|
+
this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), {
|
15538
|
+
simulcastCodecInfo
|
15539
|
+
}));
|
15540
|
+
if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
|
15541
|
+
for (const q of codec.qualities) {
|
15542
|
+
if (q.enabled) {
|
15543
|
+
newCodecs.push(codec.codec);
|
15544
|
+
break;
|
15545
|
+
}
|
15546
|
+
}
|
15547
|
+
} else if (simulcastCodecInfo.encodings) {
|
15548
|
+
this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext);
|
15549
|
+
yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, this.log, this.logContext);
|
15550
|
+
}
|
15340
15551
|
}
|
15341
|
-
throw new SignalReconnectError();
|
15342
|
-
}
|
15343
|
-
if (this.shouldFailNext) {
|
15344
|
-
this.shouldFailNext = false;
|
15345
|
-
throw new Error('simulated failure');
|
15346
|
-
}
|
15347
|
-
this.client.setReconnected();
|
15348
|
-
this.emit(EngineEvent.SignalRestarted, joinResponse);
|
15349
|
-
yield this.waitForPCReconnected();
|
15350
|
-
// re-check signal connection state before setting engine as resumed
|
15351
|
-
if (this.client.currentState !== SignalConnectionState.CONNECTED) {
|
15352
|
-
throw new SignalReconnectError('Signal connection got severed during reconnect');
|
15353
15552
|
}
|
15354
|
-
|
15355
|
-
|
15356
|
-
|
15357
|
-
|
15358
|
-
|
15359
|
-
|
15360
|
-
yield
|
15361
|
-
|
15362
|
-
|
15363
|
-
// no more regions to try (or we're not on cloud)
|
15364
|
-
(_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
|
15365
|
-
throw error;
|
15553
|
+
} catch (e_5_1) {
|
15554
|
+
e_5 = {
|
15555
|
+
error: e_5_1
|
15556
|
+
};
|
15557
|
+
} finally {
|
15558
|
+
try {
|
15559
|
+
if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
|
15560
|
+
} finally {
|
15561
|
+
if (e_5) throw e_5.error;
|
15366
15562
|
}
|
15367
15563
|
}
|
15564
|
+
return newCodecs;
|
15368
15565
|
});
|
15369
15566
|
}
|
15370
|
-
|
15567
|
+
/**
|
15568
|
+
* @internal
|
15569
|
+
* Sets layers that should be publishing
|
15570
|
+
*/
|
15571
|
+
setPublishingLayers(qualities) {
|
15371
15572
|
return __awaiter(this, void 0, void 0, function* () {
|
15372
|
-
|
15373
|
-
|
15374
|
-
|
15375
|
-
|
15573
|
+
this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), {
|
15574
|
+
qualities
|
15575
|
+
}));
|
15576
|
+
if (!this.sender || !this.encodings) {
|
15577
|
+
return;
|
15376
15578
|
}
|
15377
|
-
|
15378
|
-
|
15379
|
-
|
15579
|
+
yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, this.log, this.logContext);
|
15580
|
+
});
|
15581
|
+
}
|
15582
|
+
handleAppVisibilityChanged() {
|
15583
|
+
const _super = Object.create(null, {
|
15584
|
+
handleAppVisibilityChanged: {
|
15585
|
+
get: () => super.handleAppVisibilityChanged
|
15380
15586
|
}
|
15381
|
-
|
15382
|
-
|
15383
|
-
|
15384
|
-
|
15385
|
-
|
15386
|
-
|
15387
|
-
} catch (error) {
|
15388
|
-
let message = '';
|
15389
|
-
if (error instanceof Error) {
|
15390
|
-
message = error.message;
|
15391
|
-
this.log.error(error.message, Object.assign(Object.assign({}, this.logContext), {
|
15392
|
-
error
|
15393
|
-
}));
|
15394
|
-
}
|
15395
|
-
if (error instanceof ConnectionError && error.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
|
15396
|
-
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
15397
|
-
}
|
15398
|
-
if (error instanceof ConnectionError && error.reason === 4 /* ConnectionErrorReason.LeaveRequest */) {
|
15399
|
-
throw error;
|
15400
|
-
}
|
15401
|
-
throw new SignalReconnectError(message);
|
15587
|
+
});
|
15588
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15589
|
+
yield _super.handleAppVisibilityChanged.call(this);
|
15590
|
+
if (!isMobile()) return;
|
15591
|
+
if (this.isInBackground && this.source === Track.Source.Camera) {
|
15592
|
+
this._mediaStreamTrack.enabled = false;
|
15402
15593
|
}
|
15403
|
-
|
15404
|
-
|
15405
|
-
|
15406
|
-
|
15407
|
-
|
15408
|
-
|
15594
|
+
});
|
15595
|
+
}
|
15596
|
+
}
|
15597
|
+
function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock, log, logContext) {
|
15598
|
+
return __awaiter(this, void 0, void 0, function* () {
|
15599
|
+
const unlock = yield senderLock.lock();
|
15600
|
+
log.debug('setPublishingLayersForSender', Object.assign(Object.assign({}, logContext), {
|
15601
|
+
sender,
|
15602
|
+
qualities,
|
15603
|
+
senderEncodings
|
15604
|
+
}));
|
15605
|
+
try {
|
15606
|
+
const params = sender.getParameters();
|
15607
|
+
const {
|
15608
|
+
encodings
|
15609
|
+
} = params;
|
15610
|
+
if (!encodings) {
|
15611
|
+
return;
|
15409
15612
|
}
|
15410
|
-
if (
|
15411
|
-
|
15412
|
-
|
15613
|
+
if (encodings.length !== senderEncodings.length) {
|
15614
|
+
log.warn('cannot set publishing layers, encodings mismatch', Object.assign(Object.assign({}, logContext), {
|
15615
|
+
encodings,
|
15616
|
+
senderEncodings
|
15617
|
+
}));
|
15618
|
+
return;
|
15413
15619
|
}
|
15414
|
-
|
15415
|
-
|
15416
|
-
|
15417
|
-
|
15418
|
-
|
15620
|
+
let hasChanged = false;
|
15621
|
+
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
15622
|
+
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
15623
|
+
low resolution frame and recover very quickly, but noticable
|
15624
|
+
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
15625
|
+
const closableSpatial = false;
|
15626
|
+
/* @ts-ignore */
|
15627
|
+
if (closableSpatial && encodings[0].scalabilityMode) ; else {
|
15628
|
+
// simulcast dynacast encodings
|
15629
|
+
encodings.forEach((encoding, idx) => {
|
15630
|
+
var _a;
|
15631
|
+
let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
|
15632
|
+
if (rid === '') {
|
15633
|
+
rid = 'q';
|
15634
|
+
}
|
15635
|
+
const quality = videoQualityForRid(rid);
|
15636
|
+
const subscribedQuality = qualities.find(q => q.quality === quality);
|
15637
|
+
if (!subscribedQuality) {
|
15638
|
+
return;
|
15639
|
+
}
|
15640
|
+
if (encoding.active !== subscribedQuality.enabled) {
|
15641
|
+
hasChanged = true;
|
15642
|
+
encoding.active = subscribedQuality.enabled;
|
15643
|
+
log.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'), logContext);
|
15644
|
+
// FireFox does not support setting encoding.active to false, so we
|
15645
|
+
// have a workaround of lowering its bitrate and resolution to the min.
|
15646
|
+
if (isFireFox()) {
|
15647
|
+
if (subscribedQuality.enabled) {
|
15648
|
+
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
|
15649
|
+
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
|
15650
|
+
/* @ts-ignore */
|
15651
|
+
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
|
15652
|
+
} else {
|
15653
|
+
encoding.scaleResolutionDownBy = 4;
|
15654
|
+
encoding.maxBitrate = 10;
|
15655
|
+
/* @ts-ignore */
|
15656
|
+
encoding.maxFrameRate = 2;
|
15657
|
+
}
|
15658
|
+
}
|
15659
|
+
}
|
15660
|
+
});
|
15419
15661
|
}
|
15420
|
-
|
15421
|
-
|
15422
|
-
|
15423
|
-
|
15424
|
-
|
15662
|
+
if (hasChanged) {
|
15663
|
+
params.encodings = encodings;
|
15664
|
+
log.debug("setting encodings", Object.assign(Object.assign({}, logContext), {
|
15665
|
+
encodings: params.encodings
|
15666
|
+
}));
|
15667
|
+
yield sender.setParameters(params);
|
15425
15668
|
}
|
15426
|
-
|
15427
|
-
|
15428
|
-
}
|
15669
|
+
} finally {
|
15670
|
+
unlock();
|
15671
|
+
}
|
15672
|
+
});
|
15673
|
+
}
|
15674
|
+
function videoQualityForRid(rid) {
|
15675
|
+
switch (rid) {
|
15676
|
+
case 'f':
|
15677
|
+
return VideoQuality.HIGH;
|
15678
|
+
case 'h':
|
15679
|
+
return VideoQuality.MEDIUM;
|
15680
|
+
case 'q':
|
15681
|
+
return VideoQuality.LOW;
|
15682
|
+
default:
|
15683
|
+
return VideoQuality.HIGH;
|
15429
15684
|
}
|
15430
|
-
|
15431
|
-
|
15432
|
-
|
15433
|
-
|
15434
|
-
|
15435
|
-
|
15436
|
-
|
15685
|
+
}
|
15686
|
+
function videoLayersFromEncodings(width, height, encodings, svc) {
|
15687
|
+
// default to a single layer, HQ
|
15688
|
+
if (!encodings) {
|
15689
|
+
return [new VideoLayer({
|
15690
|
+
quality: VideoQuality.HIGH,
|
15691
|
+
width,
|
15692
|
+
height,
|
15693
|
+
bitrate: 0,
|
15694
|
+
ssrc: 0
|
15695
|
+
})];
|
15696
|
+
}
|
15697
|
+
if (svc) {
|
15698
|
+
// svc layers
|
15699
|
+
/* @ts-ignore */
|
15700
|
+
const encodingSM = encodings[0].scalabilityMode;
|
15701
|
+
const sm = new ScalabilityMode(encodingSM);
|
15702
|
+
const layers = [];
|
15703
|
+
const resRatio = sm.suffix == 'h' ? 1.5 : 2;
|
15704
|
+
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
15705
|
+
for (let i = 0; i < sm.spatial; i += 1) {
|
15706
|
+
layers.push(new VideoLayer({
|
15707
|
+
quality: VideoQuality.HIGH - i,
|
15708
|
+
width: Math.ceil(width / Math.pow(resRatio, i)),
|
15709
|
+
height: Math.ceil(height / Math.pow(resRatio, i)),
|
15710
|
+
bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(bitratesRatio, i)) : 0,
|
15711
|
+
ssrc: 0
|
15712
|
+
}));
|
15713
|
+
}
|
15714
|
+
return layers;
|
15437
15715
|
}
|
15438
|
-
|
15439
|
-
|
15440
|
-
|
15441
|
-
|
15442
|
-
|
15443
|
-
|
15444
|
-
|
15445
|
-
|
15446
|
-
|
15447
|
-
|
15448
|
-
this.pcState = PCState.Connected;
|
15449
|
-
} catch (e) {
|
15450
|
-
// TODO do we need a `failed` state here for the PC?
|
15451
|
-
this.pcState = PCState.Disconnected;
|
15452
|
-
throw new ConnectionError("could not establish PC connection, ".concat(e.message));
|
15453
|
-
}
|
15716
|
+
return encodings.map(encoding => {
|
15717
|
+
var _a, _b, _c;
|
15718
|
+
const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
|
15719
|
+
let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
|
15720
|
+
return new VideoLayer({
|
15721
|
+
quality,
|
15722
|
+
width: Math.ceil(width / scale),
|
15723
|
+
height: Math.ceil(height / scale),
|
15724
|
+
bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
|
15725
|
+
ssrc: 0
|
15454
15726
|
});
|
15727
|
+
});
|
15728
|
+
}
|
15729
|
+
|
15730
|
+
const lossyDataChannel = '_lossy';
|
15731
|
+
const reliableDataChannel = '_reliable';
|
15732
|
+
const minReconnectWait = 2 * 1000;
|
15733
|
+
const leaveReconnect = 'leave-reconnect';
|
15734
|
+
var PCState;
|
15735
|
+
(function (PCState) {
|
15736
|
+
PCState[PCState["New"] = 0] = "New";
|
15737
|
+
PCState[PCState["Connected"] = 1] = "Connected";
|
15738
|
+
PCState[PCState["Disconnected"] = 2] = "Disconnected";
|
15739
|
+
PCState[PCState["Reconnecting"] = 3] = "Reconnecting";
|
15740
|
+
PCState[PCState["Closed"] = 4] = "Closed";
|
15741
|
+
})(PCState || (PCState = {}));
|
15742
|
+
/** @internal */
|
15743
|
+
class RTCEngine extends eventsExports.EventEmitter {
|
15744
|
+
get isClosed() {
|
15745
|
+
return this._isClosed;
|
15455
15746
|
}
|
15456
|
-
|
15457
|
-
|
15458
|
-
return __awaiter(this, void 0, void 0, function* () {
|
15459
|
-
const msg = packet.toBinary();
|
15460
|
-
// make sure we do have a data connection
|
15461
|
-
yield this.ensurePublisherConnected(kind);
|
15462
|
-
const dc = this.dataChannelForKind(kind);
|
15463
|
-
if (dc) {
|
15464
|
-
dc.send(msg);
|
15465
|
-
}
|
15466
|
-
this.updateAndEmitDCBufferStatus(kind);
|
15467
|
-
});
|
15747
|
+
get pendingReconnect() {
|
15748
|
+
return !!this.reconnectTimeout;
|
15468
15749
|
}
|
15469
|
-
|
15470
|
-
|
15471
|
-
|
15472
|
-
|
15473
|
-
|
15474
|
-
|
15475
|
-
|
15750
|
+
constructor(options) {
|
15751
|
+
var _a;
|
15752
|
+
super();
|
15753
|
+
this.options = options;
|
15754
|
+
this.rtcConfig = {};
|
15755
|
+
this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
|
15756
|
+
this.fullReconnectOnNext = false;
|
15757
|
+
this.subscriberPrimary = false;
|
15758
|
+
this.pcState = PCState.New;
|
15759
|
+
this._isClosed = true;
|
15760
|
+
this.pendingTrackResolvers = {};
|
15761
|
+
this.reconnectAttempts = 0;
|
15762
|
+
this.reconnectStart = 0;
|
15763
|
+
this.attemptingReconnect = false;
|
15764
|
+
/** keeps track of how often an initial join connection has been tried */
|
15765
|
+
this.joinAttempts = 0;
|
15766
|
+
/** specifies how often an initial join connection is allowed to retry */
|
15767
|
+
this.maxJoinAttempts = 1;
|
15768
|
+
this.shouldFailNext = false;
|
15769
|
+
this.log = livekitLogger;
|
15770
|
+
this.handleDataChannel = _b => __awaiter(this, [_b], void 0, function (_ref) {
|
15771
|
+
var _this = this;
|
15772
|
+
let {
|
15773
|
+
channel
|
15774
|
+
} = _ref;
|
15476
15775
|
return function* () {
|
15477
|
-
|
15478
|
-
if (!_this2.pcManager) {
|
15479
|
-
throw new UnexpectedConnectionState('PC manager is closed');
|
15480
|
-
}
|
15481
|
-
const transport = subscriber ? _this2.pcManager.subscriber : _this2.pcManager.publisher;
|
15482
|
-
const transportName = subscriber ? 'Subscriber' : 'Publisher';
|
15483
|
-
if (!transport) {
|
15484
|
-
throw new ConnectionError("".concat(transportName, " connection not set"));
|
15485
|
-
}
|
15486
|
-
if (!subscriber && !_this2.pcManager.publisher.isICEConnected && _this2.pcManager.publisher.getICEConnectionState() !== 'checking') {
|
15487
|
-
// start negotiation
|
15488
|
-
_this2.negotiate();
|
15489
|
-
}
|
15490
|
-
const targetChannel = _this2.dataChannelForKind(kind, subscriber);
|
15491
|
-
if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') {
|
15776
|
+
if (!channel) {
|
15492
15777
|
return;
|
15493
15778
|
}
|
15494
|
-
|
15495
|
-
|
15496
|
-
|
15497
|
-
|
15498
|
-
|
15499
|
-
|
15500
|
-
yield sleep(50);
|
15779
|
+
if (channel.label === reliableDataChannel) {
|
15780
|
+
_this.reliableDCSub = channel;
|
15781
|
+
} else if (channel.label === lossyDataChannel) {
|
15782
|
+
_this.lossyDCSub = channel;
|
15783
|
+
} else {
|
15784
|
+
return;
|
15501
15785
|
}
|
15502
|
-
|
15786
|
+
_this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label), _this.logContext);
|
15787
|
+
channel.onmessage = _this.handleDataMessage;
|
15503
15788
|
}();
|
15504
15789
|
});
|
15505
|
-
|
15506
|
-
|
15507
|
-
|
15508
|
-
|
15509
|
-
|
15510
|
-
|
15511
|
-
|
15512
|
-
|
15513
|
-
|
15514
|
-
|
15515
|
-
|
15516
|
-
|
15517
|
-
|
15518
|
-
|
15519
|
-
|
15520
|
-
if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
|
15521
|
-
return false;
|
15522
|
-
}
|
15523
|
-
// ensure signal is connected
|
15524
|
-
if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
|
15525
|
-
return false;
|
15526
|
-
}
|
15527
|
-
return true;
|
15528
|
-
}
|
15529
|
-
/** @internal */
|
15530
|
-
negotiate() {
|
15531
|
-
return __awaiter(this, void 0, void 0, function* () {
|
15532
|
-
// observe signal state
|
15533
|
-
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
15534
|
-
if (!this.pcManager) {
|
15535
|
-
reject(new NegotiationError('PC manager is closed'));
|
15536
|
-
return;
|
15537
|
-
}
|
15538
|
-
this.pcManager.requirePublisher();
|
15539
|
-
const abortController = new AbortController();
|
15540
|
-
const handleClosed = () => {
|
15541
|
-
abortController.abort();
|
15542
|
-
this.log.debug('engine disconnected while negotiation was ongoing', this.logContext);
|
15543
|
-
resolve();
|
15790
|
+
this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () {
|
15791
|
+
var _c, _d;
|
15792
|
+
// make sure to respect incoming data message order by processing message events one after the other
|
15793
|
+
const unlock = yield this.dataProcessLock.lock();
|
15794
|
+
try {
|
15795
|
+
// decode
|
15796
|
+
let buffer;
|
15797
|
+
if (message.data instanceof ArrayBuffer) {
|
15798
|
+
buffer = message.data;
|
15799
|
+
} else if (message.data instanceof Blob) {
|
15800
|
+
buffer = yield message.data.arrayBuffer();
|
15801
|
+
} else {
|
15802
|
+
this.log.error('unsupported data type', Object.assign(Object.assign({}, this.logContext), {
|
15803
|
+
data: message.data
|
15804
|
+
}));
|
15544
15805
|
return;
|
15545
|
-
};
|
15546
|
-
if (this.isClosed) {
|
15547
|
-
reject('cannot negotiate on closed engine');
|
15548
15806
|
}
|
15549
|
-
|
15550
|
-
|
15551
|
-
|
15552
|
-
|
15553
|
-
|
15554
|
-
|
15555
|
-
rtpMap.set(rtp.payload, codec);
|
15556
|
-
}
|
15557
|
-
});
|
15558
|
-
this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap);
|
15559
|
-
});
|
15560
|
-
try {
|
15561
|
-
yield this.pcManager.negotiate(abortController);
|
15562
|
-
resolve();
|
15563
|
-
} catch (e) {
|
15564
|
-
if (e instanceof NegotiationError) {
|
15565
|
-
this.fullReconnectOnNext = true;
|
15566
|
-
}
|
15567
|
-
this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN);
|
15568
|
-
reject(e);
|
15569
|
-
} finally {
|
15570
|
-
this.off(EngineEvent.Closing, handleClosed);
|
15807
|
+
const dp = DataPacket.fromBinary(new Uint8Array(buffer));
|
15808
|
+
if (((_c = dp.value) === null || _c === void 0 ? void 0 : _c.case) === 'speaker') {
|
15809
|
+
// dispatch speaker updates
|
15810
|
+
this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
|
15811
|
+
} else if (((_d = dp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
|
15812
|
+
this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
|
15571
15813
|
}
|
15572
|
-
}
|
15573
|
-
|
15574
|
-
}
|
15575
|
-
dataChannelForKind(kind, sub) {
|
15576
|
-
if (!sub) {
|
15577
|
-
if (kind === DataPacket_Kind.LOSSY) {
|
15578
|
-
return this.lossyDC;
|
15579
|
-
}
|
15580
|
-
if (kind === DataPacket_Kind.RELIABLE) {
|
15581
|
-
return this.reliableDC;
|
15582
|
-
}
|
15583
|
-
} else {
|
15584
|
-
if (kind === DataPacket_Kind.LOSSY) {
|
15585
|
-
return this.lossyDCSub;
|
15586
|
-
}
|
15587
|
-
if (kind === DataPacket_Kind.RELIABLE) {
|
15588
|
-
return this.reliableDCSub;
|
15589
|
-
}
|
15590
|
-
}
|
15591
|
-
}
|
15592
|
-
/** @internal */
|
15593
|
-
sendSyncState(remoteTracks, localTracks) {
|
15594
|
-
var _a, _b;
|
15595
|
-
if (!this.pcManager) {
|
15596
|
-
this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
|
15597
|
-
return;
|
15598
|
-
}
|
15599
|
-
const previousAnswer = this.pcManager.subscriber.getLocalDescription();
|
15600
|
-
const previousOffer = this.pcManager.subscriber.getRemoteDescription();
|
15601
|
-
/* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
|
15602
|
-
in this case, we send unsub tracks, so server add all tracks to this
|
15603
|
-
subscribe pc and unsub special tracks from it.
|
15604
|
-
2. autosubscribe off, we send subscribed tracks.
|
15605
|
-
*/
|
15606
|
-
const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
|
15607
|
-
const trackSids = new Array();
|
15608
|
-
const trackSidsDisabled = new Array();
|
15609
|
-
remoteTracks.forEach(track => {
|
15610
|
-
if (track.isDesired !== autoSubscribe) {
|
15611
|
-
trackSids.push(track.trackSid);
|
15612
|
-
}
|
15613
|
-
if (!track.isEnabled) {
|
15614
|
-
trackSidsDisabled.push(track.trackSid);
|
15814
|
+
} finally {
|
15815
|
+
unlock();
|
15615
15816
|
}
|
15616
15817
|
});
|
15617
|
-
this.
|
15618
|
-
|
15619
|
-
|
15620
|
-
|
15621
|
-
|
15622
|
-
|
15623
|
-
|
15624
|
-
|
15625
|
-
|
15626
|
-
|
15627
|
-
|
15628
|
-
|
15629
|
-
|
15630
|
-
}),
|
15631
|
-
publishTracks: getTrackPublicationInfo(localTracks),
|
15632
|
-
dataChannels: this.dataChannelsInfo(),
|
15633
|
-
trackSidsDisabled
|
15634
|
-
}));
|
15635
|
-
}
|
15636
|
-
/* @internal */
|
15637
|
-
failNext() {
|
15638
|
-
// debugging method to fail the next reconnect/resume attempt
|
15639
|
-
this.shouldFailNext = true;
|
15640
|
-
}
|
15641
|
-
dataChannelsInfo() {
|
15642
|
-
const infos = [];
|
15643
|
-
const getInfo = (dc, target) => {
|
15644
|
-
if ((dc === null || dc === void 0 ? void 0 : dc.id) !== undefined && dc.id !== null) {
|
15645
|
-
infos.push(new DataChannelInfo({
|
15646
|
-
label: dc.label,
|
15647
|
-
id: dc.id,
|
15648
|
-
target
|
15818
|
+
this.handleDataError = event => {
|
15819
|
+
const channel = event.currentTarget;
|
15820
|
+
const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
|
15821
|
+
if (event instanceof ErrorEvent && event.error) {
|
15822
|
+
const {
|
15823
|
+
error
|
15824
|
+
} = event.error;
|
15825
|
+
this.log.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), Object.assign(Object.assign({}, this.logContext), {
|
15826
|
+
error
|
15827
|
+
}));
|
15828
|
+
} else {
|
15829
|
+
this.log.error("Unknown DataChannel error on ".concat(channelKind), Object.assign(Object.assign({}, this.logContext), {
|
15830
|
+
event
|
15649
15831
|
}));
|
15650
15832
|
}
|
15651
15833
|
};
|
15652
|
-
|
15653
|
-
|
15654
|
-
|
15655
|
-
|
15656
|
-
|
15657
|
-
|
15658
|
-
|
15659
|
-
|
15660
|
-
|
15661
|
-
|
15662
|
-
|
15663
|
-
clearPendingReconnect() {
|
15664
|
-
this.clearReconnectTimeout();
|
15665
|
-
this.reconnectAttempts = 0;
|
15666
|
-
}
|
15667
|
-
registerOnLineListener() {
|
15668
|
-
if (isWeb()) {
|
15669
|
-
window.addEventListener('online', this.handleBrowserOnLine);
|
15670
|
-
}
|
15671
|
-
}
|
15672
|
-
deregisterOnLineListener() {
|
15673
|
-
if (isWeb()) {
|
15674
|
-
window.removeEventListener('online', this.handleBrowserOnLine);
|
15675
|
-
}
|
15676
|
-
}
|
15677
|
-
}
|
15678
|
-
class SignalReconnectError extends Error {}
|
15679
|
-
|
15680
|
-
class RegionUrlProvider {
|
15681
|
-
constructor(url, token) {
|
15682
|
-
this.lastUpdateAt = 0;
|
15683
|
-
this.settingsCacheTime = 3000;
|
15684
|
-
this.attemptedRegions = [];
|
15685
|
-
this.serverUrl = new URL(url);
|
15686
|
-
this.token = token;
|
15687
|
-
}
|
15688
|
-
updateToken(token) {
|
15689
|
-
this.token = token;
|
15690
|
-
}
|
15691
|
-
isCloud() {
|
15692
|
-
return isCloud(this.serverUrl);
|
15693
|
-
}
|
15694
|
-
getServerUrl() {
|
15695
|
-
return this.serverUrl;
|
15696
|
-
}
|
15697
|
-
getNextBestRegionUrl(abortSignal) {
|
15698
|
-
return __awaiter(this, void 0, void 0, function* () {
|
15699
|
-
if (!this.isCloud()) {
|
15700
|
-
throw Error('region availability is only supported for LiveKit Cloud domains');
|
15701
|
-
}
|
15702
|
-
if (!this.regionSettings || Date.now() - this.lastUpdateAt > this.settingsCacheTime) {
|
15703
|
-
this.regionSettings = yield this.fetchRegionSettings(abortSignal);
|
15834
|
+
this.handleBufferedAmountLow = event => {
|
15835
|
+
const channel = event.currentTarget;
|
15836
|
+
const channelKind = channel.maxRetransmits === 0 ? DataPacket_Kind.LOSSY : DataPacket_Kind.RELIABLE;
|
15837
|
+
this.updateAndEmitDCBufferStatus(channelKind);
|
15838
|
+
};
|
15839
|
+
// websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
|
15840
|
+
// continues to work, we can reconnect to websocket to continue the session
|
15841
|
+
// after a number of retries, we'll close and give up permanently
|
15842
|
+
this.handleDisconnect = (connection, disconnectReason) => {
|
15843
|
+
if (this._isClosed) {
|
15844
|
+
return;
|
15704
15845
|
}
|
15705
|
-
|
15706
|
-
if (
|
15707
|
-
|
15708
|
-
this.
|
15709
|
-
livekitLogger.debug("next region: ".concat(nextRegion.region));
|
15710
|
-
return nextRegion.url;
|
15711
|
-
} else {
|
15712
|
-
return null;
|
15846
|
+
this.log.warn("".concat(connection, " disconnected"), this.logContext);
|
15847
|
+
if (this.reconnectAttempts === 0) {
|
15848
|
+
// only reset start time on the first try
|
15849
|
+
this.reconnectStart = Date.now();
|
15713
15850
|
}
|
15714
|
-
|
15715
|
-
|
15716
|
-
|
15717
|
-
|
15718
|
-
|
15719
|
-
|
15720
|
-
|
15721
|
-
|
15722
|
-
|
15723
|
-
headers: {
|
15724
|
-
authorization: "Bearer ".concat(this.token)
|
15725
|
-
},
|
15726
|
-
signal
|
15851
|
+
const disconnect = duration => {
|
15852
|
+
this.log.warn("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"), this.logContext);
|
15853
|
+
this.emit(EngineEvent.Disconnected);
|
15854
|
+
this.close();
|
15855
|
+
};
|
15856
|
+
const duration = Date.now() - this.reconnectStart;
|
15857
|
+
let delay = this.getNextRetryDelay({
|
15858
|
+
elapsedMs: duration,
|
15859
|
+
retryCount: this.reconnectAttempts
|
15727
15860
|
});
|
15728
|
-
if (
|
15729
|
-
|
15730
|
-
this.lastUpdateAt = Date.now();
|
15731
|
-
return regionSettings;
|
15732
|
-
} else {
|
15733
|
-
throw new ConnectionError("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status === 401 ? 0 /* ConnectionErrorReason.NotAllowed */ : undefined, regionSettingsResponse.status);
|
15734
|
-
}
|
15735
|
-
});
|
15736
|
-
}
|
15737
|
-
}
|
15738
|
-
function getCloudConfigUrl(serverUrl) {
|
15739
|
-
return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
|
15740
|
-
}
|
15741
|
-
|
15742
|
-
const monitorFrequency = 2000;
|
15743
|
-
function computeBitrate(currentStats, prevStats) {
|
15744
|
-
if (!prevStats) {
|
15745
|
-
return 0;
|
15746
|
-
}
|
15747
|
-
let bytesNow;
|
15748
|
-
let bytesPrev;
|
15749
|
-
if ('bytesReceived' in currentStats) {
|
15750
|
-
bytesNow = currentStats.bytesReceived;
|
15751
|
-
bytesPrev = prevStats.bytesReceived;
|
15752
|
-
} else if ('bytesSent' in currentStats) {
|
15753
|
-
bytesNow = currentStats.bytesSent;
|
15754
|
-
bytesPrev = prevStats.bytesSent;
|
15755
|
-
}
|
15756
|
-
if (bytesNow === undefined || bytesPrev === undefined || currentStats.timestamp === undefined || prevStats.timestamp === undefined) {
|
15757
|
-
return 0;
|
15758
|
-
}
|
15759
|
-
return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
|
15760
|
-
}
|
15761
|
-
|
15762
|
-
class LocalAudioTrack extends LocalTrack {
|
15763
|
-
/**
|
15764
|
-
* boolean indicating whether enhanced noise cancellation is currently being used on this track
|
15765
|
-
*/
|
15766
|
-
get enhancedNoiseCancellation() {
|
15767
|
-
return this.isKrispNoiseFilterEnabled;
|
15768
|
-
}
|
15769
|
-
/**
|
15770
|
-
*
|
15771
|
-
* @param mediaTrack
|
15772
|
-
* @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
|
15773
|
-
* @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
|
15774
|
-
*/
|
15775
|
-
constructor(mediaTrack, constraints) {
|
15776
|
-
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
15777
|
-
let audioContext = arguments.length > 3 ? arguments[3] : undefined;
|
15778
|
-
let loggerOptions = arguments.length > 4 ? arguments[4] : undefined;
|
15779
|
-
super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack, loggerOptions);
|
15780
|
-
/** @internal */
|
15781
|
-
this.stopOnMute = false;
|
15782
|
-
this.isKrispNoiseFilterEnabled = false;
|
15783
|
-
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
15784
|
-
if (!this.sender) {
|
15785
|
-
this._currentBitrate = 0;
|
15861
|
+
if (delay === null) {
|
15862
|
+
disconnect(duration);
|
15786
15863
|
return;
|
15787
15864
|
}
|
15788
|
-
|
15789
|
-
|
15790
|
-
stats = yield this.getSenderStats();
|
15791
|
-
} catch (e) {
|
15792
|
-
this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
|
15793
|
-
error: e
|
15794
|
-
}));
|
15795
|
-
return;
|
15865
|
+
if (connection === leaveReconnect) {
|
15866
|
+
delay = 0;
|
15796
15867
|
}
|
15797
|
-
|
15798
|
-
|
15868
|
+
this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
|
15869
|
+
this.clearReconnectTimeout();
|
15870
|
+
if (this.token && this.regionUrlProvider) {
|
15871
|
+
// token may have been refreshed, we do not want to recreate the regionUrlProvider
|
15872
|
+
// since the current engine may have inherited a regional url
|
15873
|
+
this.regionUrlProvider.updateToken(this.token);
|
15874
|
+
}
|
15875
|
+
this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
|
15876
|
+
};
|
15877
|
+
this.waitForRestarted = () => {
|
15878
|
+
return new Promise((resolve, reject) => {
|
15879
|
+
if (this.pcState === PCState.Connected) {
|
15880
|
+
resolve();
|
15881
|
+
}
|
15882
|
+
const onRestarted = () => {
|
15883
|
+
this.off(EngineEvent.Disconnected, onDisconnected);
|
15884
|
+
resolve();
|
15885
|
+
};
|
15886
|
+
const onDisconnected = () => {
|
15887
|
+
this.off(EngineEvent.Restarted, onRestarted);
|
15888
|
+
reject();
|
15889
|
+
};
|
15890
|
+
this.once(EngineEvent.Restarted, onRestarted);
|
15891
|
+
this.once(EngineEvent.Disconnected, onDisconnected);
|
15892
|
+
});
|
15893
|
+
};
|
15894
|
+
this.updateAndEmitDCBufferStatus = kind => {
|
15895
|
+
const status = this.isBufferStatusLow(kind);
|
15896
|
+
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
15897
|
+
this.dcBufferStatus.set(kind, status);
|
15898
|
+
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
15899
|
+
}
|
15900
|
+
};
|
15901
|
+
this.isBufferStatusLow = kind => {
|
15902
|
+
const dc = this.dataChannelForKind(kind);
|
15903
|
+
if (dc) {
|
15904
|
+
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
15905
|
+
}
|
15906
|
+
};
|
15907
|
+
this.handleBrowserOnLine = () => {
|
15908
|
+
// in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
|
15909
|
+
if (this.client.currentState === SignalConnectionState.RECONNECTING) {
|
15910
|
+
this.clearReconnectTimeout();
|
15911
|
+
this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
15799
15912
|
}
|
15800
|
-
this.prevStats = stats;
|
15801
|
-
});
|
15802
|
-
this.handleKrispNoiseFilterEnable = () => {
|
15803
|
-
this.isKrispNoiseFilterEnabled = true;
|
15804
|
-
this.log.debug("Krisp noise filter enabled", this.logContext);
|
15805
|
-
this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, true);
|
15806
15913
|
};
|
15807
|
-
this.
|
15808
|
-
|
15809
|
-
|
15810
|
-
|
15914
|
+
this.log = getLogger((_a = options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Engine);
|
15915
|
+
this.loggerOptions = {
|
15916
|
+
loggerName: options.loggerName,
|
15917
|
+
loggerContextCb: () => this.logContext
|
15811
15918
|
};
|
15812
|
-
this.
|
15813
|
-
this.
|
15919
|
+
this.client = new SignalClient(undefined, this.loggerOptions);
|
15920
|
+
this.client.signalLatency = this.options.expSignalLatency;
|
15921
|
+
this.reconnectPolicy = this.options.reconnectPolicy;
|
15922
|
+
this.registerOnLineListener();
|
15923
|
+
this.closingLock = new Mutex();
|
15924
|
+
this.dataProcessLock = new Mutex();
|
15925
|
+
this.dcBufferStatus = new Map([[DataPacket_Kind.LOSSY, true], [DataPacket_Kind.RELIABLE, true]]);
|
15926
|
+
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
15927
|
+
this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update);
|
15928
|
+
this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update);
|
15929
|
+
this.client.onSubscriptionError = resp => this.emit(EngineEvent.SubscriptionError, resp);
|
15930
|
+
this.client.onSubscriptionPermissionUpdate = update => this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
|
15931
|
+
this.client.onSpeakersChanged = update => this.emit(EngineEvent.SpeakersChanged, update);
|
15932
|
+
this.client.onStreamStateUpdate = update => this.emit(EngineEvent.StreamStateChanged, update);
|
15814
15933
|
}
|
15815
|
-
|
15816
|
-
|
15817
|
-
|
15818
|
-
|
15819
|
-
|
15820
|
-
this.
|
15821
|
-
|
15822
|
-
|
15823
|
-
|
15824
|
-
return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
15825
|
-
});
|
15934
|
+
/** @internal */
|
15935
|
+
get logContext() {
|
15936
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
15937
|
+
return {
|
15938
|
+
room: (_b = (_a = this.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.name,
|
15939
|
+
roomID: (_d = (_c = this.latestJoinResponse) === null || _c === void 0 ? void 0 : _c.room) === null || _d === void 0 ? void 0 : _d.sid,
|
15940
|
+
participant: (_f = (_e = this.latestJoinResponse) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.identity,
|
15941
|
+
pID: (_h = (_g = this.latestJoinResponse) === null || _g === void 0 ? void 0 : _g.participant) === null || _h === void 0 ? void 0 : _h.sid
|
15942
|
+
};
|
15826
15943
|
}
|
15827
|
-
|
15828
|
-
const _super = Object.create(null, {
|
15829
|
-
mute: {
|
15830
|
-
get: () => super.mute
|
15831
|
-
}
|
15832
|
-
});
|
15944
|
+
join(url, token, opts, abortSignal) {
|
15833
15945
|
return __awaiter(this, void 0, void 0, function* () {
|
15834
|
-
|
15946
|
+
this.url = url;
|
15947
|
+
this.token = token;
|
15948
|
+
this.signalOpts = opts;
|
15949
|
+
this.maxJoinAttempts = opts.maxRetries;
|
15835
15950
|
try {
|
15836
|
-
|
15837
|
-
|
15838
|
-
|
15951
|
+
this.joinAttempts += 1;
|
15952
|
+
this.setupSignalClientCallbacks();
|
15953
|
+
const joinResponse = yield this.client.join(url, token, opts, abortSignal);
|
15954
|
+
this._isClosed = false;
|
15955
|
+
this.latestJoinResponse = joinResponse;
|
15956
|
+
this.subscriberPrimary = joinResponse.subscriberPrimary;
|
15957
|
+
if (!this.pcManager) {
|
15958
|
+
yield this.configure(joinResponse);
|
15839
15959
|
}
|
15840
|
-
//
|
15841
|
-
if (
|
15842
|
-
this.
|
15843
|
-
// also stop the track, so that microphone indicator is turned off
|
15844
|
-
this._mediaStreamTrack.stop();
|
15960
|
+
// create offer
|
15961
|
+
if (!this.subscriberPrimary) {
|
15962
|
+
this.negotiate();
|
15845
15963
|
}
|
15846
|
-
|
15847
|
-
return
|
15848
|
-
}
|
15849
|
-
|
15964
|
+
this.clientConfiguration = joinResponse.clientConfiguration;
|
15965
|
+
return joinResponse;
|
15966
|
+
} catch (e) {
|
15967
|
+
if (e instanceof ConnectionError) {
|
15968
|
+
if (e.reason === 1 /* ConnectionErrorReason.ServerUnreachable */) {
|
15969
|
+
this.log.warn("Couldn't connect to server, attempt ".concat(this.joinAttempts, " of ").concat(this.maxJoinAttempts), this.logContext);
|
15970
|
+
if (this.joinAttempts < this.maxJoinAttempts) {
|
15971
|
+
return this.join(url, token, opts, abortSignal);
|
15972
|
+
}
|
15973
|
+
}
|
15974
|
+
}
|
15975
|
+
throw e;
|
15850
15976
|
}
|
15851
15977
|
});
|
15852
15978
|
}
|
15853
|
-
|
15854
|
-
const _super = Object.create(null, {
|
15855
|
-
unmute: {
|
15856
|
-
get: () => super.unmute
|
15857
|
-
}
|
15858
|
-
});
|
15979
|
+
close() {
|
15859
15980
|
return __awaiter(this, void 0, void 0, function* () {
|
15860
|
-
const unlock = yield this.
|
15981
|
+
const unlock = yield this.closingLock.lock();
|
15982
|
+
if (this.isClosed) {
|
15983
|
+
unlock();
|
15984
|
+
return;
|
15985
|
+
}
|
15861
15986
|
try {
|
15862
|
-
|
15863
|
-
|
15864
|
-
|
15865
|
-
|
15866
|
-
|
15867
|
-
|
15868
|
-
|
15869
|
-
yield this.restartTrack();
|
15870
|
-
}
|
15871
|
-
yield _super.unmute.call(this);
|
15872
|
-
return this;
|
15987
|
+
this._isClosed = true;
|
15988
|
+
this.emit(EngineEvent.Closing);
|
15989
|
+
this.removeAllListeners();
|
15990
|
+
this.deregisterOnLineListener();
|
15991
|
+
this.clearPendingReconnect();
|
15992
|
+
yield this.cleanupPeerConnections();
|
15993
|
+
yield this.cleanupClient();
|
15873
15994
|
} finally {
|
15874
15995
|
unlock();
|
15875
15996
|
}
|
15876
15997
|
});
|
15877
15998
|
}
|
15878
|
-
|
15999
|
+
cleanupPeerConnections() {
|
15879
16000
|
return __awaiter(this, void 0, void 0, function* () {
|
15880
|
-
|
15881
|
-
|
15882
|
-
|
15883
|
-
|
15884
|
-
|
15885
|
-
|
15886
|
-
|
15887
|
-
|
15888
|
-
|
15889
|
-
|
16001
|
+
var _a;
|
16002
|
+
yield (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.close();
|
16003
|
+
this.pcManager = undefined;
|
16004
|
+
const dcCleanup = dc => {
|
16005
|
+
if (!dc) return;
|
16006
|
+
dc.close();
|
16007
|
+
dc.onbufferedamountlow = null;
|
16008
|
+
dc.onclose = null;
|
16009
|
+
dc.onclosing = null;
|
16010
|
+
dc.onerror = null;
|
16011
|
+
dc.onmessage = null;
|
16012
|
+
dc.onopen = null;
|
16013
|
+
};
|
16014
|
+
dcCleanup(this.lossyDC);
|
16015
|
+
dcCleanup(this.lossyDCSub);
|
16016
|
+
dcCleanup(this.reliableDC);
|
16017
|
+
dcCleanup(this.reliableDCSub);
|
16018
|
+
this.lossyDC = undefined;
|
16019
|
+
this.lossyDCSub = undefined;
|
16020
|
+
this.reliableDC = undefined;
|
16021
|
+
this.reliableDCSub = undefined;
|
15890
16022
|
});
|
15891
16023
|
}
|
15892
|
-
|
15893
|
-
const _super = Object.create(null, {
|
15894
|
-
restart: {
|
15895
|
-
get: () => super.restart
|
15896
|
-
}
|
15897
|
-
});
|
16024
|
+
cleanupClient() {
|
15898
16025
|
return __awaiter(this, void 0, void 0, function* () {
|
15899
|
-
|
15900
|
-
this.
|
15901
|
-
return track;
|
16026
|
+
yield this.client.close();
|
16027
|
+
this.client.resetCallbacks();
|
15902
16028
|
});
|
15903
16029
|
}
|
15904
|
-
|
15905
|
-
|
15906
|
-
|
15907
|
-
return;
|
15908
|
-
}
|
15909
|
-
if (this.monitorInterval) {
|
15910
|
-
return;
|
16030
|
+
addTrack(req) {
|
16031
|
+
if (this.pendingTrackResolvers[req.cid]) {
|
16032
|
+
throw new TrackInvalidError('a track with the same ID has already been published');
|
15911
16033
|
}
|
15912
|
-
|
15913
|
-
|
15914
|
-
|
15915
|
-
|
15916
|
-
|
15917
|
-
|
15918
|
-
|
15919
|
-
|
15920
|
-
|
15921
|
-
|
15922
|
-
|
15923
|
-
|
15924
|
-
|
15925
|
-
yield this.stopProcessor();
|
15926
|
-
}
|
15927
|
-
const processorOptions = {
|
15928
|
-
kind: this.kind,
|
15929
|
-
track: this._mediaStreamTrack,
|
15930
|
-
audioContext: this.audioContext
|
15931
|
-
};
|
15932
|
-
this.log.debug("setting up audio processor ".concat(processor.name), this.logContext);
|
15933
|
-
yield processor.init(processorOptions);
|
15934
|
-
this.processor = processor;
|
15935
|
-
if (this.processor.processedTrack) {
|
15936
|
-
yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
|
15937
|
-
this.processor.processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
|
15938
|
-
this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
|
16034
|
+
return new Promise((resolve, reject) => {
|
16035
|
+
const publicationTimeout = setTimeout(() => {
|
16036
|
+
delete this.pendingTrackResolvers[req.cid];
|
16037
|
+
reject(new ConnectionError('publication of local track timed out, no response from server'));
|
16038
|
+
}, 10000);
|
16039
|
+
this.pendingTrackResolvers[req.cid] = {
|
16040
|
+
resolve: info => {
|
16041
|
+
clearTimeout(publicationTimeout);
|
16042
|
+
resolve(info);
|
16043
|
+
},
|
16044
|
+
reject: () => {
|
16045
|
+
clearTimeout(publicationTimeout);
|
16046
|
+
reject(new Error('Cancelled publication by calling unpublish'));
|
15939
16047
|
}
|
15940
|
-
|
15941
|
-
|
15942
|
-
|
16048
|
+
};
|
16049
|
+
this.client.sendAddTrack(req);
|
16050
|
+
});
|
16051
|
+
}
|
16052
|
+
/**
|
16053
|
+
* Removes sender from PeerConnection, returning true if it was removed successfully
|
16054
|
+
* and a negotiation is necessary
|
16055
|
+
* @param sender
|
16056
|
+
* @returns
|
16057
|
+
*/
|
16058
|
+
removeTrack(sender) {
|
16059
|
+
if (sender.track && this.pendingTrackResolvers[sender.track.id]) {
|
16060
|
+
const {
|
16061
|
+
reject
|
16062
|
+
} = this.pendingTrackResolvers[sender.track.id];
|
16063
|
+
if (reject) {
|
16064
|
+
reject();
|
15943
16065
|
}
|
16066
|
+
delete this.pendingTrackResolvers[sender.track.id];
|
16067
|
+
}
|
16068
|
+
try {
|
16069
|
+
this.pcManager.removeTrack(sender);
|
16070
|
+
return true;
|
16071
|
+
} catch (e) {
|
16072
|
+
this.log.warn('failed to remove track', Object.assign(Object.assign({}, this.logContext), {
|
16073
|
+
error: e
|
16074
|
+
}));
|
16075
|
+
}
|
16076
|
+
return false;
|
16077
|
+
}
|
16078
|
+
updateMuteStatus(trackSid, muted) {
|
16079
|
+
this.client.sendMuteTrack(trackSid, muted);
|
16080
|
+
}
|
16081
|
+
get dataSubscriberReadyState() {
|
16082
|
+
var _a;
|
16083
|
+
return (_a = this.reliableDCSub) === null || _a === void 0 ? void 0 : _a.readyState;
|
16084
|
+
}
|
16085
|
+
getConnectedServerAddress() {
|
16086
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16087
|
+
var _a;
|
16088
|
+
return (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getConnectedAddress();
|
15944
16089
|
});
|
15945
16090
|
}
|
15946
|
-
|
15947
|
-
|
15948
|
-
|
15949
|
-
*/
|
15950
|
-
setAudioContext(audioContext) {
|
15951
|
-
this.audioContext = audioContext;
|
16091
|
+
/* @internal */
|
16092
|
+
setRegionUrlProvider(provider) {
|
16093
|
+
this.regionUrlProvider = provider;
|
15952
16094
|
}
|
15953
|
-
|
16095
|
+
configure(joinResponse) {
|
15954
16096
|
return __awaiter(this, void 0, void 0, function* () {
|
15955
16097
|
var _a;
|
15956
|
-
|
15957
|
-
|
16098
|
+
// already configured
|
16099
|
+
if (this.pcManager && this.pcManager.currentState !== PCTransportState.NEW) {
|
16100
|
+
return;
|
15958
16101
|
}
|
15959
|
-
|
15960
|
-
|
15961
|
-
|
15962
|
-
|
15963
|
-
|
15964
|
-
|
15965
|
-
|
15966
|
-
|
15967
|
-
|
15968
|
-
|
15969
|
-
|
15970
|
-
|
15971
|
-
|
15972
|
-
|
16102
|
+
this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
|
16103
|
+
const rtcConfig = this.makeRTCConfiguration(joinResponse);
|
16104
|
+
this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
|
16105
|
+
this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
|
16106
|
+
this.pcManager.onIceCandidate = (candidate, target) => {
|
16107
|
+
this.client.sendIceCandidate(candidate, target);
|
16108
|
+
};
|
16109
|
+
this.pcManager.onPublisherOffer = offer => {
|
16110
|
+
this.client.sendOffer(offer);
|
16111
|
+
};
|
16112
|
+
this.pcManager.onDataChannel = this.handleDataChannel;
|
16113
|
+
this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () {
|
16114
|
+
this.log.debug("primary PC state changed ".concat(connectionState), this.logContext);
|
16115
|
+
if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
|
16116
|
+
// reset publisher connection promise
|
16117
|
+
this.publisherConnectionPromise = undefined;
|
16118
|
+
}
|
16119
|
+
if (connectionState === PCTransportState.CONNECTED) {
|
16120
|
+
const shouldEmit = this.pcState === PCState.New;
|
16121
|
+
this.pcState = PCState.Connected;
|
16122
|
+
if (shouldEmit) {
|
16123
|
+
this.emit(EngineEvent.Connected, joinResponse);
|
16124
|
+
}
|
16125
|
+
} else if (connectionState === PCTransportState.FAILED) {
|
16126
|
+
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
16127
|
+
if (this.pcState === PCState.Connected) {
|
16128
|
+
this.pcState = PCState.Disconnected;
|
16129
|
+
this.handleDisconnect('peerconnection failed', subscriberState === 'failed' ? ReconnectReason.RR_SUBSCRIBER_FAILED : ReconnectReason.RR_PUBLISHER_FAILED);
|
16130
|
+
}
|
16131
|
+
}
|
16132
|
+
// detect cases where both signal client and peer connection are severed and assume that user has lost network connection
|
16133
|
+
const isSignalSevered = this.client.isDisconnected || this.client.currentState === SignalConnectionState.RECONNECTING;
|
16134
|
+
const isPCSevered = [PCTransportState.FAILED, PCTransportState.CLOSING, PCTransportState.CLOSED].includes(connectionState);
|
16135
|
+
if (isSignalSevered && isPCSevered && !this._isClosed) {
|
16136
|
+
this.emit(EngineEvent.Offline);
|
15973
16137
|
}
|
15974
16138
|
});
|
15975
|
-
|
16139
|
+
this.pcManager.onTrack = ev => {
|
16140
|
+
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
16141
|
+
};
|
16142
|
+
this.createDataChannels();
|
15976
16143
|
});
|
15977
16144
|
}
|
15978
|
-
|
15979
|
-
|
15980
|
-
|
15981
|
-
if (
|
15982
|
-
|
15983
|
-
this.log.warn('silence detected on local audio track', this.logContext);
|
15984
|
-
}
|
15985
|
-
this.emit(TrackEvent.AudioSilenceDetected);
|
16145
|
+
setupSignalClientCallbacks() {
|
16146
|
+
// configure signaling client
|
16147
|
+
this.client.onAnswer = sd => __awaiter(this, void 0, void 0, function* () {
|
16148
|
+
if (!this.pcManager) {
|
16149
|
+
return;
|
15986
16150
|
}
|
15987
|
-
|
16151
|
+
this.log.debug('received server answer', Object.assign(Object.assign({}, this.logContext), {
|
16152
|
+
RTCSdpType: sd.type
|
16153
|
+
}));
|
16154
|
+
yield this.pcManager.setPublisherAnswer(sd);
|
15988
16155
|
});
|
15989
|
-
|
15990
|
-
|
15991
|
-
|
15992
|
-
|
15993
|
-
|
15994
|
-
|
15995
|
-
|
15996
|
-
|
15997
|
-
|
15998
|
-
|
15999
|
-
|
16000
|
-
|
16001
|
-
|
16002
|
-
|
16003
|
-
|
16004
|
-
|
16005
|
-
|
16006
|
-
|
16007
|
-
|
16008
|
-
|
16009
|
-
|
16010
|
-
|
16011
|
-
|
16012
|
-
|
16013
|
-
|
16014
|
-
|
16015
|
-
|
16016
|
-
|
16017
|
-
|
16018
|
-
|
16019
|
-
|
16020
|
-
|
16021
|
-
|
16022
|
-
|
16023
|
-
|
16024
|
-
|
16025
|
-
|
16026
|
-
|
16027
|
-
|
16028
|
-
|
16029
|
-
|
16030
|
-
|
16031
|
-
|
16032
|
-
|
16033
|
-
|
16034
|
-
|
16035
|
-
|
16036
|
-
|
16037
|
-
|
16038
|
-
|
16039
|
-
|
16040
|
-
|
16041
|
-
|
16042
|
-
|
16043
|
-
|
16044
|
-
|
16045
|
-
|
16046
|
-
|
16047
|
-
|
16048
|
-
|
16049
|
-
}
|
16050
|
-
const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
|
16051
|
-
const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
|
16052
|
-
const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
|
16053
|
-
if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
|
16054
|
-
// when we aren't simulcasting or svc, will need to return a single encoding without
|
16055
|
-
// capping bandwidth. we always require a encoding for dynacast
|
16056
|
-
return [{}];
|
16057
|
-
}
|
16058
|
-
if (!videoEncoding) {
|
16059
|
-
// find the right encoding based on width/height
|
16060
|
-
videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
|
16061
|
-
livekitLogger.debug('using video encoding', videoEncoding);
|
16062
|
-
}
|
16063
|
-
const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
|
16064
|
-
if (scalabilityMode && isSVCCodec(videoCodec)) {
|
16065
|
-
const sm = new ScalabilityMode(scalabilityMode);
|
16066
|
-
const encodings = [];
|
16067
|
-
if (sm.spatial > 3) {
|
16068
|
-
throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
|
16069
|
-
}
|
16070
|
-
// Before M113 in Chrome, defining multiple encodings with an SVC codec indicated
|
16071
|
-
// that SVC mode should be used. Safari still works this way.
|
16072
|
-
// This is a bit confusing but is due to how libwebrtc interpreted the encodings field
|
16073
|
-
// before M113.
|
16074
|
-
// Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
|
16075
|
-
const browser = getBrowser();
|
16076
|
-
if (isSafari() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) {
|
16077
|
-
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
16078
|
-
for (let i = 0; i < sm.spatial; i += 1) {
|
16079
|
-
// in legacy SVC, scaleResolutionDownBy cannot be set
|
16080
|
-
encodings.push({
|
16081
|
-
rid: videoRids[2 - i],
|
16082
|
-
maxBitrate: videoEncoding.maxBitrate / Math.pow(bitratesRatio, i),
|
16083
|
-
maxFramerate: original.encoding.maxFramerate
|
16084
|
-
});
|
16156
|
+
// add candidate on trickle
|
16157
|
+
this.client.onTrickle = (candidate, target) => {
|
16158
|
+
if (!this.pcManager) {
|
16159
|
+
return;
|
16160
|
+
}
|
16161
|
+
this.log.trace('got ICE candidate from peer', Object.assign(Object.assign({}, this.logContext), {
|
16162
|
+
candidate,
|
16163
|
+
target
|
16164
|
+
}));
|
16165
|
+
this.pcManager.addIceCandidate(candidate, target);
|
16166
|
+
};
|
16167
|
+
// when server creates an offer for the client
|
16168
|
+
this.client.onOffer = sd => __awaiter(this, void 0, void 0, function* () {
|
16169
|
+
if (!this.pcManager) {
|
16170
|
+
return;
|
16171
|
+
}
|
16172
|
+
const answer = yield this.pcManager.createSubscriberAnswerFromOffer(sd);
|
16173
|
+
this.client.sendAnswer(answer);
|
16174
|
+
});
|
16175
|
+
this.client.onLocalTrackPublished = res => {
|
16176
|
+
var _a;
|
16177
|
+
this.log.debug('received trackPublishedResponse', Object.assign(Object.assign({}, this.logContext), {
|
16178
|
+
cid: res.cid,
|
16179
|
+
track: (_a = res.track) === null || _a === void 0 ? void 0 : _a.sid
|
16180
|
+
}));
|
16181
|
+
if (!this.pendingTrackResolvers[res.cid]) {
|
16182
|
+
this.log.error("missing track resolver for ".concat(res.cid), Object.assign(Object.assign({}, this.logContext), {
|
16183
|
+
cid: res.cid
|
16184
|
+
}));
|
16185
|
+
return;
|
16186
|
+
}
|
16187
|
+
const {
|
16188
|
+
resolve
|
16189
|
+
} = this.pendingTrackResolvers[res.cid];
|
16190
|
+
delete this.pendingTrackResolvers[res.cid];
|
16191
|
+
resolve(res.track);
|
16192
|
+
};
|
16193
|
+
this.client.onLocalTrackUnpublished = response => {
|
16194
|
+
this.emit(EngineEvent.LocalTrackUnpublished, response);
|
16195
|
+
};
|
16196
|
+
this.client.onTokenRefresh = token => {
|
16197
|
+
this.token = token;
|
16198
|
+
};
|
16199
|
+
this.client.onRemoteMuteChanged = (trackSid, muted) => {
|
16200
|
+
this.emit(EngineEvent.RemoteMute, trackSid, muted);
|
16201
|
+
};
|
16202
|
+
this.client.onSubscribedQualityUpdate = update => {
|
16203
|
+
this.emit(EngineEvent.SubscribedQualityUpdate, update);
|
16204
|
+
};
|
16205
|
+
this.client.onClose = () => {
|
16206
|
+
this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
16207
|
+
};
|
16208
|
+
this.client.onLeave = leave => {
|
16209
|
+
if (leave === null || leave === void 0 ? void 0 : leave.canReconnect) {
|
16210
|
+
this.fullReconnectOnNext = true;
|
16211
|
+
// reconnect immediately instead of waiting for next attempt
|
16212
|
+
this.handleDisconnect(leaveReconnect);
|
16213
|
+
} else {
|
16214
|
+
this.emit(EngineEvent.Disconnected, leave === null || leave === void 0 ? void 0 : leave.reason);
|
16215
|
+
this.close();
|
16085
16216
|
}
|
16086
|
-
|
16087
|
-
|
16088
|
-
|
16089
|
-
}
|
16090
|
-
encodings.push({
|
16091
|
-
maxBitrate: videoEncoding.maxBitrate,
|
16092
|
-
maxFramerate: original.encoding.maxFramerate,
|
16093
|
-
/* @ts-ignore */
|
16094
|
-
scalabilityMode: scalabilityMode
|
16095
|
-
});
|
16096
|
-
}
|
16097
|
-
livekitLogger.debug("using svc encoding", {
|
16098
|
-
encodings
|
16099
|
-
});
|
16100
|
-
return encodings;
|
16101
|
-
}
|
16102
|
-
if (!useSimulcast) {
|
16103
|
-
return [videoEncoding];
|
16104
|
-
}
|
16105
|
-
let presets = [];
|
16106
|
-
if (isScreenShare) {
|
16107
|
-
presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
|
16108
|
-
} else {
|
16109
|
-
presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
|
16217
|
+
this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
|
16218
|
+
reason: leave === null || leave === void 0 ? void 0 : leave.reason
|
16219
|
+
}));
|
16220
|
+
};
|
16110
16221
|
}
|
16111
|
-
|
16112
|
-
|
16113
|
-
const
|
16114
|
-
if (
|
16115
|
-
|
16222
|
+
makeRTCConfiguration(serverResponse) {
|
16223
|
+
var _a;
|
16224
|
+
const rtcConfig = Object.assign({}, this.rtcConfig);
|
16225
|
+
if ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled) {
|
16226
|
+
this.log.debug('E2EE - setting up transports with insertable streams', this.logContext);
|
16227
|
+
// this makes sure that no data is sent before the transforms are ready
|
16228
|
+
// @ts-ignore
|
16229
|
+
rtcConfig.encodedInsertableStreams = true;
|
16116
16230
|
}
|
16117
|
-
//
|
16118
|
-
|
16119
|
-
|
16120
|
-
|
16121
|
-
|
16122
|
-
|
16123
|
-
|
16124
|
-
|
16125
|
-
|
16126
|
-
|
16127
|
-
|
16128
|
-
|
16231
|
+
// update ICE servers before creating PeerConnection
|
16232
|
+
if (serverResponse.iceServers && !rtcConfig.iceServers) {
|
16233
|
+
const rtcIceServers = [];
|
16234
|
+
serverResponse.iceServers.forEach(iceServer => {
|
16235
|
+
const rtcIceServer = {
|
16236
|
+
urls: iceServer.urls
|
16237
|
+
};
|
16238
|
+
if (iceServer.username) rtcIceServer.username = iceServer.username;
|
16239
|
+
if (iceServer.credential) {
|
16240
|
+
rtcIceServer.credential = iceServer.credential;
|
16241
|
+
}
|
16242
|
+
rtcIceServers.push(rtcIceServer);
|
16243
|
+
});
|
16244
|
+
rtcConfig.iceServers = rtcIceServers;
|
16129
16245
|
}
|
16130
|
-
if (
|
16131
|
-
|
16246
|
+
if (serverResponse.clientConfiguration && serverResponse.clientConfiguration.forceRelay === ClientConfigSetting.ENABLED) {
|
16247
|
+
rtcConfig.iceTransportPolicy = 'relay';
|
16132
16248
|
}
|
16249
|
+
// @ts-ignore
|
16250
|
+
rtcConfig.sdpSemantics = 'unified-plan';
|
16251
|
+
// @ts-ignore
|
16252
|
+
rtcConfig.continualGatheringPolicy = 'gather_continually';
|
16253
|
+
return rtcConfig;
|
16133
16254
|
}
|
16134
|
-
|
16135
|
-
|
16136
|
-
|
16137
|
-
var _a, _b, _c, _d;
|
16138
|
-
// backupCodec should not be true anymore, default codec is set in LocalParticipant.publish
|
16139
|
-
if (!opts.backupCodec || opts.backupCodec === true || opts.backupCodec.codec === opts.videoCodec) {
|
16140
|
-
// backup codec publishing is disabled
|
16141
|
-
return;
|
16142
|
-
}
|
16143
|
-
if (videoCodec !== opts.backupCodec.codec) {
|
16144
|
-
livekitLogger.warn('requested a different codec than specified as backup', {
|
16145
|
-
serverRequested: videoCodec,
|
16146
|
-
backup: opts.backupCodec.codec
|
16147
|
-
});
|
16148
|
-
}
|
16149
|
-
opts.videoCodec = videoCodec;
|
16150
|
-
// use backup encoding setting as videoEncoding for backup codec publishing
|
16151
|
-
opts.videoEncoding = opts.backupCodec.encoding;
|
16152
|
-
const settings = track.mediaStreamTrack.getSettings();
|
16153
|
-
const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
|
16154
|
-
const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
|
16155
|
-
const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
|
16156
|
-
return encodings;
|
16157
|
-
}
|
16158
|
-
/* @internal */
|
16159
|
-
function determineAppropriateEncoding(isScreenShare, width, height, codec) {
|
16160
|
-
const presets = presetsForResolution(isScreenShare, width, height);
|
16161
|
-
let {
|
16162
|
-
encoding
|
16163
|
-
} = presets[0];
|
16164
|
-
// handle portrait by swapping dimensions
|
16165
|
-
const size = Math.max(width, height);
|
16166
|
-
for (let i = 0; i < presets.length; i += 1) {
|
16167
|
-
const preset = presets[i];
|
16168
|
-
encoding = preset.encoding;
|
16169
|
-
if (preset.width >= size) {
|
16170
|
-
break;
|
16255
|
+
createDataChannels() {
|
16256
|
+
if (!this.pcManager) {
|
16257
|
+
return;
|
16171
16258
|
}
|
16172
|
-
|
16173
|
-
|
16174
|
-
|
16175
|
-
|
16176
|
-
// NOTE: SVC codec bitrates are inclusive of all scalability layers. while
|
16177
|
-
// bitrate for non-SVC codecs does not include other simulcast layers.
|
16178
|
-
if (codec) {
|
16179
|
-
switch (codec) {
|
16180
|
-
case 'av1':
|
16181
|
-
encoding = Object.assign({}, encoding);
|
16182
|
-
encoding.maxBitrate = encoding.maxBitrate * 0.7;
|
16183
|
-
break;
|
16184
|
-
case 'vp9':
|
16185
|
-
encoding = Object.assign({}, encoding);
|
16186
|
-
encoding.maxBitrate = encoding.maxBitrate * 0.85;
|
16187
|
-
break;
|
16259
|
+
// clear old data channel callbacks if recreate
|
16260
|
+
if (this.lossyDC) {
|
16261
|
+
this.lossyDC.onmessage = null;
|
16262
|
+
this.lossyDC.onerror = null;
|
16188
16263
|
}
|
16264
|
+
if (this.reliableDC) {
|
16265
|
+
this.reliableDC.onmessage = null;
|
16266
|
+
this.reliableDC.onerror = null;
|
16267
|
+
}
|
16268
|
+
// create data channels
|
16269
|
+
this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
|
16270
|
+
// will drop older packets that arrive
|
16271
|
+
ordered: true,
|
16272
|
+
maxRetransmits: 0
|
16273
|
+
});
|
16274
|
+
this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
|
16275
|
+
ordered: true
|
16276
|
+
});
|
16277
|
+
// also handle messages over the pub channel, for backwards compatibility
|
16278
|
+
this.lossyDC.onmessage = this.handleDataMessage;
|
16279
|
+
this.reliableDC.onmessage = this.handleDataMessage;
|
16280
|
+
// handle datachannel errors
|
16281
|
+
this.lossyDC.onerror = this.handleDataError;
|
16282
|
+
this.reliableDC.onerror = this.handleDataError;
|
16283
|
+
// set up dc buffer threshold, set to 64kB (otherwise 0 by default)
|
16284
|
+
this.lossyDC.bufferedAmountLowThreshold = 65535;
|
16285
|
+
this.reliableDC.bufferedAmountLowThreshold = 65535;
|
16286
|
+
// handle buffer amount low events
|
16287
|
+
this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
16288
|
+
this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
16189
16289
|
}
|
16190
|
-
|
16191
|
-
|
16192
|
-
|
16193
|
-
|
16194
|
-
|
16195
|
-
|
16196
|
-
|
16197
|
-
|
16198
|
-
|
16199
|
-
|
16200
|
-
|
16201
|
-
|
16202
|
-
}
|
16203
|
-
/* @internal */
|
16204
|
-
function defaultSimulcastLayers(isScreenShare, original) {
|
16205
|
-
if (isScreenShare) {
|
16206
|
-
return computeDefaultScreenShareSimulcastPresets(original);
|
16290
|
+
createSender(track, opts, encodings) {
|
16291
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16292
|
+
if (supportsTransceiver()) {
|
16293
|
+
const sender = yield this.createTransceiverRTCRtpSender(track, opts, encodings);
|
16294
|
+
return sender;
|
16295
|
+
}
|
16296
|
+
if (supportsAddTrack()) {
|
16297
|
+
this.log.warn('using add-track fallback', this.logContext);
|
16298
|
+
const sender = yield this.createRTCRtpSender(track.mediaStreamTrack);
|
16299
|
+
return sender;
|
16300
|
+
}
|
16301
|
+
throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
|
16302
|
+
});
|
16207
16303
|
}
|
16208
|
-
|
16209
|
-
|
16210
|
-
|
16211
|
-
|
16212
|
-
|
16213
|
-
|
16214
|
-
|
16304
|
+
createSimulcastSender(track, simulcastTrack, opts, encodings) {
|
16305
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16306
|
+
// store RTCRtpSender
|
16307
|
+
if (supportsTransceiver()) {
|
16308
|
+
return this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
|
16309
|
+
}
|
16310
|
+
if (supportsAddTrack()) {
|
16311
|
+
this.log.debug('using add-track fallback', this.logContext);
|
16312
|
+
return this.createRTCRtpSender(track.mediaStreamTrack);
|
16313
|
+
}
|
16314
|
+
throw new UnexpectedConnectionState('Cannot stream on this device');
|
16315
|
+
});
|
16215
16316
|
}
|
16216
|
-
|
16217
|
-
|
16218
|
-
|
16219
|
-
|
16220
|
-
|
16221
|
-
|
16222
|
-
|
16223
|
-
|
16224
|
-
|
16225
|
-
|
16226
|
-
|
16227
|
-
const encoding = {
|
16228
|
-
rid,
|
16229
|
-
scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
|
16230
|
-
maxBitrate: preset.encoding.maxBitrate
|
16231
|
-
};
|
16232
|
-
if (preset.encoding.maxFramerate) {
|
16233
|
-
encoding.maxFramerate = preset.encoding.maxFramerate;
|
16234
|
-
}
|
16235
|
-
const canSetPriority = isFireFox() || idx === 0;
|
16236
|
-
if (preset.encoding.priority && canSetPriority) {
|
16237
|
-
encoding.priority = preset.encoding.priority;
|
16238
|
-
encoding.networkPriority = preset.encoding.priority;
|
16239
|
-
}
|
16240
|
-
encodings.push(encoding);
|
16241
|
-
});
|
16242
|
-
// RN ios simulcast requires all same framerates.
|
16243
|
-
if (isReactNative() && getReactNativeOs() === 'ios') {
|
16244
|
-
let topFramerate = undefined;
|
16245
|
-
encodings.forEach(encoding => {
|
16246
|
-
if (!topFramerate) {
|
16247
|
-
topFramerate = encoding.maxFramerate;
|
16248
|
-
} else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
|
16249
|
-
topFramerate = encoding.maxFramerate;
|
16317
|
+
createTransceiverRTCRtpSender(track, opts, encodings) {
|
16318
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16319
|
+
if (!this.pcManager) {
|
16320
|
+
throw new UnexpectedConnectionState('publisher is closed');
|
16321
|
+
}
|
16322
|
+
const streams = [];
|
16323
|
+
if (track.mediaStream) {
|
16324
|
+
streams.push(track.mediaStream);
|
16325
|
+
}
|
16326
|
+
if (track instanceof LocalVideoTrack) {
|
16327
|
+
track.codec = opts.videoCodec;
|
16250
16328
|
}
|
16251
|
-
|
16252
|
-
|
16253
|
-
|
16254
|
-
|
16255
|
-
if (
|
16256
|
-
|
16257
|
-
notifyOnce = false;
|
16258
|
-
livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
|
16259
|
-
}
|
16260
|
-
livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
|
16261
|
-
encoding.maxFramerate = topFramerate;
|
16329
|
+
const transceiverInit = {
|
16330
|
+
direction: 'sendonly',
|
16331
|
+
streams
|
16332
|
+
};
|
16333
|
+
if (encodings) {
|
16334
|
+
transceiverInit.sendEncodings = encodings;
|
16262
16335
|
}
|
16336
|
+
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
16337
|
+
const transceiver = yield this.pcManager.addPublisherTransceiver(track.mediaStreamTrack, transceiverInit);
|
16338
|
+
return transceiver.sender;
|
16263
16339
|
});
|
16264
16340
|
}
|
16265
|
-
|
16266
|
-
|
16267
|
-
|
16268
|
-
|
16269
|
-
if (!presets) return;
|
16270
|
-
return presets.sort((a, b) => {
|
16271
|
-
const {
|
16272
|
-
encoding: aEnc
|
16273
|
-
} = a;
|
16274
|
-
const {
|
16275
|
-
encoding: bEnc
|
16276
|
-
} = b;
|
16277
|
-
if (aEnc.maxBitrate > bEnc.maxBitrate) {
|
16278
|
-
return 1;
|
16279
|
-
}
|
16280
|
-
if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
|
16281
|
-
if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
|
16282
|
-
return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
|
16283
|
-
}
|
16284
|
-
return 0;
|
16285
|
-
});
|
16286
|
-
}
|
16287
|
-
/** @internal */
|
16288
|
-
class ScalabilityMode {
|
16289
|
-
constructor(scalabilityMode) {
|
16290
|
-
const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
|
16291
|
-
if (!results) {
|
16292
|
-
throw new Error('invalid scalability mode');
|
16293
|
-
}
|
16294
|
-
this.spatial = parseInt(results[1]);
|
16295
|
-
this.temporal = parseInt(results[2]);
|
16296
|
-
if (results.length > 3) {
|
16297
|
-
switch (results[3]) {
|
16298
|
-
case 'h':
|
16299
|
-
case '_KEY':
|
16300
|
-
case '_KEY_SHIFT':
|
16301
|
-
this.suffix = results[3];
|
16341
|
+
createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings) {
|
16342
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16343
|
+
if (!this.pcManager) {
|
16344
|
+
throw new UnexpectedConnectionState('publisher is closed');
|
16302
16345
|
}
|
16303
|
-
|
16346
|
+
const transceiverInit = {
|
16347
|
+
direction: 'sendonly'
|
16348
|
+
};
|
16349
|
+
if (encodings) {
|
16350
|
+
transceiverInit.sendEncodings = encodings;
|
16351
|
+
}
|
16352
|
+
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
16353
|
+
const transceiver = yield this.pcManager.addPublisherTransceiver(simulcastTrack.mediaStreamTrack, transceiverInit);
|
16354
|
+
if (!opts.videoCodec) {
|
16355
|
+
return;
|
16356
|
+
}
|
16357
|
+
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
16358
|
+
return transceiver.sender;
|
16359
|
+
});
|
16304
16360
|
}
|
16305
|
-
|
16306
|
-
|
16307
|
-
|
16361
|
+
createRTCRtpSender(track) {
|
16362
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16363
|
+
if (!this.pcManager) {
|
16364
|
+
throw new UnexpectedConnectionState('publisher is closed');
|
16365
|
+
}
|
16366
|
+
return this.pcManager.addPublisherTrack(track);
|
16367
|
+
});
|
16308
16368
|
}
|
16309
|
-
|
16310
|
-
|
16311
|
-
|
16312
|
-
|
16313
|
-
/**
|
16314
|
-
*
|
16315
|
-
* @param mediaTrack
|
16316
|
-
* @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
|
16317
|
-
* @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
|
16318
|
-
*/
|
16319
|
-
constructor(mediaTrack, constraints) {
|
16320
|
-
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
16321
|
-
let loggerOptions = arguments.length > 3 ? arguments[3] : undefined;
|
16322
|
-
super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack, loggerOptions);
|
16323
|
-
/* @internal */
|
16324
|
-
this.simulcastCodecs = new Map();
|
16325
|
-
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
16326
|
-
if (!this.sender) {
|
16327
|
-
this._currentBitrate = 0;
|
16369
|
+
attemptReconnect(reason) {
|
16370
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16371
|
+
var _a, _b, _c;
|
16372
|
+
if (this._isClosed) {
|
16328
16373
|
return;
|
16329
16374
|
}
|
16330
|
-
|
16331
|
-
|
16332
|
-
|
16333
|
-
} catch (e) {
|
16334
|
-
this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
|
16335
|
-
error: e
|
16336
|
-
}));
|
16375
|
+
// guard for attempting reconnection multiple times while one attempt is still not finished
|
16376
|
+
if (this.attemptingReconnect) {
|
16377
|
+
livekitLogger.warn('already attempting reconnect, returning early', this.logContext);
|
16337
16378
|
return;
|
16338
16379
|
}
|
16339
|
-
|
16340
|
-
|
16341
|
-
|
16342
|
-
|
16343
|
-
|
16344
|
-
|
16345
|
-
|
16346
|
-
|
16347
|
-
this.
|
16380
|
+
if (((_a = this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED ||
|
16381
|
+
// signaling state could change to closed due to hardware sleep
|
16382
|
+
// those connections cannot be resumed
|
16383
|
+
((_c = (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.currentState) !== null && _c !== void 0 ? _c : PCTransportState.NEW) === PCTransportState.NEW) {
|
16384
|
+
this.fullReconnectOnNext = true;
|
16385
|
+
}
|
16386
|
+
try {
|
16387
|
+
this.attemptingReconnect = true;
|
16388
|
+
if (this.fullReconnectOnNext) {
|
16389
|
+
yield this.restartConnection();
|
16390
|
+
} else {
|
16391
|
+
yield this.resumeConnection(reason);
|
16392
|
+
}
|
16393
|
+
this.clearPendingReconnect();
|
16394
|
+
this.fullReconnectOnNext = false;
|
16395
|
+
} catch (e) {
|
16396
|
+
this.reconnectAttempts += 1;
|
16397
|
+
let recoverable = true;
|
16398
|
+
if (e instanceof UnexpectedConnectionState) {
|
16399
|
+
this.log.debug('received unrecoverable error', Object.assign(Object.assign({}, this.logContext), {
|
16400
|
+
error: e
|
16401
|
+
}));
|
16402
|
+
// unrecoverable
|
16403
|
+
recoverable = false;
|
16404
|
+
} else if (!(e instanceof SignalReconnectError)) {
|
16405
|
+
// cannot resume
|
16406
|
+
this.fullReconnectOnNext = true;
|
16407
|
+
}
|
16408
|
+
if (recoverable) {
|
16409
|
+
this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN);
|
16410
|
+
} else {
|
16411
|
+
this.log.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up"), this.logContext);
|
16412
|
+
this.emit(EngineEvent.Disconnected);
|
16413
|
+
yield this.close();
|
16414
|
+
}
|
16415
|
+
} finally {
|
16416
|
+
this.attemptingReconnect = false;
|
16348
16417
|
}
|
16349
|
-
this.prevStats = statsMap;
|
16350
16418
|
});
|
16351
|
-
this.senderLock = new Mutex();
|
16352
|
-
}
|
16353
|
-
get isSimulcast() {
|
16354
|
-
if (this.sender && this.sender.getParameters().encodings.length > 1) {
|
16355
|
-
return true;
|
16356
|
-
}
|
16357
|
-
return false;
|
16358
16419
|
}
|
16359
|
-
|
16360
|
-
|
16361
|
-
|
16362
|
-
|
16363
|
-
|
16364
|
-
|
16365
|
-
|
16366
|
-
// save original encodings
|
16367
|
-
// TODO : merge simulcast tracks stats
|
16368
|
-
const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
|
16369
|
-
if (params) {
|
16370
|
-
this.encodings = params.encodings;
|
16371
|
-
}
|
16372
|
-
if (this.monitorInterval) {
|
16373
|
-
return;
|
16420
|
+
getNextRetryDelay(context) {
|
16421
|
+
try {
|
16422
|
+
return this.reconnectPolicy.nextRetryDelayInMs(context);
|
16423
|
+
} catch (e) {
|
16424
|
+
this.log.warn('encountered error in reconnect policy', Object.assign(Object.assign({}, this.logContext), {
|
16425
|
+
error: e
|
16426
|
+
}));
|
16374
16427
|
}
|
16375
|
-
|
16376
|
-
|
16377
|
-
}, monitorFrequency);
|
16378
|
-
}
|
16379
|
-
stop() {
|
16380
|
-
this._mediaStreamTrack.getConstraints();
|
16381
|
-
this.simulcastCodecs.forEach(trackInfo => {
|
16382
|
-
trackInfo.mediaStreamTrack.stop();
|
16383
|
-
});
|
16384
|
-
super.stop();
|
16428
|
+
// error in user code with provided reconnect policy, stop reconnecting
|
16429
|
+
return null;
|
16385
16430
|
}
|
16386
|
-
|
16387
|
-
const _super = Object.create(null, {
|
16388
|
-
pauseUpstream: {
|
16389
|
-
get: () => super.pauseUpstream
|
16390
|
-
}
|
16391
|
-
});
|
16431
|
+
restartConnection(regionUrl) {
|
16392
16432
|
return __awaiter(this, void 0, void 0, function* () {
|
16393
|
-
var _a,
|
16394
|
-
var _d;
|
16395
|
-
yield _super.pauseUpstream.call(this);
|
16433
|
+
var _a, _b, _c;
|
16396
16434
|
try {
|
16397
|
-
|
16398
|
-
|
16399
|
-
|
16400
|
-
|
16401
|
-
|
16435
|
+
if (!this.url || !this.token) {
|
16436
|
+
// permanent failure, don't attempt reconnection
|
16437
|
+
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
16438
|
+
}
|
16439
|
+
this.log.info("reconnecting, attempt: ".concat(this.reconnectAttempts), this.logContext);
|
16440
|
+
this.emit(EngineEvent.Restarting);
|
16441
|
+
if (!this.client.isDisconnected) {
|
16442
|
+
yield this.client.sendLeave();
|
16443
|
+
}
|
16444
|
+
yield this.cleanupPeerConnections();
|
16445
|
+
yield this.cleanupClient();
|
16446
|
+
let joinResponse;
|
16447
|
+
try {
|
16448
|
+
if (!this.signalOpts) {
|
16449
|
+
this.log.warn('attempted connection restart, without signal options present', this.logContext);
|
16450
|
+
throw new SignalReconnectError();
|
16451
|
+
}
|
16452
|
+
// in case a regionUrl is passed, the region URL takes precedence
|
16453
|
+
joinResponse = yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts);
|
16454
|
+
} catch (e) {
|
16455
|
+
if (e instanceof ConnectionError && e.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
|
16456
|
+
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
16457
|
+
}
|
16458
|
+
throw new SignalReconnectError();
|
16459
|
+
}
|
16460
|
+
if (this.shouldFailNext) {
|
16461
|
+
this.shouldFailNext = false;
|
16462
|
+
throw new Error('simulated failure');
|
16463
|
+
}
|
16464
|
+
this.client.setReconnected();
|
16465
|
+
this.emit(EngineEvent.SignalRestarted, joinResponse);
|
16466
|
+
yield this.waitForPCReconnected();
|
16467
|
+
// re-check signal connection state before setting engine as resumed
|
16468
|
+
if (this.client.currentState !== SignalConnectionState.CONNECTED) {
|
16469
|
+
throw new SignalReconnectError('Signal connection got severed during reconnect');
|
16402
16470
|
}
|
16403
|
-
|
16404
|
-
|
16405
|
-
|
16406
|
-
|
16407
|
-
|
16408
|
-
|
16409
|
-
|
16410
|
-
|
16411
|
-
|
16471
|
+
(_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
|
16472
|
+
// reconnect success
|
16473
|
+
this.emit(EngineEvent.Restarted);
|
16474
|
+
} catch (error) {
|
16475
|
+
const nextRegionUrl = yield (_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.getNextBestRegionUrl();
|
16476
|
+
if (nextRegionUrl) {
|
16477
|
+
yield this.restartConnection(nextRegionUrl);
|
16478
|
+
return;
|
16479
|
+
} else {
|
16480
|
+
// no more regions to try (or we're not on cloud)
|
16481
|
+
(_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
|
16482
|
+
throw error;
|
16412
16483
|
}
|
16413
16484
|
}
|
16414
16485
|
});
|
16415
16486
|
}
|
16416
|
-
|
16417
|
-
const _super = Object.create(null, {
|
16418
|
-
resumeUpstream: {
|
16419
|
-
get: () => super.resumeUpstream
|
16420
|
-
}
|
16421
|
-
});
|
16487
|
+
resumeConnection(reason) {
|
16422
16488
|
return __awaiter(this, void 0, void 0, function* () {
|
16423
|
-
var _a
|
16424
|
-
|
16425
|
-
|
16426
|
-
|
16427
|
-
for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
|
16428
|
-
_c = _g.value;
|
16429
|
-
_e = false;
|
16430
|
-
const sc = _c;
|
16431
|
-
yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
|
16432
|
-
}
|
16433
|
-
} catch (e_2_1) {
|
16434
|
-
e_2 = {
|
16435
|
-
error: e_2_1
|
16436
|
-
};
|
16437
|
-
} finally {
|
16438
|
-
try {
|
16439
|
-
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
|
16440
|
-
} finally {
|
16441
|
-
if (e_2) throw e_2.error;
|
16442
|
-
}
|
16489
|
+
var _a;
|
16490
|
+
if (!this.url || !this.token) {
|
16491
|
+
// permanent failure, don't attempt reconnection
|
16492
|
+
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
16443
16493
|
}
|
16444
|
-
|
16445
|
-
|
16446
|
-
|
16447
|
-
const _super = Object.create(null, {
|
16448
|
-
mute: {
|
16449
|
-
get: () => super.mute
|
16494
|
+
// trigger publisher reconnect
|
16495
|
+
if (!this.pcManager) {
|
16496
|
+
throw new UnexpectedConnectionState('publisher and subscriber connections unset');
|
16450
16497
|
}
|
16451
|
-
|
16452
|
-
|
16453
|
-
|
16498
|
+
this.log.info("resuming signal connection, attempt ".concat(this.reconnectAttempts), this.logContext);
|
16499
|
+
this.emit(EngineEvent.Resuming);
|
16500
|
+
let res;
|
16454
16501
|
try {
|
16455
|
-
|
16456
|
-
|
16457
|
-
|
16502
|
+
this.setupSignalClientCallbacks();
|
16503
|
+
res = yield this.client.reconnect(this.url, this.token, this.participantSid, reason);
|
16504
|
+
} catch (error) {
|
16505
|
+
let message = '';
|
16506
|
+
if (error instanceof Error) {
|
16507
|
+
message = error.message;
|
16508
|
+
this.log.error(error.message, Object.assign(Object.assign({}, this.logContext), {
|
16509
|
+
error
|
16510
|
+
}));
|
16458
16511
|
}
|
16459
|
-
if (
|
16460
|
-
|
16461
|
-
// also stop the track, so that camera indicator is turned off
|
16462
|
-
this._mediaStreamTrack.stop();
|
16512
|
+
if (error instanceof ConnectionError && error.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
|
16513
|
+
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
16463
16514
|
}
|
16464
|
-
|
16465
|
-
|
16466
|
-
|
16467
|
-
|
16515
|
+
if (error instanceof ConnectionError && error.reason === 4 /* ConnectionErrorReason.LeaveRequest */) {
|
16516
|
+
throw error;
|
16517
|
+
}
|
16518
|
+
throw new SignalReconnectError(message);
|
16519
|
+
}
|
16520
|
+
this.emit(EngineEvent.SignalResumed);
|
16521
|
+
if (res) {
|
16522
|
+
const rtcConfig = this.makeRTCConfiguration(res);
|
16523
|
+
this.pcManager.updateConfiguration(rtcConfig);
|
16524
|
+
} else {
|
16525
|
+
this.log.warn('Did not receive reconnect response', this.logContext);
|
16526
|
+
}
|
16527
|
+
if (this.shouldFailNext) {
|
16528
|
+
this.shouldFailNext = false;
|
16529
|
+
throw new Error('simulated failure');
|
16530
|
+
}
|
16531
|
+
yield this.pcManager.triggerIceRestart();
|
16532
|
+
yield this.waitForPCReconnected();
|
16533
|
+
// re-check signal connection state before setting engine as resumed
|
16534
|
+
if (this.client.currentState !== SignalConnectionState.CONNECTED) {
|
16535
|
+
throw new SignalReconnectError('Signal connection got severed during reconnect');
|
16468
16536
|
}
|
16537
|
+
this.client.setReconnected();
|
16538
|
+
// recreate publish datachannel if it's id is null
|
16539
|
+
// (for safari https://bugs.webkit.org/show_bug.cgi?id=184688)
|
16540
|
+
if (((_a = this.reliableDC) === null || _a === void 0 ? void 0 : _a.readyState) === 'open' && this.reliableDC.id === null) {
|
16541
|
+
this.createDataChannels();
|
16542
|
+
}
|
16543
|
+
// resume success
|
16544
|
+
this.emit(EngineEvent.Resumed);
|
16469
16545
|
});
|
16470
16546
|
}
|
16471
|
-
|
16472
|
-
|
16473
|
-
|
16474
|
-
|
16547
|
+
waitForPCInitialConnection(timeout, abortController) {
|
16548
|
+
return __awaiter(this, void 0, void 0, function* () {
|
16549
|
+
if (!this.pcManager) {
|
16550
|
+
throw new UnexpectedConnectionState('PC manager is closed');
|
16475
16551
|
}
|
16552
|
+
yield this.pcManager.ensurePCTransportConnection(abortController, timeout);
|
16476
16553
|
});
|
16554
|
+
}
|
16555
|
+
waitForPCReconnected() {
|
16477
16556
|
return __awaiter(this, void 0, void 0, function* () {
|
16478
|
-
|
16557
|
+
this.pcState = PCState.Reconnecting;
|
16558
|
+
this.log.debug('waiting for peer connection to reconnect', this.logContext);
|
16479
16559
|
try {
|
16480
|
-
|
16481
|
-
|
16482
|
-
|
16483
|
-
}
|
16484
|
-
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
16485
|
-
this.log.debug('reacquiring camera track', this.logContext);
|
16486
|
-
yield this.restartTrack();
|
16560
|
+
yield sleep(minReconnectWait); // FIXME setTimeout again not ideal for a connection critical path
|
16561
|
+
if (!this.pcManager) {
|
16562
|
+
throw new UnexpectedConnectionState('PC manager is closed');
|
16487
16563
|
}
|
16488
|
-
yield
|
16489
|
-
|
16490
|
-
}
|
16491
|
-
|
16564
|
+
yield this.pcManager.ensurePCTransportConnection(undefined, this.peerConnectionTimeout);
|
16565
|
+
this.pcState = PCState.Connected;
|
16566
|
+
} catch (e) {
|
16567
|
+
// TODO do we need a `failed` state here for the PC?
|
16568
|
+
this.pcState = PCState.Disconnected;
|
16569
|
+
throw new ConnectionError("could not establish PC connection, ".concat(e.message));
|
16492
16570
|
}
|
16493
16571
|
});
|
16494
16572
|
}
|
16495
|
-
|
16496
|
-
|
16497
|
-
for (const sc of this.simulcastCodecs.values()) {
|
16498
|
-
sc.mediaStreamTrack.enabled = !muted;
|
16499
|
-
}
|
16500
|
-
}
|
16501
|
-
getSenderStats() {
|
16573
|
+
/* @internal */
|
16574
|
+
sendDataPacket(packet, kind) {
|
16502
16575
|
return __awaiter(this, void 0, void 0, function* () {
|
16503
|
-
|
16504
|
-
|
16505
|
-
|
16576
|
+
const msg = packet.toBinary();
|
16577
|
+
// make sure we do have a data connection
|
16578
|
+
yield this.ensurePublisherConnected(kind);
|
16579
|
+
const dc = this.dataChannelForKind(kind);
|
16580
|
+
if (dc) {
|
16581
|
+
dc.send(msg);
|
16506
16582
|
}
|
16507
|
-
|
16508
|
-
const stats = yield this.sender.getStats();
|
16509
|
-
stats.forEach(v => {
|
16510
|
-
var _a;
|
16511
|
-
if (v.type === 'outbound-rtp') {
|
16512
|
-
const vs = {
|
16513
|
-
type: 'video',
|
16514
|
-
streamId: v.id,
|
16515
|
-
frameHeight: v.frameHeight,
|
16516
|
-
frameWidth: v.frameWidth,
|
16517
|
-
framesPerSecond: v.framesPerSecond,
|
16518
|
-
framesSent: v.framesSent,
|
16519
|
-
firCount: v.firCount,
|
16520
|
-
pliCount: v.pliCount,
|
16521
|
-
nackCount: v.nackCount,
|
16522
|
-
packetsSent: v.packetsSent,
|
16523
|
-
bytesSent: v.bytesSent,
|
16524
|
-
qualityLimitationReason: v.qualityLimitationReason,
|
16525
|
-
qualityLimitationDurations: v.qualityLimitationDurations,
|
16526
|
-
qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
|
16527
|
-
rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
|
16528
|
-
retransmittedPacketsSent: v.retransmittedPacketsSent,
|
16529
|
-
targetBitrate: v.targetBitrate,
|
16530
|
-
timestamp: v.timestamp
|
16531
|
-
};
|
16532
|
-
// locate the appropriate remote-inbound-rtp item
|
16533
|
-
const r = stats.get(v.remoteId);
|
16534
|
-
if (r) {
|
16535
|
-
vs.jitter = r.jitter;
|
16536
|
-
vs.packetsLost = r.packetsLost;
|
16537
|
-
vs.roundTripTime = r.roundTripTime;
|
16538
|
-
}
|
16539
|
-
items.push(vs);
|
16540
|
-
}
|
16541
|
-
});
|
16542
|
-
// make sure highest res layer is always first
|
16543
|
-
items.sort((a, b) => {
|
16544
|
-
var _a, _b;
|
16545
|
-
return ((_a = b.frameWidth) !== null && _a !== void 0 ? _a : 0) - ((_b = a.frameWidth) !== null && _b !== void 0 ? _b : 0);
|
16546
|
-
});
|
16547
|
-
return items;
|
16583
|
+
this.updateAndEmitDCBufferStatus(kind);
|
16548
16584
|
});
|
16549
16585
|
}
|
16550
|
-
|
16551
|
-
|
16552
|
-
|
16553
|
-
|
16554
|
-
|
16555
|
-
|
16556
|
-
|
16557
|
-
|
16558
|
-
|
16559
|
-
|
16586
|
+
/**
|
16587
|
+
* @internal
|
16588
|
+
*/
|
16589
|
+
ensureDataTransportConnected(kind_1) {
|
16590
|
+
return __awaiter(this, arguments, void 0, function (kind) {
|
16591
|
+
var _this2 = this;
|
16592
|
+
let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.subscriberPrimary;
|
16593
|
+
return function* () {
|
16594
|
+
var _a;
|
16595
|
+
if (!_this2.pcManager) {
|
16596
|
+
throw new UnexpectedConnectionState('PC manager is closed');
|
16597
|
+
}
|
16598
|
+
const transport = subscriber ? _this2.pcManager.subscriber : _this2.pcManager.publisher;
|
16599
|
+
const transportName = subscriber ? 'Subscriber' : 'Publisher';
|
16600
|
+
if (!transport) {
|
16601
|
+
throw new ConnectionError("".concat(transportName, " connection not set"));
|
16602
|
+
}
|
16603
|
+
if (!subscriber && !_this2.pcManager.publisher.isICEConnected && _this2.pcManager.publisher.getICEConnectionState() !== 'checking') {
|
16604
|
+
// start negotiation
|
16605
|
+
_this2.negotiate();
|
16606
|
+
}
|
16607
|
+
const targetChannel = _this2.dataChannelForKind(kind, subscriber);
|
16608
|
+
if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') {
|
16609
|
+
return;
|
16610
|
+
}
|
16611
|
+
// wait until ICE connected
|
16612
|
+
const endTime = new Date().getTime() + _this2.peerConnectionTimeout;
|
16613
|
+
while (new Date().getTime() < endTime) {
|
16614
|
+
if (transport.isICEConnected && ((_a = _this2.dataChannelForKind(kind, subscriber)) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') {
|
16615
|
+
return;
|
16616
|
+
}
|
16617
|
+
yield sleep(50);
|
16618
|
+
}
|
16619
|
+
throw new ConnectionError("could not establish ".concat(transportName, " connection, state: ").concat(transport.getICEConnectionState()));
|
16620
|
+
}();
|
16621
|
+
});
|
16560
16622
|
}
|
16561
|
-
|
16623
|
+
ensurePublisherConnected(kind) {
|
16562
16624
|
return __awaiter(this, void 0, void 0, function* () {
|
16563
|
-
if (this.
|
16564
|
-
|
16565
|
-
}
|
16566
|
-
this._constraints.deviceId = deviceId;
|
16567
|
-
// when video is muted, underlying media stream track is stopped and
|
16568
|
-
// will be restarted later
|
16569
|
-
if (!this.isMuted) {
|
16570
|
-
yield this.restartTrack();
|
16625
|
+
if (!this.publisherConnectionPromise) {
|
16626
|
+
this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
|
16571
16627
|
}
|
16572
|
-
|
16628
|
+
yield this.publisherConnectionPromise;
|
16573
16629
|
});
|
16574
16630
|
}
|
16575
|
-
|
16631
|
+
/* @internal */
|
16632
|
+
verifyTransport() {
|
16633
|
+
if (!this.pcManager) {
|
16634
|
+
return false;
|
16635
|
+
}
|
16636
|
+
// primary connection
|
16637
|
+
if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
|
16638
|
+
return false;
|
16639
|
+
}
|
16640
|
+
// ensure signal is connected
|
16641
|
+
if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
|
16642
|
+
return false;
|
16643
|
+
}
|
16644
|
+
return true;
|
16645
|
+
}
|
16646
|
+
/** @internal */
|
16647
|
+
negotiate() {
|
16576
16648
|
return __awaiter(this, void 0, void 0, function* () {
|
16577
|
-
|
16578
|
-
|
16579
|
-
|
16580
|
-
|
16581
|
-
|
16582
|
-
});
|
16583
|
-
if (typeof streamConstraints.video !== 'boolean') {
|
16584
|
-
constraints = streamConstraints.video;
|
16585
|
-
}
|
16586
|
-
}
|
16587
|
-
yield this.restart(constraints);
|
16588
|
-
try {
|
16589
|
-
for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
16590
|
-
_c = _f.value;
|
16591
|
-
_d = false;
|
16592
|
-
const sc = _c;
|
16593
|
-
if (sc.sender) {
|
16594
|
-
sc.mediaStreamTrack = this.mediaStreamTrack.clone();
|
16595
|
-
yield sc.sender.replaceTrack(sc.mediaStreamTrack);
|
16596
|
-
}
|
16649
|
+
// observe signal state
|
16650
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
16651
|
+
if (!this.pcManager) {
|
16652
|
+
reject(new NegotiationError('PC manager is closed'));
|
16653
|
+
return;
|
16597
16654
|
}
|
16598
|
-
|
16599
|
-
|
16600
|
-
|
16655
|
+
this.pcManager.requirePublisher();
|
16656
|
+
const abortController = new AbortController();
|
16657
|
+
const handleClosed = () => {
|
16658
|
+
abortController.abort();
|
16659
|
+
this.log.debug('engine disconnected while negotiation was ongoing', this.logContext);
|
16660
|
+
resolve();
|
16661
|
+
return;
|
16601
16662
|
};
|
16602
|
-
|
16663
|
+
if (this.isClosed) {
|
16664
|
+
reject('cannot negotiate on closed engine');
|
16665
|
+
}
|
16666
|
+
this.on(EngineEvent.Closing, handleClosed);
|
16667
|
+
this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, rtpTypes => {
|
16668
|
+
const rtpMap = new Map();
|
16669
|
+
rtpTypes.forEach(rtp => {
|
16670
|
+
const codec = rtp.codec.toLowerCase();
|
16671
|
+
if (isVideoCodec(codec)) {
|
16672
|
+
rtpMap.set(rtp.payload, codec);
|
16673
|
+
}
|
16674
|
+
});
|
16675
|
+
this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap);
|
16676
|
+
});
|
16603
16677
|
try {
|
16604
|
-
|
16678
|
+
yield this.pcManager.negotiate(abortController);
|
16679
|
+
resolve();
|
16680
|
+
} catch (e) {
|
16681
|
+
if (e instanceof NegotiationError) {
|
16682
|
+
this.fullReconnectOnNext = true;
|
16683
|
+
}
|
16684
|
+
this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN);
|
16685
|
+
reject(e);
|
16605
16686
|
} finally {
|
16606
|
-
|
16687
|
+
this.off(EngineEvent.Closing, handleClosed);
|
16607
16688
|
}
|
16608
|
-
}
|
16689
|
+
}));
|
16609
16690
|
});
|
16610
16691
|
}
|
16611
|
-
|
16612
|
-
|
16613
|
-
|
16614
|
-
|
16692
|
+
dataChannelForKind(kind, sub) {
|
16693
|
+
if (!sub) {
|
16694
|
+
if (kind === DataPacket_Kind.LOSSY) {
|
16695
|
+
return this.lossyDC;
|
16615
16696
|
}
|
16616
|
-
|
16617
|
-
|
16618
|
-
|
16619
|
-
|
16620
|
-
|
16621
|
-
|
16622
|
-
|
16623
|
-
|
16624
|
-
|
16625
|
-
|
16626
|
-
|
16627
|
-
_c = _h.value;
|
16628
|
-
_f = false;
|
16629
|
-
const sc = _c;
|
16630
|
-
yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
|
16631
|
-
}
|
16632
|
-
} catch (e_4_1) {
|
16633
|
-
e_4 = {
|
16634
|
-
error: e_4_1
|
16635
|
-
};
|
16636
|
-
} finally {
|
16637
|
-
try {
|
16638
|
-
if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
|
16639
|
-
} finally {
|
16640
|
-
if (e_4) throw e_4.error;
|
16641
|
-
}
|
16642
|
-
}
|
16643
|
-
}
|
16644
|
-
}();
|
16645
|
-
});
|
16697
|
+
if (kind === DataPacket_Kind.RELIABLE) {
|
16698
|
+
return this.reliableDC;
|
16699
|
+
}
|
16700
|
+
} else {
|
16701
|
+
if (kind === DataPacket_Kind.LOSSY) {
|
16702
|
+
return this.lossyDCSub;
|
16703
|
+
}
|
16704
|
+
if (kind === DataPacket_Kind.RELIABLE) {
|
16705
|
+
return this.reliableDCSub;
|
16706
|
+
}
|
16707
|
+
}
|
16646
16708
|
}
|
16647
|
-
|
16648
|
-
|
16649
|
-
|
16709
|
+
/** @internal */
|
16710
|
+
sendSyncState(remoteTracks, localTracks) {
|
16711
|
+
var _a, _b;
|
16712
|
+
if (!this.pcManager) {
|
16713
|
+
this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
|
16650
16714
|
return;
|
16651
16715
|
}
|
16652
|
-
const
|
16653
|
-
|
16654
|
-
|
16655
|
-
|
16656
|
-
|
16716
|
+
const previousAnswer = this.pcManager.subscriber.getLocalDescription();
|
16717
|
+
const previousOffer = this.pcManager.subscriber.getRemoteDescription();
|
16718
|
+
/* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
|
16719
|
+
in this case, we send unsub tracks, so server add all tracks to this
|
16720
|
+
subscribe pc and unsub special tracks from it.
|
16721
|
+
2. autosubscribe off, we send subscribed tracks.
|
16722
|
+
*/
|
16723
|
+
const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
|
16724
|
+
const trackSids = new Array();
|
16725
|
+
const trackSidsDisabled = new Array();
|
16726
|
+
remoteTracks.forEach(track => {
|
16727
|
+
if (track.isDesired !== autoSubscribe) {
|
16728
|
+
trackSids.push(track.trackSid);
|
16729
|
+
}
|
16730
|
+
if (!track.isEnabled) {
|
16731
|
+
trackSidsDisabled.push(track.trackSid);
|
16732
|
+
}
|
16733
|
+
});
|
16734
|
+
this.client.sendSyncState(new SyncState({
|
16735
|
+
answer: previousAnswer ? toProtoSessionDescription({
|
16736
|
+
sdp: previousAnswer.sdp,
|
16737
|
+
type: previousAnswer.type
|
16738
|
+
}) : undefined,
|
16739
|
+
offer: previousOffer ? toProtoSessionDescription({
|
16740
|
+
sdp: previousOffer.sdp,
|
16741
|
+
type: previousOffer.type
|
16742
|
+
}) : undefined,
|
16743
|
+
subscription: new UpdateSubscription({
|
16744
|
+
trackSids,
|
16745
|
+
subscribe: !autoSubscribe,
|
16746
|
+
participantTracks: []
|
16747
|
+
}),
|
16748
|
+
publishTracks: getTrackPublicationInfo(localTracks),
|
16749
|
+
dataChannels: this.dataChannelsInfo(),
|
16750
|
+
trackSidsDisabled
|
16751
|
+
}));
|
16752
|
+
}
|
16753
|
+
/* @internal */
|
16754
|
+
failNext() {
|
16755
|
+
// debugging method to fail the next reconnect/resume attempt
|
16756
|
+
this.shouldFailNext = true;
|
16757
|
+
}
|
16758
|
+
dataChannelsInfo() {
|
16759
|
+
const infos = [];
|
16760
|
+
const getInfo = (dc, target) => {
|
16761
|
+
if ((dc === null || dc === void 0 ? void 0 : dc.id) !== undefined && dc.id !== null) {
|
16762
|
+
infos.push(new DataChannelInfo({
|
16763
|
+
label: dc.label,
|
16764
|
+
id: dc.id,
|
16765
|
+
target
|
16766
|
+
}));
|
16767
|
+
}
|
16657
16768
|
};
|
16658
|
-
this.
|
16659
|
-
|
16769
|
+
getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
|
16770
|
+
getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
|
16771
|
+
getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
|
16772
|
+
getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE, true), SignalTarget.SUBSCRIBER);
|
16773
|
+
return infos;
|
16774
|
+
}
|
16775
|
+
clearReconnectTimeout() {
|
16776
|
+
if (this.reconnectTimeout) {
|
16777
|
+
CriticalTimers.clearTimeout(this.reconnectTimeout);
|
16778
|
+
}
|
16779
|
+
}
|
16780
|
+
clearPendingReconnect() {
|
16781
|
+
this.clearReconnectTimeout();
|
16782
|
+
this.reconnectAttempts = 0;
|
16783
|
+
}
|
16784
|
+
registerOnLineListener() {
|
16785
|
+
if (isWeb()) {
|
16786
|
+
window.addEventListener('online', this.handleBrowserOnLine);
|
16787
|
+
}
|
16788
|
+
}
|
16789
|
+
deregisterOnLineListener() {
|
16790
|
+
if (isWeb()) {
|
16791
|
+
window.removeEventListener('online', this.handleBrowserOnLine);
|
16792
|
+
}
|
16793
|
+
}
|
16794
|
+
}
|
16795
|
+
class SignalReconnectError extends Error {}
|
16796
|
+
|
16797
|
+
class RegionUrlProvider {
|
16798
|
+
constructor(url, token) {
|
16799
|
+
this.lastUpdateAt = 0;
|
16800
|
+
this.settingsCacheTime = 3000;
|
16801
|
+
this.attemptedRegions = [];
|
16802
|
+
this.serverUrl = new URL(url);
|
16803
|
+
this.token = token;
|
16804
|
+
}
|
16805
|
+
updateToken(token) {
|
16806
|
+
this.token = token;
|
16807
|
+
}
|
16808
|
+
isCloud() {
|
16809
|
+
return isCloud(this.serverUrl);
|
16660
16810
|
}
|
16661
|
-
|
16662
|
-
|
16663
|
-
if (!simulcastCodecInfo) {
|
16664
|
-
return;
|
16665
|
-
}
|
16666
|
-
simulcastCodecInfo.sender = sender;
|
16667
|
-
// browser will reenable disabled codec/layers after new codec has been published,
|
16668
|
-
// so refresh subscribedCodecs after publish a new codec
|
16669
|
-
setTimeout(() => {
|
16670
|
-
if (this.subscribedCodecs) {
|
16671
|
-
this.setPublishingCodecs(this.subscribedCodecs);
|
16672
|
-
}
|
16673
|
-
}, refreshSubscribedCodecAfterNewCodec);
|
16811
|
+
getServerUrl() {
|
16812
|
+
return this.serverUrl;
|
16674
16813
|
}
|
16675
|
-
|
16676
|
-
* @internal
|
16677
|
-
* Sets codecs that should be publishing, returns new codecs that have not yet
|
16678
|
-
* been published
|
16679
|
-
*/
|
16680
|
-
setPublishingCodecs(codecs) {
|
16814
|
+
getNextBestRegionUrl(abortSignal) {
|
16681
16815
|
return __awaiter(this, void 0, void 0, function* () {
|
16682
|
-
|
16683
|
-
|
16684
|
-
this.log.debug('setting publishing codecs', Object.assign(Object.assign({}, this.logContext), {
|
16685
|
-
codecs,
|
16686
|
-
currentCodec: this.codec
|
16687
|
-
}));
|
16688
|
-
// only enable simulcast codec for preference codec setted
|
16689
|
-
if (!this.codec && codecs.length > 0) {
|
16690
|
-
yield this.setPublishingLayers(codecs[0].qualities);
|
16691
|
-
return [];
|
16816
|
+
if (!this.isCloud()) {
|
16817
|
+
throw Error('region availability is only supported for LiveKit Cloud domains');
|
16692
16818
|
}
|
16693
|
-
this.
|
16694
|
-
|
16695
|
-
try {
|
16696
|
-
for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
|
16697
|
-
_d = codecs_1_1.value;
|
16698
|
-
_a = false;
|
16699
|
-
const codec = _d;
|
16700
|
-
if (!this.codec || this.codec === codec.codec) {
|
16701
|
-
yield this.setPublishingLayers(codec.qualities);
|
16702
|
-
} else {
|
16703
|
-
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
|
16704
|
-
this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), {
|
16705
|
-
simulcastCodecInfo
|
16706
|
-
}));
|
16707
|
-
if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
|
16708
|
-
for (const q of codec.qualities) {
|
16709
|
-
if (q.enabled) {
|
16710
|
-
newCodecs.push(codec.codec);
|
16711
|
-
break;
|
16712
|
-
}
|
16713
|
-
}
|
16714
|
-
} else if (simulcastCodecInfo.encodings) {
|
16715
|
-
this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext);
|
16716
|
-
yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, this.log, this.logContext);
|
16717
|
-
}
|
16718
|
-
}
|
16719
|
-
}
|
16720
|
-
} catch (e_5_1) {
|
16721
|
-
e_5 = {
|
16722
|
-
error: e_5_1
|
16723
|
-
};
|
16724
|
-
} finally {
|
16725
|
-
try {
|
16726
|
-
if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
|
16727
|
-
} finally {
|
16728
|
-
if (e_5) throw e_5.error;
|
16729
|
-
}
|
16819
|
+
if (!this.regionSettings || Date.now() - this.lastUpdateAt > this.settingsCacheTime) {
|
16820
|
+
this.regionSettings = yield this.fetchRegionSettings(abortSignal);
|
16730
16821
|
}
|
16731
|
-
|
16732
|
-
|
16733
|
-
|
16734
|
-
|
16735
|
-
|
16736
|
-
|
16737
|
-
|
16738
|
-
|
16739
|
-
return __awaiter(this, void 0, void 0, function* () {
|
16740
|
-
this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), {
|
16741
|
-
qualities
|
16742
|
-
}));
|
16743
|
-
if (!this.sender || !this.encodings) {
|
16744
|
-
return;
|
16822
|
+
const regionsLeft = this.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
|
16823
|
+
if (regionsLeft.length > 0) {
|
16824
|
+
const nextRegion = regionsLeft[0];
|
16825
|
+
this.attemptedRegions.push(nextRegion);
|
16826
|
+
livekitLogger.debug("next region: ".concat(nextRegion.region));
|
16827
|
+
return nextRegion.url;
|
16828
|
+
} else {
|
16829
|
+
return null;
|
16745
16830
|
}
|
16746
|
-
yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, this.log, this.logContext);
|
16747
16831
|
});
|
16748
16832
|
}
|
16749
|
-
|
16750
|
-
|
16751
|
-
|
16752
|
-
|
16753
|
-
|
16754
|
-
});
|
16833
|
+
resetAttempts() {
|
16834
|
+
this.attemptedRegions = [];
|
16835
|
+
}
|
16836
|
+
/* @internal */
|
16837
|
+
fetchRegionSettings(signal) {
|
16755
16838
|
return __awaiter(this, void 0, void 0, function* () {
|
16756
|
-
yield
|
16757
|
-
|
16758
|
-
|
16759
|
-
|
16839
|
+
const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(this.serverUrl), "/regions"), {
|
16840
|
+
headers: {
|
16841
|
+
authorization: "Bearer ".concat(this.token)
|
16842
|
+
},
|
16843
|
+
signal
|
16844
|
+
});
|
16845
|
+
if (regionSettingsResponse.ok) {
|
16846
|
+
const regionSettings = yield regionSettingsResponse.json();
|
16847
|
+
this.lastUpdateAt = Date.now();
|
16848
|
+
return regionSettings;
|
16849
|
+
} else {
|
16850
|
+
throw new ConnectionError("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status === 401 ? 0 /* ConnectionErrorReason.NotAllowed */ : undefined, regionSettingsResponse.status);
|
16760
16851
|
}
|
16761
16852
|
});
|
16762
16853
|
}
|
16763
16854
|
}
|
16764
|
-
function
|
16765
|
-
return
|
16766
|
-
const unlock = yield senderLock.lock();
|
16767
|
-
log.debug('setPublishingLayersForSender', Object.assign(Object.assign({}, logContext), {
|
16768
|
-
sender,
|
16769
|
-
qualities,
|
16770
|
-
senderEncodings
|
16771
|
-
}));
|
16772
|
-
try {
|
16773
|
-
const params = sender.getParameters();
|
16774
|
-
const {
|
16775
|
-
encodings
|
16776
|
-
} = params;
|
16777
|
-
if (!encodings) {
|
16778
|
-
return;
|
16779
|
-
}
|
16780
|
-
if (encodings.length !== senderEncodings.length) {
|
16781
|
-
log.warn('cannot set publishing layers, encodings mismatch', Object.assign(Object.assign({}, logContext), {
|
16782
|
-
encodings,
|
16783
|
-
senderEncodings
|
16784
|
-
}));
|
16785
|
-
return;
|
16786
|
-
}
|
16787
|
-
let hasChanged = false;
|
16788
|
-
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
16789
|
-
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
16790
|
-
low resolution frame and recover very quickly, but noticable
|
16791
|
-
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
16792
|
-
const closableSpatial = false;
|
16793
|
-
/* @ts-ignore */
|
16794
|
-
if (closableSpatial && encodings[0].scalabilityMode) ; else {
|
16795
|
-
// simulcast dynacast encodings
|
16796
|
-
encodings.forEach((encoding, idx) => {
|
16797
|
-
var _a;
|
16798
|
-
let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
|
16799
|
-
if (rid === '') {
|
16800
|
-
rid = 'q';
|
16801
|
-
}
|
16802
|
-
const quality = videoQualityForRid(rid);
|
16803
|
-
const subscribedQuality = qualities.find(q => q.quality === quality);
|
16804
|
-
if (!subscribedQuality) {
|
16805
|
-
return;
|
16806
|
-
}
|
16807
|
-
if (encoding.active !== subscribedQuality.enabled) {
|
16808
|
-
hasChanged = true;
|
16809
|
-
encoding.active = subscribedQuality.enabled;
|
16810
|
-
log.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'), logContext);
|
16811
|
-
// FireFox does not support setting encoding.active to false, so we
|
16812
|
-
// have a workaround of lowering its bitrate and resolution to the min.
|
16813
|
-
if (isFireFox()) {
|
16814
|
-
if (subscribedQuality.enabled) {
|
16815
|
-
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
|
16816
|
-
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
|
16817
|
-
/* @ts-ignore */
|
16818
|
-
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
|
16819
|
-
} else {
|
16820
|
-
encoding.scaleResolutionDownBy = 4;
|
16821
|
-
encoding.maxBitrate = 10;
|
16822
|
-
/* @ts-ignore */
|
16823
|
-
encoding.maxFrameRate = 2;
|
16824
|
-
}
|
16825
|
-
}
|
16826
|
-
}
|
16827
|
-
});
|
16828
|
-
}
|
16829
|
-
if (hasChanged) {
|
16830
|
-
params.encodings = encodings;
|
16831
|
-
log.debug("setting encodings", Object.assign(Object.assign({}, logContext), {
|
16832
|
-
encodings: params.encodings
|
16833
|
-
}));
|
16834
|
-
yield sender.setParameters(params);
|
16835
|
-
}
|
16836
|
-
} finally {
|
16837
|
-
unlock();
|
16838
|
-
}
|
16839
|
-
});
|
16840
|
-
}
|
16841
|
-
function videoQualityForRid(rid) {
|
16842
|
-
switch (rid) {
|
16843
|
-
case 'f':
|
16844
|
-
return VideoQuality.HIGH;
|
16845
|
-
case 'h':
|
16846
|
-
return VideoQuality.MEDIUM;
|
16847
|
-
case 'q':
|
16848
|
-
return VideoQuality.LOW;
|
16849
|
-
default:
|
16850
|
-
return VideoQuality.HIGH;
|
16851
|
-
}
|
16852
|
-
}
|
16853
|
-
function videoLayersFromEncodings(width, height, encodings, svc) {
|
16854
|
-
// default to a single layer, HQ
|
16855
|
-
if (!encodings) {
|
16856
|
-
return [new VideoLayer({
|
16857
|
-
quality: VideoQuality.HIGH,
|
16858
|
-
width,
|
16859
|
-
height,
|
16860
|
-
bitrate: 0,
|
16861
|
-
ssrc: 0
|
16862
|
-
})];
|
16863
|
-
}
|
16864
|
-
if (svc) {
|
16865
|
-
// svc layers
|
16866
|
-
/* @ts-ignore */
|
16867
|
-
const encodingSM = encodings[0].scalabilityMode;
|
16868
|
-
const sm = new ScalabilityMode(encodingSM);
|
16869
|
-
const layers = [];
|
16870
|
-
const resRatio = sm.suffix == 'h' ? 1.5 : 2;
|
16871
|
-
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
16872
|
-
for (let i = 0; i < sm.spatial; i += 1) {
|
16873
|
-
layers.push(new VideoLayer({
|
16874
|
-
quality: VideoQuality.HIGH - i,
|
16875
|
-
width: Math.ceil(width / Math.pow(resRatio, i)),
|
16876
|
-
height: Math.ceil(height / Math.pow(resRatio, i)),
|
16877
|
-
bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(bitratesRatio, i)) : 0,
|
16878
|
-
ssrc: 0
|
16879
|
-
}));
|
16880
|
-
}
|
16881
|
-
return layers;
|
16882
|
-
}
|
16883
|
-
return encodings.map(encoding => {
|
16884
|
-
var _a, _b, _c;
|
16885
|
-
const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
|
16886
|
-
let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
|
16887
|
-
return new VideoLayer({
|
16888
|
-
quality,
|
16889
|
-
width: Math.ceil(width / scale),
|
16890
|
-
height: Math.ceil(height / scale),
|
16891
|
-
bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
|
16892
|
-
ssrc: 0
|
16893
|
-
});
|
16894
|
-
});
|
16855
|
+
function getCloudConfigUrl(serverUrl) {
|
16856
|
+
return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
|
16895
16857
|
}
|
16896
16858
|
|
16897
16859
|
class RemoteTrack extends Track {
|
@@ -18468,9 +18430,8 @@ class LocalParticipant extends Participant {
|
|
18468
18430
|
(_d = options.red) !== null && _d !== void 0 ? _d : options.red = false;
|
18469
18431
|
}
|
18470
18432
|
const opts = Object.assign(Object.assign({}, this.roomOptions.publishDefaults), options);
|
18471
|
-
|
18472
|
-
|
18473
|
-
this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari", Object.assign({}, this.logContext));
|
18433
|
+
if (!isE2EESimulcastSupported() && this.roomOptions.e2ee) {
|
18434
|
+
this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari versions and iOS browsers running iOS < v17.2", Object.assign({}, this.logContext));
|
18474
18435
|
opts.simulcast = false;
|
18475
18436
|
}
|
18476
18437
|
if (opts.source) {
|
@@ -18630,7 +18591,6 @@ class LocalParticipant extends Participant {
|
|
18630
18591
|
this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
|
18631
18592
|
codec: updatedCodec
|
18632
18593
|
}));
|
18633
|
-
/* @ts-ignore */
|
18634
18594
|
opts.videoCodec = updatedCodec;
|
18635
18595
|
// recompute encodings since bitrates/etc could have changed
|
18636
18596
|
encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
|