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