@stream-io/video-client 1.22.0 → 1.22.1

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,12 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [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)
6
+
7
+ ### Bug Fixes
8
+
9
+ - 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))
10
+
5
11
  ## [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
12
 
7
13
  ### Features
@@ -1361,6 +1361,12 @@ class PublishOption$Type extends MessageType {
1361
1361
  T: () => VideoDimension,
1362
1362
  },
1363
1363
  { no: 8, name: 'id', kind: 'scalar', T: 5 /*ScalarType.INT32*/ },
1364
+ {
1365
+ no: 9,
1366
+ name: 'use_single_layer',
1367
+ kind: 'scalar',
1368
+ T: 8 /*ScalarType.BOOL*/,
1369
+ },
1364
1370
  ]);
1365
1371
  }
1366
1372
  }
@@ -5646,7 +5652,7 @@ const aggregate = (stats) => {
5646
5652
  return report;
5647
5653
  };
5648
5654
 
5649
- const version = "1.22.0";
5655
+ const version = "1.22.1";
5650
5656
  const [major, minor, patch] = version.split('.');
5651
5657
  let sdkInfo = {
5652
5658
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -5909,6 +5915,11 @@ class SfuStatsReporter {
5909
5915
  clearTimeout(this.timeoutId);
5910
5916
  this.timeoutId = undefined;
5911
5917
  };
5918
+ this.flush = () => {
5919
+ this.run().catch((err) => {
5920
+ this.logger('warn', 'Failed to flush report stats', err);
5921
+ });
5922
+ };
5912
5923
  this.scheduleOne = (timeout) => {
5913
5924
  clearTimeout(this.timeoutId);
5914
5925
  this.timeoutId = setTimeout(() => {
@@ -6371,11 +6382,12 @@ class BasePeerConnection {
6371
6382
  // do nothing when ICE is restarting
6372
6383
  if (this.isIceRestarting)
6373
6384
  return;
6374
- if (state === 'failed' || state === 'disconnected') {
6385
+ if (state === 'failed') {
6386
+ this.onUnrecoverableError?.('ICE connection failed');
6387
+ }
6388
+ else if (state === 'disconnected') {
6375
6389
  this.logger('debug', `Attempting to restart ICE`);
6376
6390
  this.restartIce().catch((e) => {
6377
- if (this.isDisposed)
6378
- return;
6379
6391
  const reason = `ICE restart failed`;
6380
6392
  this.logger('error', reason, e);
6381
6393
  this.onUnrecoverableError?.(`${reason}: ${e}`);
@@ -6643,7 +6655,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6643
6655
  const optimalVideoLayers = [];
6644
6656
  const settings = videoTrack.getSettings();
6645
6657
  const { width = 0, height = 0 } = settings;
6646
- const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, } = publishOption;
6658
+ const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
6647
6659
  const maxBitrate = getComputedMaxBitrate(videoDimension, width, height, bitrate);
6648
6660
  let downscaleFactor = 1;
6649
6661
  let bitrateFactor = 1;
@@ -6660,7 +6672,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6660
6672
  if (svcCodec) {
6661
6673
  // for SVC codecs, we need to set the scalability mode, and the
6662
6674
  // codec will handle the rest (layers, temporal layers, etc.)
6663
- layer.scalabilityMode = toScalabilityMode(maxSpatialLayers, maxTemporalLayers);
6675
+ layer.scalabilityMode = toScalabilityMode(useSingleLayer ? 1 : maxSpatialLayers, maxTemporalLayers);
6664
6676
  }
6665
6677
  else {
6666
6678
  // for non-SVC codecs, we need to downscale proportionally (simulcast)
@@ -6675,7 +6687,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
6675
6687
  }
6676
6688
  // for simplicity, we start with all layers enabled, then this function
6677
6689
  // will clear/reassign the layers that are not needed
6678
- return withSimulcastConstraints(settings, optimalVideoLayers);
6690
+ return withSimulcastConstraints(settings, optimalVideoLayers, useSingleLayer);
6679
6691
  };
6680
6692
  /**
6681
6693
  * Computes the maximum bitrate for a given resolution.
@@ -6709,7 +6721,7 @@ const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, bi
6709
6721
  *
6710
6722
  * https://chromium.googlesource.com/external/webrtc/+/refs/heads/main/media/engine/simulcast.cc#90
6711
6723
  */
6712
- const withSimulcastConstraints = (settings, optimalVideoLayers) => {
6724
+ const withSimulcastConstraints = (settings, optimalVideoLayers, useSingleLayer) => {
6713
6725
  let layers;
6714
6726
  const size = Math.max(settings.width || 0, settings.height || 0);
6715
6727
  if (size <= 320) {
@@ -6725,9 +6737,10 @@ const withSimulcastConstraints = (settings, optimalVideoLayers) => {
6725
6737
  layers = optimalVideoLayers;
6726
6738
  }
6727
6739
  const ridMapping = ['q', 'h', 'f'];
6728
- return layers.map((layer, index) => ({
6740
+ return layers.map((layer, index, arr) => ({
6729
6741
  ...layer,
6730
6742
  rid: ridMapping[index], // reassign rid
6743
+ active: useSingleLayer && index < arr.length - 1 ? false : layer.active,
6731
6744
  }));
6732
6745
  };
6733
6746
 
@@ -6814,6 +6827,9 @@ class Publisher extends BasePeerConnection {
6814
6827
  direction: 'sendonly',
6815
6828
  sendEncodings,
6816
6829
  });
6830
+ const params = transceiver.sender.getParameters();
6831
+ params.degradationPreference = 'maintain-framerate';
6832
+ await transceiver.sender.setParameters(params);
6817
6833
  const trackType = publishOption.trackType;
6818
6834
  this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
6819
6835
  this.transceiverCache.add(publishOption, transceiver);
@@ -10993,6 +11009,7 @@ class Call {
10993
11009
  }
10994
11010
  this.statsReporter?.stop();
10995
11011
  this.statsReporter = undefined;
11012
+ this.sfuStatsReporter?.flush();
10996
11013
  this.sfuStatsReporter?.stop();
10997
11014
  this.sfuStatsReporter = undefined;
10998
11015
  this.subscriber?.dispose();
@@ -11306,7 +11323,7 @@ class Call {
11306
11323
  if (!performingRejoin && !performingFastReconnect && !performingMigration) {
11307
11324
  this.sfuStatsReporter?.sendConnectionTime((Date.now() - connectStartTime) / 1000);
11308
11325
  }
11309
- if (performingRejoin) {
11326
+ if (performingRejoin && isWsHealthy) {
11310
11327
  const strategy = WebsocketReconnectStrategy[this.reconnectStrategy];
11311
11328
  await previousSfuClient?.leaveAndClose(`Closing previous WS after reconnect with strategy: ${strategy}`);
11312
11329
  }
@@ -11558,10 +11575,10 @@ class Call {
11558
11575
  */
11559
11576
  this.reconnect = async (strategy, reason) => {
11560
11577
  if (this.state.callingState === CallingState.RECONNECTING ||
11578
+ this.state.callingState === CallingState.MIGRATING ||
11561
11579
  this.state.callingState === CallingState.RECONNECTING_FAILED)
11562
11580
  return;
11563
11581
  return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
11564
- this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
11565
11582
  const reconnectStartTime = Date.now();
11566
11583
  this.reconnectStrategy = strategy;
11567
11584
  this.reconnectReason = reason;
@@ -11581,6 +11598,7 @@ class Call {
11581
11598
  try {
11582
11599
  // wait until the network is available
11583
11600
  await this.networkAvailableTask?.promise;
11601
+ this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
11584
11602
  switch (this.reconnectStrategy) {
11585
11603
  case WebsocketReconnectStrategy.UNSPECIFIED:
11586
11604
  case WebsocketReconnectStrategy.DISCONNECT:
@@ -11620,6 +11638,7 @@ class Call {
11620
11638
  } while (this.state.callingState !== CallingState.JOINED &&
11621
11639
  this.state.callingState !== CallingState.RECONNECTING_FAILED &&
11622
11640
  this.state.callingState !== CallingState.LEFT);
11641
+ this.logger('info', '[Reconnect] Reconnection flow finished');
11623
11642
  });
11624
11643
  };
11625
11644
  /**
@@ -13723,7 +13742,7 @@ class StreamClient {
13723
13742
  this.getUserAgent = () => {
13724
13743
  if (!this.cachedUserAgent) {
13725
13744
  const { clientAppIdentifier = {} } = this.options;
13726
- const { sdkName = 'js', sdkVersion = "1.22.0", ...extras } = clientAppIdentifier;
13745
+ const { sdkName = 'js', sdkVersion = "1.22.1", ...extras } = clientAppIdentifier;
13727
13746
  this.cachedUserAgent = [
13728
13747
  `stream-video-${sdkName}-v${sdkVersion}`,
13729
13748
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),