livekit-client 1.12.2 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +83 -9
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2239 -1975
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +2 -5
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +5 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts +4 -2
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts +2 -0
- package/dist/src/e2ee/constants.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +7 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +1 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +4 -2
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/e2ee/worker/SifGuard.d.ts +11 -0
- package/dist/src/e2ee/worker/SifGuard.d.ts.map +1 -0
- package/dist/src/options.d.ts +5 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.d.ts +1 -0
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +5 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +0 -5
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +2 -2
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +9 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +3 -3
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +6 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +1 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +13 -2
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +2 -5
- package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +5 -0
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +4 -2
- package/dist/ts4.2/src/e2ee/constants.d.ts +2 -0
- package/dist/ts4.2/src/e2ee/types.d.ts +7 -1
- package/dist/ts4.2/src/e2ee/utils.d.ts +1 -0
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +4 -2
- package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +11 -0
- package/dist/ts4.2/src/options.d.ts +5 -0
- package/dist/ts4.2/src/room/DeviceManager.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +1 -1
- package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -0
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +0 -5
- package/dist/ts4.2/src/room/participant/publishUtils.d.ts +1 -1
- package/dist/ts4.2/src/room/timers.d.ts +2 -2
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +9 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +6 -0
- package/dist/ts4.2/src/room/track/options.d.ts +1 -0
- package/dist/ts4.2/src/room/track/processor/types.d.ts +13 -2
- package/dist/ts4.2/src/room/types.d.ts +1 -1
- package/package.json +15 -16
- package/src/api/SignalClient.ts +13 -9
- package/src/connectionHelper/checks/turn.ts +1 -0
- package/src/connectionHelper/checks/webrtc.ts +9 -7
- package/src/connectionHelper/checks/websocket.ts +1 -0
- package/src/e2ee/E2eeManager.ts +27 -2
- package/src/e2ee/KeyProvider.ts +9 -4
- package/src/e2ee/constants.ts +3 -0
- package/src/e2ee/types.ts +9 -1
- package/src/e2ee/utils.ts +9 -0
- package/src/e2ee/worker/FrameCryptor.ts +46 -17
- package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -0
- package/src/e2ee/worker/SifGuard.ts +47 -0
- package/src/e2ee/worker/e2ee.worker.ts +14 -0
- package/src/options.ts +6 -0
- package/src/proto/livekit_models_pb.ts +14 -0
- package/src/proto/livekit_rtc_pb.ts +14 -0
- package/src/room/DeviceManager.ts +7 -2
- package/src/room/RTCEngine.ts +7 -5
- package/src/room/Room.ts +27 -7
- package/src/room/defaults.ts +1 -0
- package/src/room/participant/LocalParticipant.ts +14 -2
- package/src/room/participant/Participant.ts +16 -0
- package/src/room/participant/RemoteParticipant.ts +0 -12
- package/src/room/participant/publishUtils.ts +4 -2
- package/src/room/track/LocalAudioTrack.ts +45 -0
- package/src/room/track/LocalTrack.ts +4 -4
- package/src/room/track/LocalVideoTrack.ts +49 -8
- package/src/room/track/RemoteAudioTrack.ts +9 -1
- package/src/room/track/RemoteTrackPublication.ts +2 -2
- package/src/room/track/options.ts +17 -16
- package/src/room/track/processor/types.ts +17 -2
- package/src/room/types.ts +5 -1
@@ -3465,10 +3465,19 @@ Timestamp.fields = proto3.util.newFieldList(() => [{
|
|
3465
3465
|
T: 5 /* ScalarType.INT32 */
|
3466
3466
|
}]);
|
3467
3467
|
|
3468
|
-
//
|
3469
|
-
//
|
3470
|
-
|
3471
|
-
//
|
3468
|
+
// Copyright 2023 LiveKit, Inc.
|
3469
|
+
//
|
3470
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
3471
|
+
// you may not use this file except in compliance with the License.
|
3472
|
+
// You may obtain a copy of the License at
|
3473
|
+
//
|
3474
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
3475
|
+
//
|
3476
|
+
// Unless required by applicable law or agreed to in writing, software
|
3477
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
3478
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
3479
|
+
// See the License for the specific language governing permissions and
|
3480
|
+
// limitations under the License.
|
3472
3481
|
/**
|
3473
3482
|
* @generated from enum livekit.AudioCodec
|
3474
3483
|
*/
|
@@ -6447,7 +6456,7 @@ function detectBrowser(window) {
|
|
6447
6456
|
};
|
6448
6457
|
|
6449
6458
|
// Fail early if it's not a browser
|
6450
|
-
if (typeof window === 'undefined' || !window.navigator) {
|
6459
|
+
if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) {
|
6451
6460
|
result.browser = 'Not a browser.';
|
6452
6461
|
return result;
|
6453
6462
|
}
|
@@ -9424,10 +9433,19 @@ adapterFactory({
|
|
9424
9433
|
window: typeof window === 'undefined' ? undefined : window
|
9425
9434
|
});
|
9426
9435
|
|
9427
|
-
//
|
9428
|
-
//
|
9429
|
-
|
9430
|
-
//
|
9436
|
+
// Copyright 2023 LiveKit, Inc.
|
9437
|
+
//
|
9438
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
9439
|
+
// you may not use this file except in compliance with the License.
|
9440
|
+
// You may obtain a copy of the License at
|
9441
|
+
//
|
9442
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
9443
|
+
//
|
9444
|
+
// Unless required by applicable law or agreed to in writing, software
|
9445
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
9446
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
9447
|
+
// See the License for the specific language governing permissions and
|
9448
|
+
// limitations under the License.
|
9431
9449
|
/**
|
9432
9450
|
* @generated from enum livekit.SignalTarget
|
9433
9451
|
*/
|
@@ -11823,7 +11841,7 @@ function getMatch(exp, ua) {
|
|
11823
11841
|
return match && match.length >= id && match[id] || '';
|
11824
11842
|
}
|
11825
11843
|
|
11826
|
-
var version$1 = "1.
|
11844
|
+
var version$1 = "1.13.0";
|
11827
11845
|
|
11828
11846
|
const version = version$1;
|
11829
11847
|
const protocolVersion = 9;
|
@@ -11880,11 +11898,11 @@ var AudioPresets;
|
|
11880
11898
|
* Sane presets for video resolution/encoding
|
11881
11899
|
*/
|
11882
11900
|
const VideoPresets = {
|
11883
|
-
h90: new VideoPreset(160, 90,
|
11884
|
-
h180: new VideoPreset(320, 180,
|
11885
|
-
h216: new VideoPreset(384, 216, 180000,
|
11886
|
-
h360: new VideoPreset(640, 360,
|
11887
|
-
h540: new VideoPreset(960, 540,
|
11901
|
+
h90: new VideoPreset(160, 90, 90000, 20),
|
11902
|
+
h180: new VideoPreset(320, 180, 160000, 20),
|
11903
|
+
h216: new VideoPreset(384, 216, 180000, 20),
|
11904
|
+
h360: new VideoPreset(640, 360, 450000, 20),
|
11905
|
+
h540: new VideoPreset(960, 540, 800000, 25),
|
11888
11906
|
h720: new VideoPreset(1280, 720, 1700000, 30),
|
11889
11907
|
h1080: new VideoPreset(1920, 1080, 3000000, 30),
|
11890
11908
|
h1440: new VideoPreset(2560, 1440, 5000000, 30),
|
@@ -11894,22 +11912,23 @@ const VideoPresets = {
|
|
11894
11912
|
* Four by three presets
|
11895
11913
|
*/
|
11896
11914
|
const VideoPresets43 = {
|
11897
|
-
h120: new VideoPreset(160, 120,
|
11898
|
-
h180: new VideoPreset(240, 180,
|
11899
|
-
h240: new VideoPreset(320, 240,
|
11915
|
+
h120: new VideoPreset(160, 120, 70000, 20),
|
11916
|
+
h180: new VideoPreset(240, 180, 125000, 20),
|
11917
|
+
h240: new VideoPreset(320, 240, 140000, 20),
|
11900
11918
|
h360: new VideoPreset(480, 360, 225000, 20),
|
11901
|
-
h480: new VideoPreset(640, 480,
|
11902
|
-
h540: new VideoPreset(720, 540,
|
11903
|
-
h720: new VideoPreset(960, 720,
|
11904
|
-
h1080: new VideoPreset(1440, 1080,
|
11905
|
-
h1440: new VideoPreset(1920, 1440,
|
11919
|
+
h480: new VideoPreset(640, 480, 500000, 20),
|
11920
|
+
h540: new VideoPreset(720, 540, 600000, 25),
|
11921
|
+
h720: new VideoPreset(960, 720, 1300000, 30),
|
11922
|
+
h1080: new VideoPreset(1440, 1080, 2300000, 30),
|
11923
|
+
h1440: new VideoPreset(1920, 1440, 3800000, 30)
|
11906
11924
|
};
|
11907
11925
|
const ScreenSharePresets = {
|
11908
11926
|
h360fps3: new VideoPreset(640, 360, 200000, 3, 'medium'),
|
11909
11927
|
h720fps5: new VideoPreset(1280, 720, 400000, 5, 'medium'),
|
11910
|
-
h720fps15: new VideoPreset(1280, 720,
|
11911
|
-
|
11912
|
-
|
11928
|
+
h720fps15: new VideoPreset(1280, 720, 1500000, 15, 'medium'),
|
11929
|
+
h720fps30: new VideoPreset(1280, 720, 2000000, 30, 'medium'),
|
11930
|
+
h1080fps15: new VideoPreset(1920, 1080, 2500000, 15, 'medium'),
|
11931
|
+
h1080fps30: new VideoPreset(1920, 1080, 4000000, 30, 'medium')
|
11913
11932
|
};
|
11914
11933
|
|
11915
11934
|
/**
|
@@ -13379,8 +13398,13 @@ class SignalClient {
|
|
13379
13398
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
13380
13399
|
const abortHandler = () => __awaiter(this, void 0, void 0, function* () {
|
13381
13400
|
this.close();
|
13401
|
+
clearTimeout(wsTimeout);
|
13382
13402
|
reject(new ConnectionError('room connection has been cancelled (signal)'));
|
13383
13403
|
});
|
13404
|
+
const wsTimeout = setTimeout(() => {
|
13405
|
+
this.close();
|
13406
|
+
reject(new ConnectionError('room connection has timed out (signal)'));
|
13407
|
+
}, opts.websocketTimeout);
|
13384
13408
|
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
13385
13409
|
abortHandler();
|
13386
13410
|
}
|
@@ -13391,8 +13415,12 @@ class SignalClient {
|
|
13391
13415
|
}
|
13392
13416
|
this.ws = new WebSocket(url + params);
|
13393
13417
|
this.ws.binaryType = 'arraybuffer';
|
13418
|
+
this.ws.onopen = () => {
|
13419
|
+
clearTimeout(wsTimeout);
|
13420
|
+
};
|
13394
13421
|
this.ws.onerror = ev => __awaiter(this, void 0, void 0, function* () {
|
13395
13422
|
if (!this.isConnected) {
|
13423
|
+
clearTimeout(wsTimeout);
|
13396
13424
|
try {
|
13397
13425
|
const resp = yield fetch("http".concat(url.substring(2), "/validate").concat(params));
|
13398
13426
|
if (resp.status.toFixed(0).startsWith('4')) {
|
@@ -13922,6 +13950,12 @@ function createKeyMaterialFromString(password) {
|
|
13922
13950
|
return keyMaterial;
|
13923
13951
|
});
|
13924
13952
|
}
|
13953
|
+
function createKeyMaterialFromBuffer(cryptoBuffer) {
|
13954
|
+
return __awaiter(this, void 0, void 0, function* () {
|
13955
|
+
const keyMaterial = yield crypto.subtle.importKey('raw', cryptoBuffer, 'HKDF', false, ['deriveBits', 'deriveKey']);
|
13956
|
+
return keyMaterial;
|
13957
|
+
});
|
13958
|
+
}
|
13925
13959
|
function getAlgoOptions(algorithmName, salt) {
|
13926
13960
|
const textEncoder = new TextEncoder();
|
13927
13961
|
const encodedSalt = textEncoder.encode(salt);
|
@@ -14046,12 +14080,14 @@ class ExternalE2EEKeyProvider extends BaseKeyProvider {
|
|
14046
14080
|
super(opts);
|
14047
14081
|
}
|
14048
14082
|
/**
|
14049
|
-
* Accepts a passphrase that's used to create the crypto keys
|
14083
|
+
* Accepts a passphrase that's used to create the crypto keys.
|
14084
|
+
* When passing in a string, PBKDF2 is used.
|
14085
|
+
* Also accepts an Array buffer of cryptographically random numbers that uses HKDF.
|
14050
14086
|
* @param key
|
14051
14087
|
*/
|
14052
14088
|
setKey(key) {
|
14053
14089
|
return __awaiter(this, void 0, void 0, function* () {
|
14054
|
-
const derivedKey = yield createKeyMaterialFromString(key);
|
14090
|
+
const derivedKey = typeof key === 'string' ? yield createKeyMaterialFromString(key) : yield createKeyMaterialFromBuffer(key);
|
14055
14091
|
this.onSetEncryptionKey(derivedKey);
|
14056
14092
|
});
|
14057
14093
|
}
|
@@ -14134,9 +14170,9 @@ class DeviceManager {
|
|
14134
14170
|
}
|
14135
14171
|
}
|
14136
14172
|
let devices = yield navigator.mediaDevices.enumerateDevices();
|
14137
|
-
if (requestPermissions &&
|
14173
|
+
if (requestPermissions &&
|
14138
14174
|
// for safari we need to skip this check, as otherwise it will re-acquire user media and fail on iOS https://bugs.webkit.org/show_bug.cgi?id=179363
|
14139
|
-
!
|
14175
|
+
!(isSafari() && this.hasDeviceInUse(kind))) {
|
14140
14176
|
const isDummyDeviceOrEmpty = devices.length === 0 || devices.some(device => {
|
14141
14177
|
const noLabel = device.label === '';
|
14142
14178
|
const isRelevant = kind ? device.kind === kind : true;
|
@@ -14172,6 +14208,9 @@ class DeviceManager {
|
|
14172
14208
|
return device === null || device === void 0 ? void 0 : device.deviceId;
|
14173
14209
|
});
|
14174
14210
|
}
|
14211
|
+
hasDeviceInUse(kind) {
|
14212
|
+
return kind ? DeviceManager.userMediaPromiseMap.has(kind) : DeviceManager.userMediaPromiseMap.size > 0;
|
14213
|
+
}
|
14175
14214
|
}
|
14176
14215
|
DeviceManager.mediaDeviceKinds = ['audioinput', 'audiooutput', 'videoinput'];
|
14177
14216
|
DeviceManager.userMediaPromiseMap = new Map();
|
@@ -14208,52 +14247,6 @@ class LocalTrack extends Track {
|
|
14208
14247
|
this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent);
|
14209
14248
|
this.emit(TrackEvent.Ended, this);
|
14210
14249
|
};
|
14211
|
-
/**
|
14212
|
-
* pauses publishing to the server without disabling the local MediaStreamTrack
|
14213
|
-
* this is used to display a user's own video locally while pausing publishing to
|
14214
|
-
* the server.
|
14215
|
-
* this API is unsupported on Safari < 12 due to a bug
|
14216
|
-
**/
|
14217
|
-
this.pauseUpstream = () => __awaiter(this, void 0, void 0, function* () {
|
14218
|
-
const unlock = yield this.pauseUpstreamLock.lock();
|
14219
|
-
try {
|
14220
|
-
if (this._isUpstreamPaused === true) {
|
14221
|
-
return;
|
14222
|
-
}
|
14223
|
-
if (!this.sender) {
|
14224
|
-
livekitLogger.warn('unable to pause upstream for an unpublished track');
|
14225
|
-
return;
|
14226
|
-
}
|
14227
|
-
this._isUpstreamPaused = true;
|
14228
|
-
this.emit(TrackEvent.UpstreamPaused, this);
|
14229
|
-
const browser = getBrowser();
|
14230
|
-
if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
|
14231
|
-
// https://bugs.webkit.org/show_bug.cgi?id=184911
|
14232
|
-
throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
|
14233
|
-
}
|
14234
|
-
yield this.sender.replaceTrack(null);
|
14235
|
-
} finally {
|
14236
|
-
unlock();
|
14237
|
-
}
|
14238
|
-
});
|
14239
|
-
this.resumeUpstream = () => __awaiter(this, void 0, void 0, function* () {
|
14240
|
-
const unlock = yield this.pauseUpstreamLock.lock();
|
14241
|
-
try {
|
14242
|
-
if (this._isUpstreamPaused === false) {
|
14243
|
-
return;
|
14244
|
-
}
|
14245
|
-
if (!this.sender) {
|
14246
|
-
livekitLogger.warn('unable to resume upstream for an unpublished track');
|
14247
|
-
return;
|
14248
|
-
}
|
14249
|
-
this._isUpstreamPaused = false;
|
14250
|
-
this.emit(TrackEvent.UpstreamResumed, this);
|
14251
|
-
// this operation is noop if mediastreamtrack is already being sent
|
14252
|
-
yield this.sender.replaceTrack(this._mediaStreamTrack);
|
14253
|
-
} finally {
|
14254
|
-
unlock();
|
14255
|
-
}
|
14256
|
-
});
|
14257
14250
|
this.reacquireTrack = false;
|
14258
14251
|
this.providedByUser = userProvidedTrack;
|
14259
14252
|
this.muteLock = new Mutex();
|
@@ -14484,6 +14477,56 @@ class LocalTrack extends Track {
|
|
14484
14477
|
(_a = this.processor) === null || _a === void 0 ? void 0 : _a.destroy();
|
14485
14478
|
this.processor = undefined;
|
14486
14479
|
}
|
14480
|
+
/**
|
14481
|
+
* pauses publishing to the server without disabling the local MediaStreamTrack
|
14482
|
+
* this is used to display a user's own video locally while pausing publishing to
|
14483
|
+
* the server.
|
14484
|
+
* this API is unsupported on Safari < 12 due to a bug
|
14485
|
+
**/
|
14486
|
+
pauseUpstream() {
|
14487
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14488
|
+
const unlock = yield this.pauseUpstreamLock.lock();
|
14489
|
+
try {
|
14490
|
+
if (this._isUpstreamPaused === true) {
|
14491
|
+
return;
|
14492
|
+
}
|
14493
|
+
if (!this.sender) {
|
14494
|
+
livekitLogger.warn('unable to pause upstream for an unpublished track');
|
14495
|
+
return;
|
14496
|
+
}
|
14497
|
+
this._isUpstreamPaused = true;
|
14498
|
+
this.emit(TrackEvent.UpstreamPaused, this);
|
14499
|
+
const browser = getBrowser();
|
14500
|
+
if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
|
14501
|
+
// https://bugs.webkit.org/show_bug.cgi?id=184911
|
14502
|
+
throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
|
14503
|
+
}
|
14504
|
+
yield this.sender.replaceTrack(null);
|
14505
|
+
} finally {
|
14506
|
+
unlock();
|
14507
|
+
}
|
14508
|
+
});
|
14509
|
+
}
|
14510
|
+
resumeUpstream() {
|
14511
|
+
return __awaiter(this, void 0, void 0, function* () {
|
14512
|
+
const unlock = yield this.pauseUpstreamLock.lock();
|
14513
|
+
try {
|
14514
|
+
if (this._isUpstreamPaused === false) {
|
14515
|
+
return;
|
14516
|
+
}
|
14517
|
+
if (!this.sender) {
|
14518
|
+
livekitLogger.warn('unable to resume upstream for an unpublished track');
|
14519
|
+
return;
|
14520
|
+
}
|
14521
|
+
this._isUpstreamPaused = false;
|
14522
|
+
this.emit(TrackEvent.UpstreamResumed, this);
|
14523
|
+
// this operation is noop if mediastreamtrack is already being sent
|
14524
|
+
yield this.sender.replaceTrack(this._mediaStreamTrack);
|
14525
|
+
} finally {
|
14526
|
+
unlock();
|
14527
|
+
}
|
14528
|
+
});
|
14529
|
+
}
|
14487
14530
|
/**
|
14488
14531
|
* Sets a processor on this track.
|
14489
14532
|
* See https://github.com/livekit/track-processors-js for example usage
|
@@ -14658,6 +14701,16 @@ class E2EEManager extends eventsExports.EventEmitter {
|
|
14658
14701
|
}
|
14659
14702
|
});
|
14660
14703
|
}
|
14704
|
+
/**
|
14705
|
+
* @internal
|
14706
|
+
*/
|
14707
|
+
setSifTrailer(trailer) {
|
14708
|
+
if (!trailer || trailer.length === 0) {
|
14709
|
+
livekitLogger.warn("ignoring server sent trailer as it's empty");
|
14710
|
+
} else {
|
14711
|
+
this.postSifTrailer(trailer);
|
14712
|
+
}
|
14713
|
+
}
|
14661
14714
|
setupEngine(engine) {
|
14662
14715
|
engine.on(EngineEvent.RTPVideoMapUpdate, rtpMap => {
|
14663
14716
|
this.postRTPMap(rtpMap);
|
@@ -14737,6 +14790,18 @@ class E2EEManager extends eventsExports.EventEmitter {
|
|
14737
14790
|
};
|
14738
14791
|
this.worker.postMessage(msg);
|
14739
14792
|
}
|
14793
|
+
postSifTrailer(trailer) {
|
14794
|
+
if (!this.worker) {
|
14795
|
+
throw Error('could not post SIF trailer, worker is missing');
|
14796
|
+
}
|
14797
|
+
const msg = {
|
14798
|
+
kind: 'setSifTrailer',
|
14799
|
+
data: {
|
14800
|
+
trailer
|
14801
|
+
}
|
14802
|
+
};
|
14803
|
+
this.worker.postMessage(msg);
|
14804
|
+
}
|
14740
14805
|
setupE2EEReceiver(track, remoteId, trackInfo) {
|
14741
14806
|
if (!track.receiver) {
|
14742
14807
|
return;
|
@@ -14826,7 +14891,7 @@ class E2EEManager extends eventsExports.EventEmitter {
|
|
14826
14891
|
return;
|
14827
14892
|
}
|
14828
14893
|
if (isScriptTransformSupported()) {
|
14829
|
-
livekitLogger.
|
14894
|
+
livekitLogger.info('initialize script transform');
|
14830
14895
|
const options = {
|
14831
14896
|
kind: 'encode',
|
14832
14897
|
participantId,
|
@@ -14836,7 +14901,7 @@ class E2EEManager extends eventsExports.EventEmitter {
|
|
14836
14901
|
// @ts-ignore
|
14837
14902
|
sender.transform = new RTCRtpScriptTransform(this.worker, options);
|
14838
14903
|
} else {
|
14839
|
-
livekitLogger.
|
14904
|
+
livekitLogger.info('initialize encoded streams');
|
14840
14905
|
// @ts-ignore
|
14841
14906
|
const senderStreams = sender.createEncodedStreams();
|
14842
14907
|
const msg = {
|
@@ -15940,7 +16005,8 @@ const roomOptionDefaults = {
|
|
15940
16005
|
const roomConnectOptionDefaults = {
|
15941
16006
|
autoSubscribe: true,
|
15942
16007
|
maxRetries: 1,
|
15943
|
-
peerConnectionTimeout: 15000
|
16008
|
+
peerConnectionTimeout: 15000,
|
16009
|
+
websocketTimeout: 15000
|
15944
16010
|
};
|
15945
16011
|
|
15946
16012
|
const lossyDataChannel = '_lossy';
|
@@ -16029,13 +16095,13 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
16029
16095
|
this.handleDataError = event => {
|
16030
16096
|
const channel = event.currentTarget;
|
16031
16097
|
const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
|
16032
|
-
if (event instanceof ErrorEvent) {
|
16098
|
+
if (event instanceof ErrorEvent && event.error) {
|
16033
16099
|
const {
|
16034
16100
|
error
|
16035
16101
|
} = event.error;
|
16036
16102
|
livekitLogger.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), error);
|
16037
16103
|
} else {
|
16038
|
-
livekitLogger.error("Unknown DataChannel
|
16104
|
+
livekitLogger.error("Unknown DataChannel error on ".concat(channelKind), event);
|
16039
16105
|
}
|
16040
16106
|
};
|
16041
16107
|
this.handleBufferedAmountLow = event => {
|
@@ -16138,8 +16204,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
16138
16204
|
this.url = url;
|
16139
16205
|
this.token = token;
|
16140
16206
|
this.signalOpts = opts;
|
16207
|
+
this.maxJoinAttempts = opts.maxRetries;
|
16141
16208
|
try {
|
16142
16209
|
this.joinAttempts += 1;
|
16210
|
+
this.setupSignalClientCallbacks();
|
16143
16211
|
const joinResponse = yield this.client.join(url, token, opts, abortSignal);
|
16144
16212
|
this._isClosed = false;
|
16145
16213
|
this.latestJoinResponse = joinResponse;
|
@@ -16151,7 +16219,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
16151
16219
|
if (!this.subscriberPrimary) {
|
16152
16220
|
this.negotiate();
|
16153
16221
|
}
|
16154
|
-
this.setupSignalClientCallbacks();
|
16155
16222
|
this.clientConfiguration = joinResponse.clientConfiguration;
|
16156
16223
|
return joinResponse;
|
16157
16224
|
} catch (e) {
|
@@ -16423,13 +16490,13 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
16423
16490
|
});
|
16424
16491
|
this.client.onLocalTrackPublished = res => {
|
16425
16492
|
livekitLogger.debug('received trackPublishedResponse', res);
|
16426
|
-
|
16427
|
-
resolve
|
16428
|
-
} = this.pendingTrackResolvers[res.cid];
|
16429
|
-
if (!resolve) {
|
16493
|
+
if (!this.pendingTrackResolvers[res.cid]) {
|
16430
16494
|
livekitLogger.error("missing track resolver for ".concat(res.cid));
|
16431
16495
|
return;
|
16432
16496
|
}
|
16497
|
+
const {
|
16498
|
+
resolve
|
16499
|
+
} = this.pendingTrackResolvers[res.cid];
|
16433
16500
|
delete this.pendingTrackResolvers[res.cid];
|
16434
16501
|
resolve(res.track);
|
16435
16502
|
};
|
@@ -17156,6 +17223,7 @@ class LocalAudioTrack extends LocalTrack {
|
|
17156
17223
|
*/
|
17157
17224
|
constructor(mediaTrack, constraints) {
|
17158
17225
|
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
17226
|
+
let audioContext = arguments.length > 3 ? arguments[3] : undefined;
|
17159
17227
|
super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack);
|
17160
17228
|
/** @internal */
|
17161
17229
|
this.stopOnMute = false;
|
@@ -17178,6 +17246,7 @@ class LocalAudioTrack extends LocalTrack {
|
|
17178
17246
|
}
|
17179
17247
|
this.prevStats = stats;
|
17180
17248
|
});
|
17249
|
+
this.audioContext = audioContext;
|
17181
17250
|
this.checkForSilence();
|
17182
17251
|
}
|
17183
17252
|
setDeviceId(deviceId) {
|
@@ -17273,6 +17342,43 @@ class LocalAudioTrack extends LocalTrack {
|
|
17273
17342
|
this.monitorSender();
|
17274
17343
|
}, monitorFrequency);
|
17275
17344
|
}
|
17345
|
+
setProcessor(processor) {
|
17346
|
+
var _a;
|
17347
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17348
|
+
const unlock = yield this.processorLock.lock();
|
17349
|
+
try {
|
17350
|
+
if (!this.audioContext) {
|
17351
|
+
throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors');
|
17352
|
+
}
|
17353
|
+
if (this.processor) {
|
17354
|
+
yield this.stopProcessor();
|
17355
|
+
}
|
17356
|
+
if (this.kind === 'unknown') {
|
17357
|
+
throw TypeError('cannot set processor on track of unknown kind');
|
17358
|
+
}
|
17359
|
+
const processorOptions = {
|
17360
|
+
kind: this.kind,
|
17361
|
+
track: this._mediaStreamTrack,
|
17362
|
+
audioContext: this.audioContext
|
17363
|
+
};
|
17364
|
+
livekitLogger.debug("setting up audio processor ".concat(processor.name));
|
17365
|
+
yield processor.init(processorOptions);
|
17366
|
+
this.processor = processor;
|
17367
|
+
if (this.processor.processedTrack) {
|
17368
|
+
yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
|
17369
|
+
}
|
17370
|
+
} finally {
|
17371
|
+
unlock();
|
17372
|
+
}
|
17373
|
+
});
|
17374
|
+
}
|
17375
|
+
/**
|
17376
|
+
* @internal
|
17377
|
+
* @experimental
|
17378
|
+
*/
|
17379
|
+
setAudioContext(audioContext) {
|
17380
|
+
this.audioContext = audioContext;
|
17381
|
+
}
|
17276
17382
|
getSenderStats() {
|
17277
17383
|
var _a;
|
17278
17384
|
return __awaiter(this, void 0, void 0, function* () {
|
@@ -17312,823 +17418,718 @@ class LocalAudioTrack extends LocalTrack {
|
|
17312
17418
|
}
|
17313
17419
|
}
|
17314
17420
|
|
17315
|
-
|
17316
|
-
|
17317
|
-
|
17318
|
-
|
17319
|
-
|
17320
|
-
|
17321
|
-
|
17322
|
-
|
17323
|
-
|
17324
|
-
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
17325
|
-
super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
|
17326
|
-
/* @internal */
|
17327
|
-
this.simulcastCodecs = new Map();
|
17328
|
-
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
17329
|
-
if (!this.sender) {
|
17330
|
-
this._currentBitrate = 0;
|
17331
|
-
return;
|
17332
|
-
}
|
17333
|
-
let stats;
|
17334
|
-
try {
|
17335
|
-
stats = yield this.getSenderStats();
|
17336
|
-
} catch (e) {
|
17337
|
-
livekitLogger.error('could not get audio sender stats', {
|
17338
|
-
error: e
|
17339
|
-
});
|
17340
|
-
return;
|
17341
|
-
}
|
17342
|
-
const statsMap = new Map(stats.map(s => [s.rid, s]));
|
17343
|
-
if (this.prevStats) {
|
17344
|
-
let totalBitrate = 0;
|
17345
|
-
statsMap.forEach((s, key) => {
|
17346
|
-
var _a;
|
17347
|
-
const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
|
17348
|
-
totalBitrate += computeBitrate(s, prev);
|
17349
|
-
});
|
17350
|
-
this._currentBitrate = totalBitrate;
|
17351
|
-
}
|
17352
|
-
this.prevStats = statsMap;
|
17353
|
-
});
|
17354
|
-
this.senderLock = new Mutex();
|
17355
|
-
}
|
17356
|
-
get isSimulcast() {
|
17357
|
-
if (this.sender && this.sender.getParameters().encodings.length > 1) {
|
17358
|
-
return true;
|
17359
|
-
}
|
17360
|
-
return false;
|
17421
|
+
/** @internal */
|
17422
|
+
function mediaTrackToLocalTrack(mediaStreamTrack, constraints) {
|
17423
|
+
switch (mediaStreamTrack.kind) {
|
17424
|
+
case 'audio':
|
17425
|
+
return new LocalAudioTrack(mediaStreamTrack, constraints, false);
|
17426
|
+
case 'video':
|
17427
|
+
return new LocalVideoTrack(mediaStreamTrack, constraints, false);
|
17428
|
+
default:
|
17429
|
+
throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
|
17361
17430
|
}
|
17362
|
-
|
17363
|
-
|
17431
|
+
}
|
17432
|
+
/* @internal */
|
17433
|
+
const presets169 = Object.values(VideoPresets);
|
17434
|
+
/* @internal */
|
17435
|
+
const presets43 = Object.values(VideoPresets43);
|
17436
|
+
/* @internal */
|
17437
|
+
const presetsScreenShare = Object.values(ScreenSharePresets);
|
17438
|
+
/* @internal */
|
17439
|
+
const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
|
17440
|
+
/* @internal */
|
17441
|
+
const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
|
17442
|
+
/* @internal */
|
17443
|
+
const computeDefaultScreenShareSimulcastPresets = fromPreset => {
|
17444
|
+
const layers = [{
|
17445
|
+
scaleResolutionDownBy: 2,
|
17446
|
+
fps: 3
|
17447
|
+
}];
|
17448
|
+
return layers.map(t => {
|
17364
17449
|
var _a;
|
17365
|
-
|
17366
|
-
|
17367
|
-
|
17368
|
-
|
17369
|
-
|
17370
|
-
|
17371
|
-
|
17372
|
-
|
17373
|
-
|
17374
|
-
|
17375
|
-
|
17376
|
-
|
17377
|
-
|
17378
|
-
|
17379
|
-
|
17380
|
-
|
17381
|
-
|
17382
|
-
|
17383
|
-
|
17384
|
-
|
17385
|
-
|
17386
|
-
|
17387
|
-
|
17450
|
+
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) / t.fps)))), t.fps, fromPreset.encoding.priority);
|
17451
|
+
});
|
17452
|
+
};
|
17453
|
+
// /**
|
17454
|
+
// *
|
17455
|
+
// * @internal
|
17456
|
+
// * @experimental
|
17457
|
+
// */
|
17458
|
+
// const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
|
17459
|
+
// // use vp8 as a default
|
17460
|
+
// const vp8 = determineAppropriateEncoding(false, width, height);
|
17461
|
+
// const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
|
17462
|
+
// const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
|
17463
|
+
// const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
|
17464
|
+
// return {
|
17465
|
+
// vp8,
|
17466
|
+
// vp9,
|
17467
|
+
// h264,
|
17468
|
+
// av1,
|
17469
|
+
// };
|
17470
|
+
// };
|
17471
|
+
const videoRids = ['q', 'h', 'f'];
|
17472
|
+
/* @internal */
|
17473
|
+
function computeVideoEncodings(isScreenShare, width, height, options) {
|
17474
|
+
var _a, _b;
|
17475
|
+
let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
|
17476
|
+
if (isScreenShare) {
|
17477
|
+
videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
|
17388
17478
|
}
|
17389
|
-
|
17390
|
-
|
17391
|
-
|
17392
|
-
|
17393
|
-
|
17394
|
-
|
17395
|
-
return
|
17396
|
-
const unlock = yield this.muteLock.lock();
|
17397
|
-
try {
|
17398
|
-
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
17399
|
-
livekitLogger.debug('stopping camera track');
|
17400
|
-
// also stop the track, so that camera indicator is turned off
|
17401
|
-
this._mediaStreamTrack.stop();
|
17402
|
-
}
|
17403
|
-
yield _super.mute.call(this);
|
17404
|
-
return this;
|
17405
|
-
} finally {
|
17406
|
-
unlock();
|
17407
|
-
}
|
17408
|
-
});
|
17479
|
+
const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
|
17480
|
+
const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
|
17481
|
+
const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
|
17482
|
+
if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
|
17483
|
+
// when we aren't simulcasting or svc, will need to return a single encoding without
|
17484
|
+
// capping bandwidth. we always require a encoding for dynacast
|
17485
|
+
return [{}];
|
17409
17486
|
}
|
17410
|
-
|
17411
|
-
|
17412
|
-
|
17413
|
-
|
17414
|
-
}
|
17415
|
-
});
|
17416
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17417
|
-
const unlock = yield this.muteLock.lock();
|
17418
|
-
try {
|
17419
|
-
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
17420
|
-
livekitLogger.debug('reacquiring camera track');
|
17421
|
-
yield this.restartTrack();
|
17422
|
-
}
|
17423
|
-
yield _super.unmute.call(this);
|
17424
|
-
return this;
|
17425
|
-
} finally {
|
17426
|
-
unlock();
|
17427
|
-
}
|
17428
|
-
});
|
17487
|
+
if (!videoEncoding) {
|
17488
|
+
// find the right encoding based on width/height
|
17489
|
+
videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
|
17490
|
+
livekitLogger.debug('using video encoding', videoEncoding);
|
17429
17491
|
}
|
17430
|
-
|
17431
|
-
|
17432
|
-
|
17433
|
-
|
17434
|
-
|
17435
|
-
|
17436
|
-
|
17437
|
-
|
17438
|
-
|
17439
|
-
|
17440
|
-
|
17441
|
-
|
17442
|
-
|
17443
|
-
|
17444
|
-
frameHeight: v.frameHeight,
|
17445
|
-
frameWidth: v.frameWidth,
|
17446
|
-
firCount: v.firCount,
|
17447
|
-
pliCount: v.pliCount,
|
17448
|
-
nackCount: v.nackCount,
|
17449
|
-
packetsSent: v.packetsSent,
|
17450
|
-
bytesSent: v.bytesSent,
|
17451
|
-
framesSent: v.framesSent,
|
17452
|
-
timestamp: v.timestamp,
|
17453
|
-
rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
|
17454
|
-
retransmittedPacketsSent: v.retransmittedPacketsSent,
|
17455
|
-
qualityLimitationReason: v.qualityLimitationReason,
|
17456
|
-
qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges
|
17457
|
-
};
|
17458
|
-
// locate the appropriate remote-inbound-rtp item
|
17459
|
-
const r = stats.get(v.remoteId);
|
17460
|
-
if (r) {
|
17461
|
-
vs.jitter = r.jitter;
|
17462
|
-
vs.packetsLost = r.packetsLost;
|
17463
|
-
vs.roundTripTime = r.roundTripTime;
|
17464
|
-
}
|
17465
|
-
items.push(vs);
|
17466
|
-
}
|
17492
|
+
const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
|
17493
|
+
if (scalabilityMode && isSVCCodec(videoCodec)) {
|
17494
|
+
livekitLogger.debug("using svc with scalabilityMode ".concat(scalabilityMode));
|
17495
|
+
const sm = new ScalabilityMode(scalabilityMode);
|
17496
|
+
const encodings = [];
|
17497
|
+
if (sm.spatial > 3) {
|
17498
|
+
throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
|
17499
|
+
}
|
17500
|
+
for (let i = 0; i < sm.spatial; i += 1) {
|
17501
|
+
encodings.push({
|
17502
|
+
rid: videoRids[2 - i],
|
17503
|
+
maxBitrate: videoEncoding.maxBitrate / Math.pow(3, i),
|
17504
|
+
/* @ts-ignore */
|
17505
|
+
maxFramerate: original.encoding.maxFramerate
|
17467
17506
|
});
|
17468
|
-
return items;
|
17469
|
-
});
|
17470
|
-
}
|
17471
|
-
setPublishingQuality(maxQuality) {
|
17472
|
-
const qualities = [];
|
17473
|
-
for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
|
17474
|
-
qualities.push(new SubscribedQuality({
|
17475
|
-
quality: q,
|
17476
|
-
enabled: q <= maxQuality
|
17477
|
-
}));
|
17478
17507
|
}
|
17479
|
-
|
17480
|
-
|
17481
|
-
|
17482
|
-
|
17483
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17484
|
-
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
|
17485
|
-
return true;
|
17486
|
-
}
|
17487
|
-
this._constraints.deviceId = deviceId;
|
17488
|
-
// when video is muted, underlying media stream track is stopped and
|
17489
|
-
// will be restarted later
|
17490
|
-
if (!this.isMuted) {
|
17491
|
-
yield this.restartTrack();
|
17492
|
-
}
|
17493
|
-
return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
17494
|
-
});
|
17508
|
+
/* @ts-ignore */
|
17509
|
+
encodings[0].scalabilityMode = scalabilityMode;
|
17510
|
+
livekitLogger.debug('encodings', encodings);
|
17511
|
+
return encodings;
|
17495
17512
|
}
|
17496
|
-
|
17497
|
-
return
|
17498
|
-
let constraints;
|
17499
|
-
if (options) {
|
17500
|
-
const streamConstraints = constraintsForOptions({
|
17501
|
-
video: options
|
17502
|
-
});
|
17503
|
-
if (typeof streamConstraints.video !== 'boolean') {
|
17504
|
-
constraints = streamConstraints.video;
|
17505
|
-
}
|
17506
|
-
}
|
17507
|
-
yield this.restart(constraints);
|
17508
|
-
});
|
17513
|
+
if (!useSimulcast) {
|
17514
|
+
return [videoEncoding];
|
17509
17515
|
}
|
17510
|
-
|
17511
|
-
|
17512
|
-
|
17513
|
-
|
17514
|
-
|
17515
|
-
codec,
|
17516
|
-
mediaStreamTrack: this.mediaStreamTrack.clone(),
|
17517
|
-
sender: undefined,
|
17518
|
-
encodings
|
17519
|
-
};
|
17520
|
-
this.simulcastCodecs.set(codec, simulcastCodecInfo);
|
17521
|
-
return simulcastCodecInfo;
|
17516
|
+
let presets = [];
|
17517
|
+
if (isScreenShare) {
|
17518
|
+
presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
|
17519
|
+
} else {
|
17520
|
+
presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
|
17522
17521
|
}
|
17523
|
-
|
17524
|
-
|
17525
|
-
|
17526
|
-
|
17522
|
+
let midPreset;
|
17523
|
+
if (presets.length > 0) {
|
17524
|
+
const lowPreset = presets[0];
|
17525
|
+
if (presets.length > 1) {
|
17526
|
+
[, midPreset] = presets;
|
17527
17527
|
}
|
17528
|
-
|
17529
|
-
//
|
17530
|
-
//
|
17531
|
-
|
17532
|
-
|
17533
|
-
|
17534
|
-
|
17535
|
-
|
17536
|
-
|
17537
|
-
|
17538
|
-
|
17539
|
-
|
17540
|
-
*/
|
17541
|
-
setPublishingCodecs(codecs) {
|
17542
|
-
var _a, codecs_1, codecs_1_1;
|
17543
|
-
var _b, e_1, _c, _d;
|
17544
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17545
|
-
livekitLogger.debug('setting publishing codecs', {
|
17546
|
-
codecs,
|
17547
|
-
currentCodec: this.codec
|
17548
|
-
});
|
17549
|
-
// only enable simulcast codec for preference codec setted
|
17550
|
-
if (!this.codec && codecs.length > 0) {
|
17551
|
-
yield this.setPublishingLayers(codecs[0].qualities);
|
17552
|
-
return [];
|
17553
|
-
}
|
17554
|
-
this.subscribedCodecs = codecs;
|
17555
|
-
const newCodecs = [];
|
17556
|
-
try {
|
17557
|
-
for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
|
17558
|
-
_d = codecs_1_1.value;
|
17559
|
-
_a = false;
|
17560
|
-
const codec = _d;
|
17561
|
-
if (!this.codec || this.codec === codec.codec) {
|
17562
|
-
yield this.setPublishingLayers(codec.qualities);
|
17563
|
-
} else {
|
17564
|
-
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
|
17565
|
-
livekitLogger.debug("try setPublishingCodec for ".concat(codec.codec), simulcastCodecInfo);
|
17566
|
-
if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
|
17567
|
-
for (const q of codec.qualities) {
|
17568
|
-
if (q.enabled) {
|
17569
|
-
newCodecs.push(codec.codec);
|
17570
|
-
break;
|
17571
|
-
}
|
17572
|
-
}
|
17573
|
-
} else if (simulcastCodecInfo.encodings) {
|
17574
|
-
livekitLogger.debug("try setPublishingLayersForSender ".concat(codec.codec));
|
17575
|
-
yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock);
|
17576
|
-
}
|
17577
|
-
}
|
17578
|
-
}
|
17579
|
-
} catch (e_1_1) {
|
17580
|
-
e_1 = {
|
17581
|
-
error: e_1_1
|
17582
|
-
};
|
17583
|
-
} finally {
|
17584
|
-
try {
|
17585
|
-
if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
|
17586
|
-
} finally {
|
17587
|
-
if (e_1) throw e_1.error;
|
17588
|
-
}
|
17589
|
-
}
|
17590
|
-
return newCodecs;
|
17591
|
-
});
|
17592
|
-
}
|
17593
|
-
/**
|
17594
|
-
* @internal
|
17595
|
-
* Sets layers that should be publishing
|
17596
|
-
*/
|
17597
|
-
setPublishingLayers(qualities) {
|
17598
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17599
|
-
livekitLogger.debug('setting publishing layers', qualities);
|
17600
|
-
if (!this.sender || !this.encodings) {
|
17601
|
-
return;
|
17602
|
-
}
|
17603
|
-
yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock);
|
17604
|
-
});
|
17605
|
-
}
|
17606
|
-
handleAppVisibilityChanged() {
|
17607
|
-
const _super = Object.create(null, {
|
17608
|
-
handleAppVisibilityChanged: {
|
17609
|
-
get: () => super.handleAppVisibilityChanged
|
17610
|
-
}
|
17611
|
-
});
|
17612
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17613
|
-
yield _super.handleAppVisibilityChanged.call(this);
|
17614
|
-
if (!isMobile()) return;
|
17615
|
-
if (this.isInBackground && this.source === Track.Source.Camera) {
|
17616
|
-
this._mediaStreamTrack.enabled = false;
|
17617
|
-
}
|
17618
|
-
});
|
17619
|
-
}
|
17620
|
-
}
|
17621
|
-
function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock) {
|
17622
|
-
return __awaiter(this, void 0, void 0, function* () {
|
17623
|
-
const unlock = yield senderLock.lock();
|
17624
|
-
livekitLogger.debug('setPublishingLayersForSender', {
|
17625
|
-
sender,
|
17626
|
-
qualities,
|
17627
|
-
senderEncodings
|
17628
|
-
});
|
17629
|
-
try {
|
17630
|
-
const params = sender.getParameters();
|
17631
|
-
const {
|
17632
|
-
encodings
|
17633
|
-
} = params;
|
17634
|
-
if (!encodings) {
|
17635
|
-
return;
|
17636
|
-
}
|
17637
|
-
if (encodings.length !== senderEncodings.length) {
|
17638
|
-
livekitLogger.warn('cannot set publishing layers, encodings mismatch');
|
17639
|
-
return;
|
17640
|
-
}
|
17641
|
-
let hasChanged = false;
|
17642
|
-
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
17643
|
-
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
17644
|
-
low resolution frame and recover very quickly, but noticable
|
17645
|
-
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
17646
|
-
const closableSpatial = false;
|
17647
|
-
/* @ts-ignore */
|
17648
|
-
if (closableSpatial && encodings[0].scalabilityMode) ; else {
|
17649
|
-
// simulcast dynacast encodings
|
17650
|
-
encodings.forEach((encoding, idx) => {
|
17651
|
-
var _a;
|
17652
|
-
let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
|
17653
|
-
if (rid === '') {
|
17654
|
-
rid = 'q';
|
17655
|
-
}
|
17656
|
-
const quality = videoQualityForRid(rid);
|
17657
|
-
const subscribedQuality = qualities.find(q => q.quality === quality);
|
17658
|
-
if (!subscribedQuality) {
|
17659
|
-
return;
|
17660
|
-
}
|
17661
|
-
if (encoding.active !== subscribedQuality.enabled) {
|
17662
|
-
hasChanged = true;
|
17663
|
-
encoding.active = subscribedQuality.enabled;
|
17664
|
-
livekitLogger.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'));
|
17665
|
-
// FireFox does not support setting encoding.active to false, so we
|
17666
|
-
// have a workaround of lowering its bitrate and resolution to the min.
|
17667
|
-
if (isFireFox()) {
|
17668
|
-
if (subscribedQuality.enabled) {
|
17669
|
-
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
|
17670
|
-
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
|
17671
|
-
/* @ts-ignore */
|
17672
|
-
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
|
17673
|
-
} else {
|
17674
|
-
encoding.scaleResolutionDownBy = 4;
|
17675
|
-
encoding.maxBitrate = 10;
|
17676
|
-
/* @ts-ignore */
|
17677
|
-
encoding.maxFrameRate = 2;
|
17678
|
-
}
|
17679
|
-
}
|
17680
|
-
}
|
17681
|
-
});
|
17682
|
-
}
|
17683
|
-
if (hasChanged) {
|
17684
|
-
params.encodings = encodings;
|
17685
|
-
livekitLogger.debug("setting encodings", params.encodings);
|
17686
|
-
yield sender.setParameters(params);
|
17687
|
-
}
|
17688
|
-
} finally {
|
17689
|
-
unlock();
|
17528
|
+
// NOTE:
|
17529
|
+
// 1. Ordering of these encodings is important. Chrome seems
|
17530
|
+
// to use the index into encodings to decide which layer
|
17531
|
+
// to disable when CPU constrained.
|
17532
|
+
// So encodings should be ordered in increasing spatial
|
17533
|
+
// resolution order.
|
17534
|
+
// 2. ion-sfu translates rids into layers. So, all encodings
|
17535
|
+
// should have the base layer `q` and then more added
|
17536
|
+
// based on other conditions.
|
17537
|
+
const size = Math.max(width, height);
|
17538
|
+
if (size >= 960 && midPreset) {
|
17539
|
+
return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
|
17690
17540
|
}
|
17691
|
-
|
17541
|
+
if (size >= 480) {
|
17542
|
+
return encodingsFromPresets(width, height, [lowPreset, original]);
|
17543
|
+
}
|
17544
|
+
}
|
17545
|
+
return encodingsFromPresets(width, height, [original]);
|
17692
17546
|
}
|
17693
|
-
function
|
17694
|
-
|
17695
|
-
|
17696
|
-
|
17697
|
-
|
17698
|
-
|
17699
|
-
|
17700
|
-
|
17701
|
-
|
17702
|
-
|
17547
|
+
function computeTrackBackupEncodings(track, videoCodec, opts) {
|
17548
|
+
var _a, _b, _c, _d;
|
17549
|
+
if (!opts.backupCodec || opts.backupCodec.codec === opts.videoCodec) {
|
17550
|
+
// backup codec publishing is disabled
|
17551
|
+
return;
|
17552
|
+
}
|
17553
|
+
if (videoCodec !== opts.backupCodec.codec) {
|
17554
|
+
livekitLogger.warn('requested a different codec than specified as backup', {
|
17555
|
+
serverRequested: videoCodec,
|
17556
|
+
backup: opts.backupCodec.codec
|
17557
|
+
});
|
17703
17558
|
}
|
17559
|
+
opts.videoCodec = videoCodec;
|
17560
|
+
// use backup encoding setting as videoEncoding for backup codec publishing
|
17561
|
+
opts.videoEncoding = opts.backupCodec.encoding;
|
17562
|
+
const settings = track.mediaStreamTrack.getSettings();
|
17563
|
+
const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
|
17564
|
+
const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
|
17565
|
+
const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
|
17566
|
+
return encodings;
|
17704
17567
|
}
|
17705
|
-
|
17706
|
-
|
17707
|
-
|
17708
|
-
|
17709
|
-
|
17710
|
-
|
17711
|
-
|
17712
|
-
|
17713
|
-
|
17714
|
-
|
17568
|
+
/* @internal */
|
17569
|
+
function determineAppropriateEncoding(isScreenShare, width, height, codec) {
|
17570
|
+
const presets = presetsForResolution(isScreenShare, width, height);
|
17571
|
+
let {
|
17572
|
+
encoding
|
17573
|
+
} = presets[0];
|
17574
|
+
// handle portrait by swapping dimensions
|
17575
|
+
const size = Math.max(width, height);
|
17576
|
+
for (let i = 0; i < presets.length; i += 1) {
|
17577
|
+
const preset = presets[i];
|
17578
|
+
encoding = preset.encoding;
|
17579
|
+
if (preset.width >= size) {
|
17580
|
+
break;
|
17581
|
+
}
|
17715
17582
|
}
|
17716
|
-
|
17717
|
-
|
17718
|
-
|
17719
|
-
|
17720
|
-
|
17721
|
-
|
17722
|
-
|
17723
|
-
|
17724
|
-
|
17725
|
-
|
17726
|
-
|
17727
|
-
|
17728
|
-
|
17583
|
+
// presets are based on the assumption of vp8 as a codec
|
17584
|
+
// for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
|
17585
|
+
// users should override these with ones that are optimized for their use case
|
17586
|
+
// NOTE: SVC codec bitrates are inclusive of all scalability layers. while
|
17587
|
+
// bitrate for non-SVC codecs does not include other simulcast layers.
|
17588
|
+
if (codec) {
|
17589
|
+
switch (codec) {
|
17590
|
+
case 'av1':
|
17591
|
+
encoding = Object.assign({}, encoding);
|
17592
|
+
encoding.maxBitrate = encoding.maxBitrate * 0.7;
|
17593
|
+
break;
|
17594
|
+
case 'vp9':
|
17595
|
+
encoding = Object.assign({}, encoding);
|
17596
|
+
encoding.maxBitrate = encoding.maxBitrate * 0.85;
|
17597
|
+
break;
|
17729
17598
|
}
|
17730
|
-
return layers;
|
17731
17599
|
}
|
17732
|
-
return
|
17733
|
-
var _a, _b, _c;
|
17734
|
-
const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
|
17735
|
-
let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
|
17736
|
-
return new VideoLayer({
|
17737
|
-
quality,
|
17738
|
-
width: width / scale,
|
17739
|
-
height: height / scale,
|
17740
|
-
bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
|
17741
|
-
ssrc: 0
|
17742
|
-
});
|
17743
|
-
});
|
17600
|
+
return encoding;
|
17744
17601
|
}
|
17745
|
-
|
17746
|
-
|
17747
|
-
|
17748
|
-
|
17749
|
-
this.sid = sid;
|
17750
|
-
this.receiver = receiver;
|
17602
|
+
/* @internal */
|
17603
|
+
function presetsForResolution(isScreenShare, width, height) {
|
17604
|
+
if (isScreenShare) {
|
17605
|
+
return presetsScreenShare;
|
17751
17606
|
}
|
17752
|
-
|
17753
|
-
|
17754
|
-
|
17755
|
-
this.isMuted = muted;
|
17756
|
-
this._mediaStreamTrack.enabled = !muted;
|
17757
|
-
this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
|
17758
|
-
}
|
17607
|
+
const aspect = width > height ? width / height : height / width;
|
17608
|
+
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
17609
|
+
return presets169;
|
17759
17610
|
}
|
17760
|
-
|
17761
|
-
|
17762
|
-
|
17763
|
-
|
17764
|
-
|
17765
|
-
|
17766
|
-
stream.onremovetrack = () => {
|
17767
|
-
this.receiver = undefined;
|
17768
|
-
this._currentBitrate = 0;
|
17769
|
-
this.emit(TrackEvent.Ended, this);
|
17770
|
-
};
|
17611
|
+
return presets43;
|
17612
|
+
}
|
17613
|
+
/* @internal */
|
17614
|
+
function defaultSimulcastLayers(isScreenShare, original) {
|
17615
|
+
if (isScreenShare) {
|
17616
|
+
return computeDefaultScreenShareSimulcastPresets(original);
|
17771
17617
|
}
|
17772
|
-
|
17773
|
-
|
17774
|
-
|
17775
|
-
|
17618
|
+
const {
|
17619
|
+
width,
|
17620
|
+
height
|
17621
|
+
} = original;
|
17622
|
+
const aspect = width > height ? width / height : height / width;
|
17623
|
+
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
17624
|
+
return defaultSimulcastPresets169;
|
17776
17625
|
}
|
17777
|
-
|
17778
|
-
|
17779
|
-
|
17780
|
-
|
17626
|
+
return defaultSimulcastPresets43;
|
17627
|
+
}
|
17628
|
+
// presets should be ordered by low, medium, high
|
17629
|
+
function encodingsFromPresets(width, height, presets) {
|
17630
|
+
const encodings = [];
|
17631
|
+
presets.forEach((preset, idx) => {
|
17632
|
+
if (idx >= videoRids.length) {
|
17633
|
+
return;
|
17634
|
+
}
|
17635
|
+
const size = Math.min(width, height);
|
17636
|
+
const rid = videoRids[idx];
|
17637
|
+
const encoding = {
|
17638
|
+
rid,
|
17639
|
+
scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
|
17640
|
+
maxBitrate: preset.encoding.maxBitrate
|
17641
|
+
};
|
17642
|
+
if (preset.encoding.maxFramerate) {
|
17643
|
+
encoding.maxFramerate = preset.encoding.maxFramerate;
|
17644
|
+
}
|
17645
|
+
const canSetPriority = isFireFox() || idx === 0;
|
17646
|
+
if (preset.encoding.priority && canSetPriority) {
|
17647
|
+
encoding.priority = preset.encoding.priority;
|
17648
|
+
encoding.networkPriority = preset.encoding.priority;
|
17649
|
+
}
|
17650
|
+
encodings.push(encoding);
|
17651
|
+
});
|
17652
|
+
// RN ios simulcast requires all same framerates.
|
17653
|
+
if (isReactNative() && getReactNativeOs() === 'ios') {
|
17654
|
+
let topFramerate = undefined;
|
17655
|
+
encodings.forEach(encoding => {
|
17656
|
+
if (!topFramerate) {
|
17657
|
+
topFramerate = encoding.maxFramerate;
|
17658
|
+
} else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
|
17659
|
+
topFramerate = encoding.maxFramerate;
|
17660
|
+
}
|
17661
|
+
});
|
17662
|
+
let notifyOnce = true;
|
17663
|
+
encodings.forEach(encoding => {
|
17664
|
+
var _a;
|
17665
|
+
if (encoding.maxFramerate != topFramerate) {
|
17666
|
+
if (notifyOnce) {
|
17667
|
+
notifyOnce = false;
|
17668
|
+
livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
|
17669
|
+
}
|
17670
|
+
livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
|
17671
|
+
encoding.maxFramerate = topFramerate;
|
17672
|
+
}
|
17673
|
+
});
|
17781
17674
|
}
|
17782
|
-
|
17783
|
-
|
17784
|
-
|
17785
|
-
|
17675
|
+
return encodings;
|
17676
|
+
}
|
17677
|
+
/** @internal */
|
17678
|
+
function sortPresets(presets) {
|
17679
|
+
if (!presets) return;
|
17680
|
+
return presets.sort((a, b) => {
|
17681
|
+
const {
|
17682
|
+
encoding: aEnc
|
17683
|
+
} = a;
|
17684
|
+
const {
|
17685
|
+
encoding: bEnc
|
17686
|
+
} = b;
|
17687
|
+
if (aEnc.maxBitrate > bEnc.maxBitrate) {
|
17688
|
+
return 1;
|
17689
|
+
}
|
17690
|
+
if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
|
17691
|
+
if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
|
17692
|
+
return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
|
17786
17693
|
}
|
17694
|
+
return 0;
|
17695
|
+
});
|
17696
|
+
}
|
17697
|
+
/** @internal */
|
17698
|
+
class ScalabilityMode {
|
17699
|
+
constructor(scalabilityMode) {
|
17700
|
+
const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
|
17701
|
+
if (!results) {
|
17702
|
+
throw new Error('invalid scalability mode');
|
17703
|
+
}
|
17704
|
+
this.spatial = parseInt(results[1]);
|
17705
|
+
this.temporal = parseInt(results[2]);
|
17706
|
+
if (results.length > 3) {
|
17707
|
+
switch (results[3]) {
|
17708
|
+
case 'h':
|
17709
|
+
case '_KEY':
|
17710
|
+
case '_KEY_SHIFT':
|
17711
|
+
this.suffix = results[3];
|
17712
|
+
}
|
17713
|
+
}
|
17714
|
+
}
|
17715
|
+
toString() {
|
17716
|
+
var _a;
|
17717
|
+
return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
|
17787
17718
|
}
|
17788
17719
|
}
|
17789
17720
|
|
17790
|
-
|
17791
|
-
|
17792
|
-
|
17793
|
-
|
17794
|
-
|
17721
|
+
const refreshSubscribedCodecAfterNewCodec = 5000;
|
17722
|
+
class LocalVideoTrack extends LocalTrack {
|
17723
|
+
/**
|
17724
|
+
*
|
17725
|
+
* @param mediaTrack
|
17726
|
+
* @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
|
17727
|
+
* @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
|
17728
|
+
*/
|
17729
|
+
constructor(mediaTrack, constraints) {
|
17730
|
+
let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
17731
|
+
super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
|
17732
|
+
/* @internal */
|
17733
|
+
this.simulcastCodecs = new Map();
|
17734
|
+
this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
|
17735
|
+
if (!this.sender) {
|
17795
17736
|
this._currentBitrate = 0;
|
17796
17737
|
return;
|
17797
17738
|
}
|
17798
|
-
|
17799
|
-
|
17800
|
-
|
17739
|
+
let stats;
|
17740
|
+
try {
|
17741
|
+
stats = yield this.getSenderStats();
|
17742
|
+
} catch (e) {
|
17743
|
+
livekitLogger.error('could not get audio sender stats', {
|
17744
|
+
error: e
|
17745
|
+
});
|
17746
|
+
return;
|
17801
17747
|
}
|
17802
|
-
|
17748
|
+
const statsMap = new Map(stats.map(s => [s.rid, s]));
|
17749
|
+
if (this.prevStats) {
|
17750
|
+
let totalBitrate = 0;
|
17751
|
+
statsMap.forEach((s, key) => {
|
17752
|
+
var _a;
|
17753
|
+
const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
|
17754
|
+
totalBitrate += computeBitrate(s, prev);
|
17755
|
+
});
|
17756
|
+
this._currentBitrate = totalBitrate;
|
17757
|
+
}
|
17758
|
+
this.prevStats = statsMap;
|
17803
17759
|
});
|
17804
|
-
this.
|
17805
|
-
|
17806
|
-
|
17807
|
-
|
17760
|
+
this.senderLock = new Mutex();
|
17761
|
+
}
|
17762
|
+
get isSimulcast() {
|
17763
|
+
if (this.sender && this.sender.getParameters().encodings.length > 1) {
|
17764
|
+
return true;
|
17808
17765
|
}
|
17766
|
+
return false;
|
17809
17767
|
}
|
17810
|
-
|
17811
|
-
|
17812
|
-
*/
|
17813
|
-
setVolume(volume) {
|
17768
|
+
/* @internal */
|
17769
|
+
startMonitor(signalClient) {
|
17814
17770
|
var _a;
|
17815
|
-
|
17816
|
-
|
17817
|
-
|
17818
|
-
} else {
|
17819
|
-
el.volume = volume;
|
17820
|
-
}
|
17771
|
+
this.signalClient = signalClient;
|
17772
|
+
if (!isWeb()) {
|
17773
|
+
return;
|
17821
17774
|
}
|
17822
|
-
|
17823
|
-
|
17824
|
-
|
17825
|
-
|
17826
|
-
|
17827
|
-
getVolume() {
|
17828
|
-
if (this.elementVolume) {
|
17829
|
-
return this.elementVolume;
|
17775
|
+
// save original encodings
|
17776
|
+
// TODO : merge simulcast tracks stats
|
17777
|
+
const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
|
17778
|
+
if (params) {
|
17779
|
+
this.encodings = params.encodings;
|
17830
17780
|
}
|
17831
|
-
|
17832
|
-
|
17833
|
-
|
17834
|
-
|
17835
|
-
|
17781
|
+
if (this.monitorInterval) {
|
17782
|
+
return;
|
17783
|
+
}
|
17784
|
+
this.monitorInterval = setInterval(() => {
|
17785
|
+
this.monitorSender();
|
17786
|
+
}, monitorFrequency);
|
17787
|
+
}
|
17788
|
+
stop() {
|
17789
|
+
this._mediaStreamTrack.getConstraints();
|
17790
|
+
this.simulcastCodecs.forEach(trackInfo => {
|
17791
|
+
trackInfo.mediaStreamTrack.stop();
|
17836
17792
|
});
|
17837
|
-
|
17793
|
+
super.stop();
|
17838
17794
|
}
|
17839
|
-
|
17840
|
-
|
17841
|
-
|
17842
|
-
|
17843
|
-
|
17795
|
+
pauseUpstream() {
|
17796
|
+
const _super = Object.create(null, {
|
17797
|
+
pauseUpstream: {
|
17798
|
+
get: () => super.pauseUpstream
|
17799
|
+
}
|
17800
|
+
});
|
17801
|
+
var _a, e_1, _b, _c;
|
17802
|
+
var _d;
|
17844
17803
|
return __awaiter(this, void 0, void 0, function* () {
|
17845
|
-
this
|
17846
|
-
|
17847
|
-
|
17848
|
-
|
17804
|
+
yield _super.pauseUpstream.call(this);
|
17805
|
+
try {
|
17806
|
+
for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
|
17807
|
+
_c = _g.value;
|
17808
|
+
_e = false;
|
17809
|
+
const sc = _c;
|
17810
|
+
yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null);
|
17849
17811
|
}
|
17850
|
-
|
17851
|
-
|
17852
|
-
|
17812
|
+
} catch (e_1_1) {
|
17813
|
+
e_1 = {
|
17814
|
+
error: e_1_1
|
17815
|
+
};
|
17816
|
+
} finally {
|
17817
|
+
try {
|
17818
|
+
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
|
17819
|
+
} finally {
|
17820
|
+
if (e_1) throw e_1.error;
|
17821
|
+
}
|
17822
|
+
}
|
17853
17823
|
});
|
17854
17824
|
}
|
17855
|
-
|
17856
|
-
const
|
17857
|
-
|
17858
|
-
|
17859
|
-
|
17860
|
-
|
17861
|
-
|
17862
|
-
|
17863
|
-
|
17864
|
-
|
17865
|
-
|
17866
|
-
|
17867
|
-
|
17868
|
-
|
17869
|
-
|
17870
|
-
|
17871
|
-
|
17872
|
-
|
17873
|
-
|
17874
|
-
|
17875
|
-
|
17876
|
-
|
17877
|
-
|
17878
|
-
|
17879
|
-
|
17880
|
-
|
17881
|
-
this.disconnectWebAudio();
|
17882
|
-
} else {
|
17883
|
-
detached = super.detach(element);
|
17884
|
-
// if there are still any attached elements after detaching, connect webaudio to the first element that's left
|
17885
|
-
// disconnect webaudio otherwise
|
17886
|
-
if (this.audioContext) {
|
17887
|
-
if (this.attachedElements.length > 0) {
|
17888
|
-
this.connectWebAudio(this.audioContext, this.attachedElements[0]);
|
17889
|
-
} else {
|
17890
|
-
this.disconnectWebAudio();
|
17825
|
+
resumeUpstream() {
|
17826
|
+
const _super = Object.create(null, {
|
17827
|
+
resumeUpstream: {
|
17828
|
+
get: () => super.resumeUpstream
|
17829
|
+
}
|
17830
|
+
});
|
17831
|
+
var _a, e_2, _b, _c;
|
17832
|
+
var _d;
|
17833
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17834
|
+
yield _super.resumeUpstream.call(this);
|
17835
|
+
try {
|
17836
|
+
for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
|
17837
|
+
_c = _g.value;
|
17838
|
+
_e = false;
|
17839
|
+
const sc = _c;
|
17840
|
+
yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
|
17841
|
+
}
|
17842
|
+
} catch (e_2_1) {
|
17843
|
+
e_2 = {
|
17844
|
+
error: e_2_1
|
17845
|
+
};
|
17846
|
+
} finally {
|
17847
|
+
try {
|
17848
|
+
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
|
17849
|
+
} finally {
|
17850
|
+
if (e_2) throw e_2.error;
|
17891
17851
|
}
|
17892
17852
|
}
|
17893
|
-
}
|
17894
|
-
return detached;
|
17895
|
-
}
|
17896
|
-
/**
|
17897
|
-
* @internal
|
17898
|
-
* @experimental
|
17899
|
-
*/
|
17900
|
-
setAudioContext(audioContext) {
|
17901
|
-
this.audioContext = audioContext;
|
17902
|
-
if (audioContext && this.attachedElements.length > 0) {
|
17903
|
-
this.connectWebAudio(audioContext, this.attachedElements[0]);
|
17904
|
-
} else if (!audioContext) {
|
17905
|
-
this.disconnectWebAudio();
|
17906
|
-
}
|
17853
|
+
});
|
17907
17854
|
}
|
17908
|
-
|
17909
|
-
|
17910
|
-
|
17911
|
-
|
17912
|
-
|
17913
|
-
|
17914
|
-
this
|
17915
|
-
|
17916
|
-
|
17917
|
-
|
17855
|
+
mute() {
|
17856
|
+
const _super = Object.create(null, {
|
17857
|
+
mute: {
|
17858
|
+
get: () => super.mute
|
17859
|
+
}
|
17860
|
+
});
|
17861
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17862
|
+
const unlock = yield this.muteLock.lock();
|
17863
|
+
try {
|
17864
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
17865
|
+
livekitLogger.debug('stopping camera track');
|
17866
|
+
// also stop the track, so that camera indicator is turned off
|
17867
|
+
this._mediaStreamTrack.stop();
|
17868
|
+
}
|
17869
|
+
yield _super.mute.call(this);
|
17870
|
+
return this;
|
17871
|
+
} finally {
|
17872
|
+
unlock();
|
17873
|
+
}
|
17874
|
+
});
|
17918
17875
|
}
|
17919
|
-
|
17920
|
-
|
17921
|
-
|
17922
|
-
|
17923
|
-
|
17924
|
-
this.webAudioPluginNodes.forEach(node => {
|
17925
|
-
lastNode.connect(node);
|
17926
|
-
lastNode = node;
|
17876
|
+
unmute() {
|
17877
|
+
const _super = Object.create(null, {
|
17878
|
+
unmute: {
|
17879
|
+
get: () => super.unmute
|
17880
|
+
}
|
17927
17881
|
});
|
17928
|
-
this
|
17929
|
-
|
17930
|
-
|
17931
|
-
|
17932
|
-
|
17933
|
-
|
17934
|
-
// try to resume the context if it isn't running already
|
17935
|
-
if (context.state !== 'running') {
|
17936
|
-
context.resume().then(() => {
|
17937
|
-
if (context.state !== 'running') {
|
17938
|
-
this.emit(TrackEvent.AudioPlaybackFailed, new Error("Audio Context couldn't be started automatically"));
|
17882
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17883
|
+
const unlock = yield this.muteLock.lock();
|
17884
|
+
try {
|
17885
|
+
if (this.source === Track.Source.Camera && !this.isUserProvided) {
|
17886
|
+
livekitLogger.debug('reacquiring camera track');
|
17887
|
+
yield this.restartTrack();
|
17939
17888
|
}
|
17940
|
-
|
17941
|
-
this
|
17942
|
-
}
|
17943
|
-
|
17889
|
+
yield _super.unmute.call(this);
|
17890
|
+
return this;
|
17891
|
+
} finally {
|
17892
|
+
unlock();
|
17893
|
+
}
|
17894
|
+
});
|
17944
17895
|
}
|
17945
|
-
|
17946
|
-
|
17947
|
-
(
|
17948
|
-
|
17949
|
-
|
17950
|
-
this.sourceNode = undefined;
|
17896
|
+
setTrackMuted(muted) {
|
17897
|
+
super.setTrackMuted(muted);
|
17898
|
+
for (const sc of this.simulcastCodecs.values()) {
|
17899
|
+
sc.mediaStreamTrack.enabled = !muted;
|
17900
|
+
}
|
17951
17901
|
}
|
17952
|
-
|
17902
|
+
getSenderStats() {
|
17903
|
+
var _a;
|
17953
17904
|
return __awaiter(this, void 0, void 0, function* () {
|
17954
|
-
if (!this.
|
17955
|
-
return;
|
17905
|
+
if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
|
17906
|
+
return [];
|
17956
17907
|
}
|
17957
|
-
const
|
17958
|
-
|
17908
|
+
const items = [];
|
17909
|
+
const stats = yield this.sender.getStats();
|
17959
17910
|
stats.forEach(v => {
|
17960
|
-
|
17961
|
-
|
17962
|
-
|
17911
|
+
var _a;
|
17912
|
+
if (v.type === 'outbound-rtp') {
|
17913
|
+
const vs = {
|
17914
|
+
type: 'video',
|
17915
|
+
streamId: v.id,
|
17916
|
+
frameHeight: v.frameHeight,
|
17917
|
+
frameWidth: v.frameWidth,
|
17918
|
+
firCount: v.firCount,
|
17919
|
+
pliCount: v.pliCount,
|
17920
|
+
nackCount: v.nackCount,
|
17921
|
+
packetsSent: v.packetsSent,
|
17922
|
+
bytesSent: v.bytesSent,
|
17923
|
+
framesSent: v.framesSent,
|
17963
17924
|
timestamp: v.timestamp,
|
17964
|
-
|
17965
|
-
|
17966
|
-
|
17967
|
-
|
17968
|
-
silentConcealedSamples: v.silentConcealedSamples,
|
17969
|
-
silentConcealmentEvents: v.silentConcealmentEvents,
|
17970
|
-
totalAudioEnergy: v.totalAudioEnergy,
|
17971
|
-
totalSamplesDuration: v.totalSamplesDuration
|
17925
|
+
rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
|
17926
|
+
retransmittedPacketsSent: v.retransmittedPacketsSent,
|
17927
|
+
qualityLimitationReason: v.qualityLimitationReason,
|
17928
|
+
qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges
|
17972
17929
|
};
|
17930
|
+
// locate the appropriate remote-inbound-rtp item
|
17931
|
+
const r = stats.get(v.remoteId);
|
17932
|
+
if (r) {
|
17933
|
+
vs.jitter = r.jitter;
|
17934
|
+
vs.packetsLost = r.packetsLost;
|
17935
|
+
vs.roundTripTime = r.roundTripTime;
|
17936
|
+
}
|
17937
|
+
items.push(vs);
|
17973
17938
|
}
|
17974
17939
|
});
|
17975
|
-
return
|
17940
|
+
return items;
|
17976
17941
|
});
|
17977
17942
|
}
|
17978
|
-
|
17979
|
-
|
17980
|
-
|
17981
|
-
|
17982
|
-
|
17983
|
-
|
17984
|
-
|
17985
|
-
|
17986
|
-
|
17987
|
-
|
17988
|
-
|
17943
|
+
setPublishingQuality(maxQuality) {
|
17944
|
+
const qualities = [];
|
17945
|
+
for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
|
17946
|
+
qualities.push(new SubscribedQuality({
|
17947
|
+
quality: q,
|
17948
|
+
enabled: q <= maxQuality
|
17949
|
+
}));
|
17950
|
+
}
|
17951
|
+
livekitLogger.debug("setting publishing quality. max quality ".concat(maxQuality));
|
17952
|
+
this.setPublishingLayers(qualities);
|
17953
|
+
}
|
17954
|
+
setDeviceId(deviceId) {
|
17955
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17956
|
+
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
|
17957
|
+
return true;
|
17989
17958
|
}
|
17990
|
-
|
17991
|
-
|
17992
|
-
|
17959
|
+
this._constraints.deviceId = deviceId;
|
17960
|
+
// when video is muted, underlying media stream track is stopped and
|
17961
|
+
// will be restarted later
|
17962
|
+
if (!this.isMuted) {
|
17963
|
+
yield this.restartTrack();
|
17993
17964
|
}
|
17994
|
-
this.
|
17965
|
+
return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
17995
17966
|
});
|
17996
|
-
this.debouncedHandleResize = r(() => {
|
17997
|
-
this.updateDimensions();
|
17998
|
-
}, REACTION_DELAY);
|
17999
|
-
this.adaptiveStreamSettings = adaptiveStreamSettings;
|
18000
|
-
}
|
18001
|
-
get isAdaptiveStream() {
|
18002
|
-
return this.adaptiveStreamSettings !== undefined;
|
18003
17967
|
}
|
18004
|
-
|
18005
|
-
|
18006
|
-
|
18007
|
-
|
18008
|
-
|
17968
|
+
restartTrack(options) {
|
17969
|
+
var _a, e_3, _b, _c;
|
17970
|
+
return __awaiter(this, void 0, void 0, function* () {
|
17971
|
+
let constraints;
|
17972
|
+
if (options) {
|
17973
|
+
const streamConstraints = constraintsForOptions({
|
17974
|
+
video: options
|
17975
|
+
});
|
17976
|
+
if (typeof streamConstraints.video !== 'boolean') {
|
17977
|
+
constraints = streamConstraints.video;
|
17978
|
+
}
|
17979
|
+
}
|
17980
|
+
yield this.restart(constraints);
|
17981
|
+
try {
|
17982
|
+
for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
17983
|
+
_c = _f.value;
|
17984
|
+
_d = false;
|
17985
|
+
const sc = _c;
|
17986
|
+
if (sc.sender) {
|
17987
|
+
sc.mediaStreamTrack = this.mediaStreamTrack.clone();
|
17988
|
+
yield sc.sender.replaceTrack(sc.mediaStreamTrack);
|
17989
|
+
}
|
17990
|
+
}
|
17991
|
+
} catch (e_3_1) {
|
17992
|
+
e_3 = {
|
17993
|
+
error: e_3_1
|
17994
|
+
};
|
17995
|
+
} finally {
|
17996
|
+
try {
|
17997
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
17998
|
+
} finally {
|
17999
|
+
if (e_3) throw e_3.error;
|
18000
|
+
}
|
18001
|
+
}
|
18002
|
+
});
|
18009
18003
|
}
|
18010
|
-
|
18011
|
-
|
18012
|
-
|
18013
|
-
|
18014
|
-
|
18015
|
-
|
18016
|
-
|
18017
|
-
|
18018
|
-
|
18004
|
+
setProcessor(processor) {
|
18005
|
+
let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
18006
|
+
const _super = Object.create(null, {
|
18007
|
+
setProcessor: {
|
18008
|
+
get: () => super.setProcessor
|
18009
|
+
}
|
18010
|
+
});
|
18011
|
+
var _a, e_4, _b, _c;
|
18012
|
+
var _d, _e;
|
18013
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18014
|
+
yield _super.setProcessor.call(this, processor, showProcessedStreamLocally);
|
18015
|
+
if ((_d = this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
|
18016
|
+
try {
|
18017
|
+
for (var _f = true, _g = __asyncValues(this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
|
18018
|
+
_c = _h.value;
|
18019
|
+
_f = false;
|
18020
|
+
const sc = _c;
|
18021
|
+
yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(this.processor.processedTrack);
|
18022
|
+
}
|
18023
|
+
} catch (e_4_1) {
|
18024
|
+
e_4 = {
|
18025
|
+
error: e_4_1
|
18026
|
+
};
|
18027
|
+
} finally {
|
18028
|
+
try {
|
18029
|
+
if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
|
18030
|
+
} finally {
|
18031
|
+
if (e_4) throw e_4.error;
|
18032
|
+
}
|
18033
|
+
}
|
18019
18034
|
}
|
18020
18035
|
});
|
18021
18036
|
}
|
18022
|
-
|
18023
|
-
if (
|
18024
|
-
|
18025
|
-
} else {
|
18026
|
-
super.attach(element);
|
18027
|
-
}
|
18028
|
-
// It's possible attach is called multiple times on an element. When that's
|
18029
|
-
// the case, we'd want to avoid adding duplicate elementInfos
|
18030
|
-
if (this.adaptiveStreamSettings && this.elementInfos.find(info => info.element === element) === undefined) {
|
18031
|
-
const elementInfo = new HTMLElementInfo(element);
|
18032
|
-
this.observeElementInfo(elementInfo);
|
18037
|
+
addSimulcastTrack(codec, encodings) {
|
18038
|
+
if (this.simulcastCodecs.has(codec)) {
|
18039
|
+
throw new Error("".concat(codec, " already added"));
|
18033
18040
|
}
|
18034
|
-
|
18041
|
+
const simulcastCodecInfo = {
|
18042
|
+
codec,
|
18043
|
+
mediaStreamTrack: this.mediaStreamTrack.clone(),
|
18044
|
+
sender: undefined,
|
18045
|
+
encodings
|
18046
|
+
};
|
18047
|
+
this.simulcastCodecs.set(codec, simulcastCodecInfo);
|
18048
|
+
return simulcastCodecInfo;
|
18035
18049
|
}
|
18036
|
-
|
18037
|
-
|
18038
|
-
|
18039
|
-
|
18040
|
-
*/
|
18041
|
-
observeElementInfo(elementInfo) {
|
18042
|
-
if (this.adaptiveStreamSettings && this.elementInfos.find(info => info === elementInfo) === undefined) {
|
18043
|
-
elementInfo.handleResize = () => {
|
18044
|
-
this.debouncedHandleResize();
|
18045
|
-
};
|
18046
|
-
elementInfo.handleVisibilityChanged = () => {
|
18047
|
-
this.updateVisibility();
|
18048
|
-
};
|
18049
|
-
this.elementInfos.push(elementInfo);
|
18050
|
-
elementInfo.observe();
|
18051
|
-
// trigger the first resize update cycle
|
18052
|
-
// if the tab is backgrounded, the initial resize event does not fire until
|
18053
|
-
// the tab comes into focus for the first time.
|
18054
|
-
this.debouncedHandleResize();
|
18055
|
-
this.updateVisibility();
|
18056
|
-
} else {
|
18057
|
-
livekitLogger.warn('visibility resize observer not triggered');
|
18050
|
+
setSimulcastTrackSender(codec, sender) {
|
18051
|
+
const simulcastCodecInfo = this.simulcastCodecs.get(codec);
|
18052
|
+
if (!simulcastCodecInfo) {
|
18053
|
+
return;
|
18058
18054
|
}
|
18055
|
+
simulcastCodecInfo.sender = sender;
|
18056
|
+
// browser will reenable disabled codec/layers after new codec has been published,
|
18057
|
+
// so refresh subscribedCodecs after publish a new codec
|
18058
|
+
setTimeout(() => {
|
18059
|
+
if (this.subscribedCodecs) {
|
18060
|
+
this.setPublishingCodecs(this.subscribedCodecs);
|
18061
|
+
}
|
18062
|
+
}, refreshSubscribedCodecAfterNewCodec);
|
18059
18063
|
}
|
18060
18064
|
/**
|
18061
|
-
* Stop observing an ElementInfo for changes.
|
18062
|
-
* @param elementInfo
|
18063
18065
|
* @internal
|
18066
|
+
* Sets codecs that should be publishing
|
18064
18067
|
*/
|
18065
|
-
|
18066
|
-
|
18067
|
-
|
18068
|
-
|
18069
|
-
|
18070
|
-
|
18071
|
-
|
18072
|
-
|
18073
|
-
|
18074
|
-
|
18075
|
-
|
18076
|
-
|
18077
|
-
|
18078
|
-
|
18079
|
-
|
18080
|
-
|
18081
|
-
|
18082
|
-
|
18083
|
-
|
18084
|
-
|
18085
|
-
|
18086
|
-
|
18087
|
-
|
18088
|
-
|
18089
|
-
|
18090
|
-
|
18091
|
-
|
18092
|
-
|
18093
|
-
|
18068
|
+
setPublishingCodecs(codecs) {
|
18069
|
+
var _a, codecs_1, codecs_1_1;
|
18070
|
+
var _b, e_5, _c, _d;
|
18071
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18072
|
+
livekitLogger.debug('setting publishing codecs', {
|
18073
|
+
codecs,
|
18074
|
+
currentCodec: this.codec
|
18075
|
+
});
|
18076
|
+
// only enable simulcast codec for preference codec setted
|
18077
|
+
if (!this.codec && codecs.length > 0) {
|
18078
|
+
yield this.setPublishingLayers(codecs[0].qualities);
|
18079
|
+
return [];
|
18080
|
+
}
|
18081
|
+
this.subscribedCodecs = codecs;
|
18082
|
+
const newCodecs = [];
|
18083
|
+
try {
|
18084
|
+
for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
|
18085
|
+
_d = codecs_1_1.value;
|
18086
|
+
_a = false;
|
18087
|
+
const codec = _d;
|
18088
|
+
if (!this.codec || this.codec === codec.codec) {
|
18089
|
+
yield this.setPublishingLayers(codec.qualities);
|
18090
|
+
} else {
|
18091
|
+
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
|
18092
|
+
livekitLogger.debug("try setPublishingCodec for ".concat(codec.codec), simulcastCodecInfo);
|
18093
|
+
if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
|
18094
|
+
for (const q of codec.qualities) {
|
18095
|
+
if (q.enabled) {
|
18096
|
+
newCodecs.push(codec.codec);
|
18097
|
+
break;
|
18098
|
+
}
|
18099
|
+
}
|
18100
|
+
} else if (simulcastCodecInfo.encodings) {
|
18101
|
+
livekitLogger.debug("try setPublishingLayersForSender ".concat(codec.codec));
|
18102
|
+
yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock);
|
18103
|
+
}
|
18104
|
+
}
|
18105
|
+
}
|
18106
|
+
} catch (e_5_1) {
|
18107
|
+
e_5 = {
|
18108
|
+
error: e_5_1
|
18109
|
+
};
|
18110
|
+
} finally {
|
18111
|
+
try {
|
18112
|
+
if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
|
18113
|
+
} finally {
|
18114
|
+
if (e_5) throw e_5.error;
|
18115
|
+
}
|
18116
|
+
}
|
18117
|
+
return newCodecs;
|
18118
|
+
});
|
18094
18119
|
}
|
18095
|
-
|
18120
|
+
/**
|
18121
|
+
* @internal
|
18122
|
+
* Sets layers that should be publishing
|
18123
|
+
*/
|
18124
|
+
setPublishingLayers(qualities) {
|
18096
18125
|
return __awaiter(this, void 0, void 0, function* () {
|
18097
|
-
|
18126
|
+
livekitLogger.debug('setting publishing layers', qualities);
|
18127
|
+
if (!this.sender || !this.encodings) {
|
18098
18128
|
return;
|
18099
18129
|
}
|
18100
|
-
|
18101
|
-
let receiverStats;
|
18102
|
-
stats.forEach(v => {
|
18103
|
-
if (v.type === 'inbound-rtp') {
|
18104
|
-
receiverStats = {
|
18105
|
-
type: 'video',
|
18106
|
-
framesDecoded: v.framesDecoded,
|
18107
|
-
framesDropped: v.framesDropped,
|
18108
|
-
framesReceived: v.framesReceived,
|
18109
|
-
packetsReceived: v.packetsReceived,
|
18110
|
-
packetsLost: v.packetsLost,
|
18111
|
-
frameWidth: v.frameWidth,
|
18112
|
-
frameHeight: v.frameHeight,
|
18113
|
-
pliCount: v.pliCount,
|
18114
|
-
firCount: v.firCount,
|
18115
|
-
nackCount: v.nackCount,
|
18116
|
-
jitter: v.jitter,
|
18117
|
-
timestamp: v.timestamp,
|
18118
|
-
bytesReceived: v.bytesReceived,
|
18119
|
-
decoderImplementation: v.decoderImplementation
|
18120
|
-
};
|
18121
|
-
}
|
18122
|
-
});
|
18123
|
-
return receiverStats;
|
18130
|
+
yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock);
|
18124
18131
|
});
|
18125
18132
|
}
|
18126
|
-
stopObservingElement(element) {
|
18127
|
-
const stopElementInfos = this.elementInfos.filter(info => info.element === element);
|
18128
|
-
for (const info of stopElementInfos) {
|
18129
|
-
this.stopObservingElementInfo(info);
|
18130
|
-
}
|
18131
|
-
}
|
18132
18133
|
handleAppVisibilityChanged() {
|
18133
18134
|
const _super = Object.create(null, {
|
18134
18135
|
handleAppVisibilityChanged: {
|
@@ -18137,1337 +18138,1574 @@ class RemoteVideoTrack extends RemoteTrack {
|
|
18137
18138
|
});
|
18138
18139
|
return __awaiter(this, void 0, void 0, function* () {
|
18139
18140
|
yield _super.handleAppVisibilityChanged.call(this);
|
18140
|
-
if (!
|
18141
|
-
this.
|
18141
|
+
if (!isMobile()) return;
|
18142
|
+
if (this.isInBackground && this.source === Track.Source.Camera) {
|
18143
|
+
this._mediaStreamTrack.enabled = false;
|
18144
|
+
}
|
18142
18145
|
});
|
18143
18146
|
}
|
18144
|
-
|
18145
|
-
|
18146
|
-
|
18147
|
-
const
|
18148
|
-
|
18149
|
-
|
18150
|
-
|
18151
|
-
|
18152
|
-
|
18153
|
-
|
18154
|
-
|
18155
|
-
|
18156
|
-
|
18157
|
-
|
18158
|
-
|
18159
|
-
|
18160
|
-
}
|
18161
|
-
this.lastVisible = isVisible;
|
18162
|
-
this.emit(TrackEvent.VisibilityChanged, isVisible, this);
|
18163
|
-
}
|
18164
|
-
updateDimensions() {
|
18165
|
-
var _a, _b;
|
18166
|
-
let maxWidth = 0;
|
18167
|
-
let maxHeight = 0;
|
18168
|
-
const pixelDensity = this.getPixelDensity();
|
18169
|
-
for (const info of this.elementInfos) {
|
18170
|
-
const currentElementWidth = info.width() * pixelDensity;
|
18171
|
-
const currentElementHeight = info.height() * pixelDensity;
|
18172
|
-
if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
|
18173
|
-
maxWidth = currentElementWidth;
|
18174
|
-
maxHeight = currentElementHeight;
|
18147
|
+
}
|
18148
|
+
function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock) {
|
18149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18150
|
+
const unlock = yield senderLock.lock();
|
18151
|
+
livekitLogger.debug('setPublishingLayersForSender', {
|
18152
|
+
sender,
|
18153
|
+
qualities,
|
18154
|
+
senderEncodings
|
18155
|
+
});
|
18156
|
+
try {
|
18157
|
+
const params = sender.getParameters();
|
18158
|
+
const {
|
18159
|
+
encodings
|
18160
|
+
} = params;
|
18161
|
+
if (!encodings) {
|
18162
|
+
return;
|
18175
18163
|
}
|
18176
|
-
|
18177
|
-
|
18178
|
-
|
18179
|
-
|
18180
|
-
|
18181
|
-
|
18182
|
-
|
18183
|
-
|
18184
|
-
|
18185
|
-
|
18186
|
-
|
18187
|
-
|
18188
|
-
|
18189
|
-
|
18190
|
-
|
18191
|
-
|
18192
|
-
|
18193
|
-
|
18194
|
-
|
18195
|
-
|
18196
|
-
|
18197
|
-
|
18198
|
-
|
18199
|
-
|
18164
|
+
if (encodings.length !== senderEncodings.length) {
|
18165
|
+
livekitLogger.warn('cannot set publishing layers, encodings mismatch');
|
18166
|
+
return;
|
18167
|
+
}
|
18168
|
+
let hasChanged = false;
|
18169
|
+
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
18170
|
+
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
18171
|
+
low resolution frame and recover very quickly, but noticable
|
18172
|
+
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
18173
|
+
const closableSpatial = false;
|
18174
|
+
/* @ts-ignore */
|
18175
|
+
if (closableSpatial && encodings[0].scalabilityMode) ; else {
|
18176
|
+
// simulcast dynacast encodings
|
18177
|
+
encodings.forEach((encoding, idx) => {
|
18178
|
+
var _a;
|
18179
|
+
let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
|
18180
|
+
if (rid === '') {
|
18181
|
+
rid = 'q';
|
18182
|
+
}
|
18183
|
+
const quality = videoQualityForRid(rid);
|
18184
|
+
const subscribedQuality = qualities.find(q => q.quality === quality);
|
18185
|
+
if (!subscribedQuality) {
|
18186
|
+
return;
|
18187
|
+
}
|
18188
|
+
if (encoding.active !== subscribedQuality.enabled) {
|
18189
|
+
hasChanged = true;
|
18190
|
+
encoding.active = subscribedQuality.enabled;
|
18191
|
+
livekitLogger.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'));
|
18192
|
+
// FireFox does not support setting encoding.active to false, so we
|
18193
|
+
// have a workaround of lowering its bitrate and resolution to the min.
|
18194
|
+
if (isFireFox()) {
|
18195
|
+
if (subscribedQuality.enabled) {
|
18196
|
+
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
|
18197
|
+
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
|
18198
|
+
/* @ts-ignore */
|
18199
|
+
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
|
18200
|
+
} else {
|
18201
|
+
encoding.scaleResolutionDownBy = 4;
|
18202
|
+
encoding.maxBitrate = 10;
|
18203
|
+
/* @ts-ignore */
|
18204
|
+
encoding.maxFrameRate = 2;
|
18205
|
+
}
|
18206
|
+
}
|
18207
|
+
}
|
18208
|
+
});
|
18209
|
+
}
|
18210
|
+
if (hasChanged) {
|
18211
|
+
params.encodings = encodings;
|
18212
|
+
livekitLogger.debug("setting encodings", params.encodings);
|
18213
|
+
yield sender.setParameters(params);
|
18200
18214
|
}
|
18215
|
+
} finally {
|
18216
|
+
unlock();
|
18201
18217
|
}
|
18202
|
-
|
18203
|
-
}
|
18218
|
+
});
|
18204
18219
|
}
|
18205
|
-
|
18206
|
-
|
18207
|
-
|
18208
|
-
|
18209
|
-
|
18210
|
-
|
18211
|
-
|
18212
|
-
|
18213
|
-
|
18214
|
-
|
18215
|
-
const {
|
18216
|
-
target,
|
18217
|
-
isIntersecting
|
18218
|
-
} = entry;
|
18219
|
-
if (target === this.element) {
|
18220
|
-
this.isIntersecting = isIntersecting;
|
18221
|
-
this.visibilityChangedAt = Date.now();
|
18222
|
-
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18223
|
-
}
|
18224
|
-
};
|
18225
|
-
this.onEnterPiP = () => {
|
18226
|
-
var _a;
|
18227
|
-
this.isPiP = true;
|
18228
|
-
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18229
|
-
};
|
18230
|
-
this.onLeavePiP = () => {
|
18231
|
-
var _a;
|
18232
|
-
this.isPiP = false;
|
18233
|
-
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18234
|
-
};
|
18235
|
-
this.element = element;
|
18236
|
-
this.isIntersecting = visible !== null && visible !== void 0 ? visible : isElementInViewport(element);
|
18237
|
-
this.isPiP = isWeb() && document.pictureInPictureElement === element;
|
18238
|
-
this.visibilityChangedAt = 0;
|
18239
|
-
}
|
18240
|
-
width() {
|
18241
|
-
return this.element.clientWidth;
|
18242
|
-
}
|
18243
|
-
height() {
|
18244
|
-
return this.element.clientHeight;
|
18245
|
-
}
|
18246
|
-
observe() {
|
18247
|
-
// make sure we update the current visible state once we start to observe
|
18248
|
-
this.isIntersecting = isElementInViewport(this.element);
|
18249
|
-
this.isPiP = document.pictureInPictureElement === this.element;
|
18250
|
-
this.element.handleResize = () => {
|
18251
|
-
var _a;
|
18252
|
-
(_a = this.handleResize) === null || _a === void 0 ? void 0 : _a.call(this);
|
18253
|
-
};
|
18254
|
-
this.element.handleVisibilityChanged = this.onVisibilityChanged;
|
18255
|
-
getIntersectionObserver().observe(this.element);
|
18256
|
-
getResizeObserver().observe(this.element);
|
18257
|
-
this.element.addEventListener('enterpictureinpicture', this.onEnterPiP);
|
18258
|
-
this.element.addEventListener('leavepictureinpicture', this.onLeavePiP);
|
18259
|
-
}
|
18260
|
-
stopObserving() {
|
18261
|
-
var _a, _b;
|
18262
|
-
(_a = getIntersectionObserver()) === null || _a === void 0 ? void 0 : _a.unobserve(this.element);
|
18263
|
-
(_b = getResizeObserver()) === null || _b === void 0 ? void 0 : _b.unobserve(this.element);
|
18264
|
-
this.element.removeEventListener('enterpictureinpicture', this.onEnterPiP);
|
18265
|
-
this.element.removeEventListener('leavepictureinpicture', this.onLeavePiP);
|
18220
|
+
function videoQualityForRid(rid) {
|
18221
|
+
switch (rid) {
|
18222
|
+
case 'f':
|
18223
|
+
return VideoQuality.HIGH;
|
18224
|
+
case 'h':
|
18225
|
+
return VideoQuality.MEDIUM;
|
18226
|
+
case 'q':
|
18227
|
+
return VideoQuality.LOW;
|
18228
|
+
default:
|
18229
|
+
return VideoQuality.HIGH;
|
18266
18230
|
}
|
18267
18231
|
}
|
18268
|
-
|
18269
|
-
|
18270
|
-
|
18271
|
-
|
18272
|
-
|
18273
|
-
|
18274
|
-
|
18275
|
-
|
18276
|
-
|
18277
|
-
|
18278
|
-
opacity,
|
18279
|
-
display
|
18280
|
-
} = getComputedStyle(el);
|
18281
|
-
while (el.offsetParent) {
|
18282
|
-
el = el.offsetParent;
|
18283
|
-
top += el.offsetTop;
|
18284
|
-
left += el.offsetLeft;
|
18232
|
+
function videoLayersFromEncodings(width, height, encodings, svc) {
|
18233
|
+
// default to a single layer, HQ
|
18234
|
+
if (!encodings) {
|
18235
|
+
return [new VideoLayer({
|
18236
|
+
quality: VideoQuality.HIGH,
|
18237
|
+
width,
|
18238
|
+
height,
|
18239
|
+
bitrate: 0,
|
18240
|
+
ssrc: 0
|
18241
|
+
})];
|
18285
18242
|
}
|
18286
|
-
|
18243
|
+
if (svc) {
|
18244
|
+
// svc layers
|
18245
|
+
/* @ts-ignore */
|
18246
|
+
const encodingSM = encodings[0].scalabilityMode;
|
18247
|
+
const sm = new ScalabilityMode(encodingSM);
|
18248
|
+
const layers = [];
|
18249
|
+
for (let i = 0; i < sm.spatial; i += 1) {
|
18250
|
+
layers.push(new VideoLayer({
|
18251
|
+
quality: VideoQuality.HIGH - i,
|
18252
|
+
width: Math.ceil(width / Math.pow(2, i)),
|
18253
|
+
height: Math.ceil(height / Math.pow(2, i)),
|
18254
|
+
bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(3, i)) : 0,
|
18255
|
+
ssrc: 0
|
18256
|
+
}));
|
18257
|
+
}
|
18258
|
+
return layers;
|
18259
|
+
}
|
18260
|
+
return encodings.map(encoding => {
|
18261
|
+
var _a, _b, _c;
|
18262
|
+
const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
|
18263
|
+
let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
|
18264
|
+
return new VideoLayer({
|
18265
|
+
quality,
|
18266
|
+
width: Math.ceil(width / scale),
|
18267
|
+
height: Math.ceil(height / scale),
|
18268
|
+
bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
|
18269
|
+
ssrc: 0
|
18270
|
+
});
|
18271
|
+
});
|
18287
18272
|
}
|
18288
18273
|
|
18289
|
-
class
|
18290
|
-
constructor(
|
18291
|
-
super();
|
18292
|
-
this.
|
18293
|
-
this.
|
18294
|
-
this.handleMuted = () => {
|
18295
|
-
this.emit(TrackEvent.Muted);
|
18296
|
-
};
|
18297
|
-
this.handleUnmuted = () => {
|
18298
|
-
this.emit(TrackEvent.Unmuted);
|
18299
|
-
};
|
18300
|
-
this.setMaxListeners(100);
|
18301
|
-
this.kind = kind;
|
18302
|
-
this.trackSid = id;
|
18303
|
-
this.trackName = name;
|
18304
|
-
this.source = Track.Source.Unknown;
|
18274
|
+
class RemoteTrack extends Track {
|
18275
|
+
constructor(mediaTrack, sid, kind, receiver) {
|
18276
|
+
super(mediaTrack, kind);
|
18277
|
+
this.sid = sid;
|
18278
|
+
this.receiver = receiver;
|
18305
18279
|
}
|
18306
18280
|
/** @internal */
|
18307
|
-
|
18308
|
-
if (this.
|
18309
|
-
this.
|
18310
|
-
this.
|
18311
|
-
|
18312
|
-
this.track = track;
|
18313
|
-
if (track) {
|
18314
|
-
// forward events
|
18315
|
-
track.on(TrackEvent.Muted, this.handleMuted);
|
18316
|
-
track.on(TrackEvent.Unmuted, this.handleUnmuted);
|
18281
|
+
setMuted(muted) {
|
18282
|
+
if (this.isMuted !== muted) {
|
18283
|
+
this.isMuted = muted;
|
18284
|
+
this._mediaStreamTrack.enabled = !muted;
|
18285
|
+
this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
|
18317
18286
|
}
|
18318
18287
|
}
|
18319
|
-
|
18320
|
-
|
18288
|
+
/** @internal */
|
18289
|
+
setMediaStream(stream) {
|
18290
|
+
// this is needed to determine when the track is finished
|
18291
|
+
// we send each track down in its own MediaStream, so we can assume the
|
18292
|
+
// current track is the only one that can be removed.
|
18293
|
+
this.mediaStream = stream;
|
18294
|
+
stream.onremovetrack = () => {
|
18295
|
+
this.receiver = undefined;
|
18296
|
+
this._currentBitrate = 0;
|
18297
|
+
this.emit(TrackEvent.Ended, this);
|
18298
|
+
};
|
18321
18299
|
}
|
18322
|
-
|
18323
|
-
|
18300
|
+
start() {
|
18301
|
+
this.startMonitor();
|
18302
|
+
// use `enabled` of track to enable re-use of transceiver
|
18303
|
+
super.enable();
|
18324
18304
|
}
|
18325
|
-
|
18326
|
-
|
18305
|
+
stop() {
|
18306
|
+
this.stopMonitor();
|
18307
|
+
// use `enabled` of track to enable re-use of transceiver
|
18308
|
+
super.disable();
|
18327
18309
|
}
|
18328
|
-
|
18329
|
-
|
18310
|
+
/* @internal */
|
18311
|
+
startMonitor() {
|
18312
|
+
if (!this.monitorInterval) {
|
18313
|
+
this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
|
18314
|
+
}
|
18315
|
+
}
|
18316
|
+
}
|
18317
|
+
|
18318
|
+
class RemoteAudioTrack extends RemoteTrack {
|
18319
|
+
constructor(mediaTrack, sid, receiver, audioContext, audioOutput) {
|
18320
|
+
super(mediaTrack, sid, Track.Kind.Audio, receiver);
|
18321
|
+
this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
|
18322
|
+
if (!this.receiver) {
|
18323
|
+
this._currentBitrate = 0;
|
18324
|
+
return;
|
18325
|
+
}
|
18326
|
+
const stats = yield this.getReceiverStats();
|
18327
|
+
if (stats && this.prevStats && this.receiver) {
|
18328
|
+
this._currentBitrate = computeBitrate(stats, this.prevStats);
|
18329
|
+
}
|
18330
|
+
this.prevStats = stats;
|
18331
|
+
});
|
18332
|
+
this.audioContext = audioContext;
|
18333
|
+
this.webAudioPluginNodes = [];
|
18334
|
+
if (audioOutput) {
|
18335
|
+
this.sinkId = audioOutput.deviceId;
|
18336
|
+
}
|
18330
18337
|
}
|
18331
18338
|
/**
|
18332
|
-
*
|
18339
|
+
* sets the volume for all attached audio elements
|
18333
18340
|
*/
|
18334
|
-
|
18335
|
-
|
18336
|
-
|
18341
|
+
setVolume(volume) {
|
18342
|
+
var _a;
|
18343
|
+
for (const el of this.attachedElements) {
|
18344
|
+
if (this.audioContext) {
|
18345
|
+
(_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setTargetAtTime(volume, 0, 0.1);
|
18346
|
+
} else {
|
18347
|
+
el.volume = volume;
|
18348
|
+
}
|
18349
|
+
}
|
18350
|
+
if (isReactNative()) {
|
18351
|
+
// @ts-ignore
|
18352
|
+
this._mediaStreamTrack._setVolume(volume);
|
18337
18353
|
}
|
18354
|
+
this.elementVolume = volume;
|
18338
18355
|
}
|
18339
18356
|
/**
|
18340
|
-
*
|
18357
|
+
* gets the volume of attached audio elements (loudest)
|
18341
18358
|
*/
|
18342
|
-
|
18343
|
-
if (this.
|
18344
|
-
return this.
|
18359
|
+
getVolume() {
|
18360
|
+
if (this.elementVolume) {
|
18361
|
+
return this.elementVolume;
|
18345
18362
|
}
|
18346
|
-
|
18347
|
-
|
18348
|
-
|
18349
|
-
this.trackSid = info.sid;
|
18350
|
-
this.trackName = info.name;
|
18351
|
-
this.source = Track.sourceFromProto(info.source);
|
18352
|
-
this.mimeType = info.mimeType;
|
18353
|
-
if (this.kind === Track.Kind.Video && info.width > 0) {
|
18354
|
-
this.dimensions = {
|
18355
|
-
width: info.width,
|
18356
|
-
height: info.height
|
18357
|
-
};
|
18358
|
-
this.simulcasted = info.simulcast;
|
18363
|
+
if (isReactNative()) {
|
18364
|
+
// RN volume value defaults to 1.0 if hasn't been changed.
|
18365
|
+
return 1.0;
|
18359
18366
|
}
|
18360
|
-
|
18361
|
-
this.
|
18362
|
-
|
18363
|
-
|
18367
|
+
let highestVolume = 0;
|
18368
|
+
this.attachedElements.forEach(element => {
|
18369
|
+
if (element.volume > highestVolume) {
|
18370
|
+
highestVolume = element.volume;
|
18371
|
+
}
|
18364
18372
|
});
|
18373
|
+
return highestVolume;
|
18365
18374
|
}
|
18366
|
-
|
18367
|
-
|
18368
|
-
|
18369
|
-
|
18370
|
-
|
18371
|
-
|
18372
|
-
|
18373
|
-
|
18374
|
-
|
18375
|
-
|
18376
|
-
|
18377
|
-
|
18378
|
-
|
18379
|
-
|
18380
|
-
|
18381
|
-
var _a;
|
18382
|
-
return (_a = this.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused;
|
18383
|
-
}
|
18384
|
-
constructor(kind, ti, track) {
|
18385
|
-
super(kind, ti.sid, ti.name);
|
18386
|
-
this.track = undefined;
|
18387
|
-
this.handleTrackEnded = () => {
|
18388
|
-
this.emit(TrackEvent.Ended);
|
18389
|
-
};
|
18390
|
-
this.updateInfo(ti);
|
18391
|
-
this.setTrack(track);
|
18375
|
+
/**
|
18376
|
+
* calls setSinkId on all attached elements, if supported
|
18377
|
+
* @param deviceId audio output device
|
18378
|
+
*/
|
18379
|
+
setSinkId(deviceId) {
|
18380
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18381
|
+
this.sinkId = deviceId;
|
18382
|
+
yield Promise.all(this.attachedElements.map(elm => {
|
18383
|
+
if (!supportsSetSinkId(elm)) {
|
18384
|
+
return;
|
18385
|
+
}
|
18386
|
+
/* @ts-ignore */
|
18387
|
+
return elm.setSinkId(deviceId);
|
18388
|
+
}));
|
18389
|
+
});
|
18392
18390
|
}
|
18393
|
-
|
18394
|
-
|
18395
|
-
|
18391
|
+
attach(element) {
|
18392
|
+
const needsNewWebAudioConnection = this.attachedElements.length === 0;
|
18393
|
+
if (!element) {
|
18394
|
+
element = super.attach();
|
18395
|
+
} else {
|
18396
|
+
super.attach(element);
|
18396
18397
|
}
|
18397
|
-
|
18398
|
-
|
18399
|
-
|
18398
|
+
if (this.elementVolume) {
|
18399
|
+
element.volume = this.elementVolume;
|
18400
|
+
}
|
18401
|
+
if (this.sinkId && supportsSetSinkId(element)) {
|
18402
|
+
/* @ts-ignore */
|
18403
|
+
element.setSinkId(this.sinkId);
|
18400
18404
|
}
|
18401
|
-
|
18402
|
-
|
18403
|
-
|
18404
|
-
|
18405
|
+
if (this.audioContext && needsNewWebAudioConnection) {
|
18406
|
+
livekitLogger.debug('using audio context mapping');
|
18407
|
+
this.connectWebAudio(this.audioContext, element);
|
18408
|
+
element.volume = 0;
|
18409
|
+
element.muted = true;
|
18405
18410
|
}
|
18406
|
-
return
|
18407
|
-
}
|
18408
|
-
get audioTrack() {
|
18409
|
-
return super.audioTrack;
|
18411
|
+
return element;
|
18410
18412
|
}
|
18411
|
-
|
18412
|
-
|
18413
|
+
detach(element) {
|
18414
|
+
let detached;
|
18415
|
+
if (!element) {
|
18416
|
+
detached = super.detach();
|
18417
|
+
this.disconnectWebAudio();
|
18418
|
+
} else {
|
18419
|
+
detached = super.detach(element);
|
18420
|
+
// if there are still any attached elements after detaching, connect webaudio to the first element that's left
|
18421
|
+
// disconnect webaudio otherwise
|
18422
|
+
if (this.audioContext) {
|
18423
|
+
if (this.attachedElements.length > 0) {
|
18424
|
+
this.connectWebAudio(this.audioContext, this.attachedElements[0]);
|
18425
|
+
} else {
|
18426
|
+
this.disconnectWebAudio();
|
18427
|
+
}
|
18428
|
+
}
|
18429
|
+
}
|
18430
|
+
return detached;
|
18413
18431
|
}
|
18414
18432
|
/**
|
18415
|
-
*
|
18433
|
+
* @internal
|
18434
|
+
* @experimental
|
18416
18435
|
*/
|
18417
|
-
|
18418
|
-
|
18419
|
-
|
18420
|
-
|
18421
|
-
})
|
18436
|
+
setAudioContext(audioContext) {
|
18437
|
+
this.audioContext = audioContext;
|
18438
|
+
if (audioContext && this.attachedElements.length > 0) {
|
18439
|
+
this.connectWebAudio(audioContext, this.attachedElements[0]);
|
18440
|
+
} else if (!audioContext) {
|
18441
|
+
this.disconnectWebAudio();
|
18442
|
+
}
|
18422
18443
|
}
|
18423
18444
|
/**
|
18424
|
-
*
|
18445
|
+
* @internal
|
18446
|
+
* @experimental
|
18447
|
+
* @param {AudioNode[]} nodes - An array of WebAudio nodes. These nodes should not be connected to each other when passed, as the sdk will take care of connecting them in the order of the array.
|
18425
18448
|
*/
|
18426
|
-
|
18427
|
-
|
18428
|
-
|
18429
|
-
|
18430
|
-
}
|
18449
|
+
setWebAudioPlugins(nodes) {
|
18450
|
+
this.webAudioPluginNodes = nodes;
|
18451
|
+
if (this.attachedElements.length > 0 && this.audioContext) {
|
18452
|
+
this.connectWebAudio(this.audioContext, this.attachedElements[0]);
|
18453
|
+
}
|
18431
18454
|
}
|
18432
|
-
|
18433
|
-
|
18434
|
-
|
18435
|
-
|
18436
|
-
|
18437
|
-
|
18438
|
-
|
18439
|
-
|
18440
|
-
yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.pauseUpstream();
|
18455
|
+
connectWebAudio(context, element) {
|
18456
|
+
this.disconnectWebAudio();
|
18457
|
+
// @ts-ignore attached elements always have a srcObject set
|
18458
|
+
this.sourceNode = context.createMediaStreamSource(element.srcObject);
|
18459
|
+
let lastNode = this.sourceNode;
|
18460
|
+
this.webAudioPluginNodes.forEach(node => {
|
18461
|
+
lastNode.connect(node);
|
18462
|
+
lastNode = node;
|
18441
18463
|
});
|
18464
|
+
this.gainNode = context.createGain();
|
18465
|
+
lastNode.connect(this.gainNode);
|
18466
|
+
this.gainNode.connect(context.destination);
|
18467
|
+
if (this.elementVolume) {
|
18468
|
+
this.gainNode.gain.setTargetAtTime(this.elementVolume, 0, 0.1);
|
18469
|
+
}
|
18470
|
+
// try to resume the context if it isn't running already
|
18471
|
+
if (context.state !== 'running') {
|
18472
|
+
context.resume().then(() => {
|
18473
|
+
if (context.state !== 'running') {
|
18474
|
+
this.emit(TrackEvent.AudioPlaybackFailed, new Error("Audio Context couldn't be started automatically"));
|
18475
|
+
}
|
18476
|
+
}).catch(e => {
|
18477
|
+
this.emit(TrackEvent.AudioPlaybackFailed, e);
|
18478
|
+
});
|
18479
|
+
}
|
18442
18480
|
}
|
18443
|
-
|
18444
|
-
|
18445
|
-
|
18446
|
-
|
18447
|
-
|
18448
|
-
|
18481
|
+
disconnectWebAudio() {
|
18482
|
+
var _a, _b;
|
18483
|
+
(_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.disconnect();
|
18484
|
+
(_b = this.sourceNode) === null || _b === void 0 ? void 0 : _b.disconnect();
|
18485
|
+
this.gainNode = undefined;
|
18486
|
+
this.sourceNode = undefined;
|
18487
|
+
}
|
18488
|
+
getReceiverStats() {
|
18449
18489
|
return __awaiter(this, void 0, void 0, function* () {
|
18450
|
-
|
18490
|
+
if (!this.receiver || !this.receiver.getStats) {
|
18491
|
+
return;
|
18492
|
+
}
|
18493
|
+
const stats = yield this.receiver.getStats();
|
18494
|
+
let receiverStats;
|
18495
|
+
stats.forEach(v => {
|
18496
|
+
if (v.type === 'inbound-rtp') {
|
18497
|
+
receiverStats = {
|
18498
|
+
type: 'audio',
|
18499
|
+
timestamp: v.timestamp,
|
18500
|
+
jitter: v.jitter,
|
18501
|
+
bytesReceived: v.bytesReceived,
|
18502
|
+
concealedSamples: v.concealedSamples,
|
18503
|
+
concealmentEvents: v.concealmentEvents,
|
18504
|
+
silentConcealedSamples: v.silentConcealedSamples,
|
18505
|
+
silentConcealmentEvents: v.silentConcealmentEvents,
|
18506
|
+
totalAudioEnergy: v.totalAudioEnergy,
|
18507
|
+
totalSamplesDuration: v.totalSamplesDuration
|
18508
|
+
};
|
18509
|
+
}
|
18510
|
+
});
|
18511
|
+
return receiverStats;
|
18451
18512
|
});
|
18452
18513
|
}
|
18453
18514
|
}
|
18454
18515
|
|
18455
|
-
|
18456
|
-
|
18457
|
-
|
18458
|
-
|
18459
|
-
|
18460
|
-
|
18461
|
-
|
18462
|
-
|
18463
|
-
|
18464
|
-
|
18465
|
-
|
18466
|
-
|
18467
|
-
|
18468
|
-
|
18469
|
-
|
18470
|
-
|
18471
|
-
|
18516
|
+
const REACTION_DELAY = 100;
|
18517
|
+
class RemoteVideoTrack extends RemoteTrack {
|
18518
|
+
constructor(mediaTrack, sid, receiver, adaptiveStreamSettings) {
|
18519
|
+
super(mediaTrack, sid, Track.Kind.Video, receiver);
|
18520
|
+
this.elementInfos = [];
|
18521
|
+
this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
|
18522
|
+
if (!this.receiver) {
|
18523
|
+
this._currentBitrate = 0;
|
18524
|
+
return;
|
18525
|
+
}
|
18526
|
+
const stats = yield this.getReceiverStats();
|
18527
|
+
if (stats && this.prevStats && this.receiver) {
|
18528
|
+
this._currentBitrate = computeBitrate(stats, this.prevStats);
|
18529
|
+
}
|
18530
|
+
this.prevStats = stats;
|
18531
|
+
});
|
18532
|
+
this.debouncedHandleResize = r(() => {
|
18533
|
+
this.updateDimensions();
|
18534
|
+
}, REACTION_DELAY);
|
18535
|
+
this.adaptiveStreamSettings = adaptiveStreamSettings;
|
18472
18536
|
}
|
18473
|
-
|
18474
|
-
|
18475
|
-
|
18476
|
-
|
18537
|
+
get isAdaptiveStream() {
|
18538
|
+
return this.adaptiveStreamSettings !== undefined;
|
18539
|
+
}
|
18540
|
+
/**
|
18541
|
+
* Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
|
18542
|
+
*/
|
18543
|
+
get mediaStreamTrack() {
|
18544
|
+
return this._mediaStreamTrack;
|
18477
18545
|
}
|
18478
18546
|
/** @internal */
|
18479
|
-
|
18480
|
-
super();
|
18481
|
-
|
18482
|
-
|
18483
|
-
|
18484
|
-
|
18485
|
-
|
18486
|
-
|
18487
|
-
|
18488
|
-
|
18489
|
-
this.name = name;
|
18490
|
-
this.metadata = metadata;
|
18491
|
-
this.audioTracks = new Map();
|
18492
|
-
this.videoTracks = new Map();
|
18493
|
-
this.tracks = new Map();
|
18547
|
+
setMuted(muted) {
|
18548
|
+
super.setMuted(muted);
|
18549
|
+
this.attachedElements.forEach(element => {
|
18550
|
+
// detach or attach
|
18551
|
+
if (muted) {
|
18552
|
+
detachTrack(this._mediaStreamTrack, element);
|
18553
|
+
} else {
|
18554
|
+
attachToElement(this._mediaStreamTrack, element);
|
18555
|
+
}
|
18556
|
+
});
|
18494
18557
|
}
|
18495
|
-
|
18496
|
-
|
18558
|
+
attach(element) {
|
18559
|
+
if (!element) {
|
18560
|
+
element = super.attach();
|
18561
|
+
} else {
|
18562
|
+
super.attach(element);
|
18563
|
+
}
|
18564
|
+
// It's possible attach is called multiple times on an element. When that's
|
18565
|
+
// the case, we'd want to avoid adding duplicate elementInfos
|
18566
|
+
if (this.adaptiveStreamSettings && this.elementInfos.find(info => info.element === element) === undefined) {
|
18567
|
+
const elementInfo = new HTMLElementInfo(element);
|
18568
|
+
this.observeElementInfo(elementInfo);
|
18569
|
+
}
|
18570
|
+
return element;
|
18497
18571
|
}
|
18498
18572
|
/**
|
18499
|
-
*
|
18500
|
-
*
|
18501
|
-
* @
|
18502
|
-
* @returns
|
18573
|
+
* Observe an ElementInfo for changes when adaptive streaming.
|
18574
|
+
* @param elementInfo
|
18575
|
+
* @internal
|
18503
18576
|
*/
|
18504
|
-
|
18505
|
-
|
18506
|
-
|
18507
|
-
|
18508
|
-
}
|
18577
|
+
observeElementInfo(elementInfo) {
|
18578
|
+
if (this.adaptiveStreamSettings && this.elementInfos.find(info => info === elementInfo) === undefined) {
|
18579
|
+
elementInfo.handleResize = () => {
|
18580
|
+
this.debouncedHandleResize();
|
18581
|
+
};
|
18582
|
+
elementInfo.handleVisibilityChanged = () => {
|
18583
|
+
this.updateVisibility();
|
18584
|
+
};
|
18585
|
+
this.elementInfos.push(elementInfo);
|
18586
|
+
elementInfo.observe();
|
18587
|
+
// trigger the first resize update cycle
|
18588
|
+
// if the tab is backgrounded, the initial resize event does not fire until
|
18589
|
+
// the tab comes into focus for the first time.
|
18590
|
+
this.debouncedHandleResize();
|
18591
|
+
this.updateVisibility();
|
18592
|
+
} else {
|
18593
|
+
livekitLogger.warn('visibility resize observer not triggered');
|
18509
18594
|
}
|
18510
18595
|
}
|
18511
18596
|
/**
|
18512
|
-
*
|
18513
|
-
* @param
|
18514
|
-
* @
|
18597
|
+
* Stop observing an ElementInfo for changes.
|
18598
|
+
* @param elementInfo
|
18599
|
+
* @internal
|
18515
18600
|
*/
|
18516
|
-
|
18517
|
-
|
18518
|
-
|
18519
|
-
|
18520
|
-
|
18601
|
+
stopObservingElementInfo(elementInfo) {
|
18602
|
+
if (!this.isAdaptiveStream) {
|
18603
|
+
livekitLogger.warn('stopObservingElementInfo ignored');
|
18604
|
+
return;
|
18605
|
+
}
|
18606
|
+
const stopElementInfos = this.elementInfos.filter(info => info === elementInfo);
|
18607
|
+
for (const info of stopElementInfos) {
|
18608
|
+
info.stopObserving();
|
18521
18609
|
}
|
18610
|
+
this.elementInfos = this.elementInfos.filter(info => info !== elementInfo);
|
18611
|
+
this.updateVisibility();
|
18612
|
+
this.debouncedHandleResize();
|
18522
18613
|
}
|
18523
|
-
|
18524
|
-
|
18614
|
+
detach(element) {
|
18615
|
+
let detachedElements = [];
|
18616
|
+
if (element) {
|
18617
|
+
this.stopObservingElement(element);
|
18618
|
+
return super.detach(element);
|
18619
|
+
}
|
18620
|
+
detachedElements = super.detach();
|
18621
|
+
for (const e of detachedElements) {
|
18622
|
+
this.stopObservingElement(e);
|
18623
|
+
}
|
18624
|
+
return detachedElements;
|
18525
18625
|
}
|
18526
|
-
|
18626
|
+
/** @internal */
|
18627
|
+
getDecoderImplementation() {
|
18527
18628
|
var _a;
|
18528
|
-
|
18529
|
-
return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
|
18629
|
+
return (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.decoderImplementation;
|
18530
18630
|
}
|
18531
|
-
|
18532
|
-
|
18533
|
-
|
18534
|
-
|
18631
|
+
getReceiverStats() {
|
18632
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18633
|
+
if (!this.receiver || !this.receiver.getStats) {
|
18634
|
+
return;
|
18635
|
+
}
|
18636
|
+
const stats = yield this.receiver.getStats();
|
18637
|
+
let receiverStats;
|
18638
|
+
stats.forEach(v => {
|
18639
|
+
if (v.type === 'inbound-rtp') {
|
18640
|
+
receiverStats = {
|
18641
|
+
type: 'video',
|
18642
|
+
framesDecoded: v.framesDecoded,
|
18643
|
+
framesDropped: v.framesDropped,
|
18644
|
+
framesReceived: v.framesReceived,
|
18645
|
+
packetsReceived: v.packetsReceived,
|
18646
|
+
packetsLost: v.packetsLost,
|
18647
|
+
frameWidth: v.frameWidth,
|
18648
|
+
frameHeight: v.frameHeight,
|
18649
|
+
pliCount: v.pliCount,
|
18650
|
+
firCount: v.firCount,
|
18651
|
+
nackCount: v.nackCount,
|
18652
|
+
jitter: v.jitter,
|
18653
|
+
timestamp: v.timestamp,
|
18654
|
+
bytesReceived: v.bytesReceived,
|
18655
|
+
decoderImplementation: v.decoderImplementation
|
18656
|
+
};
|
18657
|
+
}
|
18658
|
+
});
|
18659
|
+
return receiverStats;
|
18660
|
+
});
|
18535
18661
|
}
|
18536
|
-
|
18537
|
-
const
|
18538
|
-
|
18662
|
+
stopObservingElement(element) {
|
18663
|
+
const stopElementInfos = this.elementInfos.filter(info => info.element === element);
|
18664
|
+
for (const info of stopElementInfos) {
|
18665
|
+
this.stopObservingElementInfo(info);
|
18666
|
+
}
|
18539
18667
|
}
|
18540
|
-
|
18541
|
-
|
18668
|
+
handleAppVisibilityChanged() {
|
18669
|
+
const _super = Object.create(null, {
|
18670
|
+
handleAppVisibilityChanged: {
|
18671
|
+
get: () => super.handleAppVisibilityChanged
|
18672
|
+
}
|
18673
|
+
});
|
18674
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18675
|
+
yield _super.handleAppVisibilityChanged.call(this);
|
18676
|
+
if (!this.isAdaptiveStream) return;
|
18677
|
+
this.updateVisibility();
|
18678
|
+
});
|
18542
18679
|
}
|
18543
|
-
|
18544
|
-
|
18545
|
-
|
18546
|
-
|
18680
|
+
updateVisibility() {
|
18681
|
+
var _a, _b;
|
18682
|
+
const lastVisibilityChange = this.elementInfos.reduce((prev, info) => Math.max(prev, info.visibilityChangedAt || 0), 0);
|
18683
|
+
const backgroundPause = ((_b = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pauseVideoInBackground) !== null && _b !== void 0 ? _b : true // default to true
|
18684
|
+
) ? this.isInBackground : false;
|
18685
|
+
const isPiPMode = this.elementInfos.some(info => info.pictureInPicture);
|
18686
|
+
const isVisible = this.elementInfos.some(info => info.visible) && !backgroundPause || isPiPMode;
|
18687
|
+
if (this.lastVisible === isVisible) {
|
18688
|
+
return;
|
18547
18689
|
}
|
18548
|
-
|
18690
|
+
if (!isVisible && Date.now() - lastVisibilityChange < REACTION_DELAY) {
|
18691
|
+
// delay hidden events
|
18692
|
+
CriticalTimers.setTimeout(() => {
|
18693
|
+
this.updateVisibility();
|
18694
|
+
}, REACTION_DELAY);
|
18695
|
+
return;
|
18696
|
+
}
|
18697
|
+
this.lastVisible = isVisible;
|
18698
|
+
this.emit(TrackEvent.VisibilityChanged, isVisible, this);
|
18549
18699
|
}
|
18550
|
-
|
18551
|
-
|
18552
|
-
|
18553
|
-
|
18554
|
-
|
18555
|
-
|
18556
|
-
|
18557
|
-
|
18558
|
-
|
18559
|
-
|
18700
|
+
updateDimensions() {
|
18701
|
+
var _a, _b;
|
18702
|
+
let maxWidth = 0;
|
18703
|
+
let maxHeight = 0;
|
18704
|
+
const pixelDensity = this.getPixelDensity();
|
18705
|
+
for (const info of this.elementInfos) {
|
18706
|
+
const currentElementWidth = info.width() * pixelDensity;
|
18707
|
+
const currentElementHeight = info.height() * pixelDensity;
|
18708
|
+
if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
|
18709
|
+
maxWidth = currentElementWidth;
|
18710
|
+
maxHeight = currentElementHeight;
|
18711
|
+
}
|
18560
18712
|
}
|
18561
|
-
this.
|
18562
|
-
|
18563
|
-
this.setName(info.name);
|
18564
|
-
this.setMetadata(info.metadata);
|
18565
|
-
if (info.permission) {
|
18566
|
-
this.setPermissions(info.permission);
|
18713
|
+
if (((_a = this.lastDimensions) === null || _a === void 0 ? void 0 : _a.width) === maxWidth && ((_b = this.lastDimensions) === null || _b === void 0 ? void 0 : _b.height) === maxHeight) {
|
18714
|
+
return;
|
18567
18715
|
}
|
18568
|
-
|
18569
|
-
|
18570
|
-
|
18571
|
-
|
18572
|
-
|
18573
|
-
return true;
|
18716
|
+
this.lastDimensions = {
|
18717
|
+
width: maxWidth,
|
18718
|
+
height: maxHeight
|
18719
|
+
};
|
18720
|
+
this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
|
18574
18721
|
}
|
18575
|
-
|
18576
|
-
|
18577
|
-
const
|
18578
|
-
|
18579
|
-
|
18580
|
-
if (
|
18581
|
-
|
18722
|
+
getPixelDensity() {
|
18723
|
+
var _a;
|
18724
|
+
const pixelDensity = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pixelDensity;
|
18725
|
+
if (pixelDensity === 'screen') {
|
18726
|
+
return getDevicePixelRatio();
|
18727
|
+
} else if (!pixelDensity) {
|
18728
|
+
// when unset, we'll pick a sane default here.
|
18729
|
+
// for higher pixel density devices (mobile phones, etc), we'll use 2
|
18730
|
+
// otherwise it defaults to 1
|
18731
|
+
const devicePixelRatio = getDevicePixelRatio();
|
18732
|
+
if (devicePixelRatio > 2) {
|
18733
|
+
return 2;
|
18734
|
+
} else {
|
18735
|
+
return 1;
|
18736
|
+
}
|
18582
18737
|
}
|
18738
|
+
return pixelDensity;
|
18583
18739
|
}
|
18584
|
-
|
18585
|
-
|
18586
|
-
|
18587
|
-
|
18588
|
-
this.emit(ParticipantEvent.ParticipantNameChanged, name);
|
18589
|
-
}
|
18740
|
+
}
|
18741
|
+
class HTMLElementInfo {
|
18742
|
+
get visible() {
|
18743
|
+
return this.isPiP || this.isIntersecting;
|
18590
18744
|
}
|
18591
|
-
|
18592
|
-
|
18593
|
-
|
18594
|
-
|
18595
|
-
|
18745
|
+
get pictureInPicture() {
|
18746
|
+
return this.isPiP;
|
18747
|
+
}
|
18748
|
+
constructor(element, visible) {
|
18749
|
+
this.onVisibilityChanged = entry => {
|
18596
18750
|
var _a;
|
18597
|
-
|
18598
|
-
|
18599
|
-
|
18600
|
-
|
18601
|
-
this.
|
18602
|
-
|
18603
|
-
|
18751
|
+
const {
|
18752
|
+
target,
|
18753
|
+
isIntersecting
|
18754
|
+
} = entry;
|
18755
|
+
if (target === this.element) {
|
18756
|
+
this.isIntersecting = isIntersecting;
|
18757
|
+
this.visibilityChangedAt = Date.now();
|
18758
|
+
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18759
|
+
}
|
18760
|
+
};
|
18761
|
+
this.onEnterPiP = () => {
|
18762
|
+
var _a;
|
18763
|
+
this.isPiP = true;
|
18764
|
+
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18765
|
+
};
|
18766
|
+
this.onLeavePiP = () => {
|
18767
|
+
var _a;
|
18768
|
+
this.isPiP = false;
|
18769
|
+
(_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this);
|
18770
|
+
};
|
18771
|
+
this.element = element;
|
18772
|
+
this.isIntersecting = visible !== null && visible !== void 0 ? visible : isElementInViewport(element);
|
18773
|
+
this.isPiP = isWeb() && document.pictureInPictureElement === element;
|
18774
|
+
this.visibilityChangedAt = 0;
|
18604
18775
|
}
|
18605
|
-
|
18606
|
-
|
18607
|
-
if (speaking === this.isSpeaking) {
|
18608
|
-
return;
|
18609
|
-
}
|
18610
|
-
this.isSpeaking = speaking;
|
18611
|
-
if (speaking) {
|
18612
|
-
this.lastSpokeAt = new Date();
|
18613
|
-
}
|
18614
|
-
this.emit(ParticipantEvent.IsSpeakingChanged, speaking);
|
18776
|
+
width() {
|
18777
|
+
return this.element.clientWidth;
|
18615
18778
|
}
|
18616
|
-
|
18617
|
-
|
18618
|
-
|
18619
|
-
|
18620
|
-
|
18621
|
-
|
18622
|
-
|
18779
|
+
height() {
|
18780
|
+
return this.element.clientHeight;
|
18781
|
+
}
|
18782
|
+
observe() {
|
18783
|
+
// make sure we update the current visible state once we start to observe
|
18784
|
+
this.isIntersecting = isElementInViewport(this.element);
|
18785
|
+
this.isPiP = document.pictureInPictureElement === this.element;
|
18786
|
+
this.element.handleResize = () => {
|
18787
|
+
var _a;
|
18788
|
+
(_a = this.handleResize) === null || _a === void 0 ? void 0 : _a.call(this);
|
18789
|
+
};
|
18790
|
+
this.element.handleVisibilityChanged = this.onVisibilityChanged;
|
18791
|
+
getIntersectionObserver().observe(this.element);
|
18792
|
+
getResizeObserver().observe(this.element);
|
18793
|
+
this.element.addEventListener('enterpictureinpicture', this.onEnterPiP);
|
18794
|
+
this.element.addEventListener('leavepictureinpicture', this.onLeavePiP);
|
18623
18795
|
}
|
18624
|
-
|
18625
|
-
|
18626
|
-
|
18627
|
-
|
18628
|
-
|
18629
|
-
|
18630
|
-
this.emit(ParticipantEvent.TrackUnmuted, publication);
|
18631
|
-
});
|
18632
|
-
const pub = publication;
|
18633
|
-
if (pub.track) {
|
18634
|
-
pub.track.sid = publication.trackSid;
|
18635
|
-
}
|
18636
|
-
this.tracks.set(publication.trackSid, publication);
|
18637
|
-
switch (publication.kind) {
|
18638
|
-
case Track.Kind.Audio:
|
18639
|
-
this.audioTracks.set(publication.trackSid, publication);
|
18640
|
-
break;
|
18641
|
-
case Track.Kind.Video:
|
18642
|
-
this.videoTracks.set(publication.trackSid, publication);
|
18643
|
-
break;
|
18644
|
-
}
|
18796
|
+
stopObserving() {
|
18797
|
+
var _a, _b;
|
18798
|
+
(_a = getIntersectionObserver()) === null || _a === void 0 ? void 0 : _a.unobserve(this.element);
|
18799
|
+
(_b = getResizeObserver()) === null || _b === void 0 ? void 0 : _b.unobserve(this.element);
|
18800
|
+
this.element.removeEventListener('enterpictureinpicture', this.onEnterPiP);
|
18801
|
+
this.element.removeEventListener('leavepictureinpicture', this.onLeavePiP);
|
18645
18802
|
}
|
18646
18803
|
}
|
18647
|
-
|
18648
|
-
function
|
18649
|
-
|
18650
|
-
|
18651
|
-
|
18804
|
+
// does not account for occlusion by other elements
|
18805
|
+
function isElementInViewport(el) {
|
18806
|
+
let top = el.offsetTop;
|
18807
|
+
let left = el.offsetLeft;
|
18808
|
+
const width = el.offsetWidth;
|
18809
|
+
const height = el.offsetHeight;
|
18810
|
+
const {
|
18811
|
+
hidden
|
18812
|
+
} = el;
|
18813
|
+
const {
|
18814
|
+
opacity,
|
18815
|
+
display
|
18816
|
+
} = getComputedStyle(el);
|
18817
|
+
while (el.offsetParent) {
|
18818
|
+
el = el.offsetParent;
|
18819
|
+
top += el.offsetTop;
|
18820
|
+
left += el.offsetLeft;
|
18652
18821
|
}
|
18653
|
-
return
|
18654
|
-
participantIdentity: (_a = perms.participantIdentity) !== null && _a !== void 0 ? _a : '',
|
18655
|
-
participantSid: (_b = perms.participantSid) !== null && _b !== void 0 ? _b : '',
|
18656
|
-
allTracks: (_c = perms.allowAll) !== null && _c !== void 0 ? _c : false,
|
18657
|
-
trackSids: perms.allowedTrackSids || []
|
18658
|
-
});
|
18822
|
+
return top < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && top + height > window.pageYOffset && left + width > window.pageXOffset && !hidden && (opacity !== '' ? parseFloat(opacity) > 0 : true) && display !== 'none';
|
18659
18823
|
}
|
18660
18824
|
|
18661
|
-
class
|
18662
|
-
constructor(kind,
|
18663
|
-
super(
|
18664
|
-
this.
|
18665
|
-
|
18666
|
-
this.
|
18667
|
-
|
18668
|
-
this.currentVideoQuality = VideoQuality.HIGH;
|
18669
|
-
this.handleEnded = track => {
|
18670
|
-
this.setTrack(undefined);
|
18671
|
-
this.emit(TrackEvent.Ended, track);
|
18672
|
-
};
|
18673
|
-
this.handleVisibilityChange = visible => {
|
18674
|
-
livekitLogger.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), {
|
18675
|
-
trackSid: this.trackSid
|
18676
|
-
});
|
18677
|
-
this.disabled = !visible;
|
18678
|
-
this.emitTrackUpdate();
|
18825
|
+
class TrackPublication extends eventsExports.EventEmitter {
|
18826
|
+
constructor(kind, id, name) {
|
18827
|
+
super();
|
18828
|
+
this.metadataMuted = false;
|
18829
|
+
this.encryption = Encryption_Type.NONE;
|
18830
|
+
this.handleMuted = () => {
|
18831
|
+
this.emit(TrackEvent.Muted);
|
18679
18832
|
};
|
18680
|
-
this.
|
18681
|
-
|
18682
|
-
trackSid: this.trackSid
|
18683
|
-
});
|
18684
|
-
this.videoDimensions = dimensions;
|
18685
|
-
this.emitTrackUpdate();
|
18833
|
+
this.handleUnmuted = () => {
|
18834
|
+
this.emit(TrackEvent.Unmuted);
|
18686
18835
|
};
|
18687
|
-
this.
|
18688
|
-
this.
|
18689
|
-
|
18690
|
-
|
18691
|
-
|
18692
|
-
* @param subscribed true to subscribe to a track, false to unsubscribe
|
18693
|
-
*/
|
18694
|
-
setSubscribed(subscribed) {
|
18695
|
-
const prevStatus = this.subscriptionStatus;
|
18696
|
-
const prevPermission = this.permissionStatus;
|
18697
|
-
this.subscribed = subscribed;
|
18698
|
-
// reset allowed status when desired subscription state changes
|
18699
|
-
// server will notify client via signal message if it's not allowed
|
18700
|
-
if (subscribed) {
|
18701
|
-
this.allowed = true;
|
18702
|
-
}
|
18703
|
-
const sub = new UpdateSubscription({
|
18704
|
-
trackSids: [this.trackSid],
|
18705
|
-
subscribe: this.subscribed,
|
18706
|
-
participantTracks: [new ParticipantTracks({
|
18707
|
-
// sending an empty participant id since TrackPublication doesn't keep it
|
18708
|
-
// this is filled in by the participant that receives this message
|
18709
|
-
participantSid: '',
|
18710
|
-
trackSids: [this.trackSid]
|
18711
|
-
})]
|
18712
|
-
});
|
18713
|
-
this.emit(TrackEvent.UpdateSubscription, sub);
|
18714
|
-
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
18715
|
-
this.emitPermissionUpdateIfChanged(prevPermission);
|
18836
|
+
this.setMaxListeners(100);
|
18837
|
+
this.kind = kind;
|
18838
|
+
this.trackSid = id;
|
18839
|
+
this.trackName = name;
|
18840
|
+
this.source = Track.Source.Unknown;
|
18716
18841
|
}
|
18717
|
-
|
18718
|
-
|
18719
|
-
|
18842
|
+
/** @internal */
|
18843
|
+
setTrack(track) {
|
18844
|
+
if (this.track) {
|
18845
|
+
this.track.off(TrackEvent.Muted, this.handleMuted);
|
18846
|
+
this.track.off(TrackEvent.Unmuted, this.handleUnmuted);
|
18720
18847
|
}
|
18721
|
-
|
18722
|
-
|
18848
|
+
this.track = track;
|
18849
|
+
if (track) {
|
18850
|
+
// forward events
|
18851
|
+
track.on(TrackEvent.Muted, this.handleMuted);
|
18852
|
+
track.on(TrackEvent.Unmuted, this.handleUnmuted);
|
18723
18853
|
}
|
18724
|
-
return TrackPublication.SubscriptionStatus.Subscribed;
|
18725
18854
|
}
|
18726
|
-
get
|
18727
|
-
return this.
|
18855
|
+
get isMuted() {
|
18856
|
+
return this.metadataMuted;
|
18728
18857
|
}
|
18729
|
-
|
18730
|
-
|
18731
|
-
*/
|
18732
|
-
get isSubscribed() {
|
18733
|
-
if (this.subscribed === false) {
|
18734
|
-
return false;
|
18735
|
-
}
|
18736
|
-
return super.isSubscribed;
|
18858
|
+
get isEnabled() {
|
18859
|
+
return true;
|
18737
18860
|
}
|
18738
|
-
|
18739
|
-
|
18740
|
-
return this.subscribed !== false;
|
18861
|
+
get isSubscribed() {
|
18862
|
+
return this.track !== undefined;
|
18741
18863
|
}
|
18742
|
-
get
|
18743
|
-
return
|
18864
|
+
get isEncrypted() {
|
18865
|
+
return this.encryption !== Encryption_Type.NONE;
|
18744
18866
|
}
|
18745
18867
|
/**
|
18746
|
-
*
|
18747
|
-
* the participant is off screen, you may disable streaming down their video
|
18748
|
-
* to reduce bandwidth requirements
|
18749
|
-
* @param enabled
|
18868
|
+
* an [AudioTrack] if this publication holds an audio track
|
18750
18869
|
*/
|
18751
|
-
|
18752
|
-
if (
|
18753
|
-
return;
|
18870
|
+
get audioTrack() {
|
18871
|
+
if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
|
18872
|
+
return this.track;
|
18754
18873
|
}
|
18755
|
-
this.disabled = !enabled;
|
18756
|
-
this.emitTrackUpdate();
|
18757
18874
|
}
|
18758
18875
|
/**
|
18759
|
-
*
|
18760
|
-
*
|
18761
|
-
* This indicates the highest quality the client can accept. if network
|
18762
|
-
* bandwidth does not allow, server will automatically reduce quality to
|
18763
|
-
* optimize for uninterrupted video
|
18876
|
+
* an [VideoTrack] if this publication holds a video track
|
18764
18877
|
*/
|
18765
|
-
|
18766
|
-
if (
|
18767
|
-
return;
|
18878
|
+
get videoTrack() {
|
18879
|
+
if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
|
18880
|
+
return this.track;
|
18768
18881
|
}
|
18769
|
-
this.currentVideoQuality = quality;
|
18770
|
-
this.videoDimensions = undefined;
|
18771
|
-
this.emitTrackUpdate();
|
18772
18882
|
}
|
18773
|
-
|
18774
|
-
|
18775
|
-
|
18776
|
-
|
18777
|
-
|
18778
|
-
|
18779
|
-
|
18780
|
-
|
18781
|
-
|
18782
|
-
|
18883
|
+
/** @internal */
|
18884
|
+
updateInfo(info) {
|
18885
|
+
this.trackSid = info.sid;
|
18886
|
+
this.trackName = info.name;
|
18887
|
+
this.source = Track.sourceFromProto(info.source);
|
18888
|
+
this.mimeType = info.mimeType;
|
18889
|
+
if (this.kind === Track.Kind.Video && info.width > 0) {
|
18890
|
+
this.dimensions = {
|
18891
|
+
width: info.width,
|
18892
|
+
height: info.height
|
18893
|
+
};
|
18894
|
+
this.simulcasted = info.simulcast;
|
18783
18895
|
}
|
18784
|
-
this.
|
18785
|
-
this.
|
18896
|
+
this.encryption = info.encryption;
|
18897
|
+
this.trackInfo = info;
|
18898
|
+
livekitLogger.debug('update publication info', {
|
18899
|
+
info
|
18900
|
+
});
|
18786
18901
|
}
|
18787
|
-
|
18788
|
-
|
18789
|
-
|
18790
|
-
|
18791
|
-
|
18792
|
-
|
18793
|
-
|
18794
|
-
|
18795
|
-
|
18796
|
-
|
18797
|
-
|
18798
|
-
|
18902
|
+
}
|
18903
|
+
(function (TrackPublication) {
|
18904
|
+
(function (SubscriptionStatus) {
|
18905
|
+
SubscriptionStatus["Desired"] = "desired";
|
18906
|
+
SubscriptionStatus["Subscribed"] = "subscribed";
|
18907
|
+
SubscriptionStatus["Unsubscribed"] = "unsubscribed";
|
18908
|
+
})(TrackPublication.SubscriptionStatus || (TrackPublication.SubscriptionStatus = {}));
|
18909
|
+
(function (PermissionStatus) {
|
18910
|
+
PermissionStatus["Allowed"] = "allowed";
|
18911
|
+
PermissionStatus["NotAllowed"] = "not_allowed";
|
18912
|
+
})(TrackPublication.PermissionStatus || (TrackPublication.PermissionStatus = {}));
|
18913
|
+
})(TrackPublication || (TrackPublication = {}));
|
18914
|
+
|
18915
|
+
class LocalTrackPublication extends TrackPublication {
|
18916
|
+
get isUpstreamPaused() {
|
18917
|
+
var _a;
|
18918
|
+
return (_a = this.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused;
|
18799
18919
|
}
|
18800
|
-
|
18801
|
-
|
18920
|
+
constructor(kind, ti, track) {
|
18921
|
+
super(kind, ti.sid, ti.name);
|
18922
|
+
this.track = undefined;
|
18923
|
+
this.handleTrackEnded = () => {
|
18924
|
+
this.emit(TrackEvent.Ended);
|
18925
|
+
};
|
18926
|
+
this.updateInfo(ti);
|
18927
|
+
this.setTrack(track);
|
18802
18928
|
}
|
18803
|
-
/** @internal */
|
18804
18929
|
setTrack(track) {
|
18805
|
-
|
18806
|
-
|
18807
|
-
const prevTrack = this.track;
|
18808
|
-
if (prevTrack === track) {
|
18809
|
-
return;
|
18810
|
-
}
|
18811
|
-
if (prevTrack) {
|
18812
|
-
// unregister listener
|
18813
|
-
prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
|
18814
|
-
prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
18815
|
-
prevTrack.off(TrackEvent.Ended, this.handleEnded);
|
18816
|
-
prevTrack.detach();
|
18817
|
-
prevTrack.stopMonitor();
|
18818
|
-
this.emit(TrackEvent.Unsubscribed, prevTrack);
|
18930
|
+
if (this.track) {
|
18931
|
+
this.track.off(TrackEvent.Ended, this.handleTrackEnded);
|
18819
18932
|
}
|
18820
18933
|
super.setTrack(track);
|
18821
18934
|
if (track) {
|
18822
|
-
track.
|
18823
|
-
track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
|
18824
|
-
track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
18825
|
-
track.on(TrackEvent.Ended, this.handleEnded);
|
18826
|
-
this.emit(TrackEvent.Subscribed, track);
|
18935
|
+
track.on(TrackEvent.Ended, this.handleTrackEnded);
|
18827
18936
|
}
|
18828
|
-
this.emitPermissionUpdateIfChanged(prevPermission);
|
18829
|
-
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
18830
|
-
}
|
18831
|
-
/** @internal */
|
18832
|
-
setAllowed(allowed) {
|
18833
|
-
const prevStatus = this.subscriptionStatus;
|
18834
|
-
const prevPermission = this.permissionStatus;
|
18835
|
-
this.allowed = allowed;
|
18836
|
-
this.emitPermissionUpdateIfChanged(prevPermission);
|
18837
|
-
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
18838
|
-
}
|
18839
|
-
/** @internal */
|
18840
|
-
setSubscriptionError(error) {
|
18841
|
-
this.emit(TrackEvent.SubscriptionFailed, error);
|
18842
18937
|
}
|
18843
|
-
|
18844
|
-
updateInfo(info) {
|
18845
|
-
super.updateInfo(info);
|
18846
|
-
const prevMetadataMuted = this.metadataMuted;
|
18847
|
-
this.metadataMuted = info.muted;
|
18938
|
+
get isMuted() {
|
18848
18939
|
if (this.track) {
|
18849
|
-
this.track.
|
18850
|
-
} else if (prevMetadataMuted !== info.muted) {
|
18851
|
-
this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
|
18940
|
+
return this.track.isMuted;
|
18852
18941
|
}
|
18942
|
+
return super.isMuted;
|
18853
18943
|
}
|
18854
|
-
|
18855
|
-
|
18856
|
-
if (previousStatus === currentStatus) {
|
18857
|
-
return;
|
18858
|
-
}
|
18859
|
-
this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
|
18944
|
+
get audioTrack() {
|
18945
|
+
return super.audioTrack;
|
18860
18946
|
}
|
18861
|
-
|
18862
|
-
|
18863
|
-
if (currentPermissionStatus !== previousPermissionStatus) {
|
18864
|
-
this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
|
18865
|
-
}
|
18947
|
+
get videoTrack() {
|
18948
|
+
return super.videoTrack;
|
18866
18949
|
}
|
18867
|
-
|
18868
|
-
|
18869
|
-
|
18870
|
-
|
18871
|
-
|
18872
|
-
|
18873
|
-
|
18874
|
-
|
18875
|
-
livekitLogger.warn('cannot update track settings when not subscribed', {
|
18876
|
-
trackSid: this.trackSid
|
18877
|
-
});
|
18878
|
-
return false;
|
18879
|
-
}
|
18880
|
-
return true;
|
18950
|
+
/**
|
18951
|
+
* Mute the track associated with this publication
|
18952
|
+
*/
|
18953
|
+
mute() {
|
18954
|
+
var _a;
|
18955
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18956
|
+
return (_a = this.track) === null || _a === void 0 ? void 0 : _a.mute();
|
18957
|
+
});
|
18881
18958
|
}
|
18882
|
-
|
18883
|
-
|
18959
|
+
/**
|
18960
|
+
* Unmute track associated with this publication
|
18961
|
+
*/
|
18962
|
+
unmute() {
|
18963
|
+
var _a;
|
18964
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18965
|
+
return (_a = this.track) === null || _a === void 0 ? void 0 : _a.unmute();
|
18966
|
+
});
|
18884
18967
|
}
|
18885
|
-
|
18886
|
-
|
18887
|
-
|
18888
|
-
|
18889
|
-
|
18890
|
-
|
18968
|
+
/**
|
18969
|
+
* Pauses the media stream track associated with this publication from being sent to the server
|
18970
|
+
* and signals "muted" event to other participants
|
18971
|
+
* Useful if you want to pause the stream without pausing the local media stream track
|
18972
|
+
*/
|
18973
|
+
pauseUpstream() {
|
18974
|
+
var _a;
|
18975
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18976
|
+
yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.pauseUpstream();
|
18977
|
+
});
|
18978
|
+
}
|
18979
|
+
/**
|
18980
|
+
* Resumes sending the media stream track associated with this publication to the server after a call to [[pauseUpstream()]]
|
18981
|
+
* and signals "unmuted" event to other participants (unless the track is explicitly muted)
|
18982
|
+
*/
|
18983
|
+
resumeUpstream() {
|
18984
|
+
var _a;
|
18985
|
+
return __awaiter(this, void 0, void 0, function* () {
|
18986
|
+
yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.resumeUpstream();
|
18891
18987
|
});
|
18892
|
-
if (this.videoDimensions) {
|
18893
|
-
settings.width = this.videoDimensions.width;
|
18894
|
-
settings.height = this.videoDimensions.height;
|
18895
|
-
} else if (this.currentVideoQuality !== undefined) {
|
18896
|
-
settings.quality = this.currentVideoQuality;
|
18897
|
-
} else {
|
18898
|
-
// defaults to high quality
|
18899
|
-
settings.quality = VideoQuality.HIGH;
|
18900
|
-
}
|
18901
|
-
this.emit(TrackEvent.UpdateSettings, settings);
|
18902
18988
|
}
|
18903
18989
|
}
|
18904
18990
|
|
18905
|
-
|
18906
|
-
|
18907
|
-
|
18908
|
-
|
18991
|
+
var ConnectionQuality;
|
18992
|
+
(function (ConnectionQuality) {
|
18993
|
+
ConnectionQuality["Excellent"] = "excellent";
|
18994
|
+
ConnectionQuality["Good"] = "good";
|
18995
|
+
ConnectionQuality["Poor"] = "poor";
|
18996
|
+
ConnectionQuality["Unknown"] = "unknown";
|
18997
|
+
})(ConnectionQuality || (ConnectionQuality = {}));
|
18998
|
+
function qualityFromProto(q) {
|
18999
|
+
switch (q) {
|
19000
|
+
case ConnectionQuality$1.EXCELLENT:
|
19001
|
+
return ConnectionQuality.Excellent;
|
19002
|
+
case ConnectionQuality$1.GOOD:
|
19003
|
+
return ConnectionQuality.Good;
|
19004
|
+
case ConnectionQuality$1.POOR:
|
19005
|
+
return ConnectionQuality.Poor;
|
19006
|
+
default:
|
19007
|
+
return ConnectionQuality.Unknown;
|
19008
|
+
}
|
19009
|
+
}
|
19010
|
+
class Participant extends eventsExports.EventEmitter {
|
19011
|
+
get isEncrypted() {
|
19012
|
+
return this.tracks.size > 0 && Array.from(this.tracks.values()).every(tr => tr.isEncrypted);
|
18909
19013
|
}
|
18910
19014
|
/** @internal */
|
18911
|
-
constructor(
|
18912
|
-
super(
|
18913
|
-
|
18914
|
-
this.
|
19015
|
+
constructor(sid, identity, name, metadata) {
|
19016
|
+
super();
|
19017
|
+
/** audio level between 0-1.0, 1 being loudest, 0 being softest */
|
19018
|
+
this.audioLevel = 0;
|
19019
|
+
/** if participant is currently speaking */
|
19020
|
+
this.isSpeaking = false;
|
19021
|
+
this._connectionQuality = ConnectionQuality.Unknown;
|
19022
|
+
this.setMaxListeners(100);
|
19023
|
+
this.sid = sid;
|
19024
|
+
this.identity = identity;
|
19025
|
+
this.name = name;
|
19026
|
+
this.metadata = metadata;
|
18915
19027
|
this.audioTracks = new Map();
|
18916
19028
|
this.videoTracks = new Map();
|
18917
|
-
this.
|
19029
|
+
this.tracks = new Map();
|
18918
19030
|
}
|
18919
|
-
|
18920
|
-
|
18921
|
-
// register action events
|
18922
|
-
publication.on(TrackEvent.UpdateSettings, settings => {
|
18923
|
-
livekitLogger.debug('send update settings', settings);
|
18924
|
-
this.signalClient.sendUpdateTrackSettings(settings);
|
18925
|
-
});
|
18926
|
-
publication.on(TrackEvent.UpdateSubscription, sub => {
|
18927
|
-
sub.participantTracks.forEach(pt => {
|
18928
|
-
pt.participantSid = this.sid;
|
18929
|
-
});
|
18930
|
-
this.signalClient.sendUpdateSubscription(sub);
|
18931
|
-
});
|
18932
|
-
publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
|
18933
|
-
this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
|
18934
|
-
});
|
18935
|
-
publication.on(TrackEvent.SubscriptionStatusChanged, status => {
|
18936
|
-
this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
|
18937
|
-
});
|
18938
|
-
publication.on(TrackEvent.Subscribed, track => {
|
18939
|
-
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
18940
|
-
});
|
18941
|
-
publication.on(TrackEvent.Unsubscribed, previousTrack => {
|
18942
|
-
this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
|
18943
|
-
});
|
18944
|
-
publication.on(TrackEvent.SubscriptionFailed, error => {
|
18945
|
-
this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
|
18946
|
-
});
|
19031
|
+
getTracks() {
|
19032
|
+
return Array.from(this.tracks.values());
|
18947
19033
|
}
|
19034
|
+
/**
|
19035
|
+
* Finds the first track that matches the source filter, for example, getting
|
19036
|
+
* the user's camera track with getTrackBySource(Track.Source.Camera).
|
19037
|
+
* @param source
|
19038
|
+
* @returns
|
19039
|
+
*/
|
18948
19040
|
getTrack(source) {
|
18949
|
-
const
|
18950
|
-
|
18951
|
-
|
19041
|
+
for (const [, pub] of this.tracks) {
|
19042
|
+
if (pub.source === source) {
|
19043
|
+
return pub;
|
19044
|
+
}
|
19045
|
+
}
|
19046
|
+
}
|
19047
|
+
/**
|
19048
|
+
* Finds the first track that matches the track's name.
|
19049
|
+
* @param name
|
19050
|
+
* @returns
|
19051
|
+
*/
|
19052
|
+
getTrackByName(name) {
|
19053
|
+
for (const [, pub] of this.tracks) {
|
19054
|
+
if (pub.trackName === name) {
|
19055
|
+
return pub;
|
19056
|
+
}
|
19057
|
+
}
|
19058
|
+
}
|
19059
|
+
get connectionQuality() {
|
19060
|
+
return this._connectionQuality;
|
19061
|
+
}
|
19062
|
+
get isCameraEnabled() {
|
19063
|
+
var _a;
|
19064
|
+
const track = this.getTrack(Track.Source.Camera);
|
19065
|
+
return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
|
19066
|
+
}
|
19067
|
+
get isMicrophoneEnabled() {
|
19068
|
+
var _a;
|
19069
|
+
const track = this.getTrack(Track.Source.Microphone);
|
19070
|
+
return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
|
19071
|
+
}
|
19072
|
+
get isScreenShareEnabled() {
|
19073
|
+
const track = this.getTrack(Track.Source.ScreenShare);
|
19074
|
+
return !!track;
|
19075
|
+
}
|
19076
|
+
get isLocal() {
|
19077
|
+
return false;
|
19078
|
+
}
|
19079
|
+
/** when participant joined the room */
|
19080
|
+
get joinedAt() {
|
19081
|
+
if (this.participantInfo) {
|
19082
|
+
return new Date(Number.parseInt(this.participantInfo.joinedAt.toString()) * 1000);
|
18952
19083
|
}
|
19084
|
+
return new Date();
|
18953
19085
|
}
|
18954
|
-
|
18955
|
-
|
18956
|
-
|
18957
|
-
|
19086
|
+
/** @internal */
|
19087
|
+
updateInfo(info) {
|
19088
|
+
// it's possible the update could be applied out of order due to await
|
19089
|
+
// during reconnect sequences. when that happens, it's possible for server
|
19090
|
+
// to have sent more recent version of participant info while JS is waiting
|
19091
|
+
// to process the existing payload.
|
19092
|
+
// when the participant sid remains the same, and we already have a later version
|
19093
|
+
// of the payload, they can be safely skipped
|
19094
|
+
if (this.participantInfo && this.participantInfo.sid === info.sid && this.participantInfo.version > info.version) {
|
19095
|
+
return false;
|
19096
|
+
}
|
19097
|
+
this.identity = info.identity;
|
19098
|
+
this.sid = info.sid;
|
19099
|
+
this.setName(info.name);
|
19100
|
+
this.setMetadata(info.metadata);
|
19101
|
+
if (info.permission) {
|
19102
|
+
this.setPermissions(info.permission);
|
18958
19103
|
}
|
19104
|
+
// set this last so setMetadata can detect changes
|
19105
|
+
this.participantInfo = info;
|
19106
|
+
livekitLogger.trace('update participant info', {
|
19107
|
+
info
|
19108
|
+
});
|
19109
|
+
return true;
|
18959
19110
|
}
|
18960
|
-
/**
|
18961
|
-
|
18962
|
-
|
18963
|
-
|
18964
|
-
|
18965
|
-
|
18966
|
-
|
18967
|
-
let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
|
18968
|
-
this.volumeMap.set(source, volume);
|
18969
|
-
const audioPublication = this.getTrack(source);
|
18970
|
-
if (audioPublication && audioPublication.track) {
|
18971
|
-
audioPublication.track.setVolume(volume);
|
19111
|
+
/** @internal */
|
19112
|
+
setMetadata(md) {
|
19113
|
+
const changed = this.metadata !== md;
|
19114
|
+
const prevMetadata = this.metadata;
|
19115
|
+
this.metadata = md;
|
19116
|
+
if (changed) {
|
19117
|
+
this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
|
18972
19118
|
}
|
18973
19119
|
}
|
18974
|
-
|
18975
|
-
|
18976
|
-
|
18977
|
-
|
18978
|
-
|
18979
|
-
const audioPublication = this.getTrack(source);
|
18980
|
-
if (audioPublication && audioPublication.track) {
|
18981
|
-
return audioPublication.track.getVolume();
|
19120
|
+
setName(name) {
|
19121
|
+
const changed = this.name !== name;
|
19122
|
+
this.name = name;
|
19123
|
+
if (changed) {
|
19124
|
+
this.emit(ParticipantEvent.ParticipantNameChanged, name);
|
18982
19125
|
}
|
18983
|
-
return this.volumeMap.get(source);
|
18984
19126
|
}
|
18985
19127
|
/** @internal */
|
18986
|
-
|
18987
|
-
|
18988
|
-
|
18989
|
-
|
18990
|
-
|
18991
|
-
|
18992
|
-
|
18993
|
-
|
18994
|
-
|
18995
|
-
|
18996
|
-
if (!publication && mediaTrack.kind === p.kind.toString()) {
|
18997
|
-
publication = p;
|
18998
|
-
}
|
18999
|
-
});
|
19000
|
-
}
|
19001
|
-
}
|
19002
|
-
// when we couldn't locate the track, it's possible that the metadata hasn't
|
19003
|
-
// yet arrived. Wait a bit longer for it to arrive, or fire an error
|
19004
|
-
if (!publication) {
|
19005
|
-
if (triesLeft === 0) {
|
19006
|
-
livekitLogger.error('could not find published track', {
|
19007
|
-
participant: this.sid,
|
19008
|
-
trackSid: sid
|
19009
|
-
});
|
19010
|
-
this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
|
19011
|
-
return;
|
19012
|
-
}
|
19013
|
-
if (triesLeft === undefined) triesLeft = 20;
|
19014
|
-
setTimeout(() => {
|
19015
|
-
this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
|
19016
|
-
}, 150);
|
19017
|
-
return;
|
19128
|
+
setPermissions(permissions) {
|
19129
|
+
var _a, _b, _c, _d, _e;
|
19130
|
+
const prevPermissions = this.permissions;
|
19131
|
+
const changed = permissions.canPublish !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublish) || permissions.canSubscribe !== ((_b = this.permissions) === null || _b === void 0 ? void 0 : _b.canSubscribe) || permissions.canPublishData !== ((_c = this.permissions) === null || _c === void 0 ? void 0 : _c.canPublishData) || permissions.hidden !== ((_d = this.permissions) === null || _d === void 0 ? void 0 : _d.hidden) || permissions.recorder !== ((_e = this.permissions) === null || _e === void 0 ? void 0 : _e.recorder) || permissions.canPublishSources.length !== this.permissions.canPublishSources.length || permissions.canPublishSources.some((value, index) => {
|
19132
|
+
var _a;
|
19133
|
+
return value !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublishSources[index]);
|
19134
|
+
});
|
19135
|
+
this.permissions = permissions;
|
19136
|
+
if (changed) {
|
19137
|
+
this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
|
19018
19138
|
}
|
19019
|
-
|
19020
|
-
|
19021
|
-
|
19022
|
-
|
19023
|
-
|
19024
|
-
this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
|
19139
|
+
return changed;
|
19140
|
+
}
|
19141
|
+
/** @internal */
|
19142
|
+
setIsSpeaking(speaking) {
|
19143
|
+
if (speaking === this.isSpeaking) {
|
19025
19144
|
return;
|
19026
19145
|
}
|
19027
|
-
|
19028
|
-
|
19029
|
-
|
19030
|
-
track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
|
19031
|
-
} else {
|
19032
|
-
track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
|
19033
|
-
}
|
19034
|
-
// set track info
|
19035
|
-
track.source = publication.source;
|
19036
|
-
// keep publication's muted status
|
19037
|
-
track.isMuted = publication.isMuted;
|
19038
|
-
track.setMediaStream(mediaStream);
|
19039
|
-
track.start();
|
19040
|
-
publication.setTrack(track);
|
19041
|
-
// set participant volumes on new audio tracks
|
19042
|
-
if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
|
19043
|
-
track.setVolume(this.volumeMap.get(publication.source));
|
19146
|
+
this.isSpeaking = speaking;
|
19147
|
+
if (speaking) {
|
19148
|
+
this.lastSpokeAt = new Date();
|
19044
19149
|
}
|
19045
|
-
|
19150
|
+
this.emit(ParticipantEvent.IsSpeakingChanged, speaking);
|
19046
19151
|
}
|
19047
19152
|
/** @internal */
|
19048
|
-
|
19049
|
-
|
19153
|
+
setConnectionQuality(q) {
|
19154
|
+
const prevQuality = this._connectionQuality;
|
19155
|
+
this._connectionQuality = qualityFromProto(q);
|
19156
|
+
if (prevQuality !== this._connectionQuality) {
|
19157
|
+
this.emit(ParticipantEvent.ConnectionQualityChanged, this._connectionQuality);
|
19158
|
+
}
|
19050
19159
|
}
|
19051
|
-
|
19052
|
-
|
19160
|
+
/**
|
19161
|
+
* @internal
|
19162
|
+
*/
|
19163
|
+
setAudioContext(ctx) {
|
19164
|
+
this.audioContext = ctx;
|
19165
|
+
this.audioTracks.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
|
19053
19166
|
}
|
19054
|
-
|
19055
|
-
|
19056
|
-
|
19057
|
-
|
19058
|
-
}
|
19059
|
-
// we are getting a list of all available tracks, reconcile in here
|
19060
|
-
// and send out events for changes
|
19061
|
-
// reconcile track publications, publish events only if metadata is already there
|
19062
|
-
// i.e. changes since the local participant has joined
|
19063
|
-
const validTracks = new Map();
|
19064
|
-
const newTracks = new Map();
|
19065
|
-
info.tracks.forEach(ti => {
|
19066
|
-
var _a;
|
19067
|
-
let publication = this.getTrackPublication(ti.sid);
|
19068
|
-
if (!publication) {
|
19069
|
-
// new publication
|
19070
|
-
const kind = Track.kindFromProto(ti.type);
|
19071
|
-
if (!kind) {
|
19072
|
-
return;
|
19073
|
-
}
|
19074
|
-
publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe);
|
19075
|
-
publication.updateInfo(ti);
|
19076
|
-
newTracks.set(ti.sid, publication);
|
19077
|
-
const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
|
19078
|
-
if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
|
19079
|
-
livekitLogger.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
|
19080
|
-
oldTrack: existingTrackOfSource,
|
19081
|
-
newTrack: publication,
|
19082
|
-
participant: this,
|
19083
|
-
participantInfo: info
|
19084
|
-
});
|
19085
|
-
}
|
19086
|
-
this.addTrackPublication(publication);
|
19087
|
-
} else {
|
19088
|
-
publication.updateInfo(ti);
|
19089
|
-
}
|
19090
|
-
validTracks.set(ti.sid, publication);
|
19091
|
-
});
|
19092
|
-
// detect removed tracks
|
19093
|
-
this.tracks.forEach(publication => {
|
19094
|
-
if (!validTracks.has(publication.trackSid)) {
|
19095
|
-
livekitLogger.trace('detected removed track on remote participant, unpublishing', {
|
19096
|
-
publication,
|
19097
|
-
participantSid: this.sid
|
19098
|
-
});
|
19099
|
-
this.unpublishTrack(publication.trackSid, true);
|
19100
|
-
}
|
19167
|
+
addTrackPublication(publication) {
|
19168
|
+
// forward publication driven events
|
19169
|
+
publication.on(TrackEvent.Muted, () => {
|
19170
|
+
this.emit(ParticipantEvent.TrackMuted, publication);
|
19101
19171
|
});
|
19102
|
-
|
19103
|
-
|
19104
|
-
this.emit(ParticipantEvent.TrackPublished, publication);
|
19172
|
+
publication.on(TrackEvent.Unmuted, () => {
|
19173
|
+
this.emit(ParticipantEvent.TrackUnmuted, publication);
|
19105
19174
|
});
|
19106
|
-
|
19175
|
+
const pub = publication;
|
19176
|
+
if (pub.track) {
|
19177
|
+
pub.track.sid = publication.trackSid;
|
19178
|
+
}
|
19179
|
+
this.tracks.set(publication.trackSid, publication);
|
19180
|
+
switch (publication.kind) {
|
19181
|
+
case Track.Kind.Audio:
|
19182
|
+
this.audioTracks.set(publication.trackSid, publication);
|
19183
|
+
break;
|
19184
|
+
case Track.Kind.Video:
|
19185
|
+
this.videoTracks.set(publication.trackSid, publication);
|
19186
|
+
break;
|
19187
|
+
}
|
19188
|
+
}
|
19189
|
+
}
|
19190
|
+
|
19191
|
+
function trackPermissionToProto(perms) {
|
19192
|
+
var _a, _b, _c;
|
19193
|
+
if (!perms.participantSid && !perms.participantIdentity) {
|
19194
|
+
throw new Error('Invalid track permission, must provide at least one of participantIdentity and participantSid');
|
19195
|
+
}
|
19196
|
+
return new TrackPermission({
|
19197
|
+
participantIdentity: (_a = perms.participantIdentity) !== null && _a !== void 0 ? _a : '',
|
19198
|
+
participantSid: (_b = perms.participantSid) !== null && _b !== void 0 ? _b : '',
|
19199
|
+
allTracks: (_c = perms.allowAll) !== null && _c !== void 0 ? _c : false,
|
19200
|
+
trackSids: perms.allowedTrackSids || []
|
19201
|
+
});
|
19202
|
+
}
|
19203
|
+
|
19204
|
+
class RemoteTrackPublication extends TrackPublication {
|
19205
|
+
constructor(kind, ti, autoSubscribe) {
|
19206
|
+
super(kind, ti.sid, ti.name);
|
19207
|
+
this.track = undefined;
|
19208
|
+
/** @internal */
|
19209
|
+
this.allowed = true;
|
19210
|
+
this.disabled = false;
|
19211
|
+
this.currentVideoQuality = VideoQuality.HIGH;
|
19212
|
+
this.handleEnded = track => {
|
19213
|
+
this.setTrack(undefined);
|
19214
|
+
this.emit(TrackEvent.Ended, track);
|
19215
|
+
};
|
19216
|
+
this.handleVisibilityChange = visible => {
|
19217
|
+
livekitLogger.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), {
|
19218
|
+
trackSid: this.trackSid
|
19219
|
+
});
|
19220
|
+
this.disabled = !visible;
|
19221
|
+
this.emitTrackUpdate();
|
19222
|
+
};
|
19223
|
+
this.handleVideoDimensionsChange = dimensions => {
|
19224
|
+
livekitLogger.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), {
|
19225
|
+
trackSid: this.trackSid
|
19226
|
+
});
|
19227
|
+
this.videoDimensions = dimensions;
|
19228
|
+
this.emitTrackUpdate();
|
19229
|
+
};
|
19230
|
+
this.subscribed = autoSubscribe;
|
19231
|
+
this.updateInfo(ti);
|
19107
19232
|
}
|
19108
|
-
/**
|
19109
|
-
|
19110
|
-
|
19111
|
-
|
19112
|
-
|
19233
|
+
/**
|
19234
|
+
* Subscribe or unsubscribe to this remote track
|
19235
|
+
* @param subscribed true to subscribe to a track, false to unsubscribe
|
19236
|
+
*/
|
19237
|
+
setSubscribed(subscribed) {
|
19238
|
+
const prevStatus = this.subscriptionStatus;
|
19239
|
+
const prevPermission = this.permissionStatus;
|
19240
|
+
this.subscribed = subscribed;
|
19241
|
+
// reset allowed status when desired subscription state changes
|
19242
|
+
// server will notify client via signal message if it's not allowed
|
19243
|
+
if (subscribed) {
|
19244
|
+
this.allowed = true;
|
19113
19245
|
}
|
19114
|
-
|
19115
|
-
|
19116
|
-
|
19117
|
-
|
19118
|
-
|
19119
|
-
|
19120
|
-
|
19246
|
+
const sub = new UpdateSubscription({
|
19247
|
+
trackSids: [this.trackSid],
|
19248
|
+
subscribe: this.subscribed,
|
19249
|
+
participantTracks: [new ParticipantTracks({
|
19250
|
+
// sending an empty participant id since TrackPublication doesn't keep it
|
19251
|
+
// this is filled in by the participant that receives this message
|
19252
|
+
participantSid: '',
|
19253
|
+
trackSids: [this.trackSid]
|
19254
|
+
})]
|
19255
|
+
});
|
19256
|
+
this.emit(TrackEvent.UpdateSubscription, sub);
|
19257
|
+
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
19258
|
+
this.emitPermissionUpdateIfChanged(prevPermission);
|
19259
|
+
}
|
19260
|
+
get subscriptionStatus() {
|
19261
|
+
if (this.subscribed === false) {
|
19262
|
+
return TrackPublication.SubscriptionStatus.Unsubscribed;
|
19121
19263
|
}
|
19122
|
-
|
19123
|
-
|
19124
|
-
// remove from the right type map
|
19125
|
-
switch (publication.kind) {
|
19126
|
-
case Track.Kind.Audio:
|
19127
|
-
this.audioTracks.delete(sid);
|
19128
|
-
break;
|
19129
|
-
case Track.Kind.Video:
|
19130
|
-
this.videoTracks.delete(sid);
|
19131
|
-
break;
|
19264
|
+
if (!super.isSubscribed) {
|
19265
|
+
return TrackPublication.SubscriptionStatus.Desired;
|
19132
19266
|
}
|
19133
|
-
|
19134
|
-
|
19267
|
+
return TrackPublication.SubscriptionStatus.Subscribed;
|
19268
|
+
}
|
19269
|
+
get permissionStatus() {
|
19270
|
+
return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
|
19271
|
+
}
|
19272
|
+
/**
|
19273
|
+
* Returns true if track is subscribed, and ready for playback
|
19274
|
+
*/
|
19275
|
+
get isSubscribed() {
|
19276
|
+
if (this.subscribed === false) {
|
19277
|
+
return false;
|
19135
19278
|
}
|
19279
|
+
return super.isSubscribed;
|
19280
|
+
}
|
19281
|
+
// returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
|
19282
|
+
get isDesired() {
|
19283
|
+
return this.subscribed !== false;
|
19284
|
+
}
|
19285
|
+
get isEnabled() {
|
19286
|
+
return !this.disabled;
|
19136
19287
|
}
|
19137
19288
|
/**
|
19138
|
-
*
|
19289
|
+
* disable server from sending down data for this track. this is useful when
|
19290
|
+
* the participant is off screen, you may disable streaming down their video
|
19291
|
+
* to reduce bandwidth requirements
|
19292
|
+
* @param enabled
|
19139
19293
|
*/
|
19140
|
-
|
19141
|
-
this.
|
19142
|
-
|
19294
|
+
setEnabled(enabled) {
|
19295
|
+
if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
|
19296
|
+
return;
|
19297
|
+
}
|
19298
|
+
this.disabled = !enabled;
|
19299
|
+
this.emitTrackUpdate();
|
19143
19300
|
}
|
19144
19301
|
/**
|
19145
|
-
*
|
19302
|
+
* for tracks that support simulcasting, adjust subscribed quality
|
19303
|
+
*
|
19304
|
+
* This indicates the highest quality the client can accept. if network
|
19305
|
+
* bandwidth does not allow, server will automatically reduce quality to
|
19306
|
+
* optimize for uninterrupted video
|
19146
19307
|
*/
|
19147
|
-
|
19148
|
-
|
19149
|
-
|
19150
|
-
|
19151
|
-
|
19152
|
-
|
19153
|
-
|
19154
|
-
promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
|
19155
|
-
}
|
19156
|
-
});
|
19157
|
-
yield Promise.all(promises);
|
19158
|
-
});
|
19308
|
+
setVideoQuality(quality) {
|
19309
|
+
if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
|
19310
|
+
return;
|
19311
|
+
}
|
19312
|
+
this.currentVideoQuality = quality;
|
19313
|
+
this.videoDimensions = undefined;
|
19314
|
+
this.emitTrackUpdate();
|
19159
19315
|
}
|
19160
|
-
|
19161
|
-
|
19162
|
-
|
19163
|
-
|
19316
|
+
setVideoDimensions(dimensions) {
|
19317
|
+
var _a, _b;
|
19318
|
+
if (!this.isManualOperationAllowed()) {
|
19319
|
+
return;
|
19164
19320
|
}
|
19165
|
-
|
19166
|
-
|
19167
|
-
|
19168
|
-
|
19169
|
-
|
19170
|
-
|
19321
|
+
if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
|
19322
|
+
return;
|
19323
|
+
}
|
19324
|
+
if (this.track instanceof RemoteVideoTrack) {
|
19325
|
+
this.videoDimensions = dimensions;
|
19326
|
+
}
|
19327
|
+
this.currentVideoQuality = undefined;
|
19328
|
+
this.emitTrackUpdate();
|
19171
19329
|
}
|
19172
|
-
|
19173
|
-
|
19174
|
-
|
19175
|
-
|
19176
|
-
|
19177
|
-
|
19178
|
-
|
19179
|
-
|
19180
|
-
return
|
19181
|
-
|
19182
|
-
|
19330
|
+
setVideoFPS(fps) {
|
19331
|
+
if (!this.isManualOperationAllowed()) {
|
19332
|
+
return;
|
19333
|
+
}
|
19334
|
+
if (!(this.track instanceof RemoteVideoTrack)) {
|
19335
|
+
return;
|
19336
|
+
}
|
19337
|
+
if (this.fps === fps) {
|
19338
|
+
return;
|
19339
|
+
}
|
19340
|
+
this.fps = fps;
|
19341
|
+
this.emitTrackUpdate();
|
19183
19342
|
}
|
19184
|
-
|
19185
|
-
|
19186
|
-
const presets169 = Object.values(VideoPresets);
|
19187
|
-
/* @internal */
|
19188
|
-
const presets43 = Object.values(VideoPresets43);
|
19189
|
-
/* @internal */
|
19190
|
-
const presetsScreenShare = Object.values(ScreenSharePresets);
|
19191
|
-
/* @internal */
|
19192
|
-
const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
|
19193
|
-
/* @internal */
|
19194
|
-
const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
|
19195
|
-
/* @internal */
|
19196
|
-
const computeDefaultScreenShareSimulcastPresets = fromPreset => {
|
19197
|
-
const layers = [{
|
19198
|
-
scaleResolutionDownBy: 2,
|
19199
|
-
fps: 3
|
19200
|
-
}];
|
19201
|
-
return layers.map(t => {
|
19202
|
-
var _a;
|
19203
|
-
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) / t.fps)))), t.fps, fromPreset.encoding.priority);
|
19204
|
-
});
|
19205
|
-
};
|
19206
|
-
// /**
|
19207
|
-
// *
|
19208
|
-
// * @internal
|
19209
|
-
// * @experimental
|
19210
|
-
// */
|
19211
|
-
// const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
|
19212
|
-
// // use vp8 as a default
|
19213
|
-
// const vp8 = determineAppropriateEncoding(false, width, height);
|
19214
|
-
// const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
|
19215
|
-
// const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
|
19216
|
-
// const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
|
19217
|
-
// return {
|
19218
|
-
// vp8,
|
19219
|
-
// vp9,
|
19220
|
-
// h264,
|
19221
|
-
// av1,
|
19222
|
-
// };
|
19223
|
-
// };
|
19224
|
-
const videoRids = ['q', 'h', 'f'];
|
19225
|
-
/* @internal */
|
19226
|
-
function computeVideoEncodings(isScreenShare, width, height, options) {
|
19227
|
-
var _a, _b;
|
19228
|
-
let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
|
19229
|
-
if (isScreenShare) {
|
19230
|
-
videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
|
19343
|
+
get videoQuality() {
|
19344
|
+
return this.currentVideoQuality;
|
19231
19345
|
}
|
19232
|
-
|
19233
|
-
|
19234
|
-
|
19235
|
-
|
19236
|
-
|
19237
|
-
|
19238
|
-
|
19346
|
+
/** @internal */
|
19347
|
+
setTrack(track) {
|
19348
|
+
const prevStatus = this.subscriptionStatus;
|
19349
|
+
const prevPermission = this.permissionStatus;
|
19350
|
+
const prevTrack = this.track;
|
19351
|
+
if (prevTrack === track) {
|
19352
|
+
return;
|
19353
|
+
}
|
19354
|
+
if (prevTrack) {
|
19355
|
+
// unregister listener
|
19356
|
+
prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
|
19357
|
+
prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
19358
|
+
prevTrack.off(TrackEvent.Ended, this.handleEnded);
|
19359
|
+
prevTrack.detach();
|
19360
|
+
prevTrack.stopMonitor();
|
19361
|
+
this.emit(TrackEvent.Unsubscribed, prevTrack);
|
19362
|
+
}
|
19363
|
+
super.setTrack(track);
|
19364
|
+
if (track) {
|
19365
|
+
track.sid = this.trackSid;
|
19366
|
+
track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
|
19367
|
+
track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
|
19368
|
+
track.on(TrackEvent.Ended, this.handleEnded);
|
19369
|
+
this.emit(TrackEvent.Subscribed, track);
|
19370
|
+
}
|
19371
|
+
this.emitPermissionUpdateIfChanged(prevPermission);
|
19372
|
+
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
19239
19373
|
}
|
19240
|
-
|
19241
|
-
|
19242
|
-
|
19243
|
-
|
19374
|
+
/** @internal */
|
19375
|
+
setAllowed(allowed) {
|
19376
|
+
const prevStatus = this.subscriptionStatus;
|
19377
|
+
const prevPermission = this.permissionStatus;
|
19378
|
+
this.allowed = allowed;
|
19379
|
+
this.emitPermissionUpdateIfChanged(prevPermission);
|
19380
|
+
this.emitSubscriptionUpdateIfChanged(prevStatus);
|
19244
19381
|
}
|
19245
|
-
|
19246
|
-
|
19247
|
-
|
19248
|
-
|
19249
|
-
|
19250
|
-
|
19251
|
-
|
19252
|
-
|
19253
|
-
|
19254
|
-
|
19255
|
-
|
19256
|
-
|
19257
|
-
|
19258
|
-
maxFramerate: original.encoding.maxFramerate
|
19259
|
-
});
|
19382
|
+
/** @internal */
|
19383
|
+
setSubscriptionError(error) {
|
19384
|
+
this.emit(TrackEvent.SubscriptionFailed, error);
|
19385
|
+
}
|
19386
|
+
/** @internal */
|
19387
|
+
updateInfo(info) {
|
19388
|
+
super.updateInfo(info);
|
19389
|
+
const prevMetadataMuted = this.metadataMuted;
|
19390
|
+
this.metadataMuted = info.muted;
|
19391
|
+
if (this.track) {
|
19392
|
+
this.track.setMuted(info.muted);
|
19393
|
+
} else if (prevMetadataMuted !== info.muted) {
|
19394
|
+
this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
|
19260
19395
|
}
|
19261
|
-
/* @ts-ignore */
|
19262
|
-
encodings[0].scalabilityMode = scalabilityMode;
|
19263
|
-
livekitLogger.debug('encodings', encodings);
|
19264
|
-
return encodings;
|
19265
19396
|
}
|
19266
|
-
|
19267
|
-
|
19397
|
+
emitSubscriptionUpdateIfChanged(previousStatus) {
|
19398
|
+
const currentStatus = this.subscriptionStatus;
|
19399
|
+
if (previousStatus === currentStatus) {
|
19400
|
+
return;
|
19401
|
+
}
|
19402
|
+
this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
|
19268
19403
|
}
|
19269
|
-
|
19270
|
-
|
19271
|
-
|
19272
|
-
|
19273
|
-
|
19404
|
+
emitPermissionUpdateIfChanged(previousPermissionStatus) {
|
19405
|
+
const currentPermissionStatus = this.permissionStatus;
|
19406
|
+
if (currentPermissionStatus !== previousPermissionStatus) {
|
19407
|
+
this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
|
19408
|
+
}
|
19274
19409
|
}
|
19275
|
-
|
19276
|
-
|
19277
|
-
|
19278
|
-
|
19279
|
-
|
19410
|
+
isManualOperationAllowed() {
|
19411
|
+
if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
|
19412
|
+
livekitLogger.warn('adaptive stream is enabled, cannot change video track settings', {
|
19413
|
+
trackSid: this.trackSid
|
19414
|
+
});
|
19415
|
+
return false;
|
19280
19416
|
}
|
19281
|
-
|
19282
|
-
|
19283
|
-
|
19284
|
-
|
19285
|
-
|
19286
|
-
// resolution order.
|
19287
|
-
// 2. ion-sfu translates rids into layers. So, all encodings
|
19288
|
-
// should have the base layer `q` and then more added
|
19289
|
-
// based on other conditions.
|
19290
|
-
const size = Math.max(width, height);
|
19291
|
-
if (size >= 960 && midPreset) {
|
19292
|
-
return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
|
19417
|
+
if (!this.isDesired) {
|
19418
|
+
livekitLogger.warn('cannot update track settings when not subscribed', {
|
19419
|
+
trackSid: this.trackSid
|
19420
|
+
});
|
19421
|
+
return false;
|
19293
19422
|
}
|
19294
|
-
|
19295
|
-
|
19423
|
+
return true;
|
19424
|
+
}
|
19425
|
+
get isAdaptiveStream() {
|
19426
|
+
return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
|
19427
|
+
}
|
19428
|
+
/* @internal */
|
19429
|
+
emitTrackUpdate() {
|
19430
|
+
const settings = new UpdateTrackSettings({
|
19431
|
+
trackSids: [this.trackSid],
|
19432
|
+
disabled: this.disabled,
|
19433
|
+
fps: this.fps
|
19434
|
+
});
|
19435
|
+
if (this.videoDimensions) {
|
19436
|
+
settings.width = Math.ceil(this.videoDimensions.width);
|
19437
|
+
settings.height = Math.ceil(this.videoDimensions.height);
|
19438
|
+
} else if (this.currentVideoQuality !== undefined) {
|
19439
|
+
settings.quality = this.currentVideoQuality;
|
19440
|
+
} else {
|
19441
|
+
// defaults to high quality
|
19442
|
+
settings.quality = VideoQuality.HIGH;
|
19296
19443
|
}
|
19444
|
+
this.emit(TrackEvent.UpdateSettings, settings);
|
19297
19445
|
}
|
19298
|
-
return encodingsFromPresets(width, height, [original]);
|
19299
19446
|
}
|
19300
|
-
|
19301
|
-
|
19302
|
-
|
19303
|
-
|
19304
|
-
return;
|
19447
|
+
|
19448
|
+
class RemoteParticipant extends Participant {
|
19449
|
+
/** @internal */
|
19450
|
+
static fromParticipantInfo(signalClient, pi) {
|
19451
|
+
return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
|
19305
19452
|
}
|
19306
|
-
|
19307
|
-
|
19308
|
-
|
19309
|
-
|
19453
|
+
/** @internal */
|
19454
|
+
constructor(signalClient, sid, identity, name, metadata) {
|
19455
|
+
super(sid, identity || '', name, metadata);
|
19456
|
+
this.signalClient = signalClient;
|
19457
|
+
this.tracks = new Map();
|
19458
|
+
this.audioTracks = new Map();
|
19459
|
+
this.videoTracks = new Map();
|
19460
|
+
this.volumeMap = new Map();
|
19461
|
+
}
|
19462
|
+
addTrackPublication(publication) {
|
19463
|
+
super.addTrackPublication(publication);
|
19464
|
+
// register action events
|
19465
|
+
publication.on(TrackEvent.UpdateSettings, settings => {
|
19466
|
+
livekitLogger.debug('send update settings', settings);
|
19467
|
+
this.signalClient.sendUpdateTrackSettings(settings);
|
19468
|
+
});
|
19469
|
+
publication.on(TrackEvent.UpdateSubscription, sub => {
|
19470
|
+
sub.participantTracks.forEach(pt => {
|
19471
|
+
pt.participantSid = this.sid;
|
19472
|
+
});
|
19473
|
+
this.signalClient.sendUpdateSubscription(sub);
|
19474
|
+
});
|
19475
|
+
publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
|
19476
|
+
this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
|
19477
|
+
});
|
19478
|
+
publication.on(TrackEvent.SubscriptionStatusChanged, status => {
|
19479
|
+
this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
|
19480
|
+
});
|
19481
|
+
publication.on(TrackEvent.Subscribed, track => {
|
19482
|
+
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
19483
|
+
});
|
19484
|
+
publication.on(TrackEvent.Unsubscribed, previousTrack => {
|
19485
|
+
this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
|
19486
|
+
});
|
19487
|
+
publication.on(TrackEvent.SubscriptionFailed, error => {
|
19488
|
+
this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
|
19310
19489
|
});
|
19311
19490
|
}
|
19312
|
-
|
19313
|
-
|
19314
|
-
|
19315
|
-
|
19316
|
-
|
19317
|
-
|
19318
|
-
|
19319
|
-
|
19320
|
-
|
19321
|
-
|
19322
|
-
|
19323
|
-
|
19324
|
-
|
19325
|
-
|
19326
|
-
|
19327
|
-
|
19328
|
-
|
19329
|
-
|
19330
|
-
|
19331
|
-
|
19332
|
-
|
19333
|
-
|
19491
|
+
getTrack(source) {
|
19492
|
+
const track = super.getTrack(source);
|
19493
|
+
if (track) {
|
19494
|
+
return track;
|
19495
|
+
}
|
19496
|
+
}
|
19497
|
+
getTrackByName(name) {
|
19498
|
+
const track = super.getTrackByName(name);
|
19499
|
+
if (track) {
|
19500
|
+
return track;
|
19501
|
+
}
|
19502
|
+
}
|
19503
|
+
/**
|
19504
|
+
* sets the volume on the participant's audio track
|
19505
|
+
* by default, this affects the microphone publication
|
19506
|
+
* a different source can be passed in as a second argument
|
19507
|
+
* if no track exists the volume will be applied when the microphone track is added
|
19508
|
+
*/
|
19509
|
+
setVolume(volume) {
|
19510
|
+
let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
|
19511
|
+
this.volumeMap.set(source, volume);
|
19512
|
+
const audioPublication = this.getTrack(source);
|
19513
|
+
if (audioPublication && audioPublication.track) {
|
19514
|
+
audioPublication.track.setVolume(volume);
|
19515
|
+
}
|
19516
|
+
}
|
19517
|
+
/**
|
19518
|
+
* gets the volume on the participant's microphone track
|
19519
|
+
*/
|
19520
|
+
getVolume() {
|
19521
|
+
let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
|
19522
|
+
const audioPublication = this.getTrack(source);
|
19523
|
+
if (audioPublication && audioPublication.track) {
|
19524
|
+
return audioPublication.track.getVolume();
|
19525
|
+
}
|
19526
|
+
return this.volumeMap.get(source);
|
19527
|
+
}
|
19528
|
+
/** @internal */
|
19529
|
+
addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
|
19530
|
+
// find the track publication
|
19531
|
+
// it's possible for the media track to arrive before participant info
|
19532
|
+
let publication = this.getTrackPublication(sid);
|
19533
|
+
// it's also possible that the browser didn't honor our original track id
|
19534
|
+
// FireFox would use its own local uuid instead of server track id
|
19535
|
+
if (!publication) {
|
19536
|
+
if (!sid.startsWith('TR')) {
|
19537
|
+
// find the first track that matches type
|
19538
|
+
this.tracks.forEach(p => {
|
19539
|
+
if (!publication && mediaTrack.kind === p.kind.toString()) {
|
19540
|
+
publication = p;
|
19541
|
+
}
|
19542
|
+
});
|
19543
|
+
}
|
19544
|
+
}
|
19545
|
+
// when we couldn't locate the track, it's possible that the metadata hasn't
|
19546
|
+
// yet arrived. Wait a bit longer for it to arrive, or fire an error
|
19547
|
+
if (!publication) {
|
19548
|
+
if (triesLeft === 0) {
|
19549
|
+
livekitLogger.error('could not find published track', {
|
19550
|
+
participant: this.sid,
|
19551
|
+
trackSid: sid
|
19552
|
+
});
|
19553
|
+
this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
|
19554
|
+
return;
|
19555
|
+
}
|
19556
|
+
if (triesLeft === undefined) triesLeft = 20;
|
19557
|
+
setTimeout(() => {
|
19558
|
+
this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
|
19559
|
+
}, 150);
|
19560
|
+
return;
|
19561
|
+
}
|
19562
|
+
if (mediaTrack.readyState === 'ended') {
|
19563
|
+
livekitLogger.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', {
|
19564
|
+
participant: this.sid,
|
19565
|
+
trackSid: sid
|
19566
|
+
});
|
19567
|
+
this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
|
19568
|
+
return;
|
19334
19569
|
}
|
19335
|
-
|
19336
|
-
|
19337
|
-
|
19338
|
-
|
19339
|
-
|
19340
|
-
|
19341
|
-
case 'av1':
|
19342
|
-
encoding = Object.assign({}, encoding);
|
19343
|
-
encoding.maxBitrate = encoding.maxBitrate * 0.7;
|
19344
|
-
break;
|
19345
|
-
case 'vp9':
|
19346
|
-
encoding = Object.assign({}, encoding);
|
19347
|
-
encoding.maxBitrate = encoding.maxBitrate * 0.85;
|
19348
|
-
break;
|
19570
|
+
const isVideo = mediaTrack.kind === 'video';
|
19571
|
+
let track;
|
19572
|
+
if (isVideo) {
|
19573
|
+
track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
|
19574
|
+
} else {
|
19575
|
+
track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
|
19349
19576
|
}
|
19577
|
+
// set track info
|
19578
|
+
track.source = publication.source;
|
19579
|
+
// keep publication's muted status
|
19580
|
+
track.isMuted = publication.isMuted;
|
19581
|
+
track.setMediaStream(mediaStream);
|
19582
|
+
track.start();
|
19583
|
+
publication.setTrack(track);
|
19584
|
+
// set participant volumes on new audio tracks
|
19585
|
+
if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
|
19586
|
+
track.setVolume(this.volumeMap.get(publication.source));
|
19587
|
+
}
|
19588
|
+
return publication;
|
19350
19589
|
}
|
19351
|
-
|
19352
|
-
|
19353
|
-
|
19354
|
-
function presetsForResolution(isScreenShare, width, height) {
|
19355
|
-
if (isScreenShare) {
|
19356
|
-
return presetsScreenShare;
|
19357
|
-
}
|
19358
|
-
const aspect = width > height ? width / height : height / width;
|
19359
|
-
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
19360
|
-
return presets169;
|
19361
|
-
}
|
19362
|
-
return presets43;
|
19363
|
-
}
|
19364
|
-
/* @internal */
|
19365
|
-
function defaultSimulcastLayers(isScreenShare, original) {
|
19366
|
-
if (isScreenShare) {
|
19367
|
-
return computeDefaultScreenShareSimulcastPresets(original);
|
19590
|
+
/** @internal */
|
19591
|
+
get hasMetadata() {
|
19592
|
+
return !!this.participantInfo;
|
19368
19593
|
}
|
19369
|
-
|
19370
|
-
|
19371
|
-
height
|
19372
|
-
} = original;
|
19373
|
-
const aspect = width > height ? width / height : height / width;
|
19374
|
-
if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
|
19375
|
-
return defaultSimulcastPresets169;
|
19594
|
+
getTrackPublication(sid) {
|
19595
|
+
return this.tracks.get(sid);
|
19376
19596
|
}
|
19377
|
-
|
19378
|
-
|
19379
|
-
|
19380
|
-
|
19381
|
-
const encodings = [];
|
19382
|
-
presets.forEach((preset, idx) => {
|
19383
|
-
if (idx >= videoRids.length) {
|
19384
|
-
return;
|
19385
|
-
}
|
19386
|
-
const size = Math.min(width, height);
|
19387
|
-
const rid = videoRids[idx];
|
19388
|
-
const encoding = {
|
19389
|
-
rid,
|
19390
|
-
scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
|
19391
|
-
maxBitrate: preset.encoding.maxBitrate
|
19392
|
-
};
|
19393
|
-
if (preset.encoding.maxFramerate) {
|
19394
|
-
encoding.maxFramerate = preset.encoding.maxFramerate;
|
19395
|
-
}
|
19396
|
-
const canSetPriority = isFireFox() || idx === 0;
|
19397
|
-
if (preset.encoding.priority && canSetPriority) {
|
19398
|
-
encoding.priority = preset.encoding.priority;
|
19399
|
-
encoding.networkPriority = preset.encoding.priority;
|
19597
|
+
/** @internal */
|
19598
|
+
updateInfo(info) {
|
19599
|
+
if (!super.updateInfo(info)) {
|
19600
|
+
return false;
|
19400
19601
|
}
|
19401
|
-
|
19402
|
-
|
19403
|
-
|
19404
|
-
|
19405
|
-
|
19406
|
-
|
19407
|
-
|
19408
|
-
topFramerate = encoding.maxFramerate;
|
19409
|
-
} else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
|
19410
|
-
topFramerate = encoding.maxFramerate;
|
19411
|
-
}
|
19412
|
-
});
|
19413
|
-
let notifyOnce = true;
|
19414
|
-
encodings.forEach(encoding => {
|
19602
|
+
// we are getting a list of all available tracks, reconcile in here
|
19603
|
+
// and send out events for changes
|
19604
|
+
// reconcile track publications, publish events only if metadata is already there
|
19605
|
+
// i.e. changes since the local participant has joined
|
19606
|
+
const validTracks = new Map();
|
19607
|
+
const newTracks = new Map();
|
19608
|
+
info.tracks.forEach(ti => {
|
19415
19609
|
var _a;
|
19416
|
-
|
19417
|
-
|
19418
|
-
|
19419
|
-
|
19610
|
+
let publication = this.getTrackPublication(ti.sid);
|
19611
|
+
if (!publication) {
|
19612
|
+
// new publication
|
19613
|
+
const kind = Track.kindFromProto(ti.type);
|
19614
|
+
if (!kind) {
|
19615
|
+
return;
|
19420
19616
|
}
|
19421
|
-
|
19422
|
-
|
19617
|
+
publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe);
|
19618
|
+
publication.updateInfo(ti);
|
19619
|
+
newTracks.set(ti.sid, publication);
|
19620
|
+
const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
|
19621
|
+
if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
|
19622
|
+
livekitLogger.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
|
19623
|
+
oldTrack: existingTrackOfSource,
|
19624
|
+
newTrack: publication,
|
19625
|
+
participant: this,
|
19626
|
+
participantInfo: info
|
19627
|
+
});
|
19628
|
+
}
|
19629
|
+
this.addTrackPublication(publication);
|
19630
|
+
} else {
|
19631
|
+
publication.updateInfo(ti);
|
19632
|
+
}
|
19633
|
+
validTracks.set(ti.sid, publication);
|
19634
|
+
});
|
19635
|
+
// detect removed tracks
|
19636
|
+
this.tracks.forEach(publication => {
|
19637
|
+
if (!validTracks.has(publication.trackSid)) {
|
19638
|
+
livekitLogger.trace('detected removed track on remote participant, unpublishing', {
|
19639
|
+
publication,
|
19640
|
+
participantSid: this.sid
|
19641
|
+
});
|
19642
|
+
this.unpublishTrack(publication.trackSid, true);
|
19423
19643
|
}
|
19424
19644
|
});
|
19645
|
+
// always emit events for new publications, Room will not forward them unless it's ready
|
19646
|
+
newTracks.forEach(publication => {
|
19647
|
+
this.emit(ParticipantEvent.TrackPublished, publication);
|
19648
|
+
});
|
19649
|
+
return true;
|
19425
19650
|
}
|
19426
|
-
|
19427
|
-
|
19428
|
-
|
19429
|
-
|
19430
|
-
|
19431
|
-
return presets.sort((a, b) => {
|
19432
|
-
const {
|
19433
|
-
encoding: aEnc
|
19434
|
-
} = a;
|
19435
|
-
const {
|
19436
|
-
encoding: bEnc
|
19437
|
-
} = b;
|
19438
|
-
if (aEnc.maxBitrate > bEnc.maxBitrate) {
|
19439
|
-
return 1;
|
19651
|
+
/** @internal */
|
19652
|
+
unpublishTrack(sid, sendUnpublish) {
|
19653
|
+
const publication = this.tracks.get(sid);
|
19654
|
+
if (!publication) {
|
19655
|
+
return;
|
19440
19656
|
}
|
19441
|
-
if
|
19442
|
-
|
19443
|
-
|
19657
|
+
// also send unsubscribe, if track is actively subscribed
|
19658
|
+
const {
|
19659
|
+
track
|
19660
|
+
} = publication;
|
19661
|
+
if (track) {
|
19662
|
+
track.stop();
|
19663
|
+
publication.setTrack(undefined);
|
19444
19664
|
}
|
19445
|
-
|
19446
|
-
|
19447
|
-
|
19448
|
-
|
19449
|
-
|
19450
|
-
|
19451
|
-
|
19452
|
-
|
19453
|
-
|
19665
|
+
// remove track from maps only after unsubscribed has been fired
|
19666
|
+
this.tracks.delete(sid);
|
19667
|
+
// remove from the right type map
|
19668
|
+
switch (publication.kind) {
|
19669
|
+
case Track.Kind.Audio:
|
19670
|
+
this.audioTracks.delete(sid);
|
19671
|
+
break;
|
19672
|
+
case Track.Kind.Video:
|
19673
|
+
this.videoTracks.delete(sid);
|
19674
|
+
break;
|
19454
19675
|
}
|
19455
|
-
|
19456
|
-
|
19457
|
-
if (results.length > 3) {
|
19458
|
-
switch (results[3]) {
|
19459
|
-
case 'h':
|
19460
|
-
case '_KEY':
|
19461
|
-
case '_KEY_SHIFT':
|
19462
|
-
this.suffix = results[3];
|
19463
|
-
}
|
19676
|
+
if (sendUnpublish) {
|
19677
|
+
this.emit(ParticipantEvent.TrackUnpublished, publication);
|
19464
19678
|
}
|
19465
19679
|
}
|
19466
|
-
|
19467
|
-
|
19468
|
-
|
19680
|
+
/**
|
19681
|
+
* @internal
|
19682
|
+
*/
|
19683
|
+
setAudioOutput(output) {
|
19684
|
+
return __awaiter(this, void 0, void 0, function* () {
|
19685
|
+
this.audioOutput = output;
|
19686
|
+
const promises = [];
|
19687
|
+
this.audioTracks.forEach(pub => {
|
19688
|
+
var _a;
|
19689
|
+
if (pub.track instanceof RemoteAudioTrack) {
|
19690
|
+
promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
|
19691
|
+
}
|
19692
|
+
});
|
19693
|
+
yield Promise.all(promises);
|
19694
|
+
});
|
19469
19695
|
}
|
19470
|
-
|
19696
|
+
/** @internal */
|
19697
|
+
emit(event) {
|
19698
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
19699
|
+
args[_key - 1] = arguments[_key];
|
19700
|
+
}
|
19701
|
+
livekitLogger.trace('participant event', {
|
19702
|
+
participant: this.sid,
|
19703
|
+
event,
|
19704
|
+
args
|
19705
|
+
});
|
19706
|
+
return super.emit(event, ...args);
|
19707
|
+
}
|
19708
|
+
}
|
19471
19709
|
|
19472
19710
|
class LocalParticipant extends Participant {
|
19473
19711
|
/** @internal */
|
@@ -19910,7 +20148,7 @@ class LocalParticipant extends Participant {
|
|
19910
20148
|
screenVideo.source = Track.Source.ScreenShare;
|
19911
20149
|
const localTracks = [screenVideo];
|
19912
20150
|
if (stream.getAudioTracks().length > 0) {
|
19913
|
-
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false);
|
20151
|
+
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext);
|
19914
20152
|
screenAudio.source = Track.Source.ScreenShareAudio;
|
19915
20153
|
localTracks.push(screenAudio);
|
19916
20154
|
}
|
@@ -19954,7 +20192,7 @@ class LocalParticipant extends Participant {
|
|
19954
20192
|
if (track instanceof MediaStreamTrack) {
|
19955
20193
|
switch (track.kind) {
|
19956
20194
|
case 'audio':
|
19957
|
-
track = new LocalAudioTrack(track, defaultConstraints, true);
|
20195
|
+
track = new LocalAudioTrack(track, defaultConstraints, true, this.audioContext);
|
19958
20196
|
break;
|
19959
20197
|
case 'video':
|
19960
20198
|
track = new LocalVideoTrack(track, defaultConstraints, true);
|
@@ -19963,6 +20201,9 @@ class LocalParticipant extends Participant {
|
|
19963
20201
|
throw new TrackInvalidError("unsupported MediaStreamTrack kind ".concat(track.kind));
|
19964
20202
|
}
|
19965
20203
|
}
|
20204
|
+
if (track instanceof LocalAudioTrack) {
|
20205
|
+
track.setAudioContext(this.audioContext);
|
20206
|
+
}
|
19966
20207
|
// is it already published? if so skip
|
19967
20208
|
let existingPublication;
|
19968
20209
|
this.tracks.forEach(publication => {
|
@@ -20100,6 +20341,9 @@ class LocalParticipant extends Participant {
|
|
20100
20341
|
}
|
20101
20342
|
// set up backup
|
20102
20343
|
if (opts.videoCodec && opts.backupCodec && opts.videoCodec !== opts.backupCodec.codec) {
|
20344
|
+
if (!this.roomOptions.dynacast) {
|
20345
|
+
this.roomOptions.dynacast = true;
|
20346
|
+
}
|
20103
20347
|
const simOpts = Object.assign({}, opts);
|
20104
20348
|
simOpts.simulcast = true;
|
20105
20349
|
simEncodings = computeTrackBackupEncodings(track, opts.backupCodec.codec, simOpts);
|
@@ -20598,11 +20842,13 @@ class Room extends eventsExports.EventEmitter {
|
|
20598
20842
|
if (this.abortController) {
|
20599
20843
|
this.abortController.abort();
|
20600
20844
|
}
|
20601
|
-
|
20845
|
+
// explicit creation as local var needed to satisfy TS compiler when passing it to `attemptConnection` further down
|
20846
|
+
const abortController = new AbortController();
|
20847
|
+
this.abortController = abortController;
|
20602
20848
|
// at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again
|
20603
20849
|
unlockDisconnect === null || unlockDisconnect === void 0 ? void 0 : unlockDisconnect();
|
20604
20850
|
try {
|
20605
|
-
yield this.attemptConnection(regionUrl !== null && regionUrl !== void 0 ? regionUrl : url, token, opts,
|
20851
|
+
yield this.attemptConnection(regionUrl !== null && regionUrl !== void 0 ? regionUrl : url, token, opts, abortController);
|
20606
20852
|
this.abortController = undefined;
|
20607
20853
|
resolve();
|
20608
20854
|
} catch (e) {
|
@@ -20644,7 +20890,8 @@ class Room extends eventsExports.EventEmitter {
|
|
20644
20890
|
publishOnly: connectOptions.publishOnly,
|
20645
20891
|
adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
20646
20892
|
maxRetries: connectOptions.maxRetries,
|
20647
|
-
e2eeEnabled: !!this.e2eeManager
|
20893
|
+
e2eeEnabled: !!this.e2eeManager,
|
20894
|
+
websocketTimeout: connectOptions.websocketTimeout
|
20648
20895
|
}, abortController.signal);
|
20649
20896
|
let serverInfo = joinResponse.serverInfo;
|
20650
20897
|
if (!serverInfo) {
|
@@ -20676,6 +20923,9 @@ class Room extends eventsExports.EventEmitter {
|
|
20676
20923
|
if (joinResponse.room) {
|
20677
20924
|
this.handleRoomUpdate(joinResponse.room);
|
20678
20925
|
}
|
20926
|
+
if (this.options.e2ee && this.e2eeManager) {
|
20927
|
+
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
20928
|
+
}
|
20679
20929
|
};
|
20680
20930
|
this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
|
20681
20931
|
var _d, _e;
|
@@ -21254,7 +21504,7 @@ class Room extends eventsExports.EventEmitter {
|
|
21254
21504
|
/**
|
21255
21505
|
* @internal for testing
|
21256
21506
|
*/
|
21257
|
-
simulateScenario(scenario) {
|
21507
|
+
simulateScenario(scenario, arg) {
|
21258
21508
|
return __awaiter(this, void 0, void 0, function* () {
|
21259
21509
|
let postAction = () => {};
|
21260
21510
|
let req;
|
@@ -21323,6 +21573,17 @@ class Room extends eventsExports.EventEmitter {
|
|
21323
21573
|
}
|
21324
21574
|
});
|
21325
21575
|
break;
|
21576
|
+
case 'subscriber-bandwidth':
|
21577
|
+
if (arg === undefined || typeof arg !== 'number') {
|
21578
|
+
throw new Error('subscriber-bandwidth requires a number as argument');
|
21579
|
+
}
|
21580
|
+
req = new SimulateScenario({
|
21581
|
+
scenario: {
|
21582
|
+
case: 'subscriberBandwidth',
|
21583
|
+
value: BigInt(arg)
|
21584
|
+
}
|
21585
|
+
});
|
21586
|
+
break;
|
21326
21587
|
}
|
21327
21588
|
if (req) {
|
21328
21589
|
this.engine.client.sendSimulateScenario(req);
|
@@ -21339,7 +21600,6 @@ class Room extends eventsExports.EventEmitter {
|
|
21339
21600
|
*/
|
21340
21601
|
startAudio() {
|
21341
21602
|
return __awaiter(this, void 0, void 0, function* () {
|
21342
|
-
yield this.acquireAudioContext();
|
21343
21603
|
const elements = [];
|
21344
21604
|
const browser = getBrowser();
|
21345
21605
|
if (browser && browser.os === 'iOS') {
|
@@ -21386,10 +21646,10 @@ class Room extends eventsExports.EventEmitter {
|
|
21386
21646
|
});
|
21387
21647
|
});
|
21388
21648
|
try {
|
21389
|
-
yield Promise.all(elements.map(e => {
|
21649
|
+
yield Promise.all([this.acquireAudioContext(), ...elements.map(e => {
|
21390
21650
|
e.muted = false;
|
21391
21651
|
return e.play();
|
21392
|
-
}));
|
21652
|
+
})]);
|
21393
21653
|
this.handleAudioPlaybackStarted();
|
21394
21654
|
} catch (err) {
|
21395
21655
|
this.handleAudioPlaybackFailed(err);
|
@@ -21630,6 +21890,7 @@ class Room extends eventsExports.EventEmitter {
|
|
21630
21890
|
if (this.options.expWebAudioMix) {
|
21631
21891
|
this.participants.forEach(participant => participant.setAudioContext(this.audioContext));
|
21632
21892
|
}
|
21893
|
+
this.localParticipant.setAudioContext(this.audioContext);
|
21633
21894
|
const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
|
21634
21895
|
if (newContextIsRunning !== this.canPlaybackAudio) {
|
21635
21896
|
this.audioEnabled = newContextIsRunning;
|
@@ -22258,7 +22519,8 @@ class TURNCheck extends Checker {
|
|
22258
22519
|
const joinRes = yield signalClient.join(this.url, this.token, {
|
22259
22520
|
autoSubscribe: true,
|
22260
22521
|
maxRetries: 0,
|
22261
|
-
e2eeEnabled: false
|
22522
|
+
e2eeEnabled: false,
|
22523
|
+
websocketTimeout: 15000
|
22262
22524
|
});
|
22263
22525
|
let hasTLS = false;
|
22264
22526
|
let hasTURN = false;
|
@@ -22310,18 +22572,19 @@ class WebRTCCheck extends Checker {
|
|
22310
22572
|
this.room.on(RoomEvent.SignalConnected, () => {
|
22311
22573
|
const prevTrickle = this.room.engine.client.onTrickle;
|
22312
22574
|
this.room.engine.client.onTrickle = (sd, target) => {
|
22313
|
-
console.log('got candidate', sd);
|
22314
22575
|
if (sd.candidate) {
|
22315
22576
|
const candidate = new RTCIceCandidate(sd);
|
22316
22577
|
let str = "".concat(candidate.protocol, " ").concat(candidate.address, ":").concat(candidate.port, " ").concat(candidate.type);
|
22317
|
-
if (candidate.
|
22318
|
-
hasTcp = true;
|
22319
|
-
str += ' (active)';
|
22320
|
-
} else if (candidate.protocol === 'udp' && candidate.address) {
|
22578
|
+
if (candidate.address) {
|
22321
22579
|
if (isIPPrivate(candidate.address)) {
|
22322
22580
|
str += ' (private)';
|
22323
22581
|
} else {
|
22324
|
-
|
22582
|
+
if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') {
|
22583
|
+
hasTcp = true;
|
22584
|
+
str += ' (passive)';
|
22585
|
+
} else if (candidate.protocol === 'udp') {
|
22586
|
+
hasIpv4Udp = true;
|
22587
|
+
}
|
22325
22588
|
}
|
22326
22589
|
}
|
22327
22590
|
this.appendMessage(str);
|
@@ -22340,7 +22603,7 @@ class WebRTCCheck extends Checker {
|
|
22340
22603
|
});
|
22341
22604
|
try {
|
22342
22605
|
yield this.connect();
|
22343
|
-
|
22606
|
+
livekitLogger.info('now the room is connected');
|
22344
22607
|
} catch (err) {
|
22345
22608
|
this.appendWarning('ports need to be open on firewall in order to connect.');
|
22346
22609
|
throw err;
|
@@ -22385,7 +22648,8 @@ class WebSocketCheck extends Checker {
|
|
22385
22648
|
const joinRes = yield signalClient.join(this.url, this.token, {
|
22386
22649
|
autoSubscribe: true,
|
22387
22650
|
maxRetries: 0,
|
22388
|
-
e2eeEnabled: false
|
22651
|
+
e2eeEnabled: false,
|
22652
|
+
websocketTimeout: 15000
|
22389
22653
|
});
|
22390
22654
|
this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
|
22391
22655
|
if (((_a = joinRes.serverInfo) === null || _a === void 0 ? void 0 : _a.edition) === ServerInfo_Edition.Cloud && ((_b = joinRes.serverInfo) === null || _b === void 0 ? void 0 : _b.region)) {
|
@@ -22549,5 +22813,5 @@ function isFacingModeValue(item) {
|
|
22549
22813
|
return item === undefined || allowedValues.includes(item);
|
22550
22814
|
}
|
22551
22815
|
|
22552
|
-
export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, importKey, isBackupCodec, isBrowserSupported, isCodecEqual, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, mimeTypeToVideoCodecString, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs };
|
22816
|
+
export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, importKey, isBackupCodec, isBrowserSupported, isCodecEqual, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, mimeTypeToVideoCodecString, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs };
|
22553
22817
|
//# sourceMappingURL=livekit-client.esm.mjs.map
|