@stream-io/video-client 1.22.0 → 1.22.2

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.22.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.22.1...@stream-io/video-client-1.22.2) (2025-05-15)
6
+
7
+ - adjust ErrorFromResponse class ([#1791](https://github.com/GetStream/stream-video-js/issues/1791)) ([c0abcba](https://github.com/GetStream/stream-video-js/commit/c0abcbacfddeb87d8378c4418f80e6770981cdc8)), closes [GetStream/chat#1540](https://github.com/GetStream/chat/issues/1540)
8
+
9
+ ### Bug Fixes
10
+
11
+ - enable chore releases ([#1792](https://github.com/GetStream/stream-video-js/issues/1792)) ([6046654](https://github.com/GetStream/stream-video-js/commit/6046654fe19505a1c115a4fb838759d010540614))
12
+
13
+ ## [1.22.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.22.0...@stream-io/video-client-1.22.1) (2025-05-14)
14
+
15
+ ### Bug Fixes
16
+
17
+ - fixes an edge case where tracks weren't restored after a reconnect ([#1789](https://github.com/GetStream/stream-video-js/issues/1789)) ([d825e8e](https://github.com/GetStream/stream-video-js/commit/d825e8e39ac8cbd072ec9d5124e1ea0226216e08))
18
+
5
19
  ## [1.22.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.21.0...@stream-io/video-client-1.22.0) (2025-05-08)
6
20
 
7
21
  ### Features
@@ -298,6 +298,34 @@ const VideoSettingsResponseCameraFacingEnum = {
298
298
  };
299
299
 
300
300
  class ErrorFromResponse extends Error {
301
+ constructor({ message, code, status, response, unrecoverable, }) {
302
+ super(message);
303
+ this.name = 'ErrorFromResponse';
304
+ this.code = code;
305
+ this.response = response;
306
+ this.status = status;
307
+ this.unrecoverable = unrecoverable;
308
+ }
309
+ // Vitest helper (serialized errors are too large to read)
310
+ // https://github.com/vitest-dev/vitest/blob/v3.1.3/packages/utils/src/error.ts#L60-L62
311
+ toJSON() {
312
+ const extra = [
313
+ ['status', this.status],
314
+ ['code', this.code],
315
+ ['unrecoverable', this.unrecoverable],
316
+ ];
317
+ const joinable = [];
318
+ for (const [key, value] of extra) {
319
+ if (typeof value !== 'undefined' && value !== null) {
320
+ joinable.push(`${key}: ${value}`);
321
+ }
322
+ }
323
+ return {
324
+ message: `(${joinable.join(', ')}) - ${this.message}`,
325
+ stack: this.stack,
326
+ name: this.name,
327
+ };
328
+ }
301
329
  }
302
330
 
303
331
  // @generated by protobuf-ts 2.9.6 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
@@ -1361,6 +1389,12 @@ class PublishOption$Type extends MessageType {
1361
1389
  T: () => VideoDimension,
1362
1390
  },
1363
1391
  { no: 8, name: 'id', kind: 'scalar', T: 5 /*ScalarType.INT32*/ },
1392
+ {
1393
+ no: 9,
1394
+ name: 'use_single_layer',
1395
+ kind: 'scalar',
1396
+ T: 8 /*ScalarType.BOOL*/,
1397
+ },
1364
1398
  ]);
1365
1399
  }
1366
1400
  }
@@ -5646,7 +5680,7 @@ const aggregate = (stats) => {
5646
5680
  return report;
5647
5681
  };
5648
5682
 
5649
- const version = "1.22.0";
5683
+ const version = "1.22.2";
5650
5684
  const [major, minor, patch] = version.split('.');
5651
5685
  let sdkInfo = {
5652
5686
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -5909,6 +5943,11 @@ class SfuStatsReporter {
5909
5943
  clearTimeout(this.timeoutId);
5910
5944
  this.timeoutId = undefined;
5911
5945
  };
5946
+ this.flush = () => {
5947
+ this.run().catch((err) => {
5948
+ this.logger('warn', 'Failed to flush report stats', err);
5949
+ });
5950
+ };
5912
5951
  this.scheduleOne = (timeout) => {
5913
5952
  clearTimeout(this.timeoutId);
5914
5953
  this.timeoutId = setTimeout(() => {
@@ -6371,11 +6410,12 @@ class BasePeerConnection {
6371
6410
  // do nothing when ICE is restarting
6372
6411
  if (this.isIceRestarting)
6373
6412
  return;
6374
- if (state === 'failed' || state === 'disconnected') {
6413
+ if (state === 'failed') {
6414
+ this.onUnrecoverableError?.('ICE connection failed');
6415
+ }
6416
+ else if (state === 'disconnected') {
6375
6417
  this.logger('debug', `Attempting to restart ICE`);
6376
6418
  this.restartIce().catch((e) => {
6377
- if (this.isDisposed)
6378
- return;
6379
6419
  const reason = `ICE restart failed`;
6380
6420
  this.logger('error', reason, e);
6381
6421
  this.onUnrecoverableError?.(`${reason}: ${e}`);
@@ -6643,7 +6683,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6643
6683
  const optimalVideoLayers = [];
6644
6684
  const settings = videoTrack.getSettings();
6645
6685
  const { width = 0, height = 0 } = settings;
6646
- const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, } = publishOption;
6686
+ const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
6647
6687
  const maxBitrate = getComputedMaxBitrate(videoDimension, width, height, bitrate);
6648
6688
  let downscaleFactor = 1;
6649
6689
  let bitrateFactor = 1;
@@ -6660,7 +6700,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6660
6700
  if (svcCodec) {
6661
6701
  // for SVC codecs, we need to set the scalability mode, and the
6662
6702
  // codec will handle the rest (layers, temporal layers, etc.)
6663
- layer.scalabilityMode = toScalabilityMode(maxSpatialLayers, maxTemporalLayers);
6703
+ layer.scalabilityMode = toScalabilityMode(useSingleLayer ? 1 : maxSpatialLayers, maxTemporalLayers);
6664
6704
  }
6665
6705
  else {
6666
6706
  // for non-SVC codecs, we need to downscale proportionally (simulcast)
@@ -6675,7 +6715,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6675
6715
  }
6676
6716
  // for simplicity, we start with all layers enabled, then this function
6677
6717
  // will clear/reassign the layers that are not needed
6678
- return withSimulcastConstraints(settings, optimalVideoLayers);
6718
+ return withSimulcastConstraints(settings, optimalVideoLayers, useSingleLayer);
6679
6719
  };
6680
6720
  /**
6681
6721
  * Computes the maximum bitrate for a given resolution.
@@ -6709,7 +6749,7 @@ const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, bi
6709
6749
  *
6710
6750
  * https://chromium.googlesource.com/external/webrtc/+/refs/heads/main/media/engine/simulcast.cc#90
6711
6751
  */
6712
- const withSimulcastConstraints = (settings, optimalVideoLayers) => {
6752
+ const withSimulcastConstraints = (settings, optimalVideoLayers, useSingleLayer) => {
6713
6753
  let layers;
6714
6754
  const size = Math.max(settings.width || 0, settings.height || 0);
6715
6755
  if (size <= 320) {
@@ -6725,9 +6765,10 @@ const withSimulcastConstraints = (settings, optimalVideoLayers) => {
6725
6765
  layers = optimalVideoLayers;
6726
6766
  }
6727
6767
  const ridMapping = ['q', 'h', 'f'];
6728
- return layers.map((layer, index) => ({
6768
+ return layers.map((layer, index, arr) => ({
6729
6769
  ...layer,
6730
6770
  rid: ridMapping[index], // reassign rid
6771
+ active: useSingleLayer && index < arr.length - 1 ? false : layer.active,
6731
6772
  }));
6732
6773
  };
6733
6774
 
@@ -6814,6 +6855,9 @@ class Publisher extends BasePeerConnection {
6814
6855
  direction: 'sendonly',
6815
6856
  sendEncodings,
6816
6857
  });
6858
+ const params = transceiver.sender.getParameters();
6859
+ params.degradationPreference = 'maintain-framerate';
6860
+ await transceiver.sender.setParameters(params);
6817
6861
  const trackType = publishOption.trackType;
6818
6862
  this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
6819
6863
  this.transceiverCache.add(publishOption, transceiver);
@@ -10993,6 +11037,7 @@ class Call {
10993
11037
  }
10994
11038
  this.statsReporter?.stop();
10995
11039
  this.statsReporter = undefined;
11040
+ this.sfuStatsReporter?.flush();
10996
11041
  this.sfuStatsReporter?.stop();
10997
11042
  this.sfuStatsReporter = undefined;
10998
11043
  this.subscriber?.dispose();
@@ -11306,7 +11351,7 @@ class Call {
11306
11351
  if (!performingRejoin && !performingFastReconnect && !performingMigration) {
11307
11352
  this.sfuStatsReporter?.sendConnectionTime((Date.now() - connectStartTime) / 1000);
11308
11353
  }
11309
- if (performingRejoin) {
11354
+ if (performingRejoin && isWsHealthy) {
11310
11355
  const strategy = WebsocketReconnectStrategy[this.reconnectStrategy];
11311
11356
  await previousSfuClient?.leaveAndClose(`Closing previous WS after reconnect with strategy: ${strategy}`);
11312
11357
  }
@@ -11558,10 +11603,10 @@ class Call {
11558
11603
  */
11559
11604
  this.reconnect = async (strategy, reason) => {
11560
11605
  if (this.state.callingState === CallingState.RECONNECTING ||
11606
+ this.state.callingState === CallingState.MIGRATING ||
11561
11607
  this.state.callingState === CallingState.RECONNECTING_FAILED)
11562
11608
  return;
11563
11609
  return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
11564
- this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
11565
11610
  const reconnectStartTime = Date.now();
11566
11611
  this.reconnectStrategy = strategy;
11567
11612
  this.reconnectReason = reason;
@@ -11581,6 +11626,7 @@ class Call {
11581
11626
  try {
11582
11627
  // wait until the network is available
11583
11628
  await this.networkAvailableTask?.promise;
11629
+ this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
11584
11630
  switch (this.reconnectStrategy) {
11585
11631
  case WebsocketReconnectStrategy.UNSPECIFIED:
11586
11632
  case WebsocketReconnectStrategy.DISCONNECT:
@@ -11620,6 +11666,7 @@ class Call {
11620
11666
  } while (this.state.callingState !== CallingState.JOINED &&
11621
11667
  this.state.callingState !== CallingState.RECONNECTING_FAILED &&
11622
11668
  this.state.callingState !== CallingState.LEFT);
11669
+ this.logger('info', '[Reconnect] Reconnection flow finished');
11623
11670
  });
11624
11671
  };
11625
11672
  /**
@@ -13676,13 +13723,13 @@ class StreamClient {
13676
13723
  };
13677
13724
  this.errorFromResponse = (response) => {
13678
13725
  const { data, status } = response;
13679
- const err = new ErrorFromResponse();
13680
- err.message = `Stream error code ${data.code}: ${data.message}`;
13681
- err.code = data.code;
13682
- err.unrecoverable = data.unrecoverable;
13683
- err.response = response;
13684
- err.status = status;
13685
- return err;
13726
+ return new ErrorFromResponse({
13727
+ message: `Stream error code ${data.code}: ${data.message}`,
13728
+ code: data.code ?? null,
13729
+ unrecoverable: data.unrecoverable ?? null,
13730
+ response: response,
13731
+ status: status,
13732
+ });
13686
13733
  };
13687
13734
  this.handleResponse = (response) => {
13688
13735
  const data = response.data;
@@ -13723,7 +13770,7 @@ class StreamClient {
13723
13770
  this.getUserAgent = () => {
13724
13771
  if (!this.cachedUserAgent) {
13725
13772
  const { clientAppIdentifier = {} } = this.options;
13726
- const { sdkName = 'js', sdkVersion = "1.22.0", ...extras } = clientAppIdentifier;
13773
+ const { sdkName = 'js', sdkVersion = "1.22.2", ...extras } = clientAppIdentifier;
13727
13774
  this.cachedUserAgent = [
13728
13775
  `stream-video-${sdkName}-v${sdkVersion}`,
13729
13776
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),