@stream-io/video-client 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.7.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.7.2...@stream-io/video-client-1.7.3) (2024-09-24)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * do not always error out api calls when web socket initially failed ([#1495](https://github.com/GetStream/stream-video-js/issues/1495)) ([7cdb62e](https://github.com/GetStream/stream-video-js/commit/7cdb62e75cad56098ee81eabbcc63382f93fd218))
11
+
12
+ ## [1.7.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.7.1...@stream-io/video-client-1.7.2) (2024-09-20)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * overridable bitrate and bitrate downscale factor ([#1493](https://github.com/GetStream/stream-video-js/issues/1493)) ([cce5d8e](https://github.com/GetStream/stream-video-js/commit/cce5d8e641a9182a1779952e4e62aa16ec21ab92))
18
+
5
19
  ## [1.7.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.7.0...@stream-io/video-client-1.7.1) (2024-09-20)
6
20
 
7
21
 
@@ -2875,6 +2875,13 @@ function convertErrorToJson(err) {
2875
2875
  }
2876
2876
  return jsonObj;
2877
2877
  }
2878
+ /**
2879
+ * Informs if a promise is yet to be resolved or rejected
2880
+ */
2881
+ async function isPromisePending(promise) {
2882
+ const emptyObj = {};
2883
+ return Promise.race([promise, emptyObj]).then((value) => (value === emptyObj ? true : false), () => false);
2884
+ }
2878
2885
  /**
2879
2886
  * isOnline safely return the navigator.online value for browser env
2880
2887
  * if navigator is not in global object, it always return true
@@ -3013,7 +3020,7 @@ const retryable = async (rpc, signal) => {
3013
3020
  return result;
3014
3021
  };
3015
3022
 
3016
- const version = "1.7.1";
3023
+ const version = "1.7.3";
3017
3024
  const [major, minor, patch] = version.split('.');
3018
3025
  let sdkInfo = {
3019
3026
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -3290,14 +3297,16 @@ const defaultBitratePerRid = {
3290
3297
  *
3291
3298
  * @param videoTrack the video track to find optimal layers for.
3292
3299
  * @param targetResolution the expected target resolution.
3293
- * @param preferredBitrate the preferred bitrate for the video track.
3300
+ * @param publishOptions the publish options for the track.
3294
3301
  */
3295
- const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution, preferredBitrate) => {
3302
+ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution, publishOptions) => {
3296
3303
  const optimalVideoLayers = [];
3297
3304
  const settings = videoTrack.getSettings();
3298
3305
  const { width: w = 0, height: h = 0 } = settings;
3306
+ const { preferredBitrate, bitrateDownscaleFactor = 2 } = publishOptions || {};
3299
3307
  const maxBitrate = getComputedMaxBitrate(targetResolution, w, h, preferredBitrate);
3300
3308
  let downscaleFactor = 1;
3309
+ let bitrateFactor = 1;
3301
3310
  ['f', 'h', 'q'].forEach((rid) => {
3302
3311
  // Reversing the order [f, h, q] to [q, h, f] as Chrome uses encoding index
3303
3312
  // when deciding which layer to disable when CPU or bandwidth is constrained.
@@ -3307,11 +3316,12 @@ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetReso
3307
3316
  rid,
3308
3317
  width: Math.round(w / downscaleFactor),
3309
3318
  height: Math.round(h / downscaleFactor),
3310
- maxBitrate: Math.round(maxBitrate / downscaleFactor) || defaultBitratePerRid[rid],
3319
+ maxBitrate: Math.round(maxBitrate / bitrateFactor) || defaultBitratePerRid[rid],
3311
3320
  scaleResolutionDownBy: downscaleFactor,
3312
3321
  maxFramerate: 30,
3313
3322
  });
3314
3323
  downscaleFactor *= 2;
3324
+ bitrateFactor *= bitrateDownscaleFactor;
3315
3325
  });
3316
3326
  // for simplicity, we start with all layers enabled, then this function
3317
3327
  // will clear/reassign the layers that are not needed
@@ -3371,7 +3381,8 @@ const withSimulcastConstraints = (settings, optimalVideoLayers) => {
3371
3381
  rid: ridMapping[index], // reassign rid
3372
3382
  }));
3373
3383
  };
3374
- const findOptimalScreenSharingLayers = (videoTrack, preferences, defaultMaxBitrate = 3000000) => {
3384
+ const findOptimalScreenSharingLayers = (videoTrack, publishOptions, defaultMaxBitrate = 3000000) => {
3385
+ const { screenShareSettings: preferences } = publishOptions || {};
3375
3386
  const settings = videoTrack.getSettings();
3376
3387
  return [
3377
3388
  {
@@ -5152,11 +5163,10 @@ class Publisher {
5152
5163
  const targetResolution = settings?.video
5153
5164
  .target_resolution;
5154
5165
  const screenShareBitrate = settings?.screensharing.target_resolution?.bitrate;
5155
- const { preferredBitrate, preferredCodec, screenShareSettings } = opts;
5156
5166
  const videoEncodings = trackType === TrackType.VIDEO
5157
- ? findOptimalVideoLayers(track, targetResolution, preferredBitrate)
5167
+ ? findOptimalVideoLayers(track, targetResolution, opts)
5158
5168
  : trackType === TrackType.SCREEN_SHARE
5159
- ? findOptimalScreenSharingLayers(track, screenShareSettings, screenShareBitrate)
5169
+ ? findOptimalScreenSharingLayers(track, opts, screenShareBitrate)
5160
5170
  : undefined;
5161
5171
  // listen for 'ended' event on the track as it might be ended abruptly
5162
5172
  // by an external factor as permission revokes, device disconnected, etc.
@@ -5176,6 +5186,7 @@ class Publisher {
5176
5186
  this.transceiverInitOrder.push(trackType);
5177
5187
  this.transceiverRegistry[trackType] = transceiver;
5178
5188
  this.publishOptionsPerTrackType.set(trackType, opts);
5189
+ const { preferredCodec } = opts;
5179
5190
  const codec = isReactNative() && trackType === TrackType.VIDEO && !preferredCodec
5180
5191
  ? getRNOptimalCodec()
5181
5192
  : preferredCodec;
@@ -5494,9 +5505,9 @@ class Publisher {
5494
5505
  const publishOpts = this.publishOptionsPerTrackType.get(trackType);
5495
5506
  optimalLayers =
5496
5507
  trackType === TrackType.VIDEO
5497
- ? findOptimalVideoLayers(track, targetResolution, publishOpts?.preferredBitrate)
5508
+ ? findOptimalVideoLayers(track, targetResolution, publishOpts)
5498
5509
  : trackType === TrackType.SCREEN_SHARE
5499
- ? findOptimalScreenSharingLayers(track, publishOpts?.screenShareSettings)
5510
+ ? findOptimalScreenSharingLayers(track, publishOpts)
5500
5511
  : [];
5501
5512
  this.trackLayersCache[trackType] = optimalLayers;
5502
5513
  }
@@ -8380,6 +8391,11 @@ class CameraManagerState extends InputMediaDeviceManagerState {
8380
8391
  }
8381
8392
 
8382
8393
  class CameraManager extends InputMediaDeviceManager {
8394
+ /**
8395
+ * Constructs a new CameraManager.
8396
+ *
8397
+ * @param call the call instance.
8398
+ */
8383
8399
  constructor(call) {
8384
8400
  super(call, new CameraManagerState(), TrackType.VIDEO);
8385
8401
  this.targetResolution = {
@@ -8387,17 +8403,6 @@ class CameraManager extends InputMediaDeviceManager {
8387
8403
  height: 720,
8388
8404
  };
8389
8405
  }
8390
- /**
8391
- * The publish options for the camera.
8392
- *
8393
- * @internal internal use only, not part of the public API.
8394
- */
8395
- get publishOptions() {
8396
- return {
8397
- preferredCodec: this.preferredCodec,
8398
- preferredBitrate: this.preferredBitrate,
8399
- };
8400
- }
8401
8406
  /**
8402
8407
  * Select the camera direction.
8403
8408
  *
@@ -8452,16 +8457,33 @@ class CameraManager extends InputMediaDeviceManager {
8452
8457
  * @param codec the codec to use for encoding the video.
8453
8458
  */
8454
8459
  setPreferredCodec(codec) {
8455
- this.preferredCodec = codec;
8460
+ this.updatePublishOptions({ preferredCodec: codec });
8456
8461
  }
8457
8462
  /**
8458
- * Sets the preferred bitrate for encoding the video.
8463
+ * Updates the preferred publish options for the video stream.
8459
8464
  *
8460
- * @internal internal use only, not part of the public API.
8461
- * @param bitrate the bitrate to use for encoding the video.
8465
+ * @internal
8466
+ * @param options the options to use.
8462
8467
  */
8463
- setPreferredBitrate(bitrate) {
8464
- this.preferredBitrate = bitrate;
8468
+ updatePublishOptions(options) {
8469
+ this.publishOptions = { ...this.publishOptions, ...options };
8470
+ }
8471
+ /**
8472
+ * Returns the capture resolution of the camera.
8473
+ */
8474
+ getCaptureResolution() {
8475
+ const { mediaStream } = this.state;
8476
+ if (!mediaStream)
8477
+ return;
8478
+ const [videoTrack] = mediaStream.getVideoTracks();
8479
+ if (!videoTrack)
8480
+ return;
8481
+ const settings = videoTrack.getSettings();
8482
+ return {
8483
+ width: settings.width,
8484
+ height: settings.height,
8485
+ frameRate: settings.frameRate,
8486
+ };
8465
8487
  }
8466
8488
  getDevices() {
8467
8489
  return getVideoDevices();
@@ -11350,6 +11372,15 @@ class StableWSConnection {
11350
11372
  this._log(`_connect() - tokenProvider failed before, so going to retry`);
11351
11373
  await this.client.tokenManager.loadToken();
11352
11374
  }
11375
+ let mustSetupConnectionIdPromise = true;
11376
+ if (this.client.connectionIdPromise) {
11377
+ if (await isPromisePending(this.client.connectionIdPromise)) {
11378
+ mustSetupConnectionIdPromise = false;
11379
+ }
11380
+ }
11381
+ if (mustSetupConnectionIdPromise) {
11382
+ this.client._setupConnectionIdPromise();
11383
+ }
11353
11384
  this._setupConnectionPromise();
11354
11385
  const wsURL = this._buildUrl();
11355
11386
  this._log(`_connect() - Connecting to ${wsURL}`, {
@@ -11375,6 +11406,7 @@ class StableWSConnection {
11375
11406
  }
11376
11407
  }
11377
11408
  catch (err) {
11409
+ await this.client._setupConnectionIdPromise();
11378
11410
  this.isConnecting = false;
11379
11411
  // @ts-ignore
11380
11412
  this._log(`_connect() - Error - `, err);
@@ -12032,10 +12064,7 @@ class StreamClient {
12032
12064
  this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
12033
12065
  return Promise.resolve();
12034
12066
  }
12035
- this.connectionIdPromise = new Promise((resolve, reject) => {
12036
- this.resolveConnectionId = resolve;
12037
- this.rejectConnectionId = reject;
12038
- });
12067
+ this._setupConnectionIdPromise();
12039
12068
  this.clientID = `${this.userID}--${randomId()}`;
12040
12069
  this.wsPromise = this.connect();
12041
12070
  return this.wsPromise;
@@ -12075,10 +12104,7 @@ class StreamClient {
12075
12104
  */
12076
12105
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
12077
12106
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12078
- this.connectionIdPromise = new Promise((resolve, reject) => {
12079
- this.resolveConnectionId = resolve;
12080
- this.rejectConnectionId = reject;
12081
- });
12107
+ this._setupConnectionIdPromise();
12082
12108
  this.anonymous = true;
12083
12109
  await this._setToken(user, tokenOrProvider, this.anonymous);
12084
12110
  this._setUser(user);
@@ -12117,6 +12143,16 @@ class StreamClient {
12117
12143
  this.logger('debug', `Removing listener for ${eventName} event`);
12118
12144
  this.listeners[eventName] = this.listeners[eventName]?.filter((value) => value !== callback);
12119
12145
  };
12146
+ /**
12147
+ * sets up the this.connectionIdPromise
12148
+ */
12149
+ this._setupConnectionIdPromise = async () => {
12150
+ /** a promise that is resolved once connection id is set */
12151
+ this.connectionIdPromise = new Promise((resolve, reject) => {
12152
+ this.resolveConnectionId = resolve;
12153
+ this.rejectConnectionId = reject;
12154
+ });
12155
+ };
12120
12156
  this._logApiRequest = (type, url, data, config) => {
12121
12157
  this.logger('trace', `client: ${type} - Request - ${url}`, {
12122
12158
  payload: data,
@@ -12139,8 +12175,18 @@ class StreamClient {
12139
12175
  await Promise.all([
12140
12176
  this.tokenManager.tokenReady(),
12141
12177
  this.guestUserCreatePromise,
12142
- this.connectionIdPromise,
12143
12178
  ]);
12179
+ // we need to wait for presence of connection id before making requests
12180
+ try {
12181
+ await this.connectionIdPromise;
12182
+ }
12183
+ catch (e) {
12184
+ // in case connection id was rejected
12185
+ // reconnection maybe in progress
12186
+ // we can wait for healthy connection to resolve, which rejects when 15s timeout is reached
12187
+ await this.wsConnection?._waitForHealthy();
12188
+ await this.connectionIdPromise;
12189
+ }
12144
12190
  }
12145
12191
  const requestConfig = this._enrichAxiosOptions(options);
12146
12192
  try {
@@ -12323,7 +12369,7 @@ class StreamClient {
12323
12369
  });
12324
12370
  };
12325
12371
  this.getUserAgent = () => {
12326
- const version = "1.7.1";
12372
+ const version = "1.7.3";
12327
12373
  return (this.userAgent ||
12328
12374
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
12329
12375
  };