@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 +6 -0
- package/dist/index.browser.es.js +31 -12
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +31 -12
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +31 -12
- package/dist/index.es.js.map +1 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +8 -0
- package/dist/src/stats/SfuStatsReporter.d.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +10 -6
- 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,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
|
package/dist/index.browser.es.js
CHANGED
|
@@ -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.
|
|
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'
|
|
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.
|
|
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}`),
|