@stream-io/video-client 1.11.10 → 1.11.12
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 +84 -65
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +84 -65
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +84 -65
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +7 -0
- package/dist/src/StreamSfuClient.d.ts +1 -3
- package/dist/src/helpers/promise.d.ts +14 -0
- package/package.json +1 -1
- package/src/Call.ts +38 -3
- package/src/StreamSfuClient.ts +27 -28
- package/src/devices/InputMediaDeviceManager.ts +0 -1
- package/src/devices/devices.ts +30 -16
- package/src/helpers/promise.ts +44 -0
- package/dist/src/helpers/withResolvers.d.ts +0 -14
- package/src/helpers/withResolvers.ts +0 -43
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.11.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.11...@stream-io/video-client-1.11.12) (2024-12-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* handle timeout on SFU WS connections ([#1600](https://github.com/GetStream/stream-video-js/issues/1600)) ([5f2db7b](https://github.com/GetStream/stream-video-js/commit/5f2db7bd5cfdf57cdc04d6a6ed752f43e5b06657))
|
|
11
|
+
|
|
12
|
+
## [1.11.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.10...@stream-io/video-client-1.11.11) (2024-11-29)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* revert [#1604](https://github.com/GetStream/stream-video-js/issues/1604) ([#1607](https://github.com/GetStream/stream-video-js/issues/1607)) ([567e4fb](https://github.com/GetStream/stream-video-js/commit/567e4fb309509b6b0d814826856d0a15efe16271))
|
|
18
|
+
|
|
5
19
|
## [1.11.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.9...@stream-io/video-client-1.11.10) (2024-11-28)
|
|
6
20
|
|
|
7
21
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -3297,7 +3297,7 @@ const retryable = async (rpc, signal) => {
|
|
|
3297
3297
|
return result;
|
|
3298
3298
|
};
|
|
3299
3299
|
|
|
3300
|
-
const version = "1.11.
|
|
3300
|
+
const version = "1.11.12";
|
|
3301
3301
|
const [major, minor, patch] = version.split('.');
|
|
3302
3302
|
let sdkInfo = {
|
|
3303
3303
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6374,6 +6374,32 @@ const createWebSocketSignalChannel = (opts) => {
|
|
|
6374
6374
|
return ws;
|
|
6375
6375
|
};
|
|
6376
6376
|
|
|
6377
|
+
/**
|
|
6378
|
+
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
6379
|
+
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
6380
|
+
* rejection is handled everywhere promise result is expected).
|
|
6381
|
+
*
|
|
6382
|
+
* To avoid that, we add both resolution and rejection handlers to the promise.
|
|
6383
|
+
* That way, the saved promise never rejects. A callback is provided as return
|
|
6384
|
+
* value to build a *new* promise, that resolves and rejects along with
|
|
6385
|
+
* the original promise.
|
|
6386
|
+
* @param promise Promise to wrap, which possibly rejects
|
|
6387
|
+
* @returns Callback to build a new promise, which resolves and rejects along
|
|
6388
|
+
* with the original promise
|
|
6389
|
+
*/
|
|
6390
|
+
function makeSafePromise(promise) {
|
|
6391
|
+
let isPending = true;
|
|
6392
|
+
const safePromise = promise
|
|
6393
|
+
.then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
|
|
6394
|
+
.finally(() => (isPending = false));
|
|
6395
|
+
const unwrapPromise = () => safePromise.then((fulfillment) => {
|
|
6396
|
+
if (fulfillment.status === 'rejected')
|
|
6397
|
+
throw fulfillment.error;
|
|
6398
|
+
return fulfillment.result;
|
|
6399
|
+
});
|
|
6400
|
+
unwrapPromise.checkPending = () => isPending;
|
|
6401
|
+
return unwrapPromise;
|
|
6402
|
+
}
|
|
6377
6403
|
/**
|
|
6378
6404
|
* Creates a new promise with resolvers.
|
|
6379
6405
|
*
|
|
@@ -6426,7 +6452,6 @@ class StreamSfuClient {
|
|
|
6426
6452
|
this.isLeaving = false;
|
|
6427
6453
|
this.pingIntervalInMs = 10 * 1000;
|
|
6428
6454
|
this.unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
|
|
6429
|
-
this.restoreWebSocketConcurrencyTag = Symbol('recoverWebSocket');
|
|
6430
6455
|
/**
|
|
6431
6456
|
* Promise that resolves when the JoinResponse is received.
|
|
6432
6457
|
* Rejects after a certain threshold if the response is not received.
|
|
@@ -6447,28 +6472,22 @@ class StreamSfuClient {
|
|
|
6447
6472
|
},
|
|
6448
6473
|
});
|
|
6449
6474
|
this.signalWs.addEventListener('close', this.handleWebSocketClose);
|
|
6450
|
-
this.
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6475
|
+
this.signalReady = makeSafePromise(Promise.race([
|
|
6476
|
+
new Promise((resolve) => {
|
|
6477
|
+
const onOpen = () => {
|
|
6478
|
+
this.signalWs.removeEventListener('open', onOpen);
|
|
6479
|
+
resolve(this.signalWs);
|
|
6480
|
+
};
|
|
6481
|
+
this.signalWs.addEventListener('open', onOpen);
|
|
6482
|
+
}),
|
|
6483
|
+
new Promise((resolve, reject) => {
|
|
6484
|
+
setTimeout(() => reject(new Error('SFU WS connection timed out')), this.joinResponseTimeout);
|
|
6485
|
+
}),
|
|
6486
|
+
]));
|
|
6458
6487
|
};
|
|
6459
6488
|
this.cleanUpWebSocket = () => {
|
|
6460
|
-
this.signalWs.removeEventListener('error', this.restoreWebSocket);
|
|
6461
6489
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
6462
6490
|
};
|
|
6463
|
-
this.restoreWebSocket = () => {
|
|
6464
|
-
withoutConcurrency(this.restoreWebSocketConcurrencyTag, async () => {
|
|
6465
|
-
await this.networkAvailableTask?.promise;
|
|
6466
|
-
this.logger('debug', 'Restoring SFU WS connection');
|
|
6467
|
-
this.cleanUpWebSocket();
|
|
6468
|
-
await sleep(500);
|
|
6469
|
-
this.createWebSocket();
|
|
6470
|
-
}).catch((err) => this.logger('debug', `Can't restore WS connection`, err));
|
|
6471
|
-
};
|
|
6472
6491
|
this.handleWebSocketClose = () => {
|
|
6473
6492
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
6474
6493
|
clearInterval(this.keepAliveInterval);
|
|
@@ -6562,7 +6581,7 @@ class StreamSfuClient {
|
|
|
6562
6581
|
};
|
|
6563
6582
|
this.join = async (data) => {
|
|
6564
6583
|
// wait for the signal web socket to be ready before sending "joinRequest"
|
|
6565
|
-
await this.signalReady;
|
|
6584
|
+
await this.signalReady();
|
|
6566
6585
|
if (this.joinResponseTask.isResolved || this.joinResponseTask.isRejected) {
|
|
6567
6586
|
// we need to lock the RPC requests until we receive a JoinResponse.
|
|
6568
6587
|
// that's why we have this primitive lock mechanism.
|
|
@@ -6617,7 +6636,7 @@ class StreamSfuClient {
|
|
|
6617
6636
|
}));
|
|
6618
6637
|
};
|
|
6619
6638
|
this.send = async (message) => {
|
|
6620
|
-
await this.signalReady; // wait for the signal ws to be open
|
|
6639
|
+
await this.signalReady(); // wait for the signal ws to be open
|
|
6621
6640
|
const msgJson = SfuRequest.toJson(message);
|
|
6622
6641
|
if (this.signalWs.readyState !== WebSocket.OPEN) {
|
|
6623
6642
|
this.logger('debug', 'Signal WS is not open. Skipping message', msgJson);
|
|
@@ -8346,7 +8365,7 @@ const getAudioStream = async (trackConstraints) => {
|
|
|
8346
8365
|
const constraints = {
|
|
8347
8366
|
audio: {
|
|
8348
8367
|
...audioDeviceConstraints.audio,
|
|
8349
|
-
...
|
|
8368
|
+
...trackConstraints,
|
|
8350
8369
|
},
|
|
8351
8370
|
};
|
|
8352
8371
|
try {
|
|
@@ -8357,6 +8376,13 @@ const getAudioStream = async (trackConstraints) => {
|
|
|
8357
8376
|
return await getStream(constraints);
|
|
8358
8377
|
}
|
|
8359
8378
|
catch (error) {
|
|
8379
|
+
if (error instanceof DOMException &&
|
|
8380
|
+
error.name === 'OverconstrainedError' &&
|
|
8381
|
+
trackConstraints?.deviceId) {
|
|
8382
|
+
const { deviceId, ...relaxedContraints } = trackConstraints;
|
|
8383
|
+
getLogger(['devices'])('warn', 'Failed to get audio stream, will try again with relaxed contraints', { error, constraints, relaxedContraints });
|
|
8384
|
+
return getAudioStream(relaxedContraints);
|
|
8385
|
+
}
|
|
8360
8386
|
getLogger(['devices'])('error', 'Failed to get audio stream', {
|
|
8361
8387
|
error,
|
|
8362
8388
|
constraints,
|
|
@@ -8376,7 +8402,7 @@ const getVideoStream = async (trackConstraints) => {
|
|
|
8376
8402
|
const constraints = {
|
|
8377
8403
|
video: {
|
|
8378
8404
|
...videoDeviceConstraints.video,
|
|
8379
|
-
...
|
|
8405
|
+
...trackConstraints,
|
|
8380
8406
|
},
|
|
8381
8407
|
};
|
|
8382
8408
|
try {
|
|
@@ -8387,6 +8413,13 @@ const getVideoStream = async (trackConstraints) => {
|
|
|
8387
8413
|
return await getStream(constraints);
|
|
8388
8414
|
}
|
|
8389
8415
|
catch (error) {
|
|
8416
|
+
if (error instanceof DOMException &&
|
|
8417
|
+
error.name === 'OverconstrainedError' &&
|
|
8418
|
+
trackConstraints?.deviceId) {
|
|
8419
|
+
const { deviceId, ...relaxedContraints } = trackConstraints;
|
|
8420
|
+
getLogger(['devices'])('warn', 'Failed to get video stream, will try again with relaxed contraints', { error, constraints, relaxedContraints });
|
|
8421
|
+
return getVideoStream(relaxedContraints);
|
|
8422
|
+
}
|
|
8390
8423
|
getLogger(['devices'])('error', 'Failed to get video stream', {
|
|
8391
8424
|
error,
|
|
8392
8425
|
constraints,
|
|
@@ -8394,16 +8427,6 @@ const getVideoStream = async (trackConstraints) => {
|
|
|
8394
8427
|
throw error;
|
|
8395
8428
|
}
|
|
8396
8429
|
};
|
|
8397
|
-
function normalizeContraints(constraints) {
|
|
8398
|
-
if (constraints?.deviceId === 'default' ||
|
|
8399
|
-
(typeof constraints?.deviceId === 'object' &&
|
|
8400
|
-
'exact' in constraints.deviceId &&
|
|
8401
|
-
constraints.deviceId.exact === 'default')) {
|
|
8402
|
-
const { deviceId, ...contraintsWithoutDeviceId } = constraints;
|
|
8403
|
-
return contraintsWithoutDeviceId;
|
|
8404
|
-
}
|
|
8405
|
-
return constraints;
|
|
8406
|
-
}
|
|
8407
8430
|
/**
|
|
8408
8431
|
* Prompts the user for a permission to share a screen.
|
|
8409
8432
|
* If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
|
|
@@ -8630,7 +8653,6 @@ class InputMediaDeviceManager {
|
|
|
8630
8653
|
}
|
|
8631
8654
|
catch (error) {
|
|
8632
8655
|
this.state.setDevice(prevDeviceId);
|
|
8633
|
-
await this.applySettingsToStream();
|
|
8634
8656
|
throw error;
|
|
8635
8657
|
}
|
|
8636
8658
|
}
|
|
@@ -9831,6 +9853,7 @@ class Call {
|
|
|
9831
9853
|
this.reconnectAttempts = 0;
|
|
9832
9854
|
this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
|
|
9833
9855
|
this.fastReconnectDeadlineSeconds = 0;
|
|
9856
|
+
this.disconnectionTimeoutSeconds = 0;
|
|
9834
9857
|
this.lastOfflineTimestamp = 0;
|
|
9835
9858
|
// maintain the order of publishing tracks to restore them after a reconnection
|
|
9836
9859
|
// it shouldn't contain duplicates
|
|
@@ -10347,6 +10370,10 @@ class Call {
|
|
|
10347
10370
|
*/
|
|
10348
10371
|
this.handleSfuSignalClose = (sfuClient) => {
|
|
10349
10372
|
this.logger('debug', '[Reconnect] SFU signal connection closed');
|
|
10373
|
+
// SFU WS closed before we finished current join, no need to schedule reconnect
|
|
10374
|
+
// because join operation will fail
|
|
10375
|
+
if (this.state.callingState === CallingState.JOINING)
|
|
10376
|
+
return;
|
|
10350
10377
|
// normal close, no need to reconnect
|
|
10351
10378
|
if (sfuClient.isLeaving)
|
|
10352
10379
|
return;
|
|
@@ -10362,10 +10389,21 @@ class Call {
|
|
|
10362
10389
|
* @param strategy the reconnection strategy to use.
|
|
10363
10390
|
*/
|
|
10364
10391
|
this.reconnect = async (strategy) => {
|
|
10392
|
+
if (this.state.callingState === CallingState.RECONNECTING ||
|
|
10393
|
+
this.state.callingState === CallingState.RECONNECTING_FAILED)
|
|
10394
|
+
return;
|
|
10365
10395
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
10366
10396
|
this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
|
|
10397
|
+
let reconnectStartTime = Date.now();
|
|
10367
10398
|
this.reconnectStrategy = strategy;
|
|
10368
10399
|
do {
|
|
10400
|
+
if (this.disconnectionTimeoutSeconds > 0 &&
|
|
10401
|
+
(Date.now() - reconnectStartTime) / 1000 >
|
|
10402
|
+
this.disconnectionTimeoutSeconds) {
|
|
10403
|
+
this.logger('warn', '[Reconnect] Stopping reconnection attempts after reaching disconnection timeout');
|
|
10404
|
+
this.state.setCallingState(CallingState.RECONNECTING_FAILED);
|
|
10405
|
+
return;
|
|
10406
|
+
}
|
|
10369
10407
|
// we don't increment reconnect attempts for the FAST strategy.
|
|
10370
10408
|
if (this.reconnectStrategy !== WebsocketReconnectStrategy.FAST) {
|
|
10371
10409
|
this.reconnectAttempts++;
|
|
@@ -10465,7 +10503,7 @@ class Call {
|
|
|
10465
10503
|
const currentPublisher = this.publisher;
|
|
10466
10504
|
currentSubscriber?.detachEventHandlers();
|
|
10467
10505
|
currentPublisher?.detachEventHandlers();
|
|
10468
|
-
const migrationTask = currentSfuClient.enterMigration();
|
|
10506
|
+
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
|
|
10469
10507
|
try {
|
|
10470
10508
|
const currentSfu = currentSfuClient.edgeName;
|
|
10471
10509
|
await this.join({ ...this.joinCallData, migrating_from: currentSfu });
|
|
@@ -10481,7 +10519,7 @@ class Call {
|
|
|
10481
10519
|
// Wait for the migration to complete, then close the previous SFU client
|
|
10482
10520
|
// and the peer connection instances. In case of failure, the migration
|
|
10483
10521
|
// task would throw an error and REJOIN would be attempted.
|
|
10484
|
-
await migrationTask;
|
|
10522
|
+
await migrationTask();
|
|
10485
10523
|
// in MIGRATE, we can consider the call as joined only after
|
|
10486
10524
|
// `participantMigrationComplete` event is received, signaled by
|
|
10487
10525
|
// the `migrationTask`
|
|
@@ -11337,6 +11375,14 @@ class Call {
|
|
|
11337
11375
|
this.dynascaleManager.setVideoTrackSubscriptionOverrides(enabled ? undefined : { enabled: false });
|
|
11338
11376
|
this.dynascaleManager.applyTrackSubscriptions();
|
|
11339
11377
|
};
|
|
11378
|
+
/**
|
|
11379
|
+
* Sets the maximum amount of time a user can remain waiting for a reconnect
|
|
11380
|
+
* after a network disruption
|
|
11381
|
+
* @param timeoutSeconds Timeout in seconds, or 0 to keep reconnecting indefinetely
|
|
11382
|
+
*/
|
|
11383
|
+
this.setDisconnectionTimeout = (timeoutSeconds) => {
|
|
11384
|
+
this.disconnectionTimeoutSeconds = timeoutSeconds;
|
|
11385
|
+
};
|
|
11340
11386
|
this.type = type;
|
|
11341
11387
|
this.id = id;
|
|
11342
11388
|
this.cid = `${type}:${id}`;
|
|
@@ -11492,33 +11538,6 @@ class Call {
|
|
|
11492
11538
|
|
|
11493
11539
|
var https = null;
|
|
11494
11540
|
|
|
11495
|
-
/**
|
|
11496
|
-
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
11497
|
-
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
11498
|
-
* rejection is handled everywhere promise result is expected).
|
|
11499
|
-
*
|
|
11500
|
-
* To avoid that, we add both resolution and rejection handlers to the promise.
|
|
11501
|
-
* That way, the saved promise never rejects. A callback is provided as return
|
|
11502
|
-
* value to build a *new* promise, that resolves and rejects along with
|
|
11503
|
-
* the original promise.
|
|
11504
|
-
* @param promise Promise to wrap, which possibly rejects
|
|
11505
|
-
* @returns Callback to build a new promise, which resolves and rejects along
|
|
11506
|
-
* with the original promise
|
|
11507
|
-
*/
|
|
11508
|
-
function makeSafePromise(promise) {
|
|
11509
|
-
let isPending = true;
|
|
11510
|
-
const safePromise = promise
|
|
11511
|
-
.then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
|
|
11512
|
-
.finally(() => (isPending = false));
|
|
11513
|
-
const unwrapPromise = () => safePromise.then((fulfillment) => {
|
|
11514
|
-
if (fulfillment.status === 'rejected')
|
|
11515
|
-
throw fulfillment.error;
|
|
11516
|
-
return fulfillment.result;
|
|
11517
|
-
});
|
|
11518
|
-
unwrapPromise.checkPending = () => isPending;
|
|
11519
|
-
return unwrapPromise;
|
|
11520
|
-
}
|
|
11521
|
-
|
|
11522
11541
|
/**
|
|
11523
11542
|
* StableWSConnection - A WS connection that reconnects upon failure.
|
|
11524
11543
|
* - the browser will sometimes report that you're online or offline
|
|
@@ -12617,7 +12636,7 @@ class StreamClient {
|
|
|
12617
12636
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
12618
12637
|
};
|
|
12619
12638
|
this.getUserAgent = () => {
|
|
12620
|
-
const version = "1.11.
|
|
12639
|
+
const version = "1.11.12";
|
|
12621
12640
|
return (this.userAgent ||
|
|
12622
12641
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
12623
12642
|
};
|