@stream-io/video-client 1.6.3 → 1.6.5

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.6.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.4...@stream-io/video-client-1.6.5) (2024-09-19)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * race condition in `applySettingsToStream` ([#1489](https://github.com/GetStream/stream-video-js/issues/1489)) ([bf2ad90](https://github.com/GetStream/stream-video-js/commit/bf2ad90224d88592d4ea27ea8d0683efe98771f7))
11
+
12
+ ## [1.6.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.3...@stream-io/video-client-1.6.4) (2024-09-13)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * allow video target bitrate override ([#1487](https://github.com/GetStream/stream-video-js/issues/1487)) ([bfe34a3](https://github.com/GetStream/stream-video-js/commit/bfe34a3609182da5bbb03331978d86569cada098))
18
+
5
19
  ## [1.6.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.2...@stream-io/video-client-1.6.3) (2024-09-11)
6
20
 
7
21
 
@@ -4,9 +4,9 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
4
4
  import axios, { AxiosHeaders } from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
+ import { UAParser } from 'ua-parser-js';
7
8
  import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of, debounce, timer } from 'rxjs';
8
9
  import * as SDP from 'sdp-transform';
9
- import { UAParser } from 'ua-parser-js';
10
10
  import WebSocket$1 from 'isomorphic-ws';
11
11
  import { fromByteArray } from 'base64-js';
12
12
 
@@ -6820,6 +6820,72 @@ const retryable = async (rpc, signal) => {
6820
6820
  return result;
6821
6821
  };
6822
6822
 
6823
+ const version = "1.6.5" ;
6824
+ const [major, minor, patch] = version.split('.');
6825
+ let sdkInfo = {
6826
+ type: SdkType.PLAIN_JAVASCRIPT,
6827
+ major,
6828
+ minor,
6829
+ patch,
6830
+ };
6831
+ let osInfo;
6832
+ let deviceInfo;
6833
+ let webRtcInfo;
6834
+ const setSdkInfo = (info) => {
6835
+ sdkInfo = info;
6836
+ };
6837
+ const getSdkInfo = () => {
6838
+ return sdkInfo;
6839
+ };
6840
+ const setOSInfo = (info) => {
6841
+ osInfo = info;
6842
+ };
6843
+ const getOSInfo = () => {
6844
+ return osInfo;
6845
+ };
6846
+ const setDeviceInfo = (info) => {
6847
+ deviceInfo = info;
6848
+ };
6849
+ const getDeviceInfo = () => {
6850
+ return deviceInfo;
6851
+ };
6852
+ const getWebRTCInfo = () => {
6853
+ return webRtcInfo;
6854
+ };
6855
+ const setWebRTCInfo = (info) => {
6856
+ webRtcInfo = info;
6857
+ };
6858
+ const getClientDetails = () => {
6859
+ if (isReactNative()) {
6860
+ // Since RN doesn't support web, sharing browser info is not required
6861
+ return {
6862
+ sdk: getSdkInfo(),
6863
+ os: getOSInfo(),
6864
+ device: getDeviceInfo(),
6865
+ };
6866
+ }
6867
+ const userAgent = new UAParser(navigator.userAgent);
6868
+ const { browser, os, device, cpu } = userAgent.getResult();
6869
+ return {
6870
+ sdk: getSdkInfo(),
6871
+ browser: {
6872
+ name: browser.name || navigator.userAgent,
6873
+ version: browser.version || '',
6874
+ },
6875
+ os: {
6876
+ name: os.name || '',
6877
+ version: os.version || '',
6878
+ architecture: cpu.architecture || '',
6879
+ },
6880
+ device: {
6881
+ name: [device.vendor, device.model, device.type]
6882
+ .filter(Boolean)
6883
+ .join(' '),
6884
+ version: '',
6885
+ },
6886
+ };
6887
+ };
6888
+
6823
6889
  /**
6824
6890
  * Returns back a list of sorted codecs, with the preferred codec first.
6825
6891
  *
@@ -6894,6 +6960,20 @@ const getGenericSdp = async (direction) => {
6894
6960
  tempPc.close();
6895
6961
  return sdp;
6896
6962
  };
6963
+ /**
6964
+ * Returns the optimal codec for RN.
6965
+ */
6966
+ const getRNOptimalCodec = () => {
6967
+ const osName = getOSInfo()?.name.toLowerCase();
6968
+ // in ipads it was noticed that if vp8 codec is used
6969
+ // then the bytes sent is 0 in the outbound-rtp
6970
+ // so we are forcing h264 codec for ipads
6971
+ if (osName === 'ipados')
6972
+ return 'h264';
6973
+ if (osName === 'android')
6974
+ return 'vp8';
6975
+ return undefined;
6976
+ };
6897
6977
 
6898
6978
  const sfuEventKinds = {
6899
6979
  subscriberOffer: undefined,
@@ -7017,12 +7097,13 @@ const defaultBitratePerRid = {
7017
7097
  *
7018
7098
  * @param videoTrack the video track to find optimal layers for.
7019
7099
  * @param targetResolution the expected target resolution.
7100
+ * @param preferredBitrate the preferred bitrate for the video track.
7020
7101
  */
7021
- const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution) => {
7102
+ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution, preferredBitrate) => {
7022
7103
  const optimalVideoLayers = [];
7023
7104
  const settings = videoTrack.getSettings();
7024
7105
  const { width: w = 0, height: h = 0 } = settings;
7025
- const maxBitrate = getComputedMaxBitrate(targetResolution, w, h);
7106
+ const maxBitrate = getComputedMaxBitrate(targetResolution, w, h, preferredBitrate);
7026
7107
  let downscaleFactor = 1;
7027
7108
  ['f', 'h', 'q'].forEach((rid) => {
7028
7109
  // Reversing the order [f, h, q] to [q, h, f] as Chrome uses encoding index
@@ -7053,18 +7134,20 @@ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetReso
7053
7134
  * @param targetResolution the target resolution.
7054
7135
  * @param currentWidth the current width of the track.
7055
7136
  * @param currentHeight the current height of the track.
7137
+ * @param preferredBitrate the preferred bitrate for the track.
7056
7138
  */
7057
- const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight) => {
7139
+ const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, preferredBitrate) => {
7058
7140
  // if the current resolution is lower than the target resolution,
7059
7141
  // we want to proportionally reduce the target bitrate
7060
- const { width: targetWidth, height: targetHeight } = targetResolution;
7142
+ const { width: targetWidth, height: targetHeight, bitrate: targetBitrate, } = targetResolution;
7143
+ const bitrate = preferredBitrate || targetBitrate;
7061
7144
  if (currentWidth < targetWidth || currentHeight < targetHeight) {
7062
7145
  const currentPixels = currentWidth * currentHeight;
7063
7146
  const targetPixels = targetWidth * targetHeight;
7064
7147
  const reductionFactor = currentPixels / targetPixels;
7065
- return Math.round(targetResolution.bitrate * reductionFactor);
7148
+ return Math.round(bitrate * reductionFactor);
7066
7149
  }
7067
- return targetResolution.bitrate;
7150
+ return bitrate;
7068
7151
  };
7069
7152
  /**
7070
7153
  * Browsers have different simulcast constraints for different video resolutions.
@@ -8748,72 +8831,6 @@ const enableHighQualityAudio = (sdp, trackMid, maxBitrate = 510000) => {
8748
8831
  return SDP.write(parsedSdp);
8749
8832
  };
8750
8833
 
8751
- const version = "1.6.3" ;
8752
- const [major, minor, patch] = version.split('.');
8753
- let sdkInfo = {
8754
- type: SdkType.PLAIN_JAVASCRIPT,
8755
- major,
8756
- minor,
8757
- patch,
8758
- };
8759
- let osInfo;
8760
- let deviceInfo;
8761
- let webRtcInfo;
8762
- const setSdkInfo = (info) => {
8763
- sdkInfo = info;
8764
- };
8765
- const getSdkInfo = () => {
8766
- return sdkInfo;
8767
- };
8768
- const setOSInfo = (info) => {
8769
- osInfo = info;
8770
- };
8771
- const getOSInfo = () => {
8772
- return osInfo;
8773
- };
8774
- const setDeviceInfo = (info) => {
8775
- deviceInfo = info;
8776
- };
8777
- const getDeviceInfo = () => {
8778
- return deviceInfo;
8779
- };
8780
- const getWebRTCInfo = () => {
8781
- return webRtcInfo;
8782
- };
8783
- const setWebRTCInfo = (info) => {
8784
- webRtcInfo = info;
8785
- };
8786
- const getClientDetails = () => {
8787
- if (isReactNative()) {
8788
- // Since RN doesn't support web, sharing browser info is not required
8789
- return {
8790
- sdk: getSdkInfo(),
8791
- os: getOSInfo(),
8792
- device: getDeviceInfo(),
8793
- };
8794
- }
8795
- const userAgent = new UAParser(navigator.userAgent);
8796
- const { browser, os, device, cpu } = userAgent.getResult();
8797
- return {
8798
- sdk: getSdkInfo(),
8799
- browser: {
8800
- name: browser.name || navigator.userAgent,
8801
- version: browser.version || '',
8802
- },
8803
- os: {
8804
- name: os.name || '',
8805
- version: os.version || '',
8806
- architecture: cpu.architecture || '',
8807
- },
8808
- device: {
8809
- name: [device.vendor, device.model, device.type]
8810
- .filter(Boolean)
8811
- .join(' '),
8812
- version: '',
8813
- },
8814
- };
8815
- };
8816
-
8817
8834
  /**
8818
8835
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
8819
8836
  *
@@ -8942,24 +8959,12 @@ class Publisher {
8942
8959
  const targetResolution = settings?.video
8943
8960
  .target_resolution;
8944
8961
  const screenShareBitrate = settings?.screensharing.target_resolution?.bitrate;
8962
+ const { preferredBitrate, preferredCodec, screenShareSettings } = opts;
8945
8963
  const videoEncodings = trackType === TrackType.VIDEO
8946
- ? findOptimalVideoLayers(track, targetResolution)
8964
+ ? findOptimalVideoLayers(track, targetResolution, preferredBitrate)
8947
8965
  : trackType === TrackType.SCREEN_SHARE
8948
- ? findOptimalScreenSharingLayers(track, opts.screenShareSettings, screenShareBitrate)
8966
+ ? findOptimalScreenSharingLayers(track, screenShareSettings, screenShareBitrate)
8949
8967
  : undefined;
8950
- let preferredCodec = opts.preferredCodec;
8951
- if (!preferredCodec && trackType === TrackType.VIDEO && isReactNative()) {
8952
- const osName = getOSInfo()?.name.toLowerCase();
8953
- if (osName === 'ipados') {
8954
- // in ipads it was noticed that if vp8 codec is used
8955
- // then the bytes sent is 0 in the outbound-rtp
8956
- // so we are forcing h264 codec for ipads
8957
- preferredCodec = 'H264';
8958
- }
8959
- else if (osName === 'android') {
8960
- preferredCodec = 'VP8';
8961
- }
8962
- }
8963
8968
  // listen for 'ended' event on the track as it might be ended abruptly
8964
8969
  // by an external factor as permission revokes, device disconnected, etc.
8965
8970
  // keep in mind that `track.stop()` doesn't trigger this event.
@@ -8978,8 +8983,11 @@ class Publisher {
8978
8983
  this.transceiverInitOrder.push(trackType);
8979
8984
  this.transceiverRegistry[trackType] = transceiver;
8980
8985
  this.publishOptionsPerTrackType.set(trackType, opts);
8986
+ const codec = isReactNative() && trackType === TrackType.VIDEO && !preferredCodec
8987
+ ? getRNOptimalCodec()
8988
+ : preferredCodec;
8981
8989
  const codecPreferences = 'setCodecPreferences' in transceiver
8982
- ? this.getCodecPreferences(trackType, preferredCodec)
8990
+ ? this.getCodecPreferences(trackType, codec)
8983
8991
  : undefined;
8984
8992
  if (codecPreferences) {
8985
8993
  this.logger('info', `Setting ${TrackType[trackType]} codec preferences`, codecPreferences);
@@ -9293,7 +9301,7 @@ class Publisher {
9293
9301
  const publishOpts = this.publishOptionsPerTrackType.get(trackType);
9294
9302
  optimalLayers =
9295
9303
  trackType === TrackType.VIDEO
9296
- ? findOptimalVideoLayers(track, targetResolution)
9304
+ ? findOptimalVideoLayers(track, targetResolution, publishOpts?.preferredBitrate)
9297
9305
  : trackType === TrackType.SCREEN_SHARE
9298
9306
  ? findOptimalScreenSharingLayers(track, publishOpts?.screenShareSettings)
9299
9307
  : [];
@@ -11761,10 +11769,12 @@ class InputMediaDeviceManager {
11761
11769
  await this.applySettingsToStream();
11762
11770
  }
11763
11771
  async applySettingsToStream() {
11764
- if (this.enabled) {
11765
- await this.muteStream();
11766
- await this.unmuteStream();
11767
- }
11772
+ await withCancellation(this.statusChangeConcurrencyTag, async () => {
11773
+ if (this.enabled) {
11774
+ await this.muteStream();
11775
+ await this.unmuteStream();
11776
+ }
11777
+ });
11768
11778
  }
11769
11779
  getTracks() {
11770
11780
  return this.state.mediaStream?.getTracks() ?? [];
@@ -11907,17 +11917,19 @@ class InputMediaDeviceManager {
11907
11917
  }
11908
11918
  if (this.state.mediaStream !== stream) {
11909
11919
  this.state.setMediaStream(stream, await rootStream);
11920
+ const handleTrackEnded = async () => {
11921
+ await this.statusChangeSettled();
11922
+ if (this.enabled) {
11923
+ this.isTrackStoppedDueToTrackEnd = true;
11924
+ setTimeout(() => {
11925
+ this.isTrackStoppedDueToTrackEnd = false;
11926
+ }, 2000);
11927
+ await this.disable();
11928
+ }
11929
+ };
11910
11930
  this.getTracks().forEach((track) => {
11911
- track.addEventListener('ended', async () => {
11912
- await this.statusChangeSettled();
11913
- if (this.enabled) {
11914
- this.isTrackStoppedDueToTrackEnd = true;
11915
- setTimeout(() => {
11916
- this.isTrackStoppedDueToTrackEnd = false;
11917
- }, 2000);
11918
- await this.disable();
11919
- }
11920
- });
11931
+ track.addEventListener('ended', handleTrackEnded);
11932
+ this.subscriptions.push(() => track.removeEventListener('ended', handleTrackEnded));
11921
11933
  });
11922
11934
  }
11923
11935
  }
@@ -12167,6 +12179,17 @@ class CameraManager extends InputMediaDeviceManager {
12167
12179
  height: 720,
12168
12180
  };
12169
12181
  }
12182
+ /**
12183
+ * The publish options for the camera.
12184
+ *
12185
+ * @internal internal use only, not part of the public API.
12186
+ */
12187
+ get publishOptions() {
12188
+ return {
12189
+ preferredCodec: this.preferredCodec,
12190
+ preferredBitrate: this.preferredBitrate,
12191
+ };
12192
+ }
12170
12193
  /**
12171
12194
  * Select the camera direction.
12172
12195
  *
@@ -12223,6 +12246,15 @@ class CameraManager extends InputMediaDeviceManager {
12223
12246
  setPreferredCodec(codec) {
12224
12247
  this.preferredCodec = codec;
12225
12248
  }
12249
+ /**
12250
+ * Sets the preferred bitrate for encoding the video.
12251
+ *
12252
+ * @internal internal use only, not part of the public API.
12253
+ * @param bitrate the bitrate to use for encoding the video.
12254
+ */
12255
+ setPreferredBitrate(bitrate) {
12256
+ this.preferredBitrate = bitrate;
12257
+ }
12226
12258
  getDevices() {
12227
12259
  return getVideoDevices();
12228
12260
  }
@@ -12238,9 +12270,7 @@ class CameraManager extends InputMediaDeviceManager {
12238
12270
  return getVideoStream(constraints);
12239
12271
  }
12240
12272
  publishStream(stream) {
12241
- return this.call.publishVideoStream(stream, {
12242
- preferredCodec: this.preferredCodec,
12243
- });
12273
+ return this.call.publishVideoStream(stream, this.publishOptions);
12244
12274
  }
12245
12275
  stopPublishStream(stopTracks) {
12246
12276
  return this.call.stopPublish(TrackType.VIDEO, stopTracks);
@@ -13633,9 +13663,7 @@ class Call {
13633
13663
  case TrackType.VIDEO:
13634
13664
  const videoStream = this.camera.state.mediaStream;
13635
13665
  if (videoStream) {
13636
- await this.publishVideoStream(videoStream, {
13637
- preferredCodec: this.camera.preferredCodec,
13638
- });
13666
+ await this.publishVideoStream(videoStream, this.camera.publishOptions);
13639
13667
  }
13640
13668
  break;
13641
13669
  case TrackType.SCREEN_SHARE:
@@ -14311,9 +14339,7 @@ class Call {
14311
14339
  if (this.camera.enabled &&
14312
14340
  this.camera.state.mediaStream &&
14313
14341
  !this.publisher?.isPublishing(TrackType.VIDEO)) {
14314
- await this.publishVideoStream(this.camera.state.mediaStream, {
14315
- preferredCodec: this.camera.preferredCodec,
14316
- });
14342
+ await this.publishVideoStream(this.camera.state.mediaStream, this.camera.publishOptions);
14317
14343
  }
14318
14344
  // Start camera if backend config specifies, and there is no local setting
14319
14345
  if (this.camera.state.status === undefined &&
@@ -16092,7 +16118,7 @@ class StreamClient {
16092
16118
  });
16093
16119
  };
16094
16120
  this.getUserAgent = () => {
16095
- const version = "1.6.3" ;
16121
+ const version = "1.6.5" ;
16096
16122
  return (this.userAgent ||
16097
16123
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
16098
16124
  };