@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/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.
|
|
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'
|
|
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
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
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.
|
|
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}`),
|