livekit-client 2.13.8 → 2.15.0

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.
Files changed (64) hide show
  1. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  2. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  3. package/dist/livekit-client.esm.mjs +276 -117
  4. package/dist/livekit-client.esm.mjs.map +1 -1
  5. package/dist/livekit-client.umd.js +1 -1
  6. package/dist/livekit-client.umd.js.map +1 -1
  7. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  8. package/dist/src/room/PCTransport.d.ts +1 -0
  9. package/dist/src/room/PCTransport.d.ts.map +1 -1
  10. package/dist/src/room/Room.d.ts.map +1 -1
  11. package/dist/src/room/events.d.ts +18 -0
  12. package/dist/src/room/events.d.ts.map +1 -1
  13. package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
  14. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  15. package/dist/src/room/participant/Participant.d.ts +3 -0
  16. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  17. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  18. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  19. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
  20. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalVideoTrack.d.ts +7 -0
  22. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  23. package/dist/src/room/track/RemoteTrackPublication.d.ts +12 -3
  24. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  25. package/dist/src/room/track/Track.d.ts +1 -0
  26. package/dist/src/room/track/Track.d.ts.map +1 -1
  27. package/dist/src/room/track/TrackPublication.d.ts +1 -0
  28. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  29. package/dist/src/room/track/options.d.ts +5 -1
  30. package/dist/src/room/track/options.d.ts.map +1 -1
  31. package/dist/src/room/track/utils.d.ts +3 -1
  32. package/dist/src/room/track/utils.d.ts.map +1 -1
  33. package/dist/src/room/utils.d.ts +2 -1
  34. package/dist/src/room/utils.d.ts.map +1 -1
  35. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -0
  36. package/dist/ts4.2/src/room/events.d.ts +18 -0
  37. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
  38. package/dist/ts4.2/src/room/participant/Participant.d.ts +3 -0
  39. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
  40. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +7 -0
  41. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +12 -3
  42. package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
  43. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +1 -0
  44. package/dist/ts4.2/src/room/track/options.d.ts +6 -1
  45. package/dist/ts4.2/src/room/track/utils.d.ts +3 -1
  46. package/dist/ts4.2/src/room/utils.d.ts +2 -1
  47. package/package.json +11 -11
  48. package/src/e2ee/E2eeManager.ts +3 -2
  49. package/src/room/PCTransport.ts +88 -77
  50. package/src/room/Room.ts +2 -0
  51. package/src/room/events.ts +21 -0
  52. package/src/room/participant/LocalParticipant.ts +14 -2
  53. package/src/room/participant/Participant.ts +3 -0
  54. package/src/room/participant/RemoteParticipant.ts +1 -0
  55. package/src/room/participant/publishUtils.ts +3 -2
  56. package/src/room/track/LocalTrackPublication.ts +9 -1
  57. package/src/room/track/LocalVideoTrack.ts +68 -1
  58. package/src/room/track/RemoteTrackPublication.ts +91 -32
  59. package/src/room/track/Track.ts +1 -0
  60. package/src/room/track/TrackPublication.ts +1 -0
  61. package/src/room/track/create.ts +2 -2
  62. package/src/room/track/options.ts +6 -1
  63. package/src/room/track/utils.ts +12 -1
  64. package/src/room/utils.ts +37 -3
@@ -4414,6 +4414,18 @@ const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", (
4414
4414
  kind: "scalar",
4415
4415
  T: 13
4416
4416
  /* ScalarType.UINT32 */
4417
+ }, {
4418
+ no: 6,
4419
+ name: "spatial_layer",
4420
+ kind: "scalar",
4421
+ T: 5
4422
+ /* ScalarType.INT32 */
4423
+ }, {
4424
+ no: 7,
4425
+ name: "rid",
4426
+ kind: "scalar",
4427
+ T: 9
4428
+ /* ScalarType.STRING */
4417
4429
  }]);
4418
4430
  const DataPacket = /* @__PURE__ */proto3.makeMessageType("livekit.DataPacket", () => [{
4419
4431
  no: 1,
@@ -4963,6 +4975,9 @@ const ClientInfo_SDK = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo.SDK",
4963
4975
  }, {
4964
4976
  no: 13,
4965
4977
  name: "UNREAL"
4978
+ }, {
4979
+ no: 14,
4980
+ name: "ESP32"
4966
4981
  }]);
4967
4982
  const ClientConfiguration = /* @__PURE__ */proto3.makeMessageType("livekit.ClientConfiguration", () => [{
4968
4983
  no: 1,
@@ -11025,6 +11040,19 @@ var ParticipantEvent;
11025
11040
  * args: ([[LocalTrackPublication]])
11026
11041
  */
11027
11042
  ParticipantEvent["LocalTrackUnpublished"] = "localTrackUnpublished";
11043
+ /**
11044
+ * A local track has been constrained by cpu.
11045
+ * This event is useful to know when to reduce the capture resolution of the track.
11046
+ *
11047
+ * This event is emitted on the local participant.
11048
+ *
11049
+ * args: ([[LocalVideoTrack]], [[LocalTrackPublication]])
11050
+ */
11051
+ ParticipantEvent["LocalTrackCpuConstrained"] = "localTrackCpuConstrained";
11052
+ /**
11053
+ * @internal
11054
+ */
11055
+ ParticipantEvent["LocalSenderCreated"] = "localSenderCreated";
11028
11056
  /**
11029
11057
  * Participant metadata is a simple way for app-specific state to be pushed to
11030
11058
  * all users.
@@ -11101,6 +11129,10 @@ var ParticipantEvent;
11101
11129
  *
11102
11130
  */
11103
11131
  ParticipantEvent["TrackSubscriptionStatusChanged"] = "trackSubscriptionStatusChanged";
11132
+ /**
11133
+ * a local track has been constrained by cpu
11134
+ */
11135
+ ParticipantEvent["TrackCpuConstrained"] = "trackCpuConstrained";
11104
11136
  // fired only on LocalParticipant
11105
11137
  /** @internal */
11106
11138
  ParticipantEvent["MediaDevicesError"] = "mediaDevicesError";
@@ -11178,6 +11210,7 @@ var TrackEvent;
11178
11210
  TrackEvent["Ended"] = "ended";
11179
11211
  TrackEvent["Subscribed"] = "subscribed";
11180
11212
  TrackEvent["Unsubscribed"] = "unsubscribed";
11213
+ TrackEvent["CpuConstrained"] = "cpuConstrained";
11181
11214
  /** @internal */
11182
11215
  TrackEvent["UpdateSettings"] = "updateSettings";
11183
11216
  /** @internal */
@@ -11331,7 +11364,7 @@ function getOSVersion(ua) {
11331
11364
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11332
11365
  }
11333
11366
 
11334
- var version$1 = "2.13.8";
11367
+ var version$1 = "2.15.0";
11335
11368
 
11336
11369
  const version = version$1;
11337
11370
  const protocolVersion = 16;
@@ -11765,7 +11798,7 @@ class VideoPreset {
11765
11798
  }
11766
11799
  }
11767
11800
  const backupCodecs = ['vp8', 'h264'];
11768
- const videoCodecs = ['vp8', 'h264', 'vp9', 'av1'];
11801
+ const videoCodecs = ['vp8', 'h264', 'vp9', 'av1', 'h265'];
11769
11802
  function isBackupCodec(codec) {
11770
11803
  return !!backupCodecs.find(backup => backup === codec);
11771
11804
  }
@@ -11902,6 +11935,10 @@ function supportsVP9() {
11902
11935
  // Safari 16 and below does not support VP9
11903
11936
  return false;
11904
11937
  }
11938
+ if ((browser === null || browser === void 0 ? void 0 : browser.os) === 'iOS' && (browser === null || browser === void 0 ? void 0 : browser.osVersion) && compareVersions(browser.osVersion, '16') < 0) {
11939
+ // Safari 16 and below on iOS does not support VP9 we need the iOS check to account for other browsers running webkit under the hood
11940
+ return false;
11941
+ }
11905
11942
  }
11906
11943
  const capabilities = RTCRtpSender.getCapabilities('video');
11907
11944
  let hasVP9 = false;
@@ -11945,16 +11982,16 @@ function isSafariBased() {
11945
11982
  const b = getBrowser();
11946
11983
  return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' || (b === null || b === void 0 ? void 0 : b.os) === 'iOS';
11947
11984
  }
11948
- function isSafari17() {
11985
+ function isSafari17Based() {
11949
11986
  const b = getBrowser();
11950
- return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.');
11987
+ return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.') || (b === null || b === void 0 ? void 0 : b.os) === 'iOS' && !!(b === null || b === void 0 ? void 0 : b.osVersion) && compareVersions(b.osVersion, '17') >= 0;
11951
11988
  }
11952
11989
  function isSafariSvcApi(browser) {
11953
11990
  if (!browser) {
11954
11991
  browser = getBrowser();
11955
11992
  }
11956
11993
  // Safari 18.4 requires legacy svc api and scaleResolutionDown to be set
11957
- return (browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '18.3') > 0;
11994
+ return (browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '18.3') > 0 || (browser === null || browser === void 0 ? void 0 : browser.os) === 'iOS' && !!(browser === null || browser === void 0 ? void 0 : browser.osVersion) && compareVersions(browser.osVersion, '18.3') > 0;
11958
11995
  }
11959
11996
  function isMobile() {
11960
11997
  var _a, _b;
@@ -12660,6 +12697,13 @@ function getTrackSourceFromProto(source) {
12660
12697
  return Track.Source.Unknown;
12661
12698
  }
12662
12699
  }
12700
+ function areDimensionsSmaller(a, b) {
12701
+ return a.width * a.height < b.width * b.height;
12702
+ }
12703
+ function layerDimensionsFor(trackInfo, quality) {
12704
+ var _a;
12705
+ return (_a = trackInfo.layers) === null || _a === void 0 ? void 0 : _a.find(l => l.quality === quality);
12706
+ }
12663
12707
 
12664
12708
  /**
12665
12709
  * @experimental
@@ -12799,8 +12843,8 @@ class E2EEManager extends eventsExports.EventEmitter {
12799
12843
  });
12800
12844
  this.setParticipantCryptorEnabled(this.room.localParticipant.isE2EEEnabled, this.room.localParticipant.identity);
12801
12845
  });
12802
- room.localParticipant.on(ParticipantEvent.LocalTrackPublished, publication => __awaiter(this, void 0, void 0, function* () {
12803
- this.setupE2EESender(publication.track, publication.track.sender);
12846
+ room.localParticipant.on(ParticipantEvent.LocalSenderCreated, (sender, track) => __awaiter(this, void 0, void 0, function* () {
12847
+ this.setupE2EESender(track, sender);
12804
12848
  }));
12805
12849
  keyProvider.on(KeyProviderEvent.SetKey, keyInfo => this.postKey(keyInfo)).on(KeyProviderEvent.RatchetRequest, (participantId, keyIndex) => this.postRatchetRequest(participantId, keyIndex));
12806
12850
  }
@@ -14789,6 +14833,7 @@ class PCTransport extends eventsExports.EventEmitter {
14789
14833
  this.loggerOptions = loggerOptions;
14790
14834
  this.config = config;
14791
14835
  this._pc = this.createPC();
14836
+ this.offerLock = new _();
14792
14837
  }
14793
14838
  createPC() {
14794
14839
  const pc = new RTCPeerConnection(this.config);
@@ -14927,92 +14972,97 @@ class PCTransport extends eventsExports.EventEmitter {
14927
14972
  createAndSendOffer(options) {
14928
14973
  return __awaiter(this, void 0, void 0, function* () {
14929
14974
  var _a;
14930
- // increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior
14931
- const offerId = this.latestOfferId + 1;
14932
- this.latestOfferId = offerId;
14933
- if (this.onOffer === undefined) {
14934
- return;
14935
- }
14936
- if (options === null || options === void 0 ? void 0 : options.iceRestart) {
14937
- this.log.debug('restarting ICE', this.logContext);
14938
- this.restartingIce = true;
14939
- }
14940
- if (this._pc && this._pc.signalingState === 'have-local-offer') {
14941
- // we're waiting for the peer to accept our offer, so we'll just wait
14942
- // the only exception to this is when ICE restart is needed
14943
- const currentSD = this._pc.remoteDescription;
14944
- if ((options === null || options === void 0 ? void 0 : options.iceRestart) && currentSD) {
14945
- // TODO: handle when ICE restart is needed but we don't have a remote description
14946
- // the best thing to do is to recreate the peerconnection
14947
- yield this._pc.setRemoteDescription(currentSD);
14948
- } else {
14949
- this.renegotiate = true;
14975
+ const unlock = yield this.offerLock.lock();
14976
+ try {
14977
+ // increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior
14978
+ const offerId = this.latestOfferId + 1;
14979
+ this.latestOfferId = offerId;
14980
+ if (this.onOffer === undefined) {
14950
14981
  return;
14951
14982
  }
14952
- } else if (!this._pc || this._pc.signalingState === 'closed') {
14953
- this.log.warn('could not createOffer with closed peer connection', this.logContext);
14954
- return;
14955
- }
14956
- // actually negotiate
14957
- this.log.debug('starting to negotiate', this.logContext);
14958
- const offer = yield this.pc.createOffer(options);
14959
- this.log.debug('original offer', Object.assign({
14960
- sdp: offer.sdp
14961
- }, this.logContext));
14962
- const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
14963
- sdpParsed.media.forEach(media => {
14964
- ensureIPAddrMatchVersion(media);
14965
- if (media.type === 'audio') {
14966
- ensureAudioNackAndStereo(media, [], []);
14967
- } else if (media.type === 'video') {
14968
- this.trackBitrates.some(trackbr => {
14969
- if (!media.msid || !trackbr.cid || !media.msid.includes(trackbr.cid)) {
14970
- return false;
14971
- }
14972
- let codecPayload = 0;
14973
- media.rtp.some(rtp => {
14974
- if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) {
14975
- codecPayload = rtp.payload;
14983
+ if (options === null || options === void 0 ? void 0 : options.iceRestart) {
14984
+ this.log.debug('restarting ICE', this.logContext);
14985
+ this.restartingIce = true;
14986
+ }
14987
+ if (this._pc && this._pc.signalingState === 'have-local-offer') {
14988
+ // we're waiting for the peer to accept our offer, so we'll just wait
14989
+ // the only exception to this is when ICE restart is needed
14990
+ const currentSD = this._pc.remoteDescription;
14991
+ if ((options === null || options === void 0 ? void 0 : options.iceRestart) && currentSD) {
14992
+ // TODO: handle when ICE restart is needed but we don't have a remote description
14993
+ // the best thing to do is to recreate the peerconnection
14994
+ yield this._pc.setRemoteDescription(currentSD);
14995
+ } else {
14996
+ this.renegotiate = true;
14997
+ return;
14998
+ }
14999
+ } else if (!this._pc || this._pc.signalingState === 'closed') {
15000
+ this.log.warn('could not createOffer with closed peer connection', this.logContext);
15001
+ return;
15002
+ }
15003
+ // actually negotiate
15004
+ this.log.debug('starting to negotiate', this.logContext);
15005
+ const offer = yield this.pc.createOffer(options);
15006
+ this.log.debug('original offer', Object.assign({
15007
+ sdp: offer.sdp
15008
+ }, this.logContext));
15009
+ const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
15010
+ sdpParsed.media.forEach(media => {
15011
+ ensureIPAddrMatchVersion(media);
15012
+ if (media.type === 'audio') {
15013
+ ensureAudioNackAndStereo(media, [], []);
15014
+ } else if (media.type === 'video') {
15015
+ this.trackBitrates.some(trackbr => {
15016
+ if (!media.msid || !trackbr.cid || !media.msid.includes(trackbr.cid)) {
15017
+ return false;
15018
+ }
15019
+ let codecPayload = 0;
15020
+ media.rtp.some(rtp => {
15021
+ if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) {
15022
+ codecPayload = rtp.payload;
15023
+ return true;
15024
+ }
15025
+ return false;
15026
+ });
15027
+ if (codecPayload === 0) {
14976
15028
  return true;
14977
15029
  }
14978
- return false;
14979
- });
14980
- if (codecPayload === 0) {
14981
- return true;
14982
- }
14983
- if (isSVCCodec(trackbr.codec)) {
14984
- this.ensureVideoDDExtensionForSVC(media, sdpParsed);
14985
- }
14986
- // TODO: av1 slow starting issue already fixed in chrome 124, clean this after some versions
14987
- // mung sdp for av1 bitrate setting that can't apply by sendEncoding
14988
- if (trackbr.codec !== 'av1') {
14989
- return true;
14990
- }
14991
- const startBitrate = Math.round(trackbr.maxbr * startBitrateForSVC);
14992
- for (const fmtp of media.fmtp) {
14993
- if (fmtp.payload === codecPayload) {
14994
- // if another track's fmtp already is set, we cannot override the bitrate
14995
- // this has the unfortunate consequence of being forced to use the
14996
- // initial track's bitrate for all tracks
14997
- if (!fmtp.config.includes('x-google-start-bitrate')) {
14998
- fmtp.config += ";x-google-start-bitrate=".concat(startBitrate);
15030
+ if (isSVCCodec(trackbr.codec)) {
15031
+ this.ensureVideoDDExtensionForSVC(media, sdpParsed);
15032
+ }
15033
+ // TODO: av1 slow starting issue already fixed in chrome 124, clean this after some versions
15034
+ // mung sdp for av1 bitrate setting that can't apply by sendEncoding
15035
+ if (trackbr.codec !== 'av1') {
15036
+ return true;
15037
+ }
15038
+ const startBitrate = Math.round(trackbr.maxbr * startBitrateForSVC);
15039
+ for (const fmtp of media.fmtp) {
15040
+ if (fmtp.payload === codecPayload) {
15041
+ // if another track's fmtp already is set, we cannot override the bitrate
15042
+ // this has the unfortunate consequence of being forced to use the
15043
+ // initial track's bitrate for all tracks
15044
+ if (!fmtp.config.includes('x-google-start-bitrate')) {
15045
+ fmtp.config += ";x-google-start-bitrate=".concat(startBitrate);
15046
+ }
15047
+ break;
14999
15048
  }
15000
- break;
15001
15049
  }
15002
- }
15003
- return true;
15004
- });
15050
+ return true;
15051
+ });
15052
+ }
15053
+ });
15054
+ if (this.latestOfferId > offerId) {
15055
+ this.log.warn('latestOfferId mismatch', Object.assign(Object.assign({}, this.logContext), {
15056
+ latestOfferId: this.latestOfferId,
15057
+ offerId
15058
+ }));
15059
+ return;
15005
15060
  }
15006
- });
15007
- if (this.latestOfferId > offerId) {
15008
- this.log.warn('latestOfferId mismatch', Object.assign(Object.assign({}, this.logContext), {
15009
- latestOfferId: this.latestOfferId,
15010
- offerId
15011
- }));
15012
- return;
15061
+ yield this.setMungedSDP(offer, libExports.write(sdpParsed));
15062
+ this.onOffer(offer, this.latestOfferId);
15063
+ } finally {
15064
+ unlock();
15013
15065
  }
15014
- yield this.setMungedSDP(offer, libExports.write(sdpParsed));
15015
- this.onOffer(offer, this.latestOfferId);
15016
15066
  });
15017
15067
  }
15018
15068
  createAndSetAnswer() {
@@ -16677,7 +16727,7 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
16677
16727
  // before M113.
16678
16728
  // Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
16679
16729
  const browser = getBrowser();
16680
- if (isSafari() ||
16730
+ if (isSafariBased() ||
16681
16731
  // Even tho RN runs M114, it does not produce SVC layers when a single encoding
16682
16732
  // is provided. So we'll use the legacy SVC specification for now.
16683
16733
  // TODO: when we upstream libwebrtc, this will need additional verification
@@ -16797,6 +16847,7 @@ function determineAppropriateEncoding(isScreenShare, width, height, codec) {
16797
16847
  if (codec) {
16798
16848
  switch (codec) {
16799
16849
  case 'av1':
16850
+ case 'h265':
16800
16851
  encoding = Object.assign({}, encoding);
16801
16852
  encoding.maxBitrate = encoding.maxBitrate * 0.7;
16802
16853
  break;
@@ -16964,6 +17015,8 @@ class LocalVideoTrack extends LocalTrack {
16964
17015
  /* @internal */
16965
17016
  this.simulcastCodecs = new Map();
16966
17017
  this.degradationPreference = 'balanced';
17018
+ this.isCpuConstrained = false;
17019
+ this.optimizeForPerformance = false;
16967
17020
  this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
16968
17021
  if (!this.sender) {
16969
17022
  this._currentBitrate = 0;
@@ -16973,12 +17026,19 @@ class LocalVideoTrack extends LocalTrack {
16973
17026
  try {
16974
17027
  stats = yield this.getSenderStats();
16975
17028
  } catch (e) {
16976
- this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
17029
+ this.log.error('could not get video sender stats', Object.assign(Object.assign({}, this.logContext), {
16977
17030
  error: e
16978
17031
  }));
16979
17032
  return;
16980
17033
  }
16981
17034
  const statsMap = new Map(stats.map(s => [s.rid, s]));
17035
+ const isCpuConstrained = stats.some(s => s.qualityLimitationReason === 'cpu');
17036
+ if (isCpuConstrained !== this.isCpuConstrained) {
17037
+ this.isCpuConstrained = isCpuConstrained;
17038
+ if (this.isCpuConstrained) {
17039
+ this.emit(TrackEvent.CpuConstrained);
17040
+ }
17041
+ }
16982
17042
  if (this.prevStats) {
16983
17043
  let totalBitrate = 0;
16984
17044
  statsMap.forEach((s, key) => {
@@ -17214,6 +17274,8 @@ class LocalVideoTrack extends LocalTrack {
17214
17274
  }
17215
17275
  }
17216
17276
  yield this.restart(constraints);
17277
+ // reset cpu constrained state after track is restarted
17278
+ this.isCpuConstrained = false;
17217
17279
  try {
17218
17280
  for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
17219
17281
  _c = _g.value;
@@ -17383,6 +17445,12 @@ class LocalVideoTrack extends LocalTrack {
17383
17445
  */
17384
17446
  setPublishingLayers(isSvc, qualities) {
17385
17447
  return __awaiter(this, void 0, void 0, function* () {
17448
+ if (this.optimizeForPerformance) {
17449
+ this.log.info('skipping setPublishingLayers due to optimized publishing performance', Object.assign(Object.assign({}, this.logContext), {
17450
+ qualities
17451
+ }));
17452
+ return;
17453
+ }
17386
17454
  this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), {
17387
17455
  qualities
17388
17456
  }));
@@ -17392,6 +17460,44 @@ class LocalVideoTrack extends LocalTrack {
17392
17460
  yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, isSvc, this.log, this.logContext);
17393
17461
  });
17394
17462
  }
17463
+ /**
17464
+ * Designed for lower powered devices, reduces video publishing quality and disables simulcast.
17465
+ * @experimental
17466
+ */
17467
+ prioritizePerformance() {
17468
+ return __awaiter(this, void 0, void 0, function* () {
17469
+ if (!this.sender) {
17470
+ throw new Error('sender not found');
17471
+ }
17472
+ const unlock = yield this.senderLock.lock();
17473
+ try {
17474
+ this.optimizeForPerformance = true;
17475
+ const params = this.sender.getParameters();
17476
+ params.encodings = params.encodings.map((e, idx) => {
17477
+ var _a;
17478
+ return Object.assign(Object.assign({}, e), {
17479
+ active: idx === 0,
17480
+ scaleResolutionDownBy: Math.max(1, Math.ceil(((_a = this.mediaStreamTrack.getSettings().height) !== null && _a !== void 0 ? _a : 360) / 360)),
17481
+ scalabilityMode: idx === 0 && isSVCCodec(this.codec) ? 'L1T3' : undefined,
17482
+ maxFramerate: idx === 0 ? 15 : 0,
17483
+ maxBitrate: idx === 0 ? e.maxBitrate : 0
17484
+ });
17485
+ });
17486
+ this.log.debug('setting performance optimised encodings', Object.assign(Object.assign({}, this.logContext), {
17487
+ encodings: params.encodings
17488
+ }));
17489
+ this.encodings = params.encodings;
17490
+ yield this.sender.setParameters(params);
17491
+ } catch (e) {
17492
+ this.log.error('failed to set performance optimised encodings', Object.assign(Object.assign({}, this.logContext), {
17493
+ error: e
17494
+ }));
17495
+ this.optimizeForPerformance = false;
17496
+ } finally {
17497
+ unlock();
17498
+ }
17499
+ });
17500
+ }
17395
17501
  handleAppVisibilityChanged() {
17396
17502
  const _super = Object.create(null, {
17397
17503
  handleAppVisibilityChanged: {
@@ -19818,16 +19924,23 @@ class LocalTrackPublication extends TrackPublication {
19818
19924
  this.handleTrackEnded = () => {
19819
19925
  this.emit(TrackEvent.Ended);
19820
19926
  };
19927
+ this.handleCpuConstrained = () => {
19928
+ if (this.track && isVideoTrack(this.track)) {
19929
+ this.emit(TrackEvent.CpuConstrained, this.track);
19930
+ }
19931
+ };
19821
19932
  this.updateInfo(ti);
19822
19933
  this.setTrack(track);
19823
19934
  }
19824
19935
  setTrack(track) {
19825
19936
  if (this.track) {
19826
19937
  this.track.off(TrackEvent.Ended, this.handleTrackEnded);
19938
+ this.track.off(TrackEvent.CpuConstrained, this.handleCpuConstrained);
19827
19939
  }
19828
19940
  super.setTrack(track);
19829
19941
  if (track) {
19830
19942
  track.on(TrackEvent.Ended, this.handleTrackEnded);
19943
+ track.on(TrackEvent.CpuConstrained, this.handleCpuConstrained);
19831
19944
  }
19832
19945
  }
19833
19946
  get isMuted() {
@@ -20065,7 +20178,7 @@ function createLocalScreenTracks(options) {
20065
20178
  if (options === undefined) {
20066
20179
  options = {};
20067
20180
  }
20068
- if (options.resolution === undefined && !isSafari17()) {
20181
+ if (options.resolution === undefined && !isSafari17Based()) {
20069
20182
  options.resolution = ScreenSharePresets.h1080fps30.resolution;
20070
20183
  }
20071
20184
  if (navigator.mediaDevices.getDisplayMedia === undefined) {
@@ -20498,6 +20611,10 @@ class LocalParticipant extends Participant {
20498
20611
  }
20499
20612
  this.engine.client.sendUpdateLocalAudioTrack(pub.trackSid, pub.getTrackFeatures());
20500
20613
  };
20614
+ this.onTrackCpuConstrained = (track, publication) => {
20615
+ this.log.debug('track cpu constrained', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20616
+ this.emit(ParticipantEvent.LocalTrackCpuConstrained, track, publication);
20617
+ };
20501
20618
  this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20502
20619
  var _a, e_1, _b, _c;
20503
20620
  var _d;
@@ -20957,7 +21074,7 @@ class LocalParticipant extends Participant {
20957
21074
  if (navigator.mediaDevices.getDisplayMedia === undefined) {
20958
21075
  throw new DeviceUnsupportedError('getDisplayMedia not supported');
20959
21076
  }
20960
- if (options.resolution === undefined && !isSafari17()) {
21077
+ if (options.resolution === undefined && !isSafari17Based()) {
20961
21078
  // we need to constrain the dimensions, otherwise it could lead to low bitrate
20962
21079
  // due to encoding a huge video. Encoding such large surfaces is really expensive
20963
21080
  // unfortunately Safari 17 has a but and cannot be constrained by default
@@ -21324,6 +21441,7 @@ class LocalParticipant extends Participant {
21324
21441
  throw new UnexpectedConnectionState('pcManager is not ready');
21325
21442
  }
21326
21443
  track.sender = yield this.engine.createSender(track, opts, encodings);
21444
+ this.emit(ParticipantEvent.LocalSenderCreated, track.sender, track);
21327
21445
  if (isLocalVideoTrack(track)) {
21328
21446
  (_a = opts.degradationPreference) !== null && _a !== void 0 ? _a : opts.degradationPreference = getDefaultDegradationPreference(track);
21329
21447
  track.setDegradationPreference(opts.degradationPreference);
@@ -21409,6 +21527,7 @@ class LocalParticipant extends Participant {
21409
21527
  loggerName: this.roomOptions.loggerName,
21410
21528
  loggerContextCb: () => this.logContext
21411
21529
  });
21530
+ publication.on(TrackEvent.CpuConstrained, constrainedTrack => this.onTrackCpuConstrained(constrainedTrack, publication));
21412
21531
  // save options for when it needs to be republished again
21413
21532
  publication.options = opts;
21414
21533
  track.sid = ti.sid;
@@ -22308,20 +22427,20 @@ class RemoteTrackPublication extends TrackPublication {
22308
22427
  this.track = undefined;
22309
22428
  /** @internal */
22310
22429
  this.allowed = true;
22311
- this.disabled = false;
22312
- this.currentVideoQuality = VideoQuality.HIGH;
22430
+ this.requestedDisabled = undefined;
22431
+ this.visible = true;
22313
22432
  this.handleEnded = track => {
22314
22433
  this.setTrack(undefined);
22315
22434
  this.emit(TrackEvent.Ended, track);
22316
22435
  };
22317
22436
  this.handleVisibilityChange = visible => {
22318
22437
  this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
22319
- this.disabled = !visible;
22438
+ this.visible = visible;
22320
22439
  this.emitTrackUpdate();
22321
22440
  };
22322
22441
  this.handleVideoDimensionsChange = dimensions => {
22323
22442
  this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
22324
- this.videoDimensions = dimensions;
22443
+ this.videoDimensionsAdaptiveStream = dimensions;
22325
22444
  this.emitTrackUpdate();
22326
22445
  };
22327
22446
  this.subscribed = autoSubscribe;
@@ -22380,7 +22499,7 @@ class RemoteTrackPublication extends TrackPublication {
22380
22499
  return this.subscribed !== false;
22381
22500
  }
22382
22501
  get isEnabled() {
22383
- return !this.disabled;
22502
+ return this.requestedDisabled !== undefined ? !this.requestedDisabled : this.isAdaptiveStream ? this.visible : true;
22384
22503
  }
22385
22504
  get isLocal() {
22386
22505
  return false;
@@ -22392,10 +22511,10 @@ class RemoteTrackPublication extends TrackPublication {
22392
22511
  * @param enabled
22393
22512
  */
22394
22513
  setEnabled(enabled) {
22395
- if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
22514
+ if (!this.isManualOperationAllowed() || this.requestedDisabled === !enabled) {
22396
22515
  return;
22397
22516
  }
22398
- this.disabled = !enabled;
22517
+ this.requestedDisabled = !enabled;
22399
22518
  this.emitTrackUpdate();
22400
22519
  }
22401
22520
  /**
@@ -22406,25 +22525,32 @@ class RemoteTrackPublication extends TrackPublication {
22406
22525
  * optimize for uninterrupted video
22407
22526
  */
22408
22527
  setVideoQuality(quality) {
22409
- if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
22528
+ if (!this.isManualOperationAllowed() || this.requestedMaxQuality === quality) {
22410
22529
  return;
22411
22530
  }
22412
- this.currentVideoQuality = quality;
22413
- this.videoDimensions = undefined;
22531
+ this.requestedMaxQuality = quality;
22532
+ this.requestedVideoDimensions = undefined;
22414
22533
  this.emitTrackUpdate();
22415
22534
  }
22535
+ /**
22536
+ * Explicitly set the video dimensions for this track.
22537
+ *
22538
+ * This will take precedence over adaptive stream dimensions.
22539
+ *
22540
+ * @param dimensions The video dimensions to set.
22541
+ */
22416
22542
  setVideoDimensions(dimensions) {
22417
22543
  var _a, _b;
22418
22544
  if (!this.isManualOperationAllowed()) {
22419
22545
  return;
22420
22546
  }
22421
- if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
22547
+ if (((_a = this.requestedVideoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.requestedVideoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
22422
22548
  return;
22423
22549
  }
22424
22550
  if (isRemoteVideoTrack(this.track)) {
22425
- this.videoDimensions = dimensions;
22551
+ this.requestedVideoDimensions = dimensions;
22426
22552
  }
22427
- this.currentVideoQuality = undefined;
22553
+ this.requestedMaxQuality = undefined;
22428
22554
  this.emitTrackUpdate();
22429
22555
  }
22430
22556
  setVideoFPS(fps) {
@@ -22441,7 +22567,8 @@ class RemoteTrackPublication extends TrackPublication {
22441
22567
  this.emitTrackUpdate();
22442
22568
  }
22443
22569
  get videoQuality() {
22444
- return this.currentVideoQuality;
22570
+ var _a;
22571
+ return (_a = this.requestedMaxQuality) !== null && _a !== void 0 ? _a : VideoQuality.HIGH;
22445
22572
  }
22446
22573
  /** @internal */
22447
22574
  setTrack(track) {
@@ -22508,10 +22635,6 @@ class RemoteTrackPublication extends TrackPublication {
22508
22635
  }
22509
22636
  }
22510
22637
  isManualOperationAllowed() {
22511
- if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
22512
- this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
22513
- return false;
22514
- }
22515
22638
  if (!this.isDesired) {
22516
22639
  this.log.warn('cannot update track settings when not subscribed', this.logContext);
22517
22640
  return false;
@@ -22525,17 +22648,46 @@ class RemoteTrackPublication extends TrackPublication {
22525
22648
  emitTrackUpdate() {
22526
22649
  const settings = new UpdateTrackSettings({
22527
22650
  trackSids: [this.trackSid],
22528
- disabled: this.disabled,
22651
+ disabled: !this.isEnabled,
22529
22652
  fps: this.fps
22530
22653
  });
22531
- if (this.videoDimensions) {
22532
- settings.width = Math.ceil(this.videoDimensions.width);
22533
- settings.height = Math.ceil(this.videoDimensions.height);
22534
- } else if (this.currentVideoQuality !== undefined) {
22535
- settings.quality = this.currentVideoQuality;
22536
- } else {
22537
- // defaults to high quality
22538
- settings.quality = VideoQuality.HIGH;
22654
+ if (this.kind === Track.Kind.Video) {
22655
+ let minDimensions = this.requestedVideoDimensions;
22656
+ if (this.videoDimensionsAdaptiveStream !== undefined) {
22657
+ if (minDimensions) {
22658
+ // check whether the adaptive stream dimensions are smaller than the requested dimensions and use smaller one
22659
+ const smallerAdaptive = areDimensionsSmaller(this.videoDimensionsAdaptiveStream, minDimensions);
22660
+ if (smallerAdaptive) {
22661
+ this.log.debug('using adaptive stream dimensions instead of requested', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream));
22662
+ minDimensions = this.videoDimensionsAdaptiveStream;
22663
+ }
22664
+ } else if (this.requestedMaxQuality !== undefined && this.trackInfo) {
22665
+ // check whether adaptive stream dimensions are smaller than the max quality layer and use smaller one
22666
+ const maxQualityLayer = layerDimensionsFor(this.trackInfo, this.requestedMaxQuality);
22667
+ if (maxQualityLayer && areDimensionsSmaller(this.videoDimensionsAdaptiveStream, maxQualityLayer)) {
22668
+ this.log.debug('using adaptive stream dimensions instead of max quality layer', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream));
22669
+ minDimensions = this.videoDimensionsAdaptiveStream;
22670
+ }
22671
+ } else {
22672
+ this.log.debug('using adaptive stream dimensions', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream));
22673
+ minDimensions = this.videoDimensionsAdaptiveStream;
22674
+ }
22675
+ }
22676
+ if (minDimensions) {
22677
+ settings.width = Math.ceil(minDimensions.width);
22678
+ settings.height = Math.ceil(minDimensions.height);
22679
+ } else if (this.requestedMaxQuality !== undefined) {
22680
+ this.log.debug('using requested max quality', Object.assign(Object.assign({}, this.logContext), {
22681
+ quality: this.requestedMaxQuality
22682
+ }));
22683
+ settings.quality = this.requestedMaxQuality;
22684
+ } else {
22685
+ this.log.debug('using default quality', Object.assign(Object.assign({}, this.logContext), {
22686
+ quality: VideoQuality.HIGH
22687
+ }));
22688
+ // defaults to high quality
22689
+ settings.quality = VideoQuality.HIGH;
22690
+ }
22539
22691
  }
22540
22692
  this.emit(TrackEvent.UpdateSettings, settings);
22541
22693
  }
@@ -22566,7 +22718,9 @@ class RemoteParticipant extends Participant {
22566
22718
  super.addTrackPublication(publication);
22567
22719
  // register action events
22568
22720
  publication.on(TrackEvent.UpdateSettings, settings => {
22569
- this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
22721
+ this.log.debug('send update settings', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)), {
22722
+ settings
22723
+ }));
22570
22724
  this.signalClient.sendUpdateTrackSettings(settings);
22571
22725
  });
22572
22726
  publication.on(TrackEvent.UpdateSubscription, sub => {
@@ -24105,6 +24259,11 @@ class Room extends eventsExports.EventEmitter {
24105
24259
  _this3.options.videoCaptureDefaults.deviceId = prevDeviceId;
24106
24260
  throw e;
24107
24261
  }
24262
+ const isMuted = tracks.some(t => {
24263
+ var _a, _b;
24264
+ return (_b = (_a = t.track) === null || _a === void 0 ? void 0 : _a.isMuted) !== null && _b !== void 0 ? _b : false;
24265
+ });
24266
+ if (success && isMuted) shouldTriggerImmediateDeviceChange = true;
24108
24267
  } else if (kind === 'audiooutput') {
24109
24268
  shouldTriggerImmediateDeviceChange = true;
24110
24269
  if (!supportsSetSinkId() && !_this3.options.webAudioMix || _this3.options.webAudioMix && _this3.audioContext && !('setSinkId' in _this3.audioContext)) {