@stream-io/video-client 1.45.0 → 1.46.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 +12 -0
- package/dist/index.browser.es.js +152 -42
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +152 -42
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +152 -42
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +5 -1
- package/dist/src/gen/video/sfu/event/events.d.ts +4 -0
- package/dist/src/helpers/DynascaleManager.d.ts +20 -0
- package/dist/src/types.d.ts +37 -5
- package/package.json +1 -1
- package/src/Call.ts +92 -40
- package/src/devices/MicrophoneManager.ts +7 -1
- package/src/devices/SpeakerManager.ts +1 -0
- package/src/events/__tests__/participant.test.ts +41 -0
- package/src/events/call.ts +3 -0
- package/src/events/participant.ts +1 -0
- package/src/gen/video/sfu/event/events.ts +5 -0
- package/src/helpers/DynascaleManager.ts +72 -1
- package/src/helpers/RNSpeechDetector.ts +7 -3
- package/src/helpers/__tests__/DynascaleManager.test.ts +120 -0
- package/src/store/stateStore.ts +1 -1
- package/src/types.ts +48 -5
package/dist/index.cjs.js
CHANGED
|
@@ -3285,6 +3285,7 @@ class ParticipantJoined$Type extends runtime.MessageType {
|
|
|
3285
3285
|
super('stream.video.sfu.event.ParticipantJoined', [
|
|
3286
3286
|
{ no: 1, name: 'call_cid', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3287
3287
|
{ no: 2, name: 'participant', kind: 'message', T: () => Participant },
|
|
3288
|
+
{ no: 3, name: 'is_pinned', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
|
3288
3289
|
]);
|
|
3289
3290
|
}
|
|
3290
3291
|
}
|
|
@@ -4804,7 +4805,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4804
4805
|
* The currently connected user.
|
|
4805
4806
|
*/
|
|
4806
4807
|
get connectedUser() {
|
|
4807
|
-
return
|
|
4808
|
+
return this.connectedUserSubject.getValue();
|
|
4808
4809
|
}
|
|
4809
4810
|
/**
|
|
4810
4811
|
* A list of {@link Call} objects created/tracked by this client.
|
|
@@ -6303,7 +6304,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6303
6304
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6304
6305
|
};
|
|
6305
6306
|
|
|
6306
|
-
const version = "1.
|
|
6307
|
+
const version = "1.46.0";
|
|
6307
6308
|
const [major, minor, patch] = version.split('.');
|
|
6308
6309
|
let sdkInfo = {
|
|
6309
6310
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -9002,6 +9003,7 @@ const watchCallRejected = (call) => {
|
|
|
9002
9003
|
else {
|
|
9003
9004
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
9004
9005
|
call.logger.info('call creator rejected, leaving call');
|
|
9006
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9005
9007
|
await call.leave({ message: 'ring: creator rejected' });
|
|
9006
9008
|
}
|
|
9007
9009
|
}
|
|
@@ -9012,6 +9014,7 @@ const watchCallRejected = (call) => {
|
|
|
9012
9014
|
*/
|
|
9013
9015
|
const watchCallEnded = (call) => {
|
|
9014
9016
|
return function onCallEnded() {
|
|
9017
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9015
9018
|
const { callingState } = call.state;
|
|
9016
9019
|
if (callingState !== exports.CallingState.IDLE &&
|
|
9017
9020
|
callingState !== exports.CallingState.LEFT) {
|
|
@@ -9043,6 +9046,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
9043
9046
|
// update the call state to reflect the call has ended.
|
|
9044
9047
|
call.state.setEndedAt(new Date());
|
|
9045
9048
|
const reason = CallEndedReason[e.reason];
|
|
9049
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9046
9050
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
9047
9051
|
}
|
|
9048
9052
|
catch (err) {
|
|
@@ -9238,6 +9242,7 @@ const watchParticipantJoined = (state) => {
|
|
|
9238
9242
|
// already announced participants.
|
|
9239
9243
|
const orphanedTracks = reconcileOrphanedTracks(state, participant);
|
|
9240
9244
|
state.updateOrAddParticipant(participant.sessionId, Object.assign(participant, orphanedTracks, {
|
|
9245
|
+
...(e.isPinned && { pin: { isLocalPin: false, pinnedAt: Date.now() } }),
|
|
9241
9246
|
viewportVisibilityState: {
|
|
9242
9247
|
videoTrack: exports.VisibilityState.UNKNOWN,
|
|
9243
9248
|
screenShareTrack: exports.VisibilityState.UNKNOWN,
|
|
@@ -9631,6 +9636,31 @@ class DynascaleManager {
|
|
|
9631
9636
|
this.logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
9632
9637
|
this.useWebAudio = false;
|
|
9633
9638
|
this.pendingSubscriptionsUpdate = null;
|
|
9639
|
+
/**
|
|
9640
|
+
* Audio elements that were blocked by the browser's autoplay policy.
|
|
9641
|
+
* These can be retried by calling `resumeAudio()` from a user gesture.
|
|
9642
|
+
*/
|
|
9643
|
+
this.blockedAudioElementsSubject = new rxjs.BehaviorSubject(new Set());
|
|
9644
|
+
/**
|
|
9645
|
+
* Whether the browser's autoplay policy is blocking audio playback.
|
|
9646
|
+
* Will be `true` when the browser blocks autoplay (e.g., no prior user interaction).
|
|
9647
|
+
* Use `resumeAudio()` within a user gesture to unblock.
|
|
9648
|
+
*/
|
|
9649
|
+
this.autoplayBlocked$ = this.blockedAudioElementsSubject.pipe(rxjs.map((elements) => elements.size > 0), rxjs.distinctUntilChanged());
|
|
9650
|
+
this.addBlockedAudioElement = (audioElement) => {
|
|
9651
|
+
setCurrentValue(this.blockedAudioElementsSubject, (elements) => {
|
|
9652
|
+
const next = new Set(elements);
|
|
9653
|
+
next.add(audioElement);
|
|
9654
|
+
return next;
|
|
9655
|
+
});
|
|
9656
|
+
};
|
|
9657
|
+
this.removeBlockedAudioElement = (audioElement) => {
|
|
9658
|
+
setCurrentValue(this.blockedAudioElementsSubject, (elements) => {
|
|
9659
|
+
const nextElements = new Set(elements);
|
|
9660
|
+
nextElements.delete(audioElement);
|
|
9661
|
+
return nextElements;
|
|
9662
|
+
});
|
|
9663
|
+
};
|
|
9634
9664
|
this.videoTrackSubscriptionOverridesSubject = new rxjs.BehaviorSubject({});
|
|
9635
9665
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
9636
9666
|
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(rxjs.map((overrides) => {
|
|
@@ -9662,6 +9692,7 @@ class DynascaleManager {
|
|
|
9662
9692
|
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
9663
9693
|
}
|
|
9664
9694
|
this.audioBindingsWatchdog?.dispose();
|
|
9695
|
+
setCurrentValue(this.blockedAudioElementsSubject, new Set());
|
|
9665
9696
|
const context = this.audioContext;
|
|
9666
9697
|
if (context && context.state !== 'closed') {
|
|
9667
9698
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
@@ -9959,8 +9990,10 @@ class DynascaleManager {
|
|
|
9959
9990
|
return;
|
|
9960
9991
|
setTimeout(() => {
|
|
9961
9992
|
audioElement.srcObject = source ?? null;
|
|
9962
|
-
if (!source)
|
|
9993
|
+
if (!source) {
|
|
9994
|
+
this.removeBlockedAudioElement(audioElement);
|
|
9963
9995
|
return;
|
|
9996
|
+
}
|
|
9964
9997
|
// Safari has a special quirk that prevents playing audio until the user
|
|
9965
9998
|
// interacts with the page or focuses on the tab where the call happens.
|
|
9966
9999
|
// This is a workaround for the issue where:
|
|
@@ -9984,6 +10017,10 @@ class DynascaleManager {
|
|
|
9984
10017
|
audioElement.muted = false;
|
|
9985
10018
|
audioElement.play().catch((e) => {
|
|
9986
10019
|
this.tracer.trace('audioPlaybackError', e.message);
|
|
10020
|
+
if (e.name === 'NotAllowedError') {
|
|
10021
|
+
this.tracer.trace('audioPlaybackBlocked', null);
|
|
10022
|
+
this.addBlockedAudioElement(audioElement);
|
|
10023
|
+
}
|
|
9987
10024
|
this.logger.warn(`Failed to play audio stream`, e);
|
|
9988
10025
|
});
|
|
9989
10026
|
}
|
|
@@ -10010,6 +10047,7 @@ class DynascaleManager {
|
|
|
10010
10047
|
audioElement.autoplay = true;
|
|
10011
10048
|
return () => {
|
|
10012
10049
|
this.audioBindingsWatchdog?.unregister(sessionId, trackType);
|
|
10050
|
+
this.removeBlockedAudioElement(audioElement);
|
|
10013
10051
|
sinkIdSubscription?.unsubscribe();
|
|
10014
10052
|
volumeSubscription.unsubscribe();
|
|
10015
10053
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -10018,6 +10056,28 @@ class DynascaleManager {
|
|
|
10018
10056
|
gainNode?.disconnect();
|
|
10019
10057
|
};
|
|
10020
10058
|
};
|
|
10059
|
+
/**
|
|
10060
|
+
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
10061
|
+
* Must be called from within a user gesture (e.g., click handler).
|
|
10062
|
+
*
|
|
10063
|
+
* @returns a promise that resolves when all blocked elements have been retried.
|
|
10064
|
+
*/
|
|
10065
|
+
this.resumeAudio = async () => {
|
|
10066
|
+
this.tracer.trace('resumeAudio', null);
|
|
10067
|
+
const blocked = new Set();
|
|
10068
|
+
await Promise.all(Array.from(getCurrentValue(this.blockedAudioElementsSubject), async (el) => {
|
|
10069
|
+
try {
|
|
10070
|
+
if (el.srcObject) {
|
|
10071
|
+
await el.play();
|
|
10072
|
+
}
|
|
10073
|
+
}
|
|
10074
|
+
catch {
|
|
10075
|
+
this.logger.warn(`Can't resume audio for element: `, el);
|
|
10076
|
+
blocked.add(el);
|
|
10077
|
+
}
|
|
10078
|
+
}));
|
|
10079
|
+
setCurrentValue(this.blockedAudioElementsSubject, blocked);
|
|
10080
|
+
};
|
|
10021
10081
|
this.getOrCreateAudioContext = () => {
|
|
10022
10082
|
if (!this.useWebAudio)
|
|
10023
10083
|
return;
|
|
@@ -11898,11 +11958,15 @@ class RNSpeechDetector {
|
|
|
11898
11958
|
? this.externalAudioStream
|
|
11899
11959
|
: await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
11900
11960
|
this.audioStream = audioStream;
|
|
11901
|
-
this.pc1.addEventListener('icecandidate',
|
|
11902
|
-
|
|
11961
|
+
this.pc1.addEventListener('icecandidate', (e) => {
|
|
11962
|
+
this.pc2.addIceCandidate(e.candidate).catch(() => {
|
|
11963
|
+
// do nothing
|
|
11964
|
+
});
|
|
11903
11965
|
});
|
|
11904
11966
|
this.pc2.addEventListener('icecandidate', async (e) => {
|
|
11905
|
-
|
|
11967
|
+
this.pc1.addIceCandidate(e.candidate).catch(() => {
|
|
11968
|
+
// do nothing
|
|
11969
|
+
});
|
|
11906
11970
|
});
|
|
11907
11971
|
this.pc2.addEventListener('track', (e) => {
|
|
11908
11972
|
e.streams[0].getTracks().forEach((track) => {
|
|
@@ -12131,6 +12195,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12131
12195
|
const deviceId = this.state.selectedDevice;
|
|
12132
12196
|
const devices = await rxjs.firstValueFrom(this.listDevices());
|
|
12133
12197
|
const label = devices.find((d) => d.deviceId === deviceId)?.label;
|
|
12198
|
+
let lastCapturesAudio;
|
|
12134
12199
|
this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
|
|
12135
12200
|
noAudioThresholdMs: this.silenceThresholdMs,
|
|
12136
12201
|
emitIntervalMs: this.silenceThresholdMs,
|
|
@@ -12142,7 +12207,10 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12142
12207
|
deviceId,
|
|
12143
12208
|
label,
|
|
12144
12209
|
};
|
|
12145
|
-
|
|
12210
|
+
if (capturesAudio !== lastCapturesAudio) {
|
|
12211
|
+
lastCapturesAudio = capturesAudio;
|
|
12212
|
+
this.call.tracer.trace('mic.capture_report', event);
|
|
12213
|
+
}
|
|
12146
12214
|
this.call.streamClient.dispatchEvent(event);
|
|
12147
12215
|
},
|
|
12148
12216
|
});
|
|
@@ -12664,6 +12732,7 @@ class SpeakerManager {
|
|
|
12664
12732
|
this.defaultDevice = defaultDevice;
|
|
12665
12733
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12666
12734
|
defaultDevice,
|
|
12735
|
+
isRingingTypeCall: this.call.ringing,
|
|
12667
12736
|
});
|
|
12668
12737
|
}
|
|
12669
12738
|
}
|
|
@@ -12863,6 +12932,7 @@ class Call {
|
|
|
12863
12932
|
const currentUserId = this.currentUserId;
|
|
12864
12933
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12865
12934
|
this.logger.info('Leaving call because of being blocked');
|
|
12935
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12866
12936
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12867
12937
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12868
12938
|
});
|
|
@@ -12899,6 +12969,7 @@ class Call {
|
|
|
12899
12969
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === exports.CallingState.RINGING;
|
|
12900
12970
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12901
12971
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12972
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12902
12973
|
this.leave().catch(() => {
|
|
12903
12974
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12904
12975
|
});
|
|
@@ -12910,6 +12981,9 @@ class Call {
|
|
|
12910
12981
|
const receiver_id = this.clientStore.connectedUser?.id;
|
|
12911
12982
|
const ended_at = callSession?.ended_at;
|
|
12912
12983
|
const created_by_id = this.state.createdBy?.id;
|
|
12984
|
+
if (this.currentUserId && created_by_id === this.currentUserId) {
|
|
12985
|
+
globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
|
|
12986
|
+
}
|
|
12913
12987
|
const rejected_by = callSession?.rejected_by;
|
|
12914
12988
|
const accepted_by = callSession?.accepted_by;
|
|
12915
12989
|
let leaveCallIdle = false;
|
|
@@ -13048,7 +13122,16 @@ class Call {
|
|
|
13048
13122
|
}
|
|
13049
13123
|
if (callingState === exports.CallingState.RINGING && reject !== false) {
|
|
13050
13124
|
if (reject) {
|
|
13051
|
-
|
|
13125
|
+
const reasonToEndCallReason = {
|
|
13126
|
+
timeout: 'missed',
|
|
13127
|
+
cancel: 'canceled',
|
|
13128
|
+
busy: 'busy',
|
|
13129
|
+
decline: 'rejected',
|
|
13130
|
+
};
|
|
13131
|
+
const rejectReason = reason ?? 'decline';
|
|
13132
|
+
const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
|
|
13133
|
+
await this.reject(rejectReason);
|
|
13134
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
|
|
13052
13135
|
}
|
|
13053
13136
|
else {
|
|
13054
13137
|
// if reject was undefined, we still have to cancel the call automatically
|
|
@@ -13056,9 +13139,11 @@ class Call {
|
|
|
13056
13139
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
13057
13140
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
13058
13141
|
await this.reject('cancel');
|
|
13142
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
13059
13143
|
}
|
|
13060
13144
|
}
|
|
13061
13145
|
}
|
|
13146
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
13062
13147
|
this.statsReporter?.stop();
|
|
13063
13148
|
this.statsReporter = undefined;
|
|
13064
13149
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -13085,7 +13170,9 @@ class Call {
|
|
|
13085
13170
|
this.ringingSubject.next(false);
|
|
13086
13171
|
this.cancelAutoDrop();
|
|
13087
13172
|
this.clientStore.unregisterCall(this);
|
|
13088
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
13173
|
+
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
13174
|
+
isRingingTypeCall: this.ringing,
|
|
13175
|
+
});
|
|
13089
13176
|
this.camera.dispose();
|
|
13090
13177
|
this.microphone.dispose();
|
|
13091
13178
|
this.screenShare.dispose();
|
|
@@ -13251,11 +13338,19 @@ class Call {
|
|
|
13251
13338
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
13252
13339
|
*/
|
|
13253
13340
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
13254
|
-
await this.setup();
|
|
13255
13341
|
const callingState = this.state.callingState;
|
|
13256
13342
|
if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
|
|
13257
13343
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
13258
13344
|
}
|
|
13345
|
+
if (data?.ring) {
|
|
13346
|
+
this.ringingSubject.next(true);
|
|
13347
|
+
}
|
|
13348
|
+
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
13349
|
+
if (callingX) {
|
|
13350
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
13351
|
+
await callingX.joinCall(this, this.clientStore.calls);
|
|
13352
|
+
}
|
|
13353
|
+
await this.setup();
|
|
13259
13354
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
13260
13355
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
13261
13356
|
// we will count the number of join failures per SFU.
|
|
@@ -13264,38 +13359,44 @@ class Call {
|
|
|
13264
13359
|
const sfuJoinFailures = new Map();
|
|
13265
13360
|
const joinData = data;
|
|
13266
13361
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
catch (err) {
|
|
13276
|
-
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13277
|
-
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13278
|
-
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13279
|
-
// if the error is unrecoverable, we should not retry as that signals
|
|
13280
|
-
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13281
|
-
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13282
|
-
throw err;
|
|
13283
|
-
}
|
|
13284
|
-
// immediately switch to a different SFU in case of recoverable join error
|
|
13285
|
-
const switchSfu = err instanceof SfuJoinError &&
|
|
13286
|
-
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13287
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
13288
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13289
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
13290
|
-
if (switchSfu || failures >= 2) {
|
|
13291
|
-
joinData.migrating_from = sfuId;
|
|
13292
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13362
|
+
try {
|
|
13363
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
13364
|
+
try {
|
|
13365
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
13366
|
+
await this.doJoin(data);
|
|
13367
|
+
delete joinData.migrating_from;
|
|
13368
|
+
delete joinData.migrating_from_list;
|
|
13369
|
+
break;
|
|
13293
13370
|
}
|
|
13294
|
-
|
|
13295
|
-
|
|
13371
|
+
catch (err) {
|
|
13372
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13373
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13374
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13375
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
13376
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13377
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13378
|
+
throw err;
|
|
13379
|
+
}
|
|
13380
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
13381
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
13382
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13383
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
13384
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13385
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
13386
|
+
if (switchSfu || failures >= 2) {
|
|
13387
|
+
joinData.migrating_from = sfuId;
|
|
13388
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13389
|
+
}
|
|
13390
|
+
if (attempt === maxJoinRetries - 1) {
|
|
13391
|
+
throw err;
|
|
13392
|
+
}
|
|
13296
13393
|
}
|
|
13394
|
+
await sleep(retryInterval(attempt));
|
|
13297
13395
|
}
|
|
13298
|
-
|
|
13396
|
+
}
|
|
13397
|
+
catch (error) {
|
|
13398
|
+
callingX?.endCall(this, 'error');
|
|
13399
|
+
throw error;
|
|
13299
13400
|
}
|
|
13300
13401
|
};
|
|
13301
13402
|
/**
|
|
@@ -13442,7 +13543,9 @@ class Call {
|
|
|
13442
13543
|
// re-apply them on later reconnections or server-side data fetches
|
|
13443
13544
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13444
13545
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
13445
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13546
|
+
globalThis.streamRNVideoSDK?.callManager.start({
|
|
13547
|
+
isRingingTypeCall: this.ringing,
|
|
13548
|
+
});
|
|
13446
13549
|
this.deviceSettingsAppliedOnce = true;
|
|
13447
13550
|
}
|
|
13448
13551
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13870,6 +13973,7 @@ class Call {
|
|
|
13870
13973
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13871
13974
|
return;
|
|
13872
13975
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13976
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13873
13977
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13874
13978
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13875
13979
|
});
|
|
@@ -14774,6 +14878,12 @@ class Call {
|
|
|
14774
14878
|
unbind();
|
|
14775
14879
|
};
|
|
14776
14880
|
};
|
|
14881
|
+
/**
|
|
14882
|
+
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
14883
|
+
*/
|
|
14884
|
+
this.resumeAudio = () => {
|
|
14885
|
+
return this.dynascaleManager.resumeAudio();
|
|
14886
|
+
};
|
|
14777
14887
|
/**
|
|
14778
14888
|
* Binds a DOM <img> element to this call's thumbnail (if enabled in settings).
|
|
14779
14889
|
*
|
|
@@ -14891,7 +15001,7 @@ class Call {
|
|
|
14891
15001
|
* A flag indicating whether the call was created by the current user.
|
|
14892
15002
|
*/
|
|
14893
15003
|
get isCreatedByMe() {
|
|
14894
|
-
return this.state.createdBy?.id === this.currentUserId;
|
|
15004
|
+
return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
|
|
14895
15005
|
}
|
|
14896
15006
|
}
|
|
14897
15007
|
|
|
@@ -16015,7 +16125,7 @@ class StreamClient {
|
|
|
16015
16125
|
this.getUserAgent = () => {
|
|
16016
16126
|
if (!this.cachedUserAgent) {
|
|
16017
16127
|
const { clientAppIdentifier = {} } = this.options;
|
|
16018
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
16128
|
+
const { sdkName = 'js', sdkVersion = "1.46.0", ...extras } = clientAppIdentifier;
|
|
16019
16129
|
this.cachedUserAgent = [
|
|
16020
16130
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16021
16131
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|