livekit-client 1.12.2 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -11823,7 +11823,7 @@ function getMatch(exp, ua) {
11823
11823
  return match && match.length >= id && match[id] || '';
11824
11824
  }
11825
11825
 
11826
- var version$1 = "1.12.2";
11826
+ var version$1 = "1.12.3";
11827
11827
 
11828
11828
  const version = version$1;
11829
11829
  const protocolVersion = 9;
@@ -11880,11 +11880,11 @@ var AudioPresets;
11880
11880
  * Sane presets for video resolution/encoding
11881
11881
  */
11882
11882
  const VideoPresets = {
11883
- h90: new VideoPreset(160, 90, 60000, 15),
11884
- h180: new VideoPreset(320, 180, 120000, 15),
11885
- h216: new VideoPreset(384, 216, 180000, 15),
11886
- h360: new VideoPreset(640, 360, 300000, 20),
11887
- h540: new VideoPreset(960, 540, 600000, 25),
11883
+ h90: new VideoPreset(160, 90, 90000, 20),
11884
+ h180: new VideoPreset(320, 180, 160000, 20),
11885
+ h216: new VideoPreset(384, 216, 180000, 20),
11886
+ h360: new VideoPreset(640, 360, 450000, 20),
11887
+ h540: new VideoPreset(960, 540, 800000, 25),
11888
11888
  h720: new VideoPreset(1280, 720, 1700000, 30),
11889
11889
  h1080: new VideoPreset(1920, 1080, 3000000, 30),
11890
11890
  h1440: new VideoPreset(2560, 1440, 5000000, 30),
@@ -11894,22 +11894,23 @@ const VideoPresets = {
11894
11894
  * Four by three presets
11895
11895
  */
11896
11896
  const VideoPresets43 = {
11897
- h120: new VideoPreset(160, 120, 80000, 15),
11898
- h180: new VideoPreset(240, 180, 100000, 15),
11899
- h240: new VideoPreset(320, 240, 150000, 15),
11897
+ h120: new VideoPreset(160, 120, 70000, 20),
11898
+ h180: new VideoPreset(240, 180, 125000, 20),
11899
+ h240: new VideoPreset(320, 240, 140000, 20),
11900
11900
  h360: new VideoPreset(480, 360, 225000, 20),
11901
- h480: new VideoPreset(640, 480, 300000, 20),
11902
- h540: new VideoPreset(720, 540, 450000, 25),
11903
- h720: new VideoPreset(960, 720, 1500000, 30),
11904
- h1080: new VideoPreset(1440, 1080, 2500000, 30),
11905
- h1440: new VideoPreset(1920, 1440, 3500000, 30)
11901
+ h480: new VideoPreset(640, 480, 500000, 20),
11902
+ h540: new VideoPreset(720, 540, 600000, 25),
11903
+ h720: new VideoPreset(960, 720, 1300000, 30),
11904
+ h1080: new VideoPreset(1440, 1080, 2300000, 30),
11905
+ h1440: new VideoPreset(1920, 1440, 3800000, 30)
11906
11906
  };
11907
11907
  const ScreenSharePresets = {
11908
11908
  h360fps3: new VideoPreset(640, 360, 200000, 3, 'medium'),
11909
11909
  h720fps5: new VideoPreset(1280, 720, 400000, 5, 'medium'),
11910
- h720fps15: new VideoPreset(1280, 720, 1000000, 15, 'medium'),
11911
- h1080fps15: new VideoPreset(1920, 1080, 1500000, 15, 'medium'),
11912
- h1080fps30: new VideoPreset(1920, 1080, 3000000, 30, 'medium')
11910
+ h720fps15: new VideoPreset(1280, 720, 1500000, 15, 'medium'),
11911
+ h720fps30: new VideoPreset(1280, 720, 2000000, 30, 'medium'),
11912
+ h1080fps15: new VideoPreset(1920, 1080, 2500000, 15, 'medium'),
11913
+ h1080fps30: new VideoPreset(1920, 1080, 4000000, 30, 'medium')
11913
11914
  };
11914
11915
 
11915
11916
  /**
@@ -16029,13 +16030,13 @@ class RTCEngine extends eventsExports.EventEmitter {
16029
16030
  this.handleDataError = event => {
16030
16031
  const channel = event.currentTarget;
16031
16032
  const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
16032
- if (event instanceof ErrorEvent) {
16033
+ if (event instanceof ErrorEvent && event.error) {
16033
16034
  const {
16034
16035
  error
16035
16036
  } = event.error;
16036
16037
  livekitLogger.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), error);
16037
16038
  } else {
16038
- livekitLogger.error("Unknown DataChannel Error on ".concat(channelKind), event);
16039
+ livekitLogger.error("Unknown DataChannel error on ".concat(channelKind), event);
16039
16040
  }
16040
16041
  };
16041
16042
  this.handleBufferedAmountLow = event => {
@@ -16423,13 +16424,13 @@ class RTCEngine extends eventsExports.EventEmitter {
16423
16424
  });
16424
16425
  this.client.onLocalTrackPublished = res => {
16425
16426
  livekitLogger.debug('received trackPublishedResponse', res);
16426
- const {
16427
- resolve
16428
- } = this.pendingTrackResolvers[res.cid];
16429
- if (!resolve) {
16427
+ if (!this.pendingTrackResolvers[res.cid]) {
16430
16428
  livekitLogger.error("missing track resolver for ".concat(res.cid));
16431
16429
  return;
16432
16430
  }
16431
+ const {
16432
+ resolve
16433
+ } = this.pendingTrackResolvers[res.cid];
16433
16434
  delete this.pendingTrackResolvers[res.cid];
16434
16435
  resolve(res.track);
16435
16436
  };
@@ -17312,251 +17313,551 @@ class LocalAudioTrack extends LocalTrack {
17312
17313
  }
17313
17314
  }
17314
17315
 
17315
- const refreshSubscribedCodecAfterNewCodec = 5000;
17316
- class LocalVideoTrack extends LocalTrack {
17317
- /**
17318
- *
17319
- * @param mediaTrack
17320
- * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
17321
- * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
17322
- */
17323
- constructor(mediaTrack, constraints) {
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();
17316
+ /** @internal */
17317
+ function mediaTrackToLocalTrack(mediaStreamTrack, constraints) {
17318
+ switch (mediaStreamTrack.kind) {
17319
+ case 'audio':
17320
+ return new LocalAudioTrack(mediaStreamTrack, constraints, false);
17321
+ case 'video':
17322
+ return new LocalVideoTrack(mediaStreamTrack, constraints, false);
17323
+ default:
17324
+ throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
17355
17325
  }
17356
- get isSimulcast() {
17357
- if (this.sender && this.sender.getParameters().encodings.length > 1) {
17358
- return true;
17326
+ }
17327
+ /* @internal */
17328
+ const presets169 = Object.values(VideoPresets);
17329
+ /* @internal */
17330
+ const presets43 = Object.values(VideoPresets43);
17331
+ /* @internal */
17332
+ const presetsScreenShare = Object.values(ScreenSharePresets);
17333
+ /* @internal */
17334
+ const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
17335
+ /* @internal */
17336
+ const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
17337
+ /* @internal */
17338
+ const computeDefaultScreenShareSimulcastPresets = fromPreset => {
17339
+ const layers = [{
17340
+ scaleResolutionDownBy: 2,
17341
+ fps: 3
17342
+ }];
17343
+ return layers.map(t => {
17344
+ var _a;
17345
+ 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);
17346
+ });
17347
+ };
17348
+ // /**
17349
+ // *
17350
+ // * @internal
17351
+ // * @experimental
17352
+ // */
17353
+ // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
17354
+ // // use vp8 as a default
17355
+ // const vp8 = determineAppropriateEncoding(false, width, height);
17356
+ // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
17357
+ // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
17358
+ // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
17359
+ // return {
17360
+ // vp8,
17361
+ // vp9,
17362
+ // h264,
17363
+ // av1,
17364
+ // };
17365
+ // };
17366
+ const videoRids = ['q', 'h', 'f'];
17367
+ /* @internal */
17368
+ function computeVideoEncodings(isScreenShare, width, height, options) {
17369
+ var _a, _b;
17370
+ let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
17371
+ if (isScreenShare) {
17372
+ videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
17373
+ }
17374
+ const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
17375
+ const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
17376
+ const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
17377
+ if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
17378
+ // when we aren't simulcasting or svc, will need to return a single encoding without
17379
+ // capping bandwidth. we always require a encoding for dynacast
17380
+ return [{}];
17381
+ }
17382
+ if (!videoEncoding) {
17383
+ // find the right encoding based on width/height
17384
+ videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
17385
+ livekitLogger.debug('using video encoding', videoEncoding);
17386
+ }
17387
+ const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
17388
+ if (scalabilityMode && isSVCCodec(videoCodec)) {
17389
+ livekitLogger.debug("using svc with scalabilityMode ".concat(scalabilityMode));
17390
+ const sm = new ScalabilityMode(scalabilityMode);
17391
+ const encodings = [];
17392
+ if (sm.spatial > 3) {
17393
+ throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
17359
17394
  }
17360
- return false;
17395
+ for (let i = 0; i < sm.spatial; i += 1) {
17396
+ encodings.push({
17397
+ rid: videoRids[2 - i],
17398
+ maxBitrate: videoEncoding.maxBitrate / Math.pow(3, i),
17399
+ /* @ts-ignore */
17400
+ maxFramerate: original.encoding.maxFramerate
17401
+ });
17402
+ }
17403
+ /* @ts-ignore */
17404
+ encodings[0].scalabilityMode = scalabilityMode;
17405
+ livekitLogger.debug('encodings', encodings);
17406
+ return encodings;
17361
17407
  }
17362
- /* @internal */
17363
- startMonitor(signalClient) {
17364
- var _a;
17365
- this.signalClient = signalClient;
17366
- if (!isWeb()) {
17367
- return;
17408
+ if (!useSimulcast) {
17409
+ return [videoEncoding];
17410
+ }
17411
+ let presets = [];
17412
+ if (isScreenShare) {
17413
+ presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
17414
+ } else {
17415
+ presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
17416
+ }
17417
+ let midPreset;
17418
+ if (presets.length > 0) {
17419
+ const lowPreset = presets[0];
17420
+ if (presets.length > 1) {
17421
+ [, midPreset] = presets;
17368
17422
  }
17369
- // save original encodings
17370
- // TODO : merge simulcast tracks stats
17371
- const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
17372
- if (params) {
17373
- this.encodings = params.encodings;
17423
+ // NOTE:
17424
+ // 1. Ordering of these encodings is important. Chrome seems
17425
+ // to use the index into encodings to decide which layer
17426
+ // to disable when CPU constrained.
17427
+ // So encodings should be ordered in increasing spatial
17428
+ // resolution order.
17429
+ // 2. ion-sfu translates rids into layers. So, all encodings
17430
+ // should have the base layer `q` and then more added
17431
+ // based on other conditions.
17432
+ const size = Math.max(width, height);
17433
+ if (size >= 960 && midPreset) {
17434
+ return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
17374
17435
  }
17375
- if (this.monitorInterval) {
17376
- return;
17436
+ if (size >= 480) {
17437
+ return encodingsFromPresets(width, height, [lowPreset, original]);
17377
17438
  }
17378
- this.monitorInterval = setInterval(() => {
17379
- this.monitorSender();
17380
- }, monitorFrequency);
17381
- }
17382
- stop() {
17383
- this._mediaStreamTrack.getConstraints();
17384
- this.simulcastCodecs.forEach(trackInfo => {
17385
- trackInfo.mediaStreamTrack.stop();
17386
- });
17387
- super.stop();
17388
17439
  }
17389
- mute() {
17390
- const _super = Object.create(null, {
17391
- mute: {
17392
- get: () => super.mute
17393
- }
17394
- });
17395
- return __awaiter(this, void 0, void 0, function* () {
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
- });
17440
+ return encodingsFromPresets(width, height, [original]);
17441
+ }
17442
+ function computeTrackBackupEncodings(track, videoCodec, opts) {
17443
+ var _a, _b, _c, _d;
17444
+ if (!opts.backupCodec || opts.backupCodec.codec === opts.videoCodec) {
17445
+ // backup codec publishing is disabled
17446
+ return;
17409
17447
  }
17410
- unmute() {
17411
- const _super = Object.create(null, {
17412
- unmute: {
17413
- get: () => super.unmute
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
- }
17448
+ if (videoCodec !== opts.backupCodec.codec) {
17449
+ livekitLogger.warn('requested a different codec than specified as backup', {
17450
+ serverRequested: videoCodec,
17451
+ backup: opts.backupCodec.codec
17428
17452
  });
17429
17453
  }
17430
- getSenderStats() {
17431
- var _a;
17432
- return __awaiter(this, void 0, void 0, function* () {
17433
- if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
17434
- return [];
17435
- }
17436
- const items = [];
17437
- const stats = yield this.sender.getStats();
17438
- stats.forEach(v => {
17439
- var _a;
17440
- if (v.type === 'outbound-rtp') {
17441
- const vs = {
17442
- type: 'video',
17443
- streamId: v.id,
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
- }
17467
- });
17468
- return items;
17469
- });
17454
+ opts.videoCodec = videoCodec;
17455
+ // use backup encoding setting as videoEncoding for backup codec publishing
17456
+ opts.videoEncoding = opts.backupCodec.encoding;
17457
+ const settings = track.mediaStreamTrack.getSettings();
17458
+ const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
17459
+ const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
17460
+ const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
17461
+ return encodings;
17462
+ }
17463
+ /* @internal */
17464
+ function determineAppropriateEncoding(isScreenShare, width, height, codec) {
17465
+ const presets = presetsForResolution(isScreenShare, width, height);
17466
+ let {
17467
+ encoding
17468
+ } = presets[0];
17469
+ // handle portrait by swapping dimensions
17470
+ const size = Math.max(width, height);
17471
+ for (let i = 0; i < presets.length; i += 1) {
17472
+ const preset = presets[i];
17473
+ encoding = preset.encoding;
17474
+ if (preset.width >= size) {
17475
+ break;
17476
+ }
17470
17477
  }
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
+ // presets are based on the assumption of vp8 as a codec
17479
+ // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
17480
+ // users should override these with ones that are optimized for their use case
17481
+ // NOTE: SVC codec bitrates are inclusive of all scalability layers. while
17482
+ // bitrate for non-SVC codecs does not include other simulcast layers.
17483
+ if (codec) {
17484
+ switch (codec) {
17485
+ case 'av1':
17486
+ encoding = Object.assign({}, encoding);
17487
+ encoding.maxBitrate = encoding.maxBitrate * 0.7;
17488
+ break;
17489
+ case 'vp9':
17490
+ encoding = Object.assign({}, encoding);
17491
+ encoding.maxBitrate = encoding.maxBitrate * 0.85;
17492
+ break;
17478
17493
  }
17479
- livekitLogger.debug("setting publishing quality. max quality ".concat(maxQuality));
17480
- this.setPublishingLayers(qualities);
17481
17494
  }
17482
- setDeviceId(deviceId) {
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();
17495
+ return encoding;
17496
+ }
17497
+ /* @internal */
17498
+ function presetsForResolution(isScreenShare, width, height) {
17499
+ if (isScreenShare) {
17500
+ return presetsScreenShare;
17501
+ }
17502
+ const aspect = width > height ? width / height : height / width;
17503
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
17504
+ return presets169;
17505
+ }
17506
+ return presets43;
17507
+ }
17508
+ /* @internal */
17509
+ function defaultSimulcastLayers(isScreenShare, original) {
17510
+ if (isScreenShare) {
17511
+ return computeDefaultScreenShareSimulcastPresets(original);
17512
+ }
17513
+ const {
17514
+ width,
17515
+ height
17516
+ } = original;
17517
+ const aspect = width > height ? width / height : height / width;
17518
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
17519
+ return defaultSimulcastPresets169;
17520
+ }
17521
+ return defaultSimulcastPresets43;
17522
+ }
17523
+ // presets should be ordered by low, medium, high
17524
+ function encodingsFromPresets(width, height, presets) {
17525
+ const encodings = [];
17526
+ presets.forEach((preset, idx) => {
17527
+ if (idx >= videoRids.length) {
17528
+ return;
17529
+ }
17530
+ const size = Math.min(width, height);
17531
+ const rid = videoRids[idx];
17532
+ const encoding = {
17533
+ rid,
17534
+ scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
17535
+ maxBitrate: preset.encoding.maxBitrate
17536
+ };
17537
+ if (preset.encoding.maxFramerate) {
17538
+ encoding.maxFramerate = preset.encoding.maxFramerate;
17539
+ }
17540
+ const canSetPriority = isFireFox() || idx === 0;
17541
+ if (preset.encoding.priority && canSetPriority) {
17542
+ encoding.priority = preset.encoding.priority;
17543
+ encoding.networkPriority = preset.encoding.priority;
17544
+ }
17545
+ encodings.push(encoding);
17546
+ });
17547
+ // RN ios simulcast requires all same framerates.
17548
+ if (isReactNative() && getReactNativeOs() === 'ios') {
17549
+ let topFramerate = undefined;
17550
+ encodings.forEach(encoding => {
17551
+ if (!topFramerate) {
17552
+ topFramerate = encoding.maxFramerate;
17553
+ } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
17554
+ topFramerate = encoding.maxFramerate;
17492
17555
  }
17493
- return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
17494
17556
  });
17495
- }
17496
- restartTrack(options) {
17497
- return __awaiter(this, void 0, void 0, function* () {
17498
- let constraints;
17499
- if (options) {
17500
- const streamConstraints = constraintsForOptions({
17501
- video: options
17502
- });
17503
- if (typeof streamConstraints.video !== 'boolean') {
17504
- constraints = streamConstraints.video;
17557
+ let notifyOnce = true;
17558
+ encodings.forEach(encoding => {
17559
+ var _a;
17560
+ if (encoding.maxFramerate != topFramerate) {
17561
+ if (notifyOnce) {
17562
+ notifyOnce = false;
17563
+ livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
17505
17564
  }
17565
+ livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
17566
+ encoding.maxFramerate = topFramerate;
17506
17567
  }
17507
- yield this.restart(constraints);
17508
17568
  });
17509
17569
  }
17510
- addSimulcastTrack(codec, encodings) {
17511
- if (this.simulcastCodecs.has(codec)) {
17512
- throw new Error("".concat(codec, " already added"));
17570
+ return encodings;
17571
+ }
17572
+ /** @internal */
17573
+ function sortPresets(presets) {
17574
+ if (!presets) return;
17575
+ return presets.sort((a, b) => {
17576
+ const {
17577
+ encoding: aEnc
17578
+ } = a;
17579
+ const {
17580
+ encoding: bEnc
17581
+ } = b;
17582
+ if (aEnc.maxBitrate > bEnc.maxBitrate) {
17583
+ return 1;
17513
17584
  }
17514
- const simulcastCodecInfo = {
17515
- codec,
17516
- mediaStreamTrack: this.mediaStreamTrack.clone(),
17517
- sender: undefined,
17518
- encodings
17519
- };
17520
- this.simulcastCodecs.set(codec, simulcastCodecInfo);
17521
- return simulcastCodecInfo;
17522
- }
17523
- setSimulcastTrackSender(codec, sender) {
17524
- const simulcastCodecInfo = this.simulcastCodecs.get(codec);
17525
- if (!simulcastCodecInfo) {
17526
- return;
17585
+ if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
17586
+ if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
17587
+ return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
17527
17588
  }
17528
- simulcastCodecInfo.sender = sender;
17529
- // browser will reenable disabled codec/layers after new codec has been published,
17530
- // so refresh subscribedCodecs after publish a new codec
17531
- setTimeout(() => {
17532
- if (this.subscribedCodecs) {
17533
- this.setPublishingCodecs(this.subscribedCodecs);
17589
+ return 0;
17590
+ });
17591
+ }
17592
+ /** @internal */
17593
+ class ScalabilityMode {
17594
+ constructor(scalabilityMode) {
17595
+ const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
17596
+ if (!results) {
17597
+ throw new Error('invalid scalability mode');
17598
+ }
17599
+ this.spatial = parseInt(results[1]);
17600
+ this.temporal = parseInt(results[2]);
17601
+ if (results.length > 3) {
17602
+ switch (results[3]) {
17603
+ case 'h':
17604
+ case '_KEY':
17605
+ case '_KEY_SHIFT':
17606
+ this.suffix = results[3];
17534
17607
  }
17535
- }, refreshSubscribedCodecAfterNewCodec);
17608
+ }
17609
+ }
17610
+ toString() {
17611
+ var _a;
17612
+ return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
17536
17613
  }
17614
+ }
17615
+
17616
+ const refreshSubscribedCodecAfterNewCodec = 5000;
17617
+ class LocalVideoTrack extends LocalTrack {
17537
17618
  /**
17538
- * @internal
17539
- * Sets codecs that should be publishing
17619
+ *
17620
+ * @param mediaTrack
17621
+ * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
17622
+ * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
17540
17623
  */
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 [];
17624
+ constructor(mediaTrack, constraints) {
17625
+ let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
17626
+ super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
17627
+ /* @internal */
17628
+ this.simulcastCodecs = new Map();
17629
+ this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
17630
+ if (!this.sender) {
17631
+ this._currentBitrate = 0;
17632
+ return;
17553
17633
  }
17554
- this.subscribedCodecs = codecs;
17555
- const newCodecs = [];
17634
+ let stats;
17556
17635
  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;
17636
+ stats = yield this.getSenderStats();
17637
+ } catch (e) {
17638
+ livekitLogger.error('could not get audio sender stats', {
17639
+ error: e
17640
+ });
17641
+ return;
17642
+ }
17643
+ const statsMap = new Map(stats.map(s => [s.rid, s]));
17644
+ if (this.prevStats) {
17645
+ let totalBitrate = 0;
17646
+ statsMap.forEach((s, key) => {
17647
+ var _a;
17648
+ const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
17649
+ totalBitrate += computeBitrate(s, prev);
17650
+ });
17651
+ this._currentBitrate = totalBitrate;
17652
+ }
17653
+ this.prevStats = statsMap;
17654
+ });
17655
+ this.senderLock = new Mutex();
17656
+ }
17657
+ get isSimulcast() {
17658
+ if (this.sender && this.sender.getParameters().encodings.length > 1) {
17659
+ return true;
17660
+ }
17661
+ return false;
17662
+ }
17663
+ /* @internal */
17664
+ startMonitor(signalClient) {
17665
+ var _a;
17666
+ this.signalClient = signalClient;
17667
+ if (!isWeb()) {
17668
+ return;
17669
+ }
17670
+ // save original encodings
17671
+ // TODO : merge simulcast tracks stats
17672
+ const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
17673
+ if (params) {
17674
+ this.encodings = params.encodings;
17675
+ }
17676
+ if (this.monitorInterval) {
17677
+ return;
17678
+ }
17679
+ this.monitorInterval = setInterval(() => {
17680
+ this.monitorSender();
17681
+ }, monitorFrequency);
17682
+ }
17683
+ stop() {
17684
+ this._mediaStreamTrack.getConstraints();
17685
+ this.simulcastCodecs.forEach(trackInfo => {
17686
+ trackInfo.mediaStreamTrack.stop();
17687
+ });
17688
+ super.stop();
17689
+ }
17690
+ mute() {
17691
+ const _super = Object.create(null, {
17692
+ mute: {
17693
+ get: () => super.mute
17694
+ }
17695
+ });
17696
+ return __awaiter(this, void 0, void 0, function* () {
17697
+ const unlock = yield this.muteLock.lock();
17698
+ try {
17699
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
17700
+ livekitLogger.debug('stopping camera track');
17701
+ // also stop the track, so that camera indicator is turned off
17702
+ this._mediaStreamTrack.stop();
17703
+ }
17704
+ yield _super.mute.call(this);
17705
+ return this;
17706
+ } finally {
17707
+ unlock();
17708
+ }
17709
+ });
17710
+ }
17711
+ unmute() {
17712
+ const _super = Object.create(null, {
17713
+ unmute: {
17714
+ get: () => super.unmute
17715
+ }
17716
+ });
17717
+ return __awaiter(this, void 0, void 0, function* () {
17718
+ const unlock = yield this.muteLock.lock();
17719
+ try {
17720
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
17721
+ livekitLogger.debug('reacquiring camera track');
17722
+ yield this.restartTrack();
17723
+ }
17724
+ yield _super.unmute.call(this);
17725
+ return this;
17726
+ } finally {
17727
+ unlock();
17728
+ }
17729
+ });
17730
+ }
17731
+ getSenderStats() {
17732
+ var _a;
17733
+ return __awaiter(this, void 0, void 0, function* () {
17734
+ if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
17735
+ return [];
17736
+ }
17737
+ const items = [];
17738
+ const stats = yield this.sender.getStats();
17739
+ stats.forEach(v => {
17740
+ var _a;
17741
+ if (v.type === 'outbound-rtp') {
17742
+ const vs = {
17743
+ type: 'video',
17744
+ streamId: v.id,
17745
+ frameHeight: v.frameHeight,
17746
+ frameWidth: v.frameWidth,
17747
+ firCount: v.firCount,
17748
+ pliCount: v.pliCount,
17749
+ nackCount: v.nackCount,
17750
+ packetsSent: v.packetsSent,
17751
+ bytesSent: v.bytesSent,
17752
+ framesSent: v.framesSent,
17753
+ timestamp: v.timestamp,
17754
+ rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
17755
+ retransmittedPacketsSent: v.retransmittedPacketsSent,
17756
+ qualityLimitationReason: v.qualityLimitationReason,
17757
+ qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges
17758
+ };
17759
+ // locate the appropriate remote-inbound-rtp item
17760
+ const r = stats.get(v.remoteId);
17761
+ if (r) {
17762
+ vs.jitter = r.jitter;
17763
+ vs.packetsLost = r.packetsLost;
17764
+ vs.roundTripTime = r.roundTripTime;
17765
+ }
17766
+ items.push(vs);
17767
+ }
17768
+ });
17769
+ return items;
17770
+ });
17771
+ }
17772
+ setPublishingQuality(maxQuality) {
17773
+ const qualities = [];
17774
+ for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
17775
+ qualities.push(new SubscribedQuality({
17776
+ quality: q,
17777
+ enabled: q <= maxQuality
17778
+ }));
17779
+ }
17780
+ livekitLogger.debug("setting publishing quality. max quality ".concat(maxQuality));
17781
+ this.setPublishingLayers(qualities);
17782
+ }
17783
+ setDeviceId(deviceId) {
17784
+ return __awaiter(this, void 0, void 0, function* () {
17785
+ if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
17786
+ return true;
17787
+ }
17788
+ this._constraints.deviceId = deviceId;
17789
+ // when video is muted, underlying media stream track is stopped and
17790
+ // will be restarted later
17791
+ if (!this.isMuted) {
17792
+ yield this.restartTrack();
17793
+ }
17794
+ return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
17795
+ });
17796
+ }
17797
+ restartTrack(options) {
17798
+ return __awaiter(this, void 0, void 0, function* () {
17799
+ let constraints;
17800
+ if (options) {
17801
+ const streamConstraints = constraintsForOptions({
17802
+ video: options
17803
+ });
17804
+ if (typeof streamConstraints.video !== 'boolean') {
17805
+ constraints = streamConstraints.video;
17806
+ }
17807
+ }
17808
+ yield this.restart(constraints);
17809
+ });
17810
+ }
17811
+ addSimulcastTrack(codec, encodings) {
17812
+ if (this.simulcastCodecs.has(codec)) {
17813
+ throw new Error("".concat(codec, " already added"));
17814
+ }
17815
+ const simulcastCodecInfo = {
17816
+ codec,
17817
+ mediaStreamTrack: this.mediaStreamTrack.clone(),
17818
+ sender: undefined,
17819
+ encodings
17820
+ };
17821
+ this.simulcastCodecs.set(codec, simulcastCodecInfo);
17822
+ return simulcastCodecInfo;
17823
+ }
17824
+ setSimulcastTrackSender(codec, sender) {
17825
+ const simulcastCodecInfo = this.simulcastCodecs.get(codec);
17826
+ if (!simulcastCodecInfo) {
17827
+ return;
17828
+ }
17829
+ simulcastCodecInfo.sender = sender;
17830
+ // browser will reenable disabled codec/layers after new codec has been published,
17831
+ // so refresh subscribedCodecs after publish a new codec
17832
+ setTimeout(() => {
17833
+ if (this.subscribedCodecs) {
17834
+ this.setPublishingCodecs(this.subscribedCodecs);
17835
+ }
17836
+ }, refreshSubscribedCodecAfterNewCodec);
17837
+ }
17838
+ /**
17839
+ * @internal
17840
+ * Sets codecs that should be publishing
17841
+ */
17842
+ setPublishingCodecs(codecs) {
17843
+ var _a, codecs_1, codecs_1_1;
17844
+ var _b, e_1, _c, _d;
17845
+ return __awaiter(this, void 0, void 0, function* () {
17846
+ livekitLogger.debug('setting publishing codecs', {
17847
+ codecs,
17848
+ currentCodec: this.codec
17849
+ });
17850
+ // only enable simulcast codec for preference codec setted
17851
+ if (!this.codec && codecs.length > 0) {
17852
+ yield this.setPublishingLayers(codecs[0].qualities);
17853
+ return [];
17854
+ }
17855
+ this.subscribedCodecs = codecs;
17856
+ const newCodecs = [];
17857
+ try {
17858
+ for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
17859
+ _d = codecs_1_1.value;
17860
+ _a = false;
17560
17861
  const codec = _d;
17561
17862
  if (!this.codec || this.codec === codec.codec) {
17562
17863
  yield this.setPublishingLayers(codec.qualities);
@@ -17716,14 +18017,15 @@ function videoLayersFromEncodings(width, height, encodings, svc) {
17716
18017
  if (svc) {
17717
18018
  // svc layers
17718
18019
  /* @ts-ignore */
17719
- const sm = new ScalabilityMode(encodings[0].scalabilityMode);
18020
+ const encodingSM = encodings[0].scalabilityMode;
18021
+ const sm = new ScalabilityMode(encodingSM);
17720
18022
  const layers = [];
17721
18023
  for (let i = 0; i < sm.spatial; i += 1) {
17722
18024
  layers.push(new VideoLayer({
17723
18025
  quality: VideoQuality.HIGH - i,
17724
- width: width / Math.pow(2, i),
17725
- height: height / Math.pow(2, i),
17726
- bitrate: encodings[0].maxBitrate ? encodings[0].maxBitrate / Math.pow(3, i) : 0,
18026
+ width: Math.ceil(width / Math.pow(2, i)),
18027
+ height: Math.ceil(height / Math.pow(2, i)),
18028
+ bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(3, i)) : 0,
17727
18029
  ssrc: 0
17728
18030
  }));
17729
18031
  }
@@ -17735,8 +18037,8 @@ function videoLayersFromEncodings(width, height, encodings, svc) {
17735
18037
  let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
17736
18038
  return new VideoLayer({
17737
18039
  quality,
17738
- width: width / scale,
17739
- height: height / scale,
18040
+ width: Math.ceil(width / scale),
18041
+ height: Math.ceil(height / scale),
17740
18042
  bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
17741
18043
  ssrc: 0
17742
18044
  });
@@ -19016,458 +19318,160 @@ class RemoteParticipant extends Participant {
19016
19318
  }, 150);
19017
19319
  return;
19018
19320
  }
19019
- if (mediaTrack.readyState === 'ended') {
19020
- livekitLogger.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', {
19021
- participant: this.sid,
19022
- trackSid: sid
19023
- });
19024
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19025
- return;
19026
- }
19027
- const isVideo = mediaTrack.kind === 'video';
19028
- let track;
19029
- if (isVideo) {
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));
19044
- }
19045
- return publication;
19046
- }
19047
- /** @internal */
19048
- get hasMetadata() {
19049
- return !!this.participantInfo;
19050
- }
19051
- getTrackPublication(sid) {
19052
- return this.tracks.get(sid);
19053
- }
19054
- /** @internal */
19055
- updateInfo(info) {
19056
- if (!super.updateInfo(info)) {
19057
- return false;
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
- }
19101
- });
19102
- // always emit events for new publications, Room will not forward them unless it's ready
19103
- newTracks.forEach(publication => {
19104
- this.emit(ParticipantEvent.TrackPublished, publication);
19105
- });
19106
- return true;
19107
- }
19108
- /** @internal */
19109
- unpublishTrack(sid, sendUnpublish) {
19110
- const publication = this.tracks.get(sid);
19111
- if (!publication) {
19112
- return;
19113
- }
19114
- // also send unsubscribe, if track is actively subscribed
19115
- const {
19116
- track
19117
- } = publication;
19118
- if (track) {
19119
- track.stop();
19120
- publication.setTrack(undefined);
19121
- }
19122
- // remove track from maps only after unsubscribed has been fired
19123
- this.tracks.delete(sid);
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;
19132
- }
19133
- if (sendUnpublish) {
19134
- this.emit(ParticipantEvent.TrackUnpublished, publication);
19135
- }
19136
- }
19137
- /**
19138
- * @internal
19139
- */
19140
- setAudioContext(ctx) {
19141
- this.audioContext = ctx;
19142
- this.audioTracks.forEach(track => track.track instanceof RemoteAudioTrack && track.track.setAudioContext(ctx));
19143
- }
19144
- /**
19145
- * @internal
19146
- */
19147
- setAudioOutput(output) {
19148
- return __awaiter(this, void 0, void 0, function* () {
19149
- this.audioOutput = output;
19150
- const promises = [];
19151
- this.audioTracks.forEach(pub => {
19152
- var _a;
19153
- if (pub.track instanceof RemoteAudioTrack) {
19154
- promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
19155
- }
19156
- });
19157
- yield Promise.all(promises);
19158
- });
19159
- }
19160
- /** @internal */
19161
- emit(event) {
19162
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
19163
- args[_key - 1] = arguments[_key];
19164
- }
19165
- livekitLogger.trace('participant event', {
19166
- participant: this.sid,
19167
- event,
19168
- args
19169
- });
19170
- return super.emit(event, ...args);
19171
- }
19172
- }
19173
-
19174
- /** @internal */
19175
- function mediaTrackToLocalTrack(mediaStreamTrack, constraints) {
19176
- switch (mediaStreamTrack.kind) {
19177
- case 'audio':
19178
- return new LocalAudioTrack(mediaStreamTrack, constraints, false);
19179
- case 'video':
19180
- return new LocalVideoTrack(mediaStreamTrack, constraints, false);
19181
- default:
19182
- throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
19183
- }
19184
- }
19185
- /* @internal */
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;
19231
- }
19232
- const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
19233
- const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
19234
- const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
19235
- if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
19236
- // when we aren't simulcasting or svc, will need to return a single encoding without
19237
- // capping bandwidth. we always require a encoding for dynacast
19238
- return [{}];
19239
- }
19240
- if (!videoEncoding) {
19241
- // find the right encoding based on width/height
19242
- videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
19243
- livekitLogger.debug('using video encoding', videoEncoding);
19244
- }
19245
- const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
19246
- if (scalabilityMode && isSVCCodec(videoCodec)) {
19247
- livekitLogger.debug("using svc with scalabilityMode ".concat(scalabilityMode));
19248
- const sm = new ScalabilityMode$1(scalabilityMode);
19249
- const encodings = [];
19250
- if (sm.spatial > 3) {
19251
- throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
19252
- }
19253
- for (let i = 0; i < sm.spatial; i += 1) {
19254
- encodings.push({
19255
- rid: videoRids[2 - i],
19256
- maxBitrate: videoEncoding.maxBitrate / Math.pow(3, i),
19257
- /* @ts-ignore */
19258
- maxFramerate: original.encoding.maxFramerate
19259
- });
19260
- }
19261
- /* @ts-ignore */
19262
- encodings[0].scalabilityMode = scalabilityMode;
19263
- livekitLogger.debug('encodings', encodings);
19264
- return encodings;
19265
- }
19266
- if (!useSimulcast) {
19267
- return [videoEncoding];
19268
- }
19269
- let presets = [];
19270
- if (isScreenShare) {
19271
- presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
19272
- } else {
19273
- presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
19274
- }
19275
- let midPreset;
19276
- if (presets.length > 0) {
19277
- const lowPreset = presets[0];
19278
- if (presets.length > 1) {
19279
- [, midPreset] = presets;
19280
- }
19281
- // NOTE:
19282
- // 1. Ordering of these encodings is important. Chrome seems
19283
- // to use the index into encodings to decide which layer
19284
- // to disable when CPU constrained.
19285
- // So encodings should be ordered in increasing spatial
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]);
19293
- }
19294
- if (size >= 480) {
19295
- return encodingsFromPresets(width, height, [lowPreset, original]);
19296
- }
19297
- }
19298
- return encodingsFromPresets(width, height, [original]);
19299
- }
19300
- function computeTrackBackupEncodings(track, videoCodec, opts) {
19301
- var _a, _b, _c, _d;
19302
- if (!opts.backupCodec || opts.backupCodec.codec === opts.videoCodec) {
19303
- // backup codec publishing is disabled
19304
- return;
19305
- }
19306
- if (videoCodec !== opts.backupCodec.codec) {
19307
- livekitLogger.warn('requested a different codec than specified as backup', {
19308
- serverRequested: videoCodec,
19309
- backup: opts.backupCodec.codec
19310
- });
19311
- }
19312
- opts.videoCodec = videoCodec;
19313
- // use backup encoding setting as videoEncoding for backup codec publishing
19314
- opts.videoEncoding = opts.backupCodec.encoding;
19315
- const settings = track.mediaStreamTrack.getSettings();
19316
- const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
19317
- const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
19318
- const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
19319
- return encodings;
19320
- }
19321
- /* @internal */
19322
- function determineAppropriateEncoding(isScreenShare, width, height, codec) {
19323
- const presets = presetsForResolution(isScreenShare, width, height);
19324
- let {
19325
- encoding
19326
- } = presets[0];
19327
- // handle portrait by swapping dimensions
19328
- const size = Math.max(width, height);
19329
- for (let i = 0; i < presets.length; i += 1) {
19330
- const preset = presets[i];
19331
- encoding = preset.encoding;
19332
- if (preset.width >= size) {
19333
- break;
19334
- }
19335
- }
19336
- // presets are based on the assumption of vp8 as a codec
19337
- // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
19338
- // TODO make the bitrate multipliers configurable per codec
19339
- if (codec) {
19340
- switch (codec) {
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;
19321
+ if (mediaTrack.readyState === 'ended') {
19322
+ livekitLogger.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', {
19323
+ participant: this.sid,
19324
+ trackSid: sid
19325
+ });
19326
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
19327
+ return;
19349
19328
  }
19329
+ const isVideo = mediaTrack.kind === 'video';
19330
+ let track;
19331
+ if (isVideo) {
19332
+ track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
19333
+ } else {
19334
+ track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
19335
+ }
19336
+ // set track info
19337
+ track.source = publication.source;
19338
+ // keep publication's muted status
19339
+ track.isMuted = publication.isMuted;
19340
+ track.setMediaStream(mediaStream);
19341
+ track.start();
19342
+ publication.setTrack(track);
19343
+ // set participant volumes on new audio tracks
19344
+ if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
19345
+ track.setVolume(this.volumeMap.get(publication.source));
19346
+ }
19347
+ return publication;
19350
19348
  }
19351
- return encoding;
19352
- }
19353
- /* @internal */
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);
19349
+ /** @internal */
19350
+ get hasMetadata() {
19351
+ return !!this.participantInfo;
19368
19352
  }
19369
- const {
19370
- width,
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;
19353
+ getTrackPublication(sid) {
19354
+ return this.tracks.get(sid);
19376
19355
  }
19377
- return defaultSimulcastPresets43;
19378
- }
19379
- // presets should be ordered by low, medium, high
19380
- function encodingsFromPresets(width, height, presets) {
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;
19356
+ /** @internal */
19357
+ updateInfo(info) {
19358
+ if (!super.updateInfo(info)) {
19359
+ return false;
19400
19360
  }
19401
- encodings.push(encoding);
19402
- });
19403
- // RN ios simulcast requires all same framerates.
19404
- if (isReactNative() && getReactNativeOs() === 'ios') {
19405
- let topFramerate = undefined;
19406
- encodings.forEach(encoding => {
19407
- if (!topFramerate) {
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 => {
19361
+ // we are getting a list of all available tracks, reconcile in here
19362
+ // and send out events for changes
19363
+ // reconcile track publications, publish events only if metadata is already there
19364
+ // i.e. changes since the local participant has joined
19365
+ const validTracks = new Map();
19366
+ const newTracks = new Map();
19367
+ info.tracks.forEach(ti => {
19415
19368
  var _a;
19416
- if (encoding.maxFramerate != topFramerate) {
19417
- if (notifyOnce) {
19418
- notifyOnce = false;
19419
- livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
19369
+ let publication = this.getTrackPublication(ti.sid);
19370
+ if (!publication) {
19371
+ // new publication
19372
+ const kind = Track.kindFromProto(ti.type);
19373
+ if (!kind) {
19374
+ return;
19420
19375
  }
19421
- livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
19422
- encoding.maxFramerate = topFramerate;
19376
+ publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe);
19377
+ publication.updateInfo(ti);
19378
+ newTracks.set(ti.sid, publication);
19379
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
19380
+ if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
19381
+ livekitLogger.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
19382
+ oldTrack: existingTrackOfSource,
19383
+ newTrack: publication,
19384
+ participant: this,
19385
+ participantInfo: info
19386
+ });
19387
+ }
19388
+ this.addTrackPublication(publication);
19389
+ } else {
19390
+ publication.updateInfo(ti);
19391
+ }
19392
+ validTracks.set(ti.sid, publication);
19393
+ });
19394
+ // detect removed tracks
19395
+ this.tracks.forEach(publication => {
19396
+ if (!validTracks.has(publication.trackSid)) {
19397
+ livekitLogger.trace('detected removed track on remote participant, unpublishing', {
19398
+ publication,
19399
+ participantSid: this.sid
19400
+ });
19401
+ this.unpublishTrack(publication.trackSid, true);
19423
19402
  }
19424
19403
  });
19404
+ // always emit events for new publications, Room will not forward them unless it's ready
19405
+ newTracks.forEach(publication => {
19406
+ this.emit(ParticipantEvent.TrackPublished, publication);
19407
+ });
19408
+ return true;
19425
19409
  }
19426
- return encodings;
19427
- }
19428
- /** @internal */
19429
- function sortPresets(presets) {
19430
- if (!presets) return;
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;
19410
+ /** @internal */
19411
+ unpublishTrack(sid, sendUnpublish) {
19412
+ const publication = this.tracks.get(sid);
19413
+ if (!publication) {
19414
+ return;
19440
19415
  }
19441
- if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
19442
- if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
19443
- return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
19416
+ // also send unsubscribe, if track is actively subscribed
19417
+ const {
19418
+ track
19419
+ } = publication;
19420
+ if (track) {
19421
+ track.stop();
19422
+ publication.setTrack(undefined);
19444
19423
  }
19445
- return 0;
19446
- });
19447
- }
19448
- /** @internal */
19449
- let ScalabilityMode$1 = class ScalabilityMode {
19450
- constructor(scalabilityMode) {
19451
- const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
19452
- if (!results) {
19453
- throw new Error('invalid scalability mode');
19424
+ // remove track from maps only after unsubscribed has been fired
19425
+ this.tracks.delete(sid);
19426
+ // remove from the right type map
19427
+ switch (publication.kind) {
19428
+ case Track.Kind.Audio:
19429
+ this.audioTracks.delete(sid);
19430
+ break;
19431
+ case Track.Kind.Video:
19432
+ this.videoTracks.delete(sid);
19433
+ break;
19454
19434
  }
19455
- this.spatial = parseInt(results[1]);
19456
- this.temporal = parseInt(results[2]);
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
- }
19435
+ if (sendUnpublish) {
19436
+ this.emit(ParticipantEvent.TrackUnpublished, publication);
19464
19437
  }
19465
19438
  }
19466
- toString() {
19467
- var _a;
19468
- return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
19439
+ /**
19440
+ * @internal
19441
+ */
19442
+ setAudioContext(ctx) {
19443
+ this.audioContext = ctx;
19444
+ this.audioTracks.forEach(track => track.track instanceof RemoteAudioTrack && track.track.setAudioContext(ctx));
19469
19445
  }
19470
- };
19446
+ /**
19447
+ * @internal
19448
+ */
19449
+ setAudioOutput(output) {
19450
+ return __awaiter(this, void 0, void 0, function* () {
19451
+ this.audioOutput = output;
19452
+ const promises = [];
19453
+ this.audioTracks.forEach(pub => {
19454
+ var _a;
19455
+ if (pub.track instanceof RemoteAudioTrack) {
19456
+ promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
19457
+ }
19458
+ });
19459
+ yield Promise.all(promises);
19460
+ });
19461
+ }
19462
+ /** @internal */
19463
+ emit(event) {
19464
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
19465
+ args[_key - 1] = arguments[_key];
19466
+ }
19467
+ livekitLogger.trace('participant event', {
19468
+ participant: this.sid,
19469
+ event,
19470
+ args
19471
+ });
19472
+ return super.emit(event, ...args);
19473
+ }
19474
+ }
19471
19475
 
19472
19476
  class LocalParticipant extends Participant {
19473
19477
  /** @internal */