@stream-io/video-client 1.44.4 → 1.44.6-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 +183 -51
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +183 -51
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +183 -51
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -1
- package/dist/src/helpers/AudioBindingsWatchdog.d.ts +37 -0
- package/dist/src/helpers/DynascaleManager.d.ts +3 -1
- package/dist/src/types.d.ts +37 -5
- package/package.json +1 -1
- package/src/Call.ts +85 -40
- package/src/devices/SpeakerManager.ts +1 -0
- package/src/devices/devices.ts +1 -3
- package/src/events/call.ts +3 -0
- package/src/helpers/AudioBindingsWatchdog.ts +118 -0
- package/src/helpers/DynascaleManager.ts +22 -24
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +325 -0
- package/src/helpers/__tests__/DynascaleManager.test.ts +64 -0
- package/src/store/stateStore.ts +1 -1
- package/src/types.ts +48 -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.44.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.4...@stream-io/video-client-1.44.5) (2026-03-27)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- make WebAudio opt-in, add AudioBindingsWatchdog ([#2171](https://github.com/GetStream/stream-video-js/issues/2171)) ([8d00f48](https://github.com/GetStream/stream-video-js/commit/8d00f485a37fec23dca340d32738a3cb1f7f325a))
|
|
10
|
+
|
|
5
11
|
## [1.44.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.3...@stream-io/video-client-1.44.4) (2026-03-20)
|
|
6
12
|
|
|
7
13
|
- trace device permission state transitions ([#2168](https://github.com/GetStream/stream-video-js/issues/2168)) ([e4203a3](https://github.com/GetStream/stream-video-js/commit/e4203a34cad1c90d1bc5612fc379dd1f0f0ebe5d))
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4784,7 +4784,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4784
4784
|
* The currently connected user.
|
|
4785
4785
|
*/
|
|
4786
4786
|
get connectedUser() {
|
|
4787
|
-
return
|
|
4787
|
+
return this.connectedUserSubject.getValue();
|
|
4788
4788
|
}
|
|
4789
4789
|
/**
|
|
4790
4790
|
* A list of {@link Call} objects created/tracked by this client.
|
|
@@ -6283,7 +6283,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6283
6283
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6284
6284
|
};
|
|
6285
6285
|
|
|
6286
|
-
const version = "1.44.
|
|
6286
|
+
const version = "1.44.6-beta.0";
|
|
6287
6287
|
const [major, minor, patch] = version.split('.');
|
|
6288
6288
|
let sdkInfo = {
|
|
6289
6289
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8982,6 +8982,7 @@ const watchCallRejected = (call) => {
|
|
|
8982
8982
|
else {
|
|
8983
8983
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
8984
8984
|
call.logger.info('call creator rejected, leaving call');
|
|
8985
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8985
8986
|
await call.leave({ message: 'ring: creator rejected' });
|
|
8986
8987
|
}
|
|
8987
8988
|
}
|
|
@@ -8992,6 +8993,7 @@ const watchCallRejected = (call) => {
|
|
|
8992
8993
|
*/
|
|
8993
8994
|
const watchCallEnded = (call) => {
|
|
8994
8995
|
return function onCallEnded() {
|
|
8996
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8995
8997
|
const { callingState } = call.state;
|
|
8996
8998
|
if (callingState !== CallingState.IDLE &&
|
|
8997
8999
|
callingState !== CallingState.LEFT) {
|
|
@@ -9023,6 +9025,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
9023
9025
|
// update the call state to reflect the call has ended.
|
|
9024
9026
|
call.state.setEndedAt(new Date());
|
|
9025
9027
|
const reason = CallEndedReason[e.reason];
|
|
9028
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9026
9029
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
9027
9030
|
}
|
|
9028
9031
|
catch (err) {
|
|
@@ -9491,6 +9494,96 @@ class ViewportTracker {
|
|
|
9491
9494
|
}
|
|
9492
9495
|
}
|
|
9493
9496
|
|
|
9497
|
+
const toBindingKey = (sessionId, trackType = 'audioTrack') => `${sessionId}/${trackType}`;
|
|
9498
|
+
/**
|
|
9499
|
+
* Tracks audio element bindings and periodically warns about
|
|
9500
|
+
* remote participants whose audio streams have no bound element.
|
|
9501
|
+
*/
|
|
9502
|
+
class AudioBindingsWatchdog {
|
|
9503
|
+
constructor(state, tracer) {
|
|
9504
|
+
this.state = state;
|
|
9505
|
+
this.tracer = tracer;
|
|
9506
|
+
this.bindings = new Map();
|
|
9507
|
+
this.enabled = true;
|
|
9508
|
+
this.logger = videoLoggerSystem.getLogger('AudioBindingsWatchdog');
|
|
9509
|
+
/**
|
|
9510
|
+
* Registers an audio element binding for the given session and track type.
|
|
9511
|
+
* Warns if a different element is already bound to the same key.
|
|
9512
|
+
*/
|
|
9513
|
+
this.register = (audioElement, sessionId, trackType) => {
|
|
9514
|
+
const key = toBindingKey(sessionId, trackType);
|
|
9515
|
+
const existing = this.bindings.get(key);
|
|
9516
|
+
if (existing && existing !== audioElement) {
|
|
9517
|
+
this.logger.warn(`Audio element already bound to ${sessionId} and ${trackType}`);
|
|
9518
|
+
this.tracer.trace('audioBinding.alreadyBoundWarning', trackType);
|
|
9519
|
+
}
|
|
9520
|
+
this.bindings.set(key, audioElement);
|
|
9521
|
+
};
|
|
9522
|
+
/**
|
|
9523
|
+
* Removes the audio element binding for the given session and track type.
|
|
9524
|
+
*/
|
|
9525
|
+
this.unregister = (sessionId, trackType) => {
|
|
9526
|
+
this.bindings.delete(toBindingKey(sessionId, trackType));
|
|
9527
|
+
};
|
|
9528
|
+
/**
|
|
9529
|
+
* Enables or disables the watchdog.
|
|
9530
|
+
* When disabled, the periodic check stops but bindings are still tracked.
|
|
9531
|
+
*/
|
|
9532
|
+
this.setEnabled = (enabled) => {
|
|
9533
|
+
this.enabled = enabled;
|
|
9534
|
+
if (enabled) {
|
|
9535
|
+
this.start();
|
|
9536
|
+
}
|
|
9537
|
+
else {
|
|
9538
|
+
this.stop();
|
|
9539
|
+
}
|
|
9540
|
+
};
|
|
9541
|
+
/**
|
|
9542
|
+
* Stops the watchdog and unsubscribes from callingState changes.
|
|
9543
|
+
*/
|
|
9544
|
+
this.dispose = () => {
|
|
9545
|
+
this.stop();
|
|
9546
|
+
this.unsubscribeCallingState();
|
|
9547
|
+
};
|
|
9548
|
+
this.start = () => {
|
|
9549
|
+
clearInterval(this.watchdogInterval);
|
|
9550
|
+
this.watchdogInterval = setInterval(() => {
|
|
9551
|
+
const danglingUserIds = [];
|
|
9552
|
+
for (const p of this.state.participants) {
|
|
9553
|
+
if (p.isLocalParticipant)
|
|
9554
|
+
continue;
|
|
9555
|
+
const { audioStream, screenShareAudioStream, sessionId, userId } = p;
|
|
9556
|
+
if (audioStream && !this.bindings.has(toBindingKey(sessionId))) {
|
|
9557
|
+
danglingUserIds.push(userId);
|
|
9558
|
+
}
|
|
9559
|
+
if (screenShareAudioStream &&
|
|
9560
|
+
!this.bindings.has(toBindingKey(sessionId, 'screenShareAudioTrack'))) {
|
|
9561
|
+
danglingUserIds.push(userId);
|
|
9562
|
+
}
|
|
9563
|
+
}
|
|
9564
|
+
if (danglingUserIds.length > 0) {
|
|
9565
|
+
const key = 'audioBinding.danglingWarning';
|
|
9566
|
+
this.tracer.traceOnce(key, key, danglingUserIds);
|
|
9567
|
+
this.logger.warn(`Dangling audio bindings detected. Did you forget to bind the audio element? user_ids: ${danglingUserIds}.`);
|
|
9568
|
+
}
|
|
9569
|
+
}, 3000);
|
|
9570
|
+
};
|
|
9571
|
+
this.stop = () => {
|
|
9572
|
+
clearInterval(this.watchdogInterval);
|
|
9573
|
+
};
|
|
9574
|
+
this.unsubscribeCallingState = createSubscription(state.callingState$, (callingState) => {
|
|
9575
|
+
if (!this.enabled)
|
|
9576
|
+
return;
|
|
9577
|
+
if (callingState !== CallingState.JOINED) {
|
|
9578
|
+
this.stop();
|
|
9579
|
+
}
|
|
9580
|
+
else {
|
|
9581
|
+
this.start();
|
|
9582
|
+
}
|
|
9583
|
+
});
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9494
9587
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
|
|
9495
9588
|
videoTrack: VisibilityState.UNKNOWN,
|
|
9496
9589
|
screenShareTrack: VisibilityState.UNKNOWN,
|
|
@@ -9516,7 +9609,7 @@ class DynascaleManager {
|
|
|
9516
9609
|
*/
|
|
9517
9610
|
this.viewportTracker = new ViewportTracker();
|
|
9518
9611
|
this.logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
9519
|
-
this.useWebAudio =
|
|
9612
|
+
this.useWebAudio = false;
|
|
9520
9613
|
this.pendingSubscriptionsUpdate = null;
|
|
9521
9614
|
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
9522
9615
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
@@ -9548,7 +9641,8 @@ class DynascaleManager {
|
|
|
9548
9641
|
if (this.pendingSubscriptionsUpdate) {
|
|
9549
9642
|
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
9550
9643
|
}
|
|
9551
|
-
|
|
9644
|
+
this.audioBindingsWatchdog?.dispose();
|
|
9645
|
+
const context = this.audioContext;
|
|
9552
9646
|
if (context && context.state !== 'closed') {
|
|
9553
9647
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
9554
9648
|
await context.close();
|
|
@@ -9747,12 +9841,13 @@ class DynascaleManager {
|
|
|
9747
9841
|
lastDimensions = currentDimensions;
|
|
9748
9842
|
});
|
|
9749
9843
|
resizeObserver?.observe(videoElement);
|
|
9844
|
+
const isVideoTrack = trackType === 'videoTrack';
|
|
9750
9845
|
// element renders and gets bound - track subscription gets
|
|
9751
9846
|
// triggered first other ones get skipped on initial subscriptions
|
|
9752
9847
|
const publishedTracksSubscription = boundParticipant.isLocalParticipant
|
|
9753
9848
|
? null
|
|
9754
9849
|
: participant$
|
|
9755
|
-
.pipe(distinctUntilKeyChanged('publishedTracks'), map((p) =>
|
|
9850
|
+
.pipe(distinctUntilKeyChanged('publishedTracks'), map((p) => (isVideoTrack ? hasVideo(p) : hasScreenShare(p))), distinctUntilChanged())
|
|
9756
9851
|
.subscribe((isPublishing) => {
|
|
9757
9852
|
if (isPublishing) {
|
|
9758
9853
|
// the participant just started to publish a track
|
|
@@ -9772,10 +9867,11 @@ class DynascaleManager {
|
|
|
9772
9867
|
// without prior user interaction:
|
|
9773
9868
|
// https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
|
|
9774
9869
|
videoElement.muted = true;
|
|
9870
|
+
const trackKey = isVideoTrack ? 'videoStream' : 'screenShareStream';
|
|
9775
9871
|
const streamSubscription = participant$
|
|
9776
|
-
.pipe(distinctUntilKeyChanged(
|
|
9872
|
+
.pipe(distinctUntilKeyChanged(trackKey))
|
|
9777
9873
|
.subscribe((p) => {
|
|
9778
|
-
const source =
|
|
9874
|
+
const source = isVideoTrack ? p.videoStream : p.screenShareStream;
|
|
9779
9875
|
if (videoElement.srcObject === source)
|
|
9780
9876
|
return;
|
|
9781
9877
|
videoElement.srcObject = source ?? null;
|
|
@@ -9814,6 +9910,7 @@ class DynascaleManager {
|
|
|
9814
9910
|
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
9815
9911
|
if (!participant || participant.isLocalParticipant)
|
|
9816
9912
|
return;
|
|
9913
|
+
this.audioBindingsWatchdog?.register(audioElement, sessionId, trackType);
|
|
9817
9914
|
const participant$ = this.callState.participants$.pipe(map((ps) => ps.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
9818
9915
|
const updateSinkId = (deviceId, audioContext) => {
|
|
9819
9916
|
if (!deviceId)
|
|
@@ -9832,14 +9929,12 @@ class DynascaleManager {
|
|
|
9832
9929
|
};
|
|
9833
9930
|
let sourceNode = undefined;
|
|
9834
9931
|
let gainNode = undefined;
|
|
9932
|
+
const isAudioTrack = trackType === 'audioTrack';
|
|
9933
|
+
const trackKey = isAudioTrack ? 'audioStream' : 'screenShareAudioStream';
|
|
9835
9934
|
const updateMediaStreamSubscription = participant$
|
|
9836
|
-
.pipe(distinctUntilKeyChanged(
|
|
9837
|
-
? 'screenShareAudioStream'
|
|
9838
|
-
: 'audioStream'))
|
|
9935
|
+
.pipe(distinctUntilKeyChanged(trackKey))
|
|
9839
9936
|
.subscribe((p) => {
|
|
9840
|
-
const source =
|
|
9841
|
-
? p.screenShareAudioStream
|
|
9842
|
-
: p.audioStream;
|
|
9937
|
+
const source = isAudioTrack ? p.audioStream : p.screenShareAudioStream;
|
|
9843
9938
|
if (audioElement.srcObject === source)
|
|
9844
9939
|
return;
|
|
9845
9940
|
setTimeout(() => {
|
|
@@ -9894,6 +9989,7 @@ class DynascaleManager {
|
|
|
9894
9989
|
});
|
|
9895
9990
|
audioElement.autoplay = true;
|
|
9896
9991
|
return () => {
|
|
9992
|
+
this.audioBindingsWatchdog?.unregister(sessionId, trackType);
|
|
9897
9993
|
sinkIdSubscription?.unsubscribe();
|
|
9898
9994
|
volumeSubscription.unsubscribe();
|
|
9899
9995
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -9954,6 +10050,9 @@ class DynascaleManager {
|
|
|
9954
10050
|
this.callState = callState;
|
|
9955
10051
|
this.speaker = speaker;
|
|
9956
10052
|
this.tracer = tracer;
|
|
10053
|
+
if (!isReactNative()) {
|
|
10054
|
+
this.audioBindingsWatchdog = new AudioBindingsWatchdog(callState, tracer);
|
|
10055
|
+
}
|
|
9957
10056
|
}
|
|
9958
10057
|
setSfuClient(sfuClient) {
|
|
9959
10058
|
this.sfuClient = sfuClient;
|
|
@@ -10342,9 +10441,6 @@ const getDevices = (permission, kind, tracer) => {
|
|
|
10342
10441
|
const checkIfAudioOutputChangeSupported = () => {
|
|
10343
10442
|
if (typeof document === 'undefined')
|
|
10344
10443
|
return false;
|
|
10345
|
-
// Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
|
|
10346
|
-
if (isSafari())
|
|
10347
|
-
return 'setSinkId' in AudioContext.prototype;
|
|
10348
10444
|
const element = document.createElement('audio');
|
|
10349
10445
|
return 'setSinkId' in element;
|
|
10350
10446
|
};
|
|
@@ -12533,6 +12629,7 @@ class SpeakerManager {
|
|
|
12533
12629
|
this.defaultDevice = defaultDevice;
|
|
12534
12630
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12535
12631
|
defaultDevice,
|
|
12632
|
+
isRingingTypeCall: this.call.ringing,
|
|
12536
12633
|
});
|
|
12537
12634
|
}
|
|
12538
12635
|
}
|
|
@@ -12732,6 +12829,7 @@ class Call {
|
|
|
12732
12829
|
const currentUserId = this.currentUserId;
|
|
12733
12830
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12734
12831
|
this.logger.info('Leaving call because of being blocked');
|
|
12832
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12735
12833
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12736
12834
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12737
12835
|
});
|
|
@@ -12768,6 +12866,7 @@ class Call {
|
|
|
12768
12866
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === CallingState.RINGING;
|
|
12769
12867
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12770
12868
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12869
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12771
12870
|
this.leave().catch(() => {
|
|
12772
12871
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12773
12872
|
});
|
|
@@ -12779,6 +12878,9 @@ class Call {
|
|
|
12779
12878
|
const receiver_id = this.clientStore.connectedUser?.id;
|
|
12780
12879
|
const ended_at = callSession?.ended_at;
|
|
12781
12880
|
const created_by_id = this.state.createdBy?.id;
|
|
12881
|
+
if (this.currentUserId && created_by_id === this.currentUserId) {
|
|
12882
|
+
globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
|
|
12883
|
+
}
|
|
12782
12884
|
const rejected_by = callSession?.rejected_by;
|
|
12783
12885
|
const accepted_by = callSession?.accepted_by;
|
|
12784
12886
|
let leaveCallIdle = false;
|
|
@@ -12917,17 +13019,28 @@ class Call {
|
|
|
12917
13019
|
}
|
|
12918
13020
|
if (callingState === CallingState.RINGING && reject !== false) {
|
|
12919
13021
|
if (reject) {
|
|
12920
|
-
|
|
13022
|
+
const reasonToEndCallReason = {
|
|
13023
|
+
timeout: 'missed',
|
|
13024
|
+
cancel: 'canceled',
|
|
13025
|
+
busy: 'busy',
|
|
13026
|
+
decline: 'rejected',
|
|
13027
|
+
};
|
|
13028
|
+
const rejectReason = reason ?? 'decline';
|
|
13029
|
+
const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
|
|
13030
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
|
|
13031
|
+
await this.reject(rejectReason);
|
|
12921
13032
|
}
|
|
12922
13033
|
else {
|
|
12923
13034
|
// if reject was undefined, we still have to cancel the call automatically
|
|
12924
13035
|
// when I am the creator and everyone else left the call
|
|
12925
13036
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
12926
13037
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
13038
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
12927
13039
|
await this.reject('cancel');
|
|
12928
13040
|
}
|
|
12929
13041
|
}
|
|
12930
13042
|
}
|
|
13043
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
12931
13044
|
this.statsReporter?.stop();
|
|
12932
13045
|
this.statsReporter = undefined;
|
|
12933
13046
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -12954,7 +13067,9 @@ class Call {
|
|
|
12954
13067
|
this.ringingSubject.next(false);
|
|
12955
13068
|
this.cancelAutoDrop();
|
|
12956
13069
|
this.clientStore.unregisterCall(this);
|
|
12957
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
13070
|
+
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
13071
|
+
isRingingTypeCall: this.ringing,
|
|
13072
|
+
});
|
|
12958
13073
|
this.camera.dispose();
|
|
12959
13074
|
this.microphone.dispose();
|
|
12960
13075
|
this.screenShare.dispose();
|
|
@@ -13120,11 +13235,19 @@ class Call {
|
|
|
13120
13235
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
13121
13236
|
*/
|
|
13122
13237
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
13123
|
-
await this.setup();
|
|
13124
13238
|
const callingState = this.state.callingState;
|
|
13125
13239
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
13126
13240
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
13127
13241
|
}
|
|
13242
|
+
if (data?.ring) {
|
|
13243
|
+
this.ringingSubject.next(true);
|
|
13244
|
+
}
|
|
13245
|
+
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
13246
|
+
if (callingX) {
|
|
13247
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
13248
|
+
await callingX.joinCall(this, this.clientStore.calls);
|
|
13249
|
+
}
|
|
13250
|
+
await this.setup();
|
|
13128
13251
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
13129
13252
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
13130
13253
|
// we will count the number of join failures per SFU.
|
|
@@ -13133,38 +13256,44 @@ class Call {
|
|
|
13133
13256
|
const sfuJoinFailures = new Map();
|
|
13134
13257
|
const joinData = data;
|
|
13135
13258
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
catch (err) {
|
|
13145
|
-
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13146
|
-
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13147
|
-
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13148
|
-
// if the error is unrecoverable, we should not retry as that signals
|
|
13149
|
-
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13150
|
-
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13151
|
-
throw err;
|
|
13152
|
-
}
|
|
13153
|
-
// immediately switch to a different SFU in case of recoverable join error
|
|
13154
|
-
const switchSfu = err instanceof SfuJoinError &&
|
|
13155
|
-
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13156
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
13157
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13158
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
13159
|
-
if (switchSfu || failures >= 2) {
|
|
13160
|
-
joinData.migrating_from = sfuId;
|
|
13161
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13259
|
+
try {
|
|
13260
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
13261
|
+
try {
|
|
13262
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
13263
|
+
await this.doJoin(data);
|
|
13264
|
+
delete joinData.migrating_from;
|
|
13265
|
+
delete joinData.migrating_from_list;
|
|
13266
|
+
break;
|
|
13162
13267
|
}
|
|
13163
|
-
|
|
13164
|
-
|
|
13268
|
+
catch (err) {
|
|
13269
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13270
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13271
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13272
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
13273
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13274
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13275
|
+
throw err;
|
|
13276
|
+
}
|
|
13277
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
13278
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
13279
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13280
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
13281
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13282
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
13283
|
+
if (switchSfu || failures >= 2) {
|
|
13284
|
+
joinData.migrating_from = sfuId;
|
|
13285
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13286
|
+
}
|
|
13287
|
+
if (attempt === maxJoinRetries - 1) {
|
|
13288
|
+
throw err;
|
|
13289
|
+
}
|
|
13165
13290
|
}
|
|
13291
|
+
await sleep(retryInterval(attempt));
|
|
13166
13292
|
}
|
|
13167
|
-
|
|
13293
|
+
}
|
|
13294
|
+
catch (error) {
|
|
13295
|
+
callingX?.endCall(this, 'error');
|
|
13296
|
+
throw error;
|
|
13168
13297
|
}
|
|
13169
13298
|
};
|
|
13170
13299
|
/**
|
|
@@ -13311,7 +13440,9 @@ class Call {
|
|
|
13311
13440
|
// re-apply them on later reconnections or server-side data fetches
|
|
13312
13441
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13313
13442
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
13314
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13443
|
+
globalThis.streamRNVideoSDK?.callManager.start({
|
|
13444
|
+
isRingingTypeCall: this.ringing,
|
|
13445
|
+
});
|
|
13315
13446
|
this.deviceSettingsAppliedOnce = true;
|
|
13316
13447
|
}
|
|
13317
13448
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13739,6 +13870,7 @@ class Call {
|
|
|
13739
13870
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13740
13871
|
return;
|
|
13741
13872
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13873
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13742
13874
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13743
13875
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13744
13876
|
});
|
|
@@ -14760,7 +14892,7 @@ class Call {
|
|
|
14760
14892
|
* A flag indicating whether the call was created by the current user.
|
|
14761
14893
|
*/
|
|
14762
14894
|
get isCreatedByMe() {
|
|
14763
|
-
return this.state.createdBy?.id === this.currentUserId;
|
|
14895
|
+
return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
|
|
14764
14896
|
}
|
|
14765
14897
|
}
|
|
14766
14898
|
|
|
@@ -15886,7 +16018,7 @@ class StreamClient {
|
|
|
15886
16018
|
this.getUserAgent = () => {
|
|
15887
16019
|
if (!this.cachedUserAgent) {
|
|
15888
16020
|
const { clientAppIdentifier = {} } = this.options;
|
|
15889
|
-
const { sdkName = 'js', sdkVersion = "1.44.
|
|
16021
|
+
const { sdkName = 'js', sdkVersion = "1.44.6-beta.0", ...extras } = clientAppIdentifier;
|
|
15890
16022
|
this.cachedUserAgent = [
|
|
15891
16023
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15892
16024
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|