@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.cjs.js
CHANGED
|
@@ -4804,7 +4804,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4804
4804
|
* The currently connected user.
|
|
4805
4805
|
*/
|
|
4806
4806
|
get connectedUser() {
|
|
4807
|
-
return
|
|
4807
|
+
return this.connectedUserSubject.getValue();
|
|
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.44.
|
|
6306
|
+
const version = "1.44.6-beta.0";
|
|
6307
6307
|
const [major, minor, patch] = version.split('.');
|
|
6308
6308
|
let sdkInfo = {
|
|
6309
6309
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -9002,6 +9002,7 @@ 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');
|
|
9005
9006
|
await call.leave({ message: 'ring: creator rejected' });
|
|
9006
9007
|
}
|
|
9007
9008
|
}
|
|
@@ -9012,6 +9013,7 @@ const watchCallRejected = (call) => {
|
|
|
9012
9013
|
*/
|
|
9013
9014
|
const watchCallEnded = (call) => {
|
|
9014
9015
|
return function onCallEnded() {
|
|
9016
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9015
9017
|
const { callingState } = call.state;
|
|
9016
9018
|
if (callingState !== exports.CallingState.IDLE &&
|
|
9017
9019
|
callingState !== exports.CallingState.LEFT) {
|
|
@@ -9043,6 +9045,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
9043
9045
|
// update the call state to reflect the call has ended.
|
|
9044
9046
|
call.state.setEndedAt(new Date());
|
|
9045
9047
|
const reason = CallEndedReason[e.reason];
|
|
9048
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9046
9049
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
9047
9050
|
}
|
|
9048
9051
|
catch (err) {
|
|
@@ -9511,6 +9514,96 @@ class ViewportTracker {
|
|
|
9511
9514
|
}
|
|
9512
9515
|
}
|
|
9513
9516
|
|
|
9517
|
+
const toBindingKey = (sessionId, trackType = 'audioTrack') => `${sessionId}/${trackType}`;
|
|
9518
|
+
/**
|
|
9519
|
+
* Tracks audio element bindings and periodically warns about
|
|
9520
|
+
* remote participants whose audio streams have no bound element.
|
|
9521
|
+
*/
|
|
9522
|
+
class AudioBindingsWatchdog {
|
|
9523
|
+
constructor(state, tracer) {
|
|
9524
|
+
this.state = state;
|
|
9525
|
+
this.tracer = tracer;
|
|
9526
|
+
this.bindings = new Map();
|
|
9527
|
+
this.enabled = true;
|
|
9528
|
+
this.logger = videoLoggerSystem.getLogger('AudioBindingsWatchdog');
|
|
9529
|
+
/**
|
|
9530
|
+
* Registers an audio element binding for the given session and track type.
|
|
9531
|
+
* Warns if a different element is already bound to the same key.
|
|
9532
|
+
*/
|
|
9533
|
+
this.register = (audioElement, sessionId, trackType) => {
|
|
9534
|
+
const key = toBindingKey(sessionId, trackType);
|
|
9535
|
+
const existing = this.bindings.get(key);
|
|
9536
|
+
if (existing && existing !== audioElement) {
|
|
9537
|
+
this.logger.warn(`Audio element already bound to ${sessionId} and ${trackType}`);
|
|
9538
|
+
this.tracer.trace('audioBinding.alreadyBoundWarning', trackType);
|
|
9539
|
+
}
|
|
9540
|
+
this.bindings.set(key, audioElement);
|
|
9541
|
+
};
|
|
9542
|
+
/**
|
|
9543
|
+
* Removes the audio element binding for the given session and track type.
|
|
9544
|
+
*/
|
|
9545
|
+
this.unregister = (sessionId, trackType) => {
|
|
9546
|
+
this.bindings.delete(toBindingKey(sessionId, trackType));
|
|
9547
|
+
};
|
|
9548
|
+
/**
|
|
9549
|
+
* Enables or disables the watchdog.
|
|
9550
|
+
* When disabled, the periodic check stops but bindings are still tracked.
|
|
9551
|
+
*/
|
|
9552
|
+
this.setEnabled = (enabled) => {
|
|
9553
|
+
this.enabled = enabled;
|
|
9554
|
+
if (enabled) {
|
|
9555
|
+
this.start();
|
|
9556
|
+
}
|
|
9557
|
+
else {
|
|
9558
|
+
this.stop();
|
|
9559
|
+
}
|
|
9560
|
+
};
|
|
9561
|
+
/**
|
|
9562
|
+
* Stops the watchdog and unsubscribes from callingState changes.
|
|
9563
|
+
*/
|
|
9564
|
+
this.dispose = () => {
|
|
9565
|
+
this.stop();
|
|
9566
|
+
this.unsubscribeCallingState();
|
|
9567
|
+
};
|
|
9568
|
+
this.start = () => {
|
|
9569
|
+
clearInterval(this.watchdogInterval);
|
|
9570
|
+
this.watchdogInterval = setInterval(() => {
|
|
9571
|
+
const danglingUserIds = [];
|
|
9572
|
+
for (const p of this.state.participants) {
|
|
9573
|
+
if (p.isLocalParticipant)
|
|
9574
|
+
continue;
|
|
9575
|
+
const { audioStream, screenShareAudioStream, sessionId, userId } = p;
|
|
9576
|
+
if (audioStream && !this.bindings.has(toBindingKey(sessionId))) {
|
|
9577
|
+
danglingUserIds.push(userId);
|
|
9578
|
+
}
|
|
9579
|
+
if (screenShareAudioStream &&
|
|
9580
|
+
!this.bindings.has(toBindingKey(sessionId, 'screenShareAudioTrack'))) {
|
|
9581
|
+
danglingUserIds.push(userId);
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
if (danglingUserIds.length > 0) {
|
|
9585
|
+
const key = 'audioBinding.danglingWarning';
|
|
9586
|
+
this.tracer.traceOnce(key, key, danglingUserIds);
|
|
9587
|
+
this.logger.warn(`Dangling audio bindings detected. Did you forget to bind the audio element? user_ids: ${danglingUserIds}.`);
|
|
9588
|
+
}
|
|
9589
|
+
}, 3000);
|
|
9590
|
+
};
|
|
9591
|
+
this.stop = () => {
|
|
9592
|
+
clearInterval(this.watchdogInterval);
|
|
9593
|
+
};
|
|
9594
|
+
this.unsubscribeCallingState = createSubscription(state.callingState$, (callingState) => {
|
|
9595
|
+
if (!this.enabled)
|
|
9596
|
+
return;
|
|
9597
|
+
if (callingState !== exports.CallingState.JOINED) {
|
|
9598
|
+
this.stop();
|
|
9599
|
+
}
|
|
9600
|
+
else {
|
|
9601
|
+
this.start();
|
|
9602
|
+
}
|
|
9603
|
+
});
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
|
|
9514
9607
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
|
|
9515
9608
|
videoTrack: exports.VisibilityState.UNKNOWN,
|
|
9516
9609
|
screenShareTrack: exports.VisibilityState.UNKNOWN,
|
|
@@ -9536,7 +9629,7 @@ class DynascaleManager {
|
|
|
9536
9629
|
*/
|
|
9537
9630
|
this.viewportTracker = new ViewportTracker();
|
|
9538
9631
|
this.logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
9539
|
-
this.useWebAudio =
|
|
9632
|
+
this.useWebAudio = false;
|
|
9540
9633
|
this.pendingSubscriptionsUpdate = null;
|
|
9541
9634
|
this.videoTrackSubscriptionOverridesSubject = new rxjs.BehaviorSubject({});
|
|
9542
9635
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
@@ -9568,7 +9661,8 @@ class DynascaleManager {
|
|
|
9568
9661
|
if (this.pendingSubscriptionsUpdate) {
|
|
9569
9662
|
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
9570
9663
|
}
|
|
9571
|
-
|
|
9664
|
+
this.audioBindingsWatchdog?.dispose();
|
|
9665
|
+
const context = this.audioContext;
|
|
9572
9666
|
if (context && context.state !== 'closed') {
|
|
9573
9667
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
9574
9668
|
await context.close();
|
|
@@ -9767,12 +9861,13 @@ class DynascaleManager {
|
|
|
9767
9861
|
lastDimensions = currentDimensions;
|
|
9768
9862
|
});
|
|
9769
9863
|
resizeObserver?.observe(videoElement);
|
|
9864
|
+
const isVideoTrack = trackType === 'videoTrack';
|
|
9770
9865
|
// element renders and gets bound - track subscription gets
|
|
9771
9866
|
// triggered first other ones get skipped on initial subscriptions
|
|
9772
9867
|
const publishedTracksSubscription = boundParticipant.isLocalParticipant
|
|
9773
9868
|
? null
|
|
9774
9869
|
: participant$
|
|
9775
|
-
.pipe(rxjs.distinctUntilKeyChanged('publishedTracks'), rxjs.map((p) =>
|
|
9870
|
+
.pipe(rxjs.distinctUntilKeyChanged('publishedTracks'), rxjs.map((p) => (isVideoTrack ? hasVideo(p) : hasScreenShare(p))), rxjs.distinctUntilChanged())
|
|
9776
9871
|
.subscribe((isPublishing) => {
|
|
9777
9872
|
if (isPublishing) {
|
|
9778
9873
|
// the participant just started to publish a track
|
|
@@ -9792,10 +9887,11 @@ class DynascaleManager {
|
|
|
9792
9887
|
// without prior user interaction:
|
|
9793
9888
|
// https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
|
|
9794
9889
|
videoElement.muted = true;
|
|
9890
|
+
const trackKey = isVideoTrack ? 'videoStream' : 'screenShareStream';
|
|
9795
9891
|
const streamSubscription = participant$
|
|
9796
|
-
.pipe(rxjs.distinctUntilKeyChanged(
|
|
9892
|
+
.pipe(rxjs.distinctUntilKeyChanged(trackKey))
|
|
9797
9893
|
.subscribe((p) => {
|
|
9798
|
-
const source =
|
|
9894
|
+
const source = isVideoTrack ? p.videoStream : p.screenShareStream;
|
|
9799
9895
|
if (videoElement.srcObject === source)
|
|
9800
9896
|
return;
|
|
9801
9897
|
videoElement.srcObject = source ?? null;
|
|
@@ -9834,6 +9930,7 @@ class DynascaleManager {
|
|
|
9834
9930
|
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
9835
9931
|
if (!participant || participant.isLocalParticipant)
|
|
9836
9932
|
return;
|
|
9933
|
+
this.audioBindingsWatchdog?.register(audioElement, sessionId, trackType);
|
|
9837
9934
|
const participant$ = this.callState.participants$.pipe(rxjs.map((ps) => ps.find((p) => p.sessionId === sessionId)), rxjs.takeWhile((p) => !!p), rxjs.distinctUntilChanged(), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
|
|
9838
9935
|
const updateSinkId = (deviceId, audioContext) => {
|
|
9839
9936
|
if (!deviceId)
|
|
@@ -9852,14 +9949,12 @@ class DynascaleManager {
|
|
|
9852
9949
|
};
|
|
9853
9950
|
let sourceNode = undefined;
|
|
9854
9951
|
let gainNode = undefined;
|
|
9952
|
+
const isAudioTrack = trackType === 'audioTrack';
|
|
9953
|
+
const trackKey = isAudioTrack ? 'audioStream' : 'screenShareAudioStream';
|
|
9855
9954
|
const updateMediaStreamSubscription = participant$
|
|
9856
|
-
.pipe(rxjs.distinctUntilKeyChanged(
|
|
9857
|
-
? 'screenShareAudioStream'
|
|
9858
|
-
: 'audioStream'))
|
|
9955
|
+
.pipe(rxjs.distinctUntilKeyChanged(trackKey))
|
|
9859
9956
|
.subscribe((p) => {
|
|
9860
|
-
const source =
|
|
9861
|
-
? p.screenShareAudioStream
|
|
9862
|
-
: p.audioStream;
|
|
9957
|
+
const source = isAudioTrack ? p.audioStream : p.screenShareAudioStream;
|
|
9863
9958
|
if (audioElement.srcObject === source)
|
|
9864
9959
|
return;
|
|
9865
9960
|
setTimeout(() => {
|
|
@@ -9914,6 +10009,7 @@ class DynascaleManager {
|
|
|
9914
10009
|
});
|
|
9915
10010
|
audioElement.autoplay = true;
|
|
9916
10011
|
return () => {
|
|
10012
|
+
this.audioBindingsWatchdog?.unregister(sessionId, trackType);
|
|
9917
10013
|
sinkIdSubscription?.unsubscribe();
|
|
9918
10014
|
volumeSubscription.unsubscribe();
|
|
9919
10015
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -9974,6 +10070,9 @@ class DynascaleManager {
|
|
|
9974
10070
|
this.callState = callState;
|
|
9975
10071
|
this.speaker = speaker;
|
|
9976
10072
|
this.tracer = tracer;
|
|
10073
|
+
if (!isReactNative()) {
|
|
10074
|
+
this.audioBindingsWatchdog = new AudioBindingsWatchdog(callState, tracer);
|
|
10075
|
+
}
|
|
9977
10076
|
}
|
|
9978
10077
|
setSfuClient(sfuClient) {
|
|
9979
10078
|
this.sfuClient = sfuClient;
|
|
@@ -10362,9 +10461,6 @@ const getDevices = (permission, kind, tracer) => {
|
|
|
10362
10461
|
const checkIfAudioOutputChangeSupported = () => {
|
|
10363
10462
|
if (typeof document === 'undefined')
|
|
10364
10463
|
return false;
|
|
10365
|
-
// Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
|
|
10366
|
-
if (isSafari())
|
|
10367
|
-
return 'setSinkId' in AudioContext.prototype;
|
|
10368
10464
|
const element = document.createElement('audio');
|
|
10369
10465
|
return 'setSinkId' in element;
|
|
10370
10466
|
};
|
|
@@ -12553,6 +12649,7 @@ class SpeakerManager {
|
|
|
12553
12649
|
this.defaultDevice = defaultDevice;
|
|
12554
12650
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12555
12651
|
defaultDevice,
|
|
12652
|
+
isRingingTypeCall: this.call.ringing,
|
|
12556
12653
|
});
|
|
12557
12654
|
}
|
|
12558
12655
|
}
|
|
@@ -12752,6 +12849,7 @@ class Call {
|
|
|
12752
12849
|
const currentUserId = this.currentUserId;
|
|
12753
12850
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12754
12851
|
this.logger.info('Leaving call because of being blocked');
|
|
12852
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12755
12853
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12756
12854
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12757
12855
|
});
|
|
@@ -12788,6 +12886,7 @@ class Call {
|
|
|
12788
12886
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === exports.CallingState.RINGING;
|
|
12789
12887
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12790
12888
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12889
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12791
12890
|
this.leave().catch(() => {
|
|
12792
12891
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12793
12892
|
});
|
|
@@ -12799,6 +12898,9 @@ class Call {
|
|
|
12799
12898
|
const receiver_id = this.clientStore.connectedUser?.id;
|
|
12800
12899
|
const ended_at = callSession?.ended_at;
|
|
12801
12900
|
const created_by_id = this.state.createdBy?.id;
|
|
12901
|
+
if (this.currentUserId && created_by_id === this.currentUserId) {
|
|
12902
|
+
globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
|
|
12903
|
+
}
|
|
12802
12904
|
const rejected_by = callSession?.rejected_by;
|
|
12803
12905
|
const accepted_by = callSession?.accepted_by;
|
|
12804
12906
|
let leaveCallIdle = false;
|
|
@@ -12937,17 +13039,28 @@ class Call {
|
|
|
12937
13039
|
}
|
|
12938
13040
|
if (callingState === exports.CallingState.RINGING && reject !== false) {
|
|
12939
13041
|
if (reject) {
|
|
12940
|
-
|
|
13042
|
+
const reasonToEndCallReason = {
|
|
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);
|
|
12941
13052
|
}
|
|
12942
13053
|
else {
|
|
12943
13054
|
// if reject was undefined, we still have to cancel the call automatically
|
|
12944
13055
|
// when I am the creator and everyone else left the call
|
|
12945
13056
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
12946
13057
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
13058
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
12947
13059
|
await this.reject('cancel');
|
|
12948
13060
|
}
|
|
12949
13061
|
}
|
|
12950
13062
|
}
|
|
13063
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
12951
13064
|
this.statsReporter?.stop();
|
|
12952
13065
|
this.statsReporter = undefined;
|
|
12953
13066
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -12974,7 +13087,9 @@ class Call {
|
|
|
12974
13087
|
this.ringingSubject.next(false);
|
|
12975
13088
|
this.cancelAutoDrop();
|
|
12976
13089
|
this.clientStore.unregisterCall(this);
|
|
12977
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
13090
|
+
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
13091
|
+
isRingingTypeCall: this.ringing,
|
|
13092
|
+
});
|
|
12978
13093
|
this.camera.dispose();
|
|
12979
13094
|
this.microphone.dispose();
|
|
12980
13095
|
this.screenShare.dispose();
|
|
@@ -13140,11 +13255,19 @@ class Call {
|
|
|
13140
13255
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
13141
13256
|
*/
|
|
13142
13257
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
13143
|
-
await this.setup();
|
|
13144
13258
|
const callingState = this.state.callingState;
|
|
13145
13259
|
if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
|
|
13146
13260
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
13147
13261
|
}
|
|
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();
|
|
13148
13271
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
13149
13272
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
13150
13273
|
// we will count the number of join failures per SFU.
|
|
@@ -13153,38 +13276,44 @@ class Call {
|
|
|
13153
13276
|
const sfuJoinFailures = new Map();
|
|
13154
13277
|
const joinData = data;
|
|
13155
13278
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
catch (err) {
|
|
13165
|
-
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13166
|
-
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13167
|
-
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13168
|
-
// if the error is unrecoverable, we should not retry as that signals
|
|
13169
|
-
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13170
|
-
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13171
|
-
throw err;
|
|
13172
|
-
}
|
|
13173
|
-
// immediately switch to a different SFU in case of recoverable join error
|
|
13174
|
-
const switchSfu = err instanceof SfuJoinError &&
|
|
13175
|
-
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13176
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
13177
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13178
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
13179
|
-
if (switchSfu || failures >= 2) {
|
|
13180
|
-
joinData.migrating_from = sfuId;
|
|
13181
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13279
|
+
try {
|
|
13280
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
13281
|
+
try {
|
|
13282
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
13283
|
+
await this.doJoin(data);
|
|
13284
|
+
delete joinData.migrating_from;
|
|
13285
|
+
delete joinData.migrating_from_list;
|
|
13286
|
+
break;
|
|
13182
13287
|
}
|
|
13183
|
-
|
|
13184
|
-
|
|
13288
|
+
catch (err) {
|
|
13289
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13290
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13291
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13292
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
13293
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13294
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13295
|
+
throw err;
|
|
13296
|
+
}
|
|
13297
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
13298
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
13299
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
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
|
+
}
|
|
13185
13310
|
}
|
|
13311
|
+
await sleep(retryInterval(attempt));
|
|
13186
13312
|
}
|
|
13187
|
-
|
|
13313
|
+
}
|
|
13314
|
+
catch (error) {
|
|
13315
|
+
callingX?.endCall(this, 'error');
|
|
13316
|
+
throw error;
|
|
13188
13317
|
}
|
|
13189
13318
|
};
|
|
13190
13319
|
/**
|
|
@@ -13331,7 +13460,9 @@ class Call {
|
|
|
13331
13460
|
// re-apply them on later reconnections or server-side data fetches
|
|
13332
13461
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13333
13462
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
13334
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13463
|
+
globalThis.streamRNVideoSDK?.callManager.start({
|
|
13464
|
+
isRingingTypeCall: this.ringing,
|
|
13465
|
+
});
|
|
13335
13466
|
this.deviceSettingsAppliedOnce = true;
|
|
13336
13467
|
}
|
|
13337
13468
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13759,6 +13890,7 @@ class Call {
|
|
|
13759
13890
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13760
13891
|
return;
|
|
13761
13892
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13893
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13762
13894
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13763
13895
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13764
13896
|
});
|
|
@@ -14780,7 +14912,7 @@ class Call {
|
|
|
14780
14912
|
* A flag indicating whether the call was created by the current user.
|
|
14781
14913
|
*/
|
|
14782
14914
|
get isCreatedByMe() {
|
|
14783
|
-
return this.state.createdBy?.id === this.currentUserId;
|
|
14915
|
+
return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
|
|
14784
14916
|
}
|
|
14785
14917
|
}
|
|
14786
14918
|
|
|
@@ -15904,7 +16036,7 @@ class StreamClient {
|
|
|
15904
16036
|
this.getUserAgent = () => {
|
|
15905
16037
|
if (!this.cachedUserAgent) {
|
|
15906
16038
|
const { clientAppIdentifier = {} } = this.options;
|
|
15907
|
-
const { sdkName = 'js', sdkVersion = "1.44.
|
|
16039
|
+
const { sdkName = 'js', sdkVersion = "1.44.6-beta.0", ...extras } = clientAppIdentifier;
|
|
15908
16040
|
this.cachedUserAgent = [
|
|
15909
16041
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15910
16042
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|