@stream-io/video-client 1.42.2 → 1.43.0-beta.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 +6 -0
- package/dist/index.browser.es.js +118 -57
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +119 -58
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +118 -57
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -0
- package/dist/src/devices/MicrophoneManager.d.ts +2 -0
- package/dist/src/helpers/no-audio-detector.d.ts +1 -7
- package/dist/src/types.d.ts +36 -5
- package/package.json +3 -3
- package/src/Call.ts +92 -48
- package/src/__tests__/Call.test.ts +22 -15
- package/src/devices/MicrophoneManager.ts +21 -5
- package/src/devices/SpeakerManager.ts +1 -0
- package/src/devices/__tests__/MicrophoneManager.test.ts +52 -0
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +26 -1
- package/src/devices/__tests__/web-audio.mocks.ts +6 -2
- package/src/events/call.ts +3 -0
- package/src/helpers/__tests__/no-audio-detector.test.ts +54 -28
- package/src/helpers/no-audio-detector.ts +25 -20
- package/src/types.ts +47 -5
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.42.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.42.2...@stream-io/video-client-1.42.3) (2026-02-16)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- guard from parallel accept/reject invocations ([#2127](https://github.com/GetStream/stream-video-js/issues/2127)) ([621218f](https://github.com/GetStream/stream-video-js/commit/621218f4ab6b4623370fd66f1b02b8cb7cb1baad))
|
|
10
|
+
|
|
5
11
|
## [1.42.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.42.1...@stream-io/video-client-1.42.2) (2026-02-13)
|
|
6
12
|
|
|
7
13
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -6231,7 +6231,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6231
6231
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6232
6232
|
};
|
|
6233
6233
|
|
|
6234
|
-
const version = "1.
|
|
6234
|
+
const version = "1.43.0-beta.0";
|
|
6235
6235
|
const [major, minor, patch] = version.split('.');
|
|
6236
6236
|
let sdkInfo = {
|
|
6237
6237
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8924,6 +8924,7 @@ const watchCallRejected = (call) => {
|
|
|
8924
8924
|
else {
|
|
8925
8925
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
8926
8926
|
call.logger.info('call creator rejected, leaving call');
|
|
8927
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8927
8928
|
await call.leave({ message: 'ring: creator rejected' });
|
|
8928
8929
|
}
|
|
8929
8930
|
}
|
|
@@ -8937,6 +8938,7 @@ const watchCallEnded = (call) => {
|
|
|
8937
8938
|
const { callingState } = call.state;
|
|
8938
8939
|
if (callingState !== CallingState.IDLE &&
|
|
8939
8940
|
callingState !== CallingState.LEFT) {
|
|
8941
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8940
8942
|
call
|
|
8941
8943
|
.leave({ message: 'call.ended event received', reject: false })
|
|
8942
8944
|
.catch((err) => {
|
|
@@ -8965,6 +8967,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
8965
8967
|
// update the call state to reflect the call has ended.
|
|
8966
8968
|
call.state.setEndedAt(new Date());
|
|
8967
8969
|
const reason = CallEndedReason[e.reason];
|
|
8970
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8968
8971
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
8969
8972
|
}
|
|
8970
8973
|
catch (err) {
|
|
@@ -11480,12 +11483,21 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
|
|
|
11480
11483
|
};
|
|
11481
11484
|
|
|
11482
11485
|
/**
|
|
11483
|
-
* Analyzes
|
|
11486
|
+
* Analyzes time-domain waveform data to determine if audio is being captured.
|
|
11487
|
+
* Uses the waveform RMS around the 128 midpoint for robust silence detection.
|
|
11484
11488
|
*/
|
|
11485
|
-
const hasAudio = (analyser
|
|
11486
|
-
const data = new Uint8Array(analyser.
|
|
11487
|
-
analyser.
|
|
11488
|
-
|
|
11489
|
+
const hasAudio = (analyser) => {
|
|
11490
|
+
const data = new Uint8Array(analyser.fftSize);
|
|
11491
|
+
analyser.getByteTimeDomainData(data);
|
|
11492
|
+
let squareSum = 0;
|
|
11493
|
+
for (const sample of data) {
|
|
11494
|
+
const centered = sample - 128;
|
|
11495
|
+
// Ignore tiny quantization/jitter around midpoint (e.g. 127/128 samples).
|
|
11496
|
+
const signal = Math.abs(centered) <= 1 ? 0 : centered;
|
|
11497
|
+
squareSum += signal * signal;
|
|
11498
|
+
}
|
|
11499
|
+
const rms = Math.sqrt(squareSum / data.length);
|
|
11500
|
+
return rms > 0;
|
|
11489
11501
|
};
|
|
11490
11502
|
/** Helper for "no event" transitions */
|
|
11491
11503
|
const noEmit = (nextState) => ({
|
|
@@ -11499,9 +11511,9 @@ const emit = (capturesAudio, nextState) => ({ shouldEmit: true, nextState, captu
|
|
|
11499
11511
|
*/
|
|
11500
11512
|
const transitionState = (state, audioDetected, options) => {
|
|
11501
11513
|
if (audioDetected) {
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11514
|
+
// Any observed audio means the microphone is capturing.
|
|
11515
|
+
// Emit recovery/success and let the caller stop the detector.
|
|
11516
|
+
return emit(true, { kind: 'IDLE' });
|
|
11505
11517
|
}
|
|
11506
11518
|
const { noAudioThresholdMs, emitIntervalMs } = options;
|
|
11507
11519
|
const now = Date.now();
|
|
@@ -11542,16 +11554,17 @@ const createAudioAnalyzer = (audioStream, fftSize) => {
|
|
|
11542
11554
|
* @returns a cleanup function which once invoked stops the no-audio detector.
|
|
11543
11555
|
*/
|
|
11544
11556
|
const createNoAudioDetector = (audioStream, options) => {
|
|
11545
|
-
const { detectionFrequencyInMs = 350,
|
|
11557
|
+
const { detectionFrequencyInMs = 350, fftSize = 512, onCaptureStatusChange, } = options;
|
|
11546
11558
|
let state = { kind: 'IDLE' };
|
|
11547
11559
|
const { audioContext, analyser } = createAudioAnalyzer(audioStream, fftSize);
|
|
11548
11560
|
const detectionIntervalId = setInterval(() => {
|
|
11549
|
-
const [
|
|
11550
|
-
if (
|
|
11561
|
+
const [track] = audioStream.getAudioTracks();
|
|
11562
|
+
if (track && !track.enabled) {
|
|
11551
11563
|
state = { kind: 'IDLE' };
|
|
11552
11564
|
return;
|
|
11553
11565
|
}
|
|
11554
|
-
|
|
11566
|
+
// Missing or ended track is treated as no-audio to surface abrupt capture loss.
|
|
11567
|
+
const audioDetected = track?.readyState === 'live' && hasAudio(analyser);
|
|
11555
11568
|
const transition = transitionState(state, audioDetected, options);
|
|
11556
11569
|
state = transition.nextState;
|
|
11557
11570
|
if (!transition.shouldEmit)
|
|
@@ -12024,6 +12037,9 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12024
12037
|
}
|
|
12025
12038
|
async startSpeakingWhileMutedDetection(deviceId) {
|
|
12026
12039
|
await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
|
|
12040
|
+
if (this.soundDetectorCleanup && this.soundDetectorDeviceId === deviceId)
|
|
12041
|
+
return;
|
|
12042
|
+
await this.teardownSpeakingWhileMutedDetection();
|
|
12027
12043
|
if (isReactNative()) {
|
|
12028
12044
|
this.rnSpeechDetector = new RNSpeechDetector();
|
|
12029
12045
|
const unsubscribe = await this.rnSpeechDetector.start((event) => {
|
|
@@ -12044,16 +12060,23 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12044
12060
|
this.state.setSpeakingWhileMuted(event.isSoundDetected);
|
|
12045
12061
|
});
|
|
12046
12062
|
}
|
|
12063
|
+
this.soundDetectorDeviceId = deviceId;
|
|
12047
12064
|
});
|
|
12048
12065
|
}
|
|
12049
12066
|
async stopSpeakingWhileMutedDetection() {
|
|
12050
12067
|
await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12068
|
+
return this.teardownSpeakingWhileMutedDetection();
|
|
12069
|
+
});
|
|
12070
|
+
}
|
|
12071
|
+
async teardownSpeakingWhileMutedDetection() {
|
|
12072
|
+
const soundDetectorCleanup = this.soundDetectorCleanup;
|
|
12073
|
+
this.soundDetectorCleanup = undefined;
|
|
12074
|
+
this.soundDetectorDeviceId = undefined;
|
|
12075
|
+
this.state.setSpeakingWhileMuted(false);
|
|
12076
|
+
if (!soundDetectorCleanup)
|
|
12077
|
+
return;
|
|
12078
|
+
await soundDetectorCleanup().catch((err) => {
|
|
12079
|
+
this.logger.warn('Failed to stop speaking while muted detector', err);
|
|
12057
12080
|
});
|
|
12058
12081
|
}
|
|
12059
12082
|
async hasPermission(permissionState) {
|
|
@@ -12322,6 +12345,7 @@ class SpeakerManager {
|
|
|
12322
12345
|
this.defaultDevice = defaultDevice;
|
|
12323
12346
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12324
12347
|
defaultDevice,
|
|
12348
|
+
isRingingTypeCall: this.call.ringing,
|
|
12325
12349
|
});
|
|
12326
12350
|
}
|
|
12327
12351
|
}
|
|
@@ -12446,6 +12470,7 @@ class Call {
|
|
|
12446
12470
|
this.hasJoinedOnce = false;
|
|
12447
12471
|
this.deviceSettingsAppliedOnce = false;
|
|
12448
12472
|
this.initialized = false;
|
|
12473
|
+
this.acceptRejectConcurrencyTag = Symbol('acceptRejectTag');
|
|
12449
12474
|
this.joinLeaveConcurrencyTag = Symbol('joinLeaveConcurrencyTag');
|
|
12450
12475
|
/**
|
|
12451
12476
|
* A list hooks/functions to invoke when the call is left.
|
|
@@ -12511,6 +12536,7 @@ class Call {
|
|
|
12511
12536
|
const currentUserId = this.currentUserId;
|
|
12512
12537
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12513
12538
|
this.logger.info('Leaving call because of being blocked');
|
|
12539
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12514
12540
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12515
12541
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12516
12542
|
});
|
|
@@ -12547,6 +12573,7 @@ class Call {
|
|
|
12547
12573
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === CallingState.RINGING;
|
|
12548
12574
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12549
12575
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12576
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12550
12577
|
this.leave().catch(() => {
|
|
12551
12578
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12552
12579
|
});
|
|
@@ -12696,17 +12723,28 @@ class Call {
|
|
|
12696
12723
|
}
|
|
12697
12724
|
if (callingState === CallingState.RINGING && reject !== false) {
|
|
12698
12725
|
if (reject) {
|
|
12699
|
-
|
|
12726
|
+
const reasonToEndCallReason = {
|
|
12727
|
+
timeout: 'missed',
|
|
12728
|
+
cancel: 'canceled',
|
|
12729
|
+
busy: 'busy',
|
|
12730
|
+
decline: 'rejected',
|
|
12731
|
+
};
|
|
12732
|
+
const rejectReason = reason ?? 'decline';
|
|
12733
|
+
const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
|
|
12734
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
|
|
12735
|
+
await this.reject(rejectReason);
|
|
12700
12736
|
}
|
|
12701
12737
|
else {
|
|
12702
12738
|
// if reject was undefined, we still have to cancel the call automatically
|
|
12703
12739
|
// when I am the creator and everyone else left the call
|
|
12704
12740
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
12705
12741
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
12742
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
12706
12743
|
await this.reject('cancel');
|
|
12707
12744
|
}
|
|
12708
12745
|
}
|
|
12709
12746
|
}
|
|
12747
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
12710
12748
|
this.statsReporter?.stop();
|
|
12711
12749
|
this.statsReporter = undefined;
|
|
12712
12750
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -12733,7 +12771,9 @@ class Call {
|
|
|
12733
12771
|
this.ringingSubject.next(false);
|
|
12734
12772
|
this.cancelAutoDrop();
|
|
12735
12773
|
this.clientStore.unregisterCall(this);
|
|
12736
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
12774
|
+
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
12775
|
+
isRingingTypeCall: this.ringing,
|
|
12776
|
+
});
|
|
12737
12777
|
this.camera.dispose();
|
|
12738
12778
|
this.microphone.dispose();
|
|
12739
12779
|
this.screenShare.dispose();
|
|
@@ -12860,8 +12900,10 @@ class Call {
|
|
|
12860
12900
|
* Unless you are implementing a custom "ringing" flow, you should not use this method.
|
|
12861
12901
|
*/
|
|
12862
12902
|
this.accept = async () => {
|
|
12863
|
-
this.
|
|
12864
|
-
|
|
12903
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12904
|
+
this.tracer.trace('call.accept', '');
|
|
12905
|
+
return this.streamClient.post(`${this.streamClientBasePath}/accept`);
|
|
12906
|
+
});
|
|
12865
12907
|
};
|
|
12866
12908
|
/**
|
|
12867
12909
|
* Marks the incoming call as rejected.
|
|
@@ -12873,8 +12915,10 @@ class Call {
|
|
|
12873
12915
|
* @param reason the reason for rejecting the call.
|
|
12874
12916
|
*/
|
|
12875
12917
|
this.reject = async (reason = 'decline') => {
|
|
12876
|
-
this.
|
|
12877
|
-
|
|
12918
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12919
|
+
this.tracer.trace('call.reject', reason);
|
|
12920
|
+
return this.streamClient.post(`${this.streamClientBasePath}/reject`, { reason });
|
|
12921
|
+
});
|
|
12878
12922
|
};
|
|
12879
12923
|
/**
|
|
12880
12924
|
* Will start to watch for call related WebSocket events and initiate a call session with the server.
|
|
@@ -12882,11 +12926,19 @@ class Call {
|
|
|
12882
12926
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
12883
12927
|
*/
|
|
12884
12928
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
12885
|
-
await this.setup();
|
|
12886
12929
|
const callingState = this.state.callingState;
|
|
12887
12930
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
12888
12931
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
12889
12932
|
}
|
|
12933
|
+
if (data?.ring) {
|
|
12934
|
+
this.ringingSubject.next(true);
|
|
12935
|
+
}
|
|
12936
|
+
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
12937
|
+
if (callingX) {
|
|
12938
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
12939
|
+
await callingX.startCall(this);
|
|
12940
|
+
}
|
|
12941
|
+
await this.setup();
|
|
12890
12942
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
12891
12943
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
12892
12944
|
// we will count the number of join failures per SFU.
|
|
@@ -12895,38 +12947,44 @@ class Call {
|
|
|
12895
12947
|
const sfuJoinFailures = new Map();
|
|
12896
12948
|
const joinData = data;
|
|
12897
12949
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
catch (err) {
|
|
12907
|
-
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
12908
|
-
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
12909
|
-
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
12910
|
-
// if the error is unrecoverable, we should not retry as that signals
|
|
12911
|
-
// that connectivity is good, but the coordinator doesn't allow the user
|
|
12912
|
-
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
12913
|
-
throw err;
|
|
12914
|
-
}
|
|
12915
|
-
// immediately switch to a different SFU in case of recoverable join error
|
|
12916
|
-
const switchSfu = err instanceof SfuJoinError &&
|
|
12917
|
-
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
12918
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
12919
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
12920
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
12921
|
-
if (switchSfu || failures >= 2) {
|
|
12922
|
-
joinData.migrating_from = sfuId;
|
|
12923
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
12950
|
+
try {
|
|
12951
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
12952
|
+
try {
|
|
12953
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
12954
|
+
await this.doJoin(data);
|
|
12955
|
+
delete joinData.migrating_from;
|
|
12956
|
+
delete joinData.migrating_from_list;
|
|
12957
|
+
break;
|
|
12924
12958
|
}
|
|
12925
|
-
|
|
12926
|
-
|
|
12959
|
+
catch (err) {
|
|
12960
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
12961
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
12962
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
12963
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
12964
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
12965
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
12966
|
+
throw err;
|
|
12967
|
+
}
|
|
12968
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
12969
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
12970
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
12971
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
12972
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
12973
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
12974
|
+
if (switchSfu || failures >= 2) {
|
|
12975
|
+
joinData.migrating_from = sfuId;
|
|
12976
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
12977
|
+
}
|
|
12978
|
+
if (attempt === maxJoinRetries - 1) {
|
|
12979
|
+
throw err;
|
|
12980
|
+
}
|
|
12927
12981
|
}
|
|
12982
|
+
await sleep(retryInterval(attempt));
|
|
12928
12983
|
}
|
|
12929
|
-
|
|
12984
|
+
}
|
|
12985
|
+
catch (error) {
|
|
12986
|
+
callingX?.endCall(this, 'error');
|
|
12987
|
+
throw error;
|
|
12930
12988
|
}
|
|
12931
12989
|
};
|
|
12932
12990
|
/**
|
|
@@ -13073,7 +13131,9 @@ class Call {
|
|
|
13073
13131
|
// re-apply them on later reconnections or server-side data fetches
|
|
13074
13132
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13075
13133
|
await this.applyDeviceConfig(this.state.settings, true);
|
|
13076
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13134
|
+
globalThis.streamRNVideoSDK?.callManager.start({
|
|
13135
|
+
isRingingTypeCall: this.ringing,
|
|
13136
|
+
});
|
|
13077
13137
|
this.deviceSettingsAppliedOnce = true;
|
|
13078
13138
|
}
|
|
13079
13139
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13501,6 +13561,7 @@ class Call {
|
|
|
13501
13561
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13502
13562
|
return;
|
|
13503
13563
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13564
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13504
13565
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13505
13566
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13506
13567
|
});
|
|
@@ -15596,7 +15657,7 @@ class StreamClient {
|
|
|
15596
15657
|
this.getUserAgent = () => {
|
|
15597
15658
|
if (!this.cachedUserAgent) {
|
|
15598
15659
|
const { clientAppIdentifier = {} } = this.options;
|
|
15599
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15660
|
+
const { sdkName = 'js', sdkVersion = "1.43.0-beta.0", ...extras } = clientAppIdentifier;
|
|
15600
15661
|
this.cachedUserAgent = [
|
|
15601
15662
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15602
15663
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|