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