@stream-io/video-client 1.32.0 → 1.33.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 (67) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.browser.es.js +307 -74
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +308 -75
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +307 -74
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +3 -2
  9. package/dist/src/devices/AudioDeviceManager.d.ts +25 -0
  10. package/dist/src/devices/AudioDeviceManagerState.d.ts +24 -0
  11. package/dist/src/devices/CameraManager.d.ts +2 -2
  12. package/dist/src/devices/CameraManagerState.d.ts +3 -4
  13. package/dist/src/devices/{InputMediaDeviceManager.d.ts → DeviceManager.d.ts} +6 -6
  14. package/dist/src/devices/{InputMediaDeviceManagerState.d.ts → DeviceManagerState.d.ts} +4 -4
  15. package/dist/src/devices/MicrophoneManager.d.ts +5 -3
  16. package/dist/src/devices/MicrophoneManagerState.d.ts +6 -10
  17. package/dist/src/devices/ScreenShareManager.d.ts +4 -2
  18. package/dist/src/devices/ScreenShareState.d.ts +6 -2
  19. package/dist/src/devices/SpeakerState.d.ts +4 -4
  20. package/dist/src/devices/index.d.ts +2 -2
  21. package/dist/src/gen/coordinator/index.d.ts +169 -2
  22. package/dist/src/gen/video/sfu/models/models.d.ts +43 -0
  23. package/dist/src/rtc/BasePeerConnection.d.ts +2 -12
  24. package/dist/src/rtc/Publisher.d.ts +9 -6
  25. package/dist/src/rtc/Subscriber.d.ts +2 -1
  26. package/dist/src/rtc/TransceiverCache.d.ts +10 -11
  27. package/dist/src/rtc/index.d.ts +1 -1
  28. package/dist/src/rtc/{videoLayers.d.ts → layers.d.ts} +7 -1
  29. package/dist/src/rtc/types.d.ts +31 -0
  30. package/package.json +3 -2
  31. package/src/Call.ts +13 -6
  32. package/src/__tests__/Call.publishing.test.ts +14 -3
  33. package/src/__tests__/StreamVideoClient.api.test.ts +1 -1
  34. package/src/devices/AudioDeviceManager.ts +61 -0
  35. package/src/devices/AudioDeviceManagerState.ts +44 -0
  36. package/src/devices/CameraManager.ts +4 -4
  37. package/src/devices/CameraManagerState.ts +9 -8
  38. package/src/devices/{InputMediaDeviceManager.ts → DeviceManager.ts} +11 -8
  39. package/src/devices/{InputMediaDeviceManagerState.ts → DeviceManagerState.ts} +7 -4
  40. package/src/devices/MicrophoneManager.ts +26 -6
  41. package/src/devices/MicrophoneManagerState.ts +18 -19
  42. package/src/devices/ScreenShareManager.ts +23 -4
  43. package/src/devices/ScreenShareState.ts +11 -3
  44. package/src/devices/SpeakerState.ts +6 -14
  45. package/src/devices/__tests__/CameraManager.test.ts +1 -0
  46. package/src/devices/__tests__/{InputMediaDeviceManager.test.ts → DeviceManager.test.ts} +4 -4
  47. package/src/devices/__tests__/{InputMediaDeviceManagerFilters.test.ts → DeviceManagerFilters.test.ts} +4 -4
  48. package/src/devices/__tests__/{InputMediaDeviceManagerState.test.ts → DeviceManagerState.test.ts} +2 -2
  49. package/src/devices/__tests__/MicrophoneManager.test.ts +41 -1
  50. package/src/devices/__tests__/NoiseCancellationStub.ts +3 -1
  51. package/src/devices/__tests__/ScreenShareManager.test.ts +5 -1
  52. package/src/devices/index.ts +2 -2
  53. package/src/events/__tests__/internal.test.ts +25 -11
  54. package/src/gen/coordinator/index.ts +169 -2
  55. package/src/gen/video/sfu/models/models.ts +65 -0
  56. package/src/rtc/BasePeerConnection.ts +1 -16
  57. package/src/rtc/Publisher.ts +74 -31
  58. package/src/rtc/Subscriber.ts +2 -4
  59. package/src/rtc/TransceiverCache.ts +23 -27
  60. package/src/rtc/__tests__/Publisher.test.ts +61 -29
  61. package/src/rtc/__tests__/{videoLayers.test.ts → layers.test.ts} +76 -1
  62. package/src/rtc/index.ts +2 -1
  63. package/src/rtc/{videoLayers.ts → layers.ts} +28 -7
  64. package/src/rtc/types.ts +44 -0
  65. package/src/sorting/presets.ts +2 -2
  66. package/src/store/CallState.ts +36 -10
  67. package/src/store/__tests__/CallState.test.ts +20 -2
@@ -113,6 +113,7 @@ const OwnCapability = {
113
113
  REMOVE_CALL_MEMBER: 'remove-call-member',
114
114
  SCREENSHARE: 'screenshare',
115
115
  SEND_AUDIO: 'send-audio',
116
+ SEND_CLOSED_CAPTIONS_CALL: 'send-closed-captions-call',
116
117
  SEND_VIDEO: 'send-video',
117
118
  START_BROADCAST_CALL: 'start-broadcast-call',
118
119
  START_CLOSED_CAPTIONS_CALL: 'start-closed-captions-call',
@@ -958,6 +959,24 @@ var ParticipantSource;
958
959
  */
959
960
  ParticipantSource[ParticipantSource["SRT"] = 5] = "SRT";
960
961
  })(ParticipantSource || (ParticipantSource = {}));
962
+ /**
963
+ * @generated from protobuf enum stream.video.sfu.models.AudioBitrateProfile
964
+ */
965
+ var AudioBitrateProfile;
966
+ (function (AudioBitrateProfile) {
967
+ /**
968
+ * @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED = 0;
969
+ */
970
+ AudioBitrateProfile[AudioBitrateProfile["VOICE_STANDARD_UNSPECIFIED"] = 0] = "VOICE_STANDARD_UNSPECIFIED";
971
+ /**
972
+ * @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_VOICE_HIGH_QUALITY = 1;
973
+ */
974
+ AudioBitrateProfile[AudioBitrateProfile["VOICE_HIGH_QUALITY"] = 1] = "VOICE_HIGH_QUALITY";
975
+ /**
976
+ * @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY = 2;
977
+ */
978
+ AudioBitrateProfile[AudioBitrateProfile["MUSIC_HIGH_QUALITY"] = 2] = "MUSIC_HIGH_QUALITY";
979
+ })(AudioBitrateProfile || (AudioBitrateProfile = {}));
961
980
  /**
962
981
  * @generated from protobuf enum stream.video.sfu.models.ErrorCode
963
982
  */
@@ -1571,6 +1590,13 @@ class PublishOption$Type extends MessageType {
1571
1590
  kind: 'scalar',
1572
1591
  T: 8 /*ScalarType.BOOL*/,
1573
1592
  },
1593
+ {
1594
+ no: 10,
1595
+ name: 'audio_bitrate_profiles',
1596
+ kind: 'message',
1597
+ repeat: 2 /*RepeatType.UNPACKED*/,
1598
+ T: () => AudioBitrate,
1599
+ },
1574
1600
  ]);
1575
1601
  }
1576
1602
  }
@@ -1634,6 +1660,28 @@ let ICETrickle$Type$1 = class ICETrickle$Type extends MessageType {
1634
1660
  */
1635
1661
  const ICETrickle$1 = new ICETrickle$Type$1();
1636
1662
  // @generated message type with reflection information, may provide speed optimized methods
1663
+ class AudioBitrate$Type extends MessageType {
1664
+ constructor() {
1665
+ super('stream.video.sfu.models.AudioBitrate', [
1666
+ {
1667
+ no: 1,
1668
+ name: 'profile',
1669
+ kind: 'enum',
1670
+ T: () => [
1671
+ 'stream.video.sfu.models.AudioBitrateProfile',
1672
+ AudioBitrateProfile,
1673
+ 'AUDIO_BITRATE_PROFILE_',
1674
+ ],
1675
+ },
1676
+ { no: 2, name: 'bitrate', kind: 'scalar', T: 5 /*ScalarType.INT32*/ },
1677
+ ]);
1678
+ }
1679
+ }
1680
+ /**
1681
+ * @generated MessageType for protobuf message stream.video.sfu.models.AudioBitrate
1682
+ */
1683
+ const AudioBitrate = new AudioBitrate$Type();
1684
+ // @generated message type with reflection information, may provide speed optimized methods
1637
1685
  class TrackInfo$Type extends MessageType {
1638
1686
  constructor() {
1639
1687
  super('stream.video.sfu.models.TrackInfo', [
@@ -1984,6 +2032,8 @@ var models = /*#__PURE__*/Object.freeze({
1984
2032
  get AndroidThermalState () { return AndroidThermalState; },
1985
2033
  AppleState: AppleState,
1986
2034
  get AppleThermalState () { return AppleThermalState; },
2035
+ AudioBitrate: AudioBitrate,
2036
+ get AudioBitrateProfile () { return AudioBitrateProfile; },
1987
2037
  Browser: Browser,
1988
2038
  Call: Call$1,
1989
2039
  get CallEndedReason () { return CallEndedReason; },
@@ -4658,11 +4708,11 @@ const ifInvisibleOrUnknownBy = conditional((a, b) => a.viewportVisibilityState?.
4658
4708
  /**
4659
4709
  * The default sorting preset.
4660
4710
  */
4661
- const defaultSortPreset = combineComparators(pinned, screenSharing, ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
4711
+ const defaultSortPreset = combineComparators(screenSharing, pinned, ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
4662
4712
  /**
4663
4713
  * The sorting preset for speaker layout.
4664
4714
  */
4665
- const speakerLayoutSortPreset = combineComparators(pinned, screenSharing, dominantSpeaker, ifInvisibleBy(combineComparators(speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
4715
+ const speakerLayoutSortPreset = combineComparators(screenSharing, pinned, dominantSpeaker, ifInvisibleBy(combineComparators(speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
4666
4716
  /**
4667
4717
  * The sorting preset for layouts that don't render all participants but
4668
4718
  * instead, render them in pages.
@@ -5009,14 +5059,32 @@ class CallState {
5009
5059
  * @param pins the latest pins from the server.
5010
5060
  */
5011
5061
  this.setServerSidePins = (pins) => {
5012
- const pinsLookup = pins.reduce((lookup, pin) => {
5013
- lookup[pin.sessionId] = Date.now();
5062
+ const now = Date.now();
5063
+ const unknownSymbol = Symbol('unknown');
5064
+ // generate a lookup table of pinnedAt timestamps by userId and sessionId
5065
+ // if there are multiple pins for the same userId, then we set the pinnedAt
5066
+ // to `unknown` (for that userId lookup) so that we don't apply any pin for that participant
5067
+ // this is to avoid conflicts during reconstruction of the pin state after reconnections
5068
+ // as sessionIds can change
5069
+ const pinnedAtByIdentifier = pins.reduce((lookup, pin, index) => {
5070
+ var _a;
5071
+ const pinnedAt = now + (pins.length - index);
5072
+ if (lookup[pin.userId]) {
5073
+ lookup[pin.userId] = unknownSymbol;
5074
+ }
5075
+ else {
5076
+ lookup[pin.userId] = pinnedAt;
5077
+ }
5078
+ lookup[_a = pin.sessionId] ?? (lookup[_a] = pinnedAt);
5014
5079
  return lookup;
5015
5080
  }, {});
5016
5081
  return this.setParticipants((participants) => participants.map((participant) => {
5017
- const serverSidePinnedAt = pinsLookup[participant.sessionId];
5082
+ // first check by sessionId as that is 100% correct, then by attempt reconstruction by userId
5083
+ const serverSidePinnedAt = pinnedAtByIdentifier[participant.sessionId] ??
5084
+ pinnedAtByIdentifier[participant.userId];
5018
5085
  // the participant is newly pinned
5019
- if (serverSidePinnedAt) {
5086
+ if (typeof serverSidePinnedAt === 'number' &&
5087
+ typeof participant.pin?.pinnedAt !== 'number') {
5020
5088
  return {
5021
5089
  ...participant,
5022
5090
  pin: {
@@ -5027,7 +5095,8 @@ class CallState {
5027
5095
  }
5028
5096
  // the participant is no longer pinned server side
5029
5097
  // we need to reset the pin
5030
- if (participant.pin && !participant.pin.isLocalPin) {
5098
+ if (typeof serverSidePinnedAt !== 'number' &&
5099
+ participant.pin?.isLocalPin === false) {
5031
5100
  return {
5032
5101
  ...participant,
5033
5102
  pin: undefined,
@@ -5764,7 +5833,7 @@ const getSdkVersion = (sdk) => {
5764
5833
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5765
5834
  };
5766
5835
 
5767
- const version = "1.32.0";
5836
+ const version = "1.33.0";
5768
5837
  const [major, minor, patch] = version.split('.');
5769
5838
  let sdkInfo = {
5770
5839
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -6962,15 +7031,24 @@ class TransceiverCache {
6962
7031
  /**
6963
7032
  * Adds a transceiver to the cache.
6964
7033
  */
6965
- this.add = (publishOption, transceiver) => {
6966
- this.cache.push({ publishOption, transceiver });
6967
- this.transceiverOrder.push(transceiver);
7034
+ this.add = (bundle) => {
7035
+ this.cache.push(bundle);
7036
+ this.transceiverOrder.push(bundle.transceiver);
6968
7037
  };
6969
7038
  /**
6970
7039
  * Gets the transceiver for the given publish option.
6971
7040
  */
6972
7041
  this.get = (publishOption) => {
6973
- return this.findTransceiver(publishOption)?.transceiver;
7042
+ return this.cache.find((bundle) => bundle.publishOption.id === publishOption.id &&
7043
+ bundle.publishOption.trackType === publishOption.trackType);
7044
+ };
7045
+ /**
7046
+ * Updates the cached bundle with the given patch.
7047
+ */
7048
+ this.update = (publishOption, patch) => {
7049
+ const bundle = this.get(publishOption);
7050
+ if (bundle)
7051
+ Object.assign(bundle, patch);
6974
7052
  };
6975
7053
  /**
6976
7054
  * Checks if the cache has the given publish option.
@@ -7016,10 +7094,6 @@ class TransceiverCache {
7016
7094
  this.layers.push({ publishOption, layers });
7017
7095
  }
7018
7096
  };
7019
- this.findTransceiver = (publishOption) => {
7020
- return this.cache.find((item) => item.publishOption.id === publishOption.id &&
7021
- item.publishOption.trackType === publishOption.trackType);
7022
- };
7023
7097
  this.findLayer = (publishOption) => {
7024
7098
  return this.layers.find((item) => item.publishOption.id === publishOption.id &&
7025
7099
  item.publishOption.trackType === publishOption.trackType);
@@ -7077,10 +7151,20 @@ const toTrackType = (trackType) => {
7077
7151
  };
7078
7152
  const isAudioTrackType = (trackType) => trackType === TrackType.AUDIO || trackType === TrackType.SCREEN_SHARE_AUDIO;
7079
7153
 
7080
- const defaultBitratePerRid = {
7081
- q: 300000,
7082
- h: 750000,
7083
- f: 1250000,
7154
+ /**
7155
+ * Prepares the audio layer for the given track.
7156
+ * Based on the provided audio bitrate profile, we apply the appropriate bitrate.
7157
+ */
7158
+ const computeAudioLayers = (publishOption, options) => {
7159
+ const { audioBitrateProfile } = options;
7160
+ const profileConfig = publishOption.audioBitrateProfiles?.find((config) => config.profile === audioBitrateProfile);
7161
+ const maxBitrate = profileConfig?.bitrate ||
7162
+ {
7163
+ [AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED]: 64000,
7164
+ [AudioBitrateProfile.VOICE_HIGH_QUALITY]: 128000,
7165
+ [AudioBitrateProfile.MUSIC_HIGH_QUALITY]: 128000,
7166
+ }[audioBitrateProfile || AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED];
7167
+ return [{ maxBitrate }];
7084
7168
  };
7085
7169
  /**
7086
7170
  * In SVC, we need to send only one video encoding (layer).
@@ -7093,7 +7177,7 @@ const toSvcEncodings = (layers) => {
7093
7177
  if (!layers)
7094
7178
  return;
7095
7179
  // we take the highest quality layer, and we assign it to `q` encoder.
7096
- const withRid = (rid) => (l) => l.rid === rid;
7180
+ const withRid = (rid) => (layer) => layer.rid === rid;
7097
7181
  const highestLayer = layers.find(withRid('f')) ||
7098
7182
  layers.find(withRid('h')) ||
7099
7183
  layers.find(withRid('q'));
@@ -7147,7 +7231,8 @@ const computeVideoLayers = (videoTrack, publishOption) => {
7147
7231
  rid,
7148
7232
  width: Math.round(width / downscaleFactor),
7149
7233
  height: Math.round(height / downscaleFactor),
7150
- maxBitrate: Math.round(maxBitrate / bitrateFactor) || defaultBitratePerRid[rid],
7234
+ maxBitrate: Math.round(maxBitrate / bitrateFactor) ||
7235
+ { q: 300000, h: 750000, f: 1250000 }[rid],
7151
7236
  maxFramerate: fps,
7152
7237
  };
7153
7238
  if (svcCodec) {
@@ -7313,8 +7398,9 @@ class Publisher extends BasePeerConnection {
7313
7398
  *
7314
7399
  * @param track the track to publish.
7315
7400
  * @param trackType the track type to publish.
7401
+ * @param options the publish options to use.
7316
7402
  */
7317
- this.publish = async (track, trackType) => {
7403
+ this.publish = async (track, trackType, options = {}) => {
7318
7404
  if (!this.publishOptions.some((o) => o.trackType === trackType)) {
7319
7405
  throw new Error(`No publish options found for ${TrackType[trackType]}`);
7320
7406
  }
@@ -7324,13 +7410,13 @@ class Publisher extends BasePeerConnection {
7324
7410
  // create a clone of the track as otherwise the same trackId will
7325
7411
  // appear in the SDP in multiple transceivers
7326
7412
  const trackToPublish = this.cloneTrack(track);
7327
- const transceiver = this.transceiverCache.get(publishOption);
7413
+ const { transceiver } = this.transceiverCache.get(publishOption) || {};
7328
7414
  if (!transceiver) {
7329
- await this.addTransceiver(trackToPublish, publishOption);
7415
+ await this.addTransceiver(trackToPublish, publishOption, options);
7330
7416
  }
7331
7417
  else {
7332
7418
  const previousTrack = transceiver.sender.track;
7333
- await this.updateTransceiver(transceiver, trackToPublish, trackType);
7419
+ await this.updateTransceiver(transceiver, trackToPublish, trackType, options);
7334
7420
  if (!isReactNative()) {
7335
7421
  this.stopTrack(previousTrack);
7336
7422
  }
@@ -7340,11 +7426,13 @@ class Publisher extends BasePeerConnection {
7340
7426
  /**
7341
7427
  * Adds a new transceiver carrying the given track to the peer connection.
7342
7428
  */
7343
- this.addTransceiver = async (track, publishOption) => {
7344
- const videoEncodings = computeVideoLayers(track, publishOption);
7429
+ this.addTransceiver = async (track, publishOption, options) => {
7430
+ const encodings = isAudioTrackType(publishOption.trackType)
7431
+ ? computeAudioLayers(publishOption, options)
7432
+ : computeVideoLayers(track, publishOption);
7345
7433
  const sendEncodings = isSvcCodec(publishOption.codec?.name)
7346
- ? toSvcEncodings(videoEncodings)
7347
- : videoEncodings;
7434
+ ? toSvcEncodings(encodings)
7435
+ : encodings;
7348
7436
  const transceiver = this.pc.addTransceiver(track, {
7349
7437
  direction: 'sendonly',
7350
7438
  sendEncodings,
@@ -7354,20 +7442,49 @@ class Publisher extends BasePeerConnection {
7354
7442
  await transceiver.sender.setParameters(params);
7355
7443
  const trackType = publishOption.trackType;
7356
7444
  this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
7357
- this.transceiverCache.add(publishOption, transceiver);
7445
+ this.transceiverCache.add({ publishOption, transceiver, options });
7358
7446
  this.trackIdToTrackType.set(track.id, trackType);
7359
7447
  await this.negotiate();
7360
7448
  };
7361
7449
  /**
7362
7450
  * Updates the transceiver with the given track and track type.
7363
7451
  */
7364
- this.updateTransceiver = async (transceiver, track, trackType) => {
7452
+ this.updateTransceiver = async (transceiver, track, trackType, options = {}) => {
7365
7453
  const sender = transceiver.sender;
7366
7454
  if (sender.track)
7367
7455
  this.trackIdToTrackType.delete(sender.track.id);
7368
7456
  await sender.replaceTrack(track);
7369
7457
  if (track)
7370
7458
  this.trackIdToTrackType.set(track.id, trackType);
7459
+ if (isAudioTrackType(trackType)) {
7460
+ await this.updateAudioPublishOptions(trackType, options);
7461
+ }
7462
+ };
7463
+ /**
7464
+ * Updates the publish options for the given track type.
7465
+ */
7466
+ this.updateAudioPublishOptions = async (trackType, options) => {
7467
+ for (const publishOption of this.publishOptions) {
7468
+ if (publishOption.trackType !== trackType)
7469
+ continue;
7470
+ const bundle = this.transceiverCache.get(publishOption);
7471
+ if (!bundle)
7472
+ continue;
7473
+ const { transceiver, options: current } = bundle;
7474
+ if (current.audioBitrateProfile !== options.audioBitrateProfile) {
7475
+ const encodings = computeAudioLayers(publishOption, options);
7476
+ if (encodings && encodings.length > 0) {
7477
+ const params = transceiver.sender.getParameters();
7478
+ const [currentEncoding] = params.encodings;
7479
+ const [targetEncoding] = encodings;
7480
+ if (currentEncoding.maxBitrate !== targetEncoding.maxBitrate) {
7481
+ currentEncoding.maxBitrate = targetEncoding.maxBitrate;
7482
+ }
7483
+ await transceiver.sender.setParameters(params);
7484
+ }
7485
+ }
7486
+ this.transceiverCache.update(publishOption, { options });
7487
+ }
7371
7488
  };
7372
7489
  /**
7373
7490
  * Synchronizes the current Publisher state with the provided publish options.
@@ -7382,12 +7499,12 @@ class Publisher extends BasePeerConnection {
7382
7499
  continue;
7383
7500
  const item = this.transceiverCache.find((i) => !!i.transceiver.sender.track &&
7384
7501
  i.publishOption.trackType === trackType);
7385
- if (!item || !item.transceiver)
7502
+ if (!item)
7386
7503
  continue;
7387
7504
  // take the track from the existing transceiver for the same track type,
7388
7505
  // clone it and publish it with the new publish options
7389
7506
  const track = this.cloneTrack(item.transceiver.sender.track);
7390
- await this.addTransceiver(track, publishOption);
7507
+ await this.addTransceiver(track, publishOption, item.options);
7391
7508
  }
7392
7509
  // stop publishing with options not required anymore -> [vp9]
7393
7510
  for (const item of this.transceiverCache.items()) {
@@ -7567,11 +7684,9 @@ class Publisher extends BasePeerConnection {
7567
7684
  this.getAnnouncedTracks = (sdp) => {
7568
7685
  const trackInfos = [];
7569
7686
  for (const bundle of this.transceiverCache.items()) {
7570
- const { transceiver, publishOption } = bundle;
7571
- const track = transceiver.sender.track;
7572
- if (!track)
7687
+ if (!bundle.transceiver.sender.track)
7573
7688
  continue;
7574
- trackInfos.push(this.toTrackInfo(transceiver, publishOption, sdp));
7689
+ trackInfos.push(this.toTrackInfo(bundle, sdp));
7575
7690
  }
7576
7691
  return trackInfos;
7577
7692
  };
@@ -7584,17 +7699,18 @@ class Publisher extends BasePeerConnection {
7584
7699
  const sdp = this.pc.localDescription?.sdp;
7585
7700
  const trackInfos = [];
7586
7701
  for (const publishOption of this.publishOptions) {
7587
- const transceiver = this.transceiverCache.get(publishOption);
7588
- if (!transceiver || !transceiver.sender.track)
7702
+ const bundle = this.transceiverCache.get(publishOption);
7703
+ if (!bundle || !bundle.transceiver.sender.track)
7589
7704
  continue;
7590
- trackInfos.push(this.toTrackInfo(transceiver, publishOption, sdp));
7705
+ trackInfos.push(this.toTrackInfo(bundle, sdp));
7591
7706
  }
7592
7707
  return trackInfos;
7593
7708
  };
7594
7709
  /**
7595
7710
  * Converts the given transceiver to a `TrackInfo` object.
7596
7711
  */
7597
- this.toTrackInfo = (transceiver, publishOption, sdp) => {
7712
+ this.toTrackInfo = (bundle, sdp) => {
7713
+ const { transceiver, publishOption } = bundle;
7598
7714
  const track = transceiver.sender.track;
7599
7715
  const isTrackLive = track.readyState === 'live';
7600
7716
  const layers = isTrackLive
@@ -7602,15 +7718,16 @@ class Publisher extends BasePeerConnection {
7602
7718
  : this.transceiverCache.getLayers(publishOption);
7603
7719
  this.transceiverCache.setLayers(publishOption, layers);
7604
7720
  const isAudioTrack = isAudioTrackType(publishOption.trackType);
7605
- const isStereo = isAudioTrack && track.getSettings().channelCount === 2;
7606
7721
  const transceiverIndex = this.transceiverCache.indexOf(transceiver);
7607
7722
  const audioSettings = this.state.settings?.audio;
7723
+ const stereo = publishOption.trackType === TrackType.SCREEN_SHARE_AUDIO ||
7724
+ (isAudioTrack && !!audioSettings?.hifi_audio_enabled);
7608
7725
  return {
7609
7726
  trackId: track.id,
7610
7727
  layers: toVideoLayers(layers),
7611
7728
  trackType: publishOption.trackType,
7612
7729
  mid: extractMid(transceiver, transceiverIndex, sdp),
7613
- stereo: isStereo,
7730
+ stereo,
7614
7731
  dtx: isAudioTrack && !!audioSettings?.opus_dtx_enabled,
7615
7732
  red: isAudioTrack && !!audioSettings?.redundant_coding_enabled,
7616
7733
  muted: !isTrackLive,
@@ -9894,7 +10011,7 @@ function resolveDeviceId(deviceId, kind) {
9894
10011
  */
9895
10012
  const isMobile = () => /Mobi/i.test(navigator.userAgent);
9896
10013
 
9897
- class InputMediaDeviceManager {
10014
+ class DeviceManager {
9898
10015
  constructor(call, state, trackType) {
9899
10016
  /**
9900
10017
  * if true, stops the media stream when call is left
@@ -10101,8 +10218,8 @@ class InputMediaDeviceManager {
10101
10218
  }
10102
10219
  });
10103
10220
  }
10104
- publishStream(stream) {
10105
- return this.call.publish(stream, this.trackType);
10221
+ publishStream(stream, options) {
10222
+ return this.call.publish(stream, this.trackType, options);
10106
10223
  }
10107
10224
  stopPublishStream() {
10108
10225
  return this.call.stopPublish(this.trackType);
@@ -10338,16 +10455,15 @@ class InputMediaDeviceManager {
10338
10455
  }
10339
10456
  }
10340
10457
 
10341
- class InputMediaDeviceManagerState {
10458
+ class DeviceManagerState {
10342
10459
  /**
10343
- * Constructs new InputMediaDeviceManagerState instance.
10460
+ * Constructs a new InputMediaDeviceManagerState instance.
10344
10461
  *
10345
10462
  * @param disableMode the disable mode to use.
10346
10463
  * @param permission the BrowserPermission to use for querying.
10347
10464
  * `undefined` means no permission is required.
10348
10465
  */
10349
- constructor(disableMode = 'stop-tracks', permission) {
10350
- this.disableMode = disableMode;
10466
+ constructor(disableMode, permission) {
10351
10467
  this.statusSubject = new BehaviorSubject(undefined);
10352
10468
  this.optimisticStatusSubject = new BehaviorSubject(undefined);
10353
10469
  this.mediaStreamSubject = new BehaviorSubject(undefined);
@@ -10378,6 +10494,7 @@ class InputMediaDeviceManagerState {
10378
10494
  * The default constraints for the device.
10379
10495
  */
10380
10496
  this.defaultConstraints$ = this.defaultConstraintsSubject.asObservable();
10497
+ this.disableMode = disableMode;
10381
10498
  this.hasBrowserPermission$ = permission
10382
10499
  ? permission.asObservable().pipe(shareReplay(1))
10383
10500
  : of(true);
@@ -10464,10 +10581,15 @@ class InputMediaDeviceManagerState {
10464
10581
  }
10465
10582
  }
10466
10583
 
10467
- class CameraManagerState extends InputMediaDeviceManagerState {
10584
+ class CameraManagerState extends DeviceManagerState {
10468
10585
  constructor() {
10469
10586
  super('stop-tracks', getVideoBrowserPermission());
10470
10587
  this.directionSubject = new BehaviorSubject(undefined);
10588
+ /**
10589
+ * Observable that emits the preferred camera direction
10590
+ * front - means the camera facing the user
10591
+ * back - means the camera facing the environment
10592
+ */
10471
10593
  this.direction$ = this.directionSubject
10472
10594
  .asObservable()
10473
10595
  .pipe(distinctUntilChanged());
@@ -10507,7 +10629,7 @@ class CameraManagerState extends InputMediaDeviceManagerState {
10507
10629
  }
10508
10630
  }
10509
10631
 
10510
- class CameraManager extends InputMediaDeviceManager {
10632
+ class CameraManager extends DeviceManager {
10511
10633
  /**
10512
10634
  * Constructs a new CameraManager.
10513
10635
  *
@@ -10653,18 +10775,87 @@ class CameraManager extends InputMediaDeviceManager {
10653
10775
  }
10654
10776
  }
10655
10777
 
10656
- class MicrophoneManagerState extends InputMediaDeviceManagerState {
10778
+ /**
10779
+ * Base class for High Fidelity enabled Device Managers.
10780
+ */
10781
+ class AudioDeviceManager extends DeviceManager {
10782
+ /**
10783
+ * Sets the audio bitrate profile and stereo mode.
10784
+ */
10785
+ async setAudioBitrateProfile(profile) {
10786
+ if (!this.call.state.settings?.audio.hifi_audio_enabled) {
10787
+ throw new Error('High Fidelity audio is not enabled for this call');
10788
+ }
10789
+ this.doSetAudioBitrateProfile(profile);
10790
+ this.state.setAudioBitrateProfile(profile);
10791
+ if (this.enabled) {
10792
+ await this.applySettingsToStream();
10793
+ }
10794
+ }
10795
+ /**
10796
+ * Overrides the default `publishStream` method to inject the audio bitrate profile.
10797
+ */
10798
+ publishStream(stream, options) {
10799
+ return super.publishStream(stream, {
10800
+ audioBitrateProfile: this.state.audioBitrateProfile,
10801
+ ...options,
10802
+ });
10803
+ }
10804
+ }
10805
+ /**
10806
+ * Prepares a new MediaTrackConstraints set based on the provided arguments.
10807
+ */
10808
+ const createAudioConstraints = (profile) => {
10809
+ const stereo = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
10810
+ return {
10811
+ echoCancellation: !stereo,
10812
+ noiseSuppression: !stereo,
10813
+ autoGainControl: !stereo,
10814
+ channelCount: { ideal: stereo ? 2 : 1 },
10815
+ };
10816
+ };
10817
+
10818
+ /**
10819
+ * Base state class for High Fidelity enabled device managers.
10820
+ */
10821
+ class AudioDeviceManagerState extends DeviceManagerState {
10822
+ /**
10823
+ * Constructs a new AudioDeviceManagerState instance.
10824
+ */
10825
+ constructor(disableMode, permission, profile) {
10826
+ super(disableMode, permission);
10827
+ this.audioBitrateProfileSubject = new BehaviorSubject(profile);
10828
+ this.audioBitrateProfile$ = this.audioBitrateProfileSubject
10829
+ .asObservable()
10830
+ .pipe(distinctUntilChanged());
10831
+ }
10832
+ /**
10833
+ * Returns the current audio bitrate profile.
10834
+ */
10835
+ get audioBitrateProfile() {
10836
+ return getCurrentValue(this.audioBitrateProfile$);
10837
+ }
10838
+ /**
10839
+ * Sets the audio bitrate profile and stereo mode.
10840
+ */
10841
+ setAudioBitrateProfile(profile) {
10842
+ setCurrentValue(this.audioBitrateProfileSubject, profile);
10843
+ }
10844
+ }
10845
+
10846
+ class MicrophoneManagerState extends AudioDeviceManagerState {
10657
10847
  constructor(disableMode) {
10658
- super(disableMode, getAudioBrowserPermission());
10848
+ super(disableMode, getAudioBrowserPermission(), AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED);
10659
10849
  this.speakingWhileMutedSubject = new BehaviorSubject(false);
10850
+ /**
10851
+ * An Observable that emits `true` if the user's microphone is muted, but they're speaking.
10852
+ */
10660
10853
  this.speakingWhileMuted$ = this.speakingWhileMutedSubject
10661
10854
  .asObservable()
10662
10855
  .pipe(distinctUntilChanged());
10663
10856
  }
10664
10857
  /**
10665
- * `true` if the user's microphone is muted but they'are speaking.
10666
- *
10667
- * This feature is not available in the React Native SDK.
10858
+ * `true` if the user's microphone is muted but they're speaking.
10668
10859
  */
10669
10860
  get speakingWhileMuted() {
10670
10861
  return getCurrentValue(this.speakingWhileMuted$);
@@ -10897,7 +11088,7 @@ class RNSpeechDetector {
10897
11088
  }
10898
11089
  }
10899
11090
 
10900
- class MicrophoneManager extends InputMediaDeviceManager {
11091
+ class MicrophoneManager extends AudioDeviceManager {
10901
11092
  constructor(call, disableMode = 'stop-tracks') {
10902
11093
  super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
10903
11094
  this.speakingWhileMutedNotificationEnabled = true;
@@ -11093,6 +11284,21 @@ class MicrophoneManager extends InputMediaDeviceManager {
11093
11284
  getStream(constraints) {
11094
11285
  return getAudioStream(constraints, this.call.tracer);
11095
11286
  }
11287
+ doSetAudioBitrateProfile(profile) {
11288
+ this.setDefaultConstraints({
11289
+ ...this.state.defaultConstraints,
11290
+ ...createAudioConstraints(profile),
11291
+ });
11292
+ if (this.noiseCancellation) {
11293
+ const disableAudioProcessing = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
11294
+ if (disableAudioProcessing) {
11295
+ this.noiseCancellation.disable(); // disable for high quality music mode
11296
+ }
11297
+ else {
11298
+ this.noiseCancellation.enable(); // restore it for other modes if available
11299
+ }
11300
+ }
11301
+ }
11096
11302
  async startSpeakingWhileMutedDetection(deviceId) {
11097
11303
  await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
11098
11304
  await this.stopSpeakingWhileMutedDetection();
@@ -11129,9 +11335,12 @@ class MicrophoneManager extends InputMediaDeviceManager {
11129
11335
  }
11130
11336
  }
11131
11337
 
11132
- class ScreenShareState extends InputMediaDeviceManagerState {
11338
+ class ScreenShareState extends AudioDeviceManagerState {
11339
+ /**
11340
+ * Constructs a new ScreenShareState instance.
11341
+ */
11133
11342
  constructor() {
11134
- super(...arguments);
11343
+ super('stop-tracks', undefined, AudioBitrateProfile.MUSIC_HIGH_QUALITY);
11135
11344
  this.audioEnabledSubject = new BehaviorSubject(true);
11136
11345
  this.settingsSubject = new BehaviorSubject(undefined);
11137
11346
  /**
@@ -11180,7 +11389,7 @@ class ScreenShareState extends InputMediaDeviceManagerState {
11180
11389
  }
11181
11390
  }
11182
11391
 
11183
- class ScreenShareManager extends InputMediaDeviceManager {
11392
+ class ScreenShareManager extends AudioDeviceManager {
11184
11393
  constructor(call) {
11185
11394
  super(call, new ScreenShareState(), TrackType.SCREEN_SHARE);
11186
11395
  }
@@ -11190,6 +11399,7 @@ class ScreenShareManager extends InputMediaDeviceManager {
11190
11399
  const maybeTargetResolution = settings?.screensharing.target_resolution;
11191
11400
  if (maybeTargetResolution) {
11192
11401
  this.setDefaultConstraints({
11402
+ ...this.state.defaultConstraints,
11193
11403
  video: {
11194
11404
  width: maybeTargetResolution.width,
11195
11405
  height: maybeTargetResolution.height,
@@ -11246,6 +11456,19 @@ class ScreenShareManager extends InputMediaDeviceManager {
11246
11456
  }
11247
11457
  return stream;
11248
11458
  }
11459
+ doSetAudioBitrateProfile(profile) {
11460
+ const { defaultConstraints } = this.state;
11461
+ const baseAudioConstraints = typeof defaultConstraints?.audio !== 'boolean'
11462
+ ? defaultConstraints?.audio
11463
+ : null;
11464
+ this.setDefaultConstraints({
11465
+ ...defaultConstraints,
11466
+ audio: {
11467
+ ...baseAudioConstraints,
11468
+ ...createAudioConstraints(profile),
11469
+ },
11470
+ });
11471
+ }
11249
11472
  async stopPublishStream() {
11250
11473
  return this.call.stopPublish(TrackType.SCREEN_SHARE, TrackType.SCREEN_SHARE_AUDIO);
11251
11474
  }
@@ -11259,19 +11482,27 @@ class ScreenShareManager extends InputMediaDeviceManager {
11259
11482
 
11260
11483
  class SpeakerState {
11261
11484
  constructor(tracer) {
11485
+ this.tracer = tracer;
11262
11486
  this.selectedDeviceSubject = new BehaviorSubject('');
11263
11487
  this.volumeSubject = new BehaviorSubject(1);
11264
11488
  /**
11265
11489
  * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
11266
11490
  */
11267
11491
  this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
11268
- this.tracer = tracer;
11492
+ /**
11493
+ * An Observable that emits the currently selected device
11494
+ *
11495
+ * Note: this feature is not supported in React Native
11496
+ */
11269
11497
  this.selectedDevice$ = this.selectedDeviceSubject
11270
11498
  .asObservable()
11271
11499
  .pipe(distinctUntilChanged());
11272
- this.volume$ = this.volumeSubject
11273
- .asObservable()
11274
- .pipe(distinctUntilChanged());
11500
+ /**
11501
+ * An Observable that emits the currently selected volume
11502
+ *
11503
+ * Note: this feature is not supported in React Native
11504
+ */
11505
+ this.volume$ = this.volumeSubject.asObservable().pipe(distinctUntilChanged());
11275
11506
  }
11276
11507
  /**
11277
11508
  * The currently selected device
@@ -12596,10 +12827,11 @@ class Call {
12596
12827
  *
12597
12828
  * @param mediaStream the media stream to publish.
12598
12829
  * @param trackType the type of the track to announce.
12830
+ * @param options the publish options.
12599
12831
  */
12600
- this.publish = async (mediaStream, trackType) => {
12832
+ this.publish = async (mediaStream, trackType, options) => {
12601
12833
  if (!this.sfuClient)
12602
- throw new Error(`Call not joined yet.`);
12834
+ throw new Error(`Call is not joined yet`);
12603
12835
  // joining is in progress, and we should wait until the client is ready
12604
12836
  await this.sfuClient.joinTask;
12605
12837
  if (!this.permissionsContext.canPublish(trackType)) {
@@ -12617,14 +12849,15 @@ class Call {
12617
12849
  throw new Error(`Can't publish ended tracks.`);
12618
12850
  }
12619
12851
  pushToIfMissing(this.trackPublishOrder, trackType);
12620
- await this.publisher.publish(track, trackType);
12852
+ await this.publisher.publish(track, trackType, options);
12621
12853
  const trackTypes = [trackType];
12622
12854
  if (trackType === TrackType.SCREEN_SHARE) {
12623
12855
  const [audioTrack] = mediaStream.getAudioTracks();
12624
12856
  if (audioTrack) {
12625
- pushToIfMissing(this.trackPublishOrder, TrackType.SCREEN_SHARE_AUDIO);
12626
- await this.publisher.publish(audioTrack, TrackType.SCREEN_SHARE_AUDIO);
12627
- trackTypes.push(TrackType.SCREEN_SHARE_AUDIO);
12857
+ const screenShareAudio = TrackType.SCREEN_SHARE_AUDIO;
12858
+ pushToIfMissing(this.trackPublishOrder, screenShareAudio);
12859
+ await this.publisher.publish(audioTrack, screenShareAudio, options);
12860
+ trackTypes.push(screenShareAudio);
12628
12861
  }
12629
12862
  }
12630
12863
  if (track.kind === 'video') {
@@ -14545,7 +14778,7 @@ class StreamClient {
14545
14778
  this.getUserAgent = () => {
14546
14779
  if (!this.cachedUserAgent) {
14547
14780
  const { clientAppIdentifier = {} } = this.options;
14548
- const { sdkName = 'js', sdkVersion = "1.32.0", ...extras } = clientAppIdentifier;
14781
+ const { sdkName = 'js', sdkVersion = "1.33.0", ...extras } = clientAppIdentifier;
14549
14782
  this.cachedUserAgent = [
14550
14783
  `stream-video-${sdkName}-v${sdkVersion}`,
14551
14784
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15142,5 +15375,5 @@ class StreamVideoClient {
15142
15375
  }
15143
15376
  StreamVideoClient._instances = new Map();
15144
15377
 
15145
- export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressVideoLayerRequestCodecEnum, InputMediaDeviceManager, InputMediaDeviceManagerState, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getLogLevel, getLogger, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, withParticipantSource };
15378
+ export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getLogLevel, getLogger, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, withParticipantSource };
15146
15379
  //# sourceMappingURL=index.browser.es.js.map