@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 +14 -0
- package/dist/index.browser.es.js +66 -19
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +66 -19
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +66 -19
- package/dist/index.es.js.map +1 -1
- package/dist/src/coordinator/connection/types.d.ts +11 -4
- package/dist/src/gen/video/sfu/models/models.d.ts +8 -0
- package/dist/src/stats/SfuStatsReporter.d.ts +1 -0
- package/package.json +2 -2
- package/src/Call.ts +10 -6
- package/src/coordinator/connection/client.ts +7 -7
- package/src/coordinator/connection/types.ts +46 -4
- package/src/gen/video/sfu/models/models.ts +14 -0
- package/src/rtc/BasePeerConnection.ts +3 -2
- package/src/rtc/Publisher.ts +4 -0
- package/src/rtc/__tests__/Publisher.test.ts +2 -2
- package/src/rtc/__tests__/Subscriber.test.ts +2 -2
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -1
- package/src/rtc/__tests__/videoLayers.test.ts +44 -0
- package/src/rtc/videoLayers.ts +6 -3
- package/src/stats/SfuStatsReporter.ts +6 -0
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
|
package/dist/index.browser.es.js
CHANGED
|
@@ -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.
|
|
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'
|
|
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
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
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.
|
|
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}`),
|