livekit-client 1.12.2 → 1.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 */