@stream-io/video-client 1.44.6-beta.0 → 1.45.0
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 +10 -0
- package/dist/index.browser.es.js +58 -79
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +58 -79
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +58 -79
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -1
- package/dist/src/coordinator/connection/types.d.ts +22 -1
- package/dist/src/devices/DeviceManager.d.ts +1 -0
- package/dist/src/types.d.ts +5 -37
- package/package.json +1 -1
- package/src/Call.ts +40 -85
- package/src/coordinator/connection/types.ts +23 -0
- package/src/devices/DeviceManager.ts +20 -1
- package/src/devices/SpeakerManager.ts +0 -1
- package/src/devices/__tests__/DeviceManager.test.ts +8 -0
- package/src/devices/__tests__/mocks.ts +4 -0
- package/src/events/call.ts +0 -3
- package/src/helpers/AudioBindingsWatchdog.ts +14 -2
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +27 -1
- package/src/store/stateStore.ts +1 -1
- package/src/types.ts +5 -48
package/dist/index.cjs.js
CHANGED
|
@@ -4804,7 +4804,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4804
4804
|
* The currently connected user.
|
|
4805
4805
|
*/
|
|
4806
4806
|
get connectedUser() {
|
|
4807
|
-
return this.connectedUserSubject
|
|
4807
|
+
return getCurrentValue(this.connectedUserSubject);
|
|
4808
4808
|
}
|
|
4809
4809
|
/**
|
|
4810
4810
|
* A list of {@link Call} objects created/tracked by this client.
|
|
@@ -6303,7 +6303,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6303
6303
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6304
6304
|
};
|
|
6305
6305
|
|
|
6306
|
-
const version = "1.
|
|
6306
|
+
const version = "1.45.0";
|
|
6307
6307
|
const [major, minor, patch] = version.split('.');
|
|
6308
6308
|
let sdkInfo = {
|
|
6309
6309
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -9002,7 +9002,6 @@ const watchCallRejected = (call) => {
|
|
|
9002
9002
|
else {
|
|
9003
9003
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
9004
9004
|
call.logger.info('call creator rejected, leaving call');
|
|
9005
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9006
9005
|
await call.leave({ message: 'ring: creator rejected' });
|
|
9007
9006
|
}
|
|
9008
9007
|
}
|
|
@@ -9013,7 +9012,6 @@ const watchCallRejected = (call) => {
|
|
|
9013
9012
|
*/
|
|
9014
9013
|
const watchCallEnded = (call) => {
|
|
9015
9014
|
return function onCallEnded() {
|
|
9016
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9017
9015
|
const { callingState } = call.state;
|
|
9018
9016
|
if (callingState !== exports.CallingState.IDLE &&
|
|
9019
9017
|
callingState !== exports.CallingState.LEFT) {
|
|
@@ -9045,7 +9043,6 @@ const watchSfuCallEnded = (call) => {
|
|
|
9045
9043
|
// update the call state to reflect the call has ended.
|
|
9046
9044
|
call.state.setEndedAt(new Date());
|
|
9047
9045
|
const reason = CallEndedReason[e.reason];
|
|
9048
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9049
9046
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
9050
9047
|
}
|
|
9051
9048
|
catch (err) {
|
|
@@ -9572,11 +9569,14 @@ class AudioBindingsWatchdog {
|
|
|
9572
9569
|
for (const p of this.state.participants) {
|
|
9573
9570
|
if (p.isLocalParticipant)
|
|
9574
9571
|
continue;
|
|
9575
|
-
const { audioStream, screenShareAudioStream, sessionId, userId } = p;
|
|
9576
|
-
if (audioStream &&
|
|
9572
|
+
const { audioStream, screenShareAudioStream, sessionId, userId, publishedTracks, } = p;
|
|
9573
|
+
if (audioStream &&
|
|
9574
|
+
publishedTracks.includes(TrackType.AUDIO) &&
|
|
9575
|
+
!this.bindings.has(toBindingKey(sessionId))) {
|
|
9577
9576
|
danglingUserIds.push(userId);
|
|
9578
9577
|
}
|
|
9579
9578
|
if (screenShareAudioStream &&
|
|
9579
|
+
publishedTracks.includes(TrackType.SCREEN_SHARE_AUDIO) &&
|
|
9580
9580
|
!this.bindings.has(toBindingKey(sessionId, 'screenShareAudioTrack'))) {
|
|
9581
9581
|
danglingUserIds.push(userId);
|
|
9582
9582
|
}
|
|
@@ -11210,6 +11210,7 @@ class DeviceManager {
|
|
|
11210
11210
|
isDeviceReplaced = true;
|
|
11211
11211
|
}
|
|
11212
11212
|
if (isDeviceDisconnected) {
|
|
11213
|
+
this.dispatchDeviceDisconnectedEvent(prevDevice);
|
|
11213
11214
|
await this.disable();
|
|
11214
11215
|
await this.select(undefined);
|
|
11215
11216
|
}
|
|
@@ -11219,7 +11220,7 @@ class DeviceManager {
|
|
|
11219
11220
|
await this.enable();
|
|
11220
11221
|
this.isTrackStoppedDueToTrackEnd = false;
|
|
11221
11222
|
}
|
|
11222
|
-
else {
|
|
11223
|
+
else if (!hasPending(this.statusChangeConcurrencyTag)) {
|
|
11223
11224
|
await this.applySettingsToStream();
|
|
11224
11225
|
}
|
|
11225
11226
|
}
|
|
@@ -11233,6 +11234,20 @@ class DeviceManager {
|
|
|
11233
11234
|
const kind = this.mediaDeviceKind;
|
|
11234
11235
|
return devices.find((d) => d.deviceId === deviceId && d.kind === kind);
|
|
11235
11236
|
}
|
|
11237
|
+
dispatchDeviceDisconnectedEvent(device) {
|
|
11238
|
+
const event = {
|
|
11239
|
+
type: 'device.disconnected',
|
|
11240
|
+
call_cid: this.call.cid,
|
|
11241
|
+
status: this.isTrackStoppedDueToTrackEnd
|
|
11242
|
+
? this.state.prevStatus
|
|
11243
|
+
: this.state.status,
|
|
11244
|
+
deviceId: device.deviceId,
|
|
11245
|
+
label: device.label,
|
|
11246
|
+
kind: device.kind,
|
|
11247
|
+
};
|
|
11248
|
+
this.call.tracer.trace('device.disconnected', event);
|
|
11249
|
+
this.call.streamClient.dispatchEvent(event);
|
|
11250
|
+
}
|
|
11236
11251
|
persistPreference(selectedDevice, status) {
|
|
11237
11252
|
const deviceKind = this.mediaDeviceKind;
|
|
11238
11253
|
const deviceKey = deviceKind === 'audioinput' ? 'microphone' : 'camera';
|
|
@@ -12649,7 +12664,6 @@ class SpeakerManager {
|
|
|
12649
12664
|
this.defaultDevice = defaultDevice;
|
|
12650
12665
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12651
12666
|
defaultDevice,
|
|
12652
|
-
isRingingTypeCall: this.call.ringing,
|
|
12653
12667
|
});
|
|
12654
12668
|
}
|
|
12655
12669
|
}
|
|
@@ -12849,7 +12863,6 @@ class Call {
|
|
|
12849
12863
|
const currentUserId = this.currentUserId;
|
|
12850
12864
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12851
12865
|
this.logger.info('Leaving call because of being blocked');
|
|
12852
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12853
12866
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12854
12867
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12855
12868
|
});
|
|
@@ -12886,7 +12899,6 @@ class Call {
|
|
|
12886
12899
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === exports.CallingState.RINGING;
|
|
12887
12900
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12888
12901
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12889
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12890
12902
|
this.leave().catch(() => {
|
|
12891
12903
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12892
12904
|
});
|
|
@@ -12898,9 +12910,6 @@ class Call {
|
|
|
12898
12910
|
const receiver_id = this.clientStore.connectedUser?.id;
|
|
12899
12911
|
const ended_at = callSession?.ended_at;
|
|
12900
12912
|
const created_by_id = this.state.createdBy?.id;
|
|
12901
|
-
if (this.currentUserId && created_by_id === this.currentUserId) {
|
|
12902
|
-
globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
|
|
12903
|
-
}
|
|
12904
12913
|
const rejected_by = callSession?.rejected_by;
|
|
12905
12914
|
const accepted_by = callSession?.accepted_by;
|
|
12906
12915
|
let leaveCallIdle = false;
|
|
@@ -13039,28 +13048,17 @@ class Call {
|
|
|
13039
13048
|
}
|
|
13040
13049
|
if (callingState === exports.CallingState.RINGING && reject !== false) {
|
|
13041
13050
|
if (reject) {
|
|
13042
|
-
|
|
13043
|
-
timeout: 'missed',
|
|
13044
|
-
cancel: 'canceled',
|
|
13045
|
-
busy: 'busy',
|
|
13046
|
-
decline: 'rejected',
|
|
13047
|
-
};
|
|
13048
|
-
const rejectReason = reason ?? 'decline';
|
|
13049
|
-
const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
|
|
13050
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
|
|
13051
|
-
await this.reject(rejectReason);
|
|
13051
|
+
await this.reject(reason ?? 'decline');
|
|
13052
13052
|
}
|
|
13053
13053
|
else {
|
|
13054
13054
|
// if reject was undefined, we still have to cancel the call automatically
|
|
13055
13055
|
// when I am the creator and everyone else left the call
|
|
13056
13056
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
13057
13057
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
13058
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
13059
13058
|
await this.reject('cancel');
|
|
13060
13059
|
}
|
|
13061
13060
|
}
|
|
13062
13061
|
}
|
|
13063
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
13064
13062
|
this.statsReporter?.stop();
|
|
13065
13063
|
this.statsReporter = undefined;
|
|
13066
13064
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -13087,9 +13085,7 @@ class Call {
|
|
|
13087
13085
|
this.ringingSubject.next(false);
|
|
13088
13086
|
this.cancelAutoDrop();
|
|
13089
13087
|
this.clientStore.unregisterCall(this);
|
|
13090
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
13091
|
-
isRingingTypeCall: this.ringing,
|
|
13092
|
-
});
|
|
13088
|
+
globalThis.streamRNVideoSDK?.callManager.stop();
|
|
13093
13089
|
this.camera.dispose();
|
|
13094
13090
|
this.microphone.dispose();
|
|
13095
13091
|
this.screenShare.dispose();
|
|
@@ -13255,19 +13251,11 @@ class Call {
|
|
|
13255
13251
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
13256
13252
|
*/
|
|
13257
13253
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
13254
|
+
await this.setup();
|
|
13258
13255
|
const callingState = this.state.callingState;
|
|
13259
13256
|
if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
|
|
13260
13257
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
13261
13258
|
}
|
|
13262
|
-
if (data?.ring) {
|
|
13263
|
-
this.ringingSubject.next(true);
|
|
13264
|
-
}
|
|
13265
|
-
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
13266
|
-
if (callingX) {
|
|
13267
|
-
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
13268
|
-
await callingX.joinCall(this, this.clientStore.calls);
|
|
13269
|
-
}
|
|
13270
|
-
await this.setup();
|
|
13271
13259
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
13272
13260
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
13273
13261
|
// we will count the number of join failures per SFU.
|
|
@@ -13276,44 +13264,38 @@ class Call {
|
|
|
13276
13264
|
const sfuJoinFailures = new Map();
|
|
13277
13265
|
const joinData = data;
|
|
13278
13266
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13267
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
13268
|
+
try {
|
|
13269
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
13270
|
+
await this.doJoin(data);
|
|
13271
|
+
delete joinData.migrating_from;
|
|
13272
|
+
delete joinData.migrating_from_list;
|
|
13273
|
+
break;
|
|
13274
|
+
}
|
|
13275
|
+
catch (err) {
|
|
13276
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13277
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13278
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13279
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
13280
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13281
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13282
|
+
throw err;
|
|
13287
13283
|
}
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
13301
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13302
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
13303
|
-
if (switchSfu || failures >= 2) {
|
|
13304
|
-
joinData.migrating_from = sfuId;
|
|
13305
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13306
|
-
}
|
|
13307
|
-
if (attempt === maxJoinRetries - 1) {
|
|
13308
|
-
throw err;
|
|
13309
|
-
}
|
|
13284
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
13285
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
13286
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13287
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
13288
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13289
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
13290
|
+
if (switchSfu || failures >= 2) {
|
|
13291
|
+
joinData.migrating_from = sfuId;
|
|
13292
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13293
|
+
}
|
|
13294
|
+
if (attempt === maxJoinRetries - 1) {
|
|
13295
|
+
throw err;
|
|
13310
13296
|
}
|
|
13311
|
-
await sleep(retryInterval(attempt));
|
|
13312
13297
|
}
|
|
13313
|
-
|
|
13314
|
-
catch (error) {
|
|
13315
|
-
callingX?.endCall(this, 'error');
|
|
13316
|
-
throw error;
|
|
13298
|
+
await sleep(retryInterval(attempt));
|
|
13317
13299
|
}
|
|
13318
13300
|
};
|
|
13319
13301
|
/**
|
|
@@ -13460,9 +13442,7 @@ class Call {
|
|
|
13460
13442
|
// re-apply them on later reconnections or server-side data fetches
|
|
13461
13443
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13462
13444
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
13463
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13464
|
-
isRingingTypeCall: this.ringing,
|
|
13465
|
-
});
|
|
13445
|
+
globalThis.streamRNVideoSDK?.callManager.start();
|
|
13466
13446
|
this.deviceSettingsAppliedOnce = true;
|
|
13467
13447
|
}
|
|
13468
13448
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13890,7 +13870,6 @@ class Call {
|
|
|
13890
13870
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13891
13871
|
return;
|
|
13892
13872
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13893
|
-
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13894
13873
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13895
13874
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13896
13875
|
});
|
|
@@ -14912,7 +14891,7 @@ class Call {
|
|
|
14912
14891
|
* A flag indicating whether the call was created by the current user.
|
|
14913
14892
|
*/
|
|
14914
14893
|
get isCreatedByMe() {
|
|
14915
|
-
return
|
|
14894
|
+
return this.state.createdBy?.id === this.currentUserId;
|
|
14916
14895
|
}
|
|
14917
14896
|
}
|
|
14918
14897
|
|
|
@@ -16036,7 +16015,7 @@ class StreamClient {
|
|
|
16036
16015
|
this.getUserAgent = () => {
|
|
16037
16016
|
if (!this.cachedUserAgent) {
|
|
16038
16017
|
const { clientAppIdentifier = {} } = this.options;
|
|
16039
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
16018
|
+
const { sdkName = 'js', sdkVersion = "1.45.0", ...extras } = clientAppIdentifier;
|
|
16040
16019
|
this.cachedUserAgent = [
|
|
16041
16020
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16042
16021
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|