@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.es.js
CHANGED
|
@@ -3266,6 +3266,7 @@ class ParticipantJoined$Type extends MessageType {
|
|
|
3266
3266
|
super('stream.video.sfu.event.ParticipantJoined', [
|
|
3267
3267
|
{ no: 1, name: 'call_cid', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3268
3268
|
{ no: 2, name: 'participant', kind: 'message', T: () => Participant },
|
|
3269
|
+
{ no: 3, name: 'is_pinned', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
|
3269
3270
|
]);
|
|
3270
3271
|
}
|
|
3271
3272
|
}
|
|
@@ -4785,7 +4786,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4785
4786
|
* The currently connected user.
|
|
4786
4787
|
*/
|
|
4787
4788
|
get connectedUser() {
|
|
4788
|
-
return
|
|
4789
|
+
return this.connectedUserSubject.getValue();
|
|
4789
4790
|
}
|
|
4790
4791
|
/**
|
|
4791
4792
|
* A list of {@link Call} objects created/tracked by this client.
|
|
@@ -6284,7 +6285,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6284
6285
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6285
6286
|
};
|
|
6286
6287
|
|
|
6287
|
-
const version = "1.
|
|
6288
|
+
const version = "1.46.0";
|
|
6288
6289
|
const [major, minor, patch] = version.split('.');
|
|
6289
6290
|
let sdkInfo = {
|
|
6290
6291
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8983,6 +8984,7 @@ const watchCallRejected = (call) => {
|
|
|
8983
8984
|
else {
|
|
8984
8985
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
8985
8986
|
call.logger.info('call creator rejected, leaving call');
|
|
8987
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8986
8988
|
await call.leave({ message: 'ring: creator rejected' });
|
|
8987
8989
|
}
|
|
8988
8990
|
}
|
|
@@ -8993,6 +8995,7 @@ const watchCallRejected = (call) => {
|
|
|
8993
8995
|
*/
|
|
8994
8996
|
const watchCallEnded = (call) => {
|
|
8995
8997
|
return function onCallEnded() {
|
|
8998
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
8996
8999
|
const { callingState } = call.state;
|
|
8997
9000
|
if (callingState !== CallingState.IDLE &&
|
|
8998
9001
|
callingState !== CallingState.LEFT) {
|
|
@@ -9024,6 +9027,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
9024
9027
|
// update the call state to reflect the call has ended.
|
|
9025
9028
|
call.state.setEndedAt(new Date());
|
|
9026
9029
|
const reason = CallEndedReason[e.reason];
|
|
9030
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
|
|
9027
9031
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
9028
9032
|
}
|
|
9029
9033
|
catch (err) {
|
|
@@ -9219,6 +9223,7 @@ const watchParticipantJoined = (state) => {
|
|
|
9219
9223
|
// already announced participants.
|
|
9220
9224
|
const orphanedTracks = reconcileOrphanedTracks(state, participant);
|
|
9221
9225
|
state.updateOrAddParticipant(participant.sessionId, Object.assign(participant, orphanedTracks, {
|
|
9226
|
+
...(e.isPinned && { pin: { isLocalPin: false, pinnedAt: Date.now() } }),
|
|
9222
9227
|
viewportVisibilityState: {
|
|
9223
9228
|
videoTrack: VisibilityState.UNKNOWN,
|
|
9224
9229
|
screenShareTrack: VisibilityState.UNKNOWN,
|
|
@@ -9612,6 +9617,31 @@ class DynascaleManager {
|
|
|
9612
9617
|
this.logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
9613
9618
|
this.useWebAudio = false;
|
|
9614
9619
|
this.pendingSubscriptionsUpdate = null;
|
|
9620
|
+
/**
|
|
9621
|
+
* Audio elements that were blocked by the browser's autoplay policy.
|
|
9622
|
+
* These can be retried by calling `resumeAudio()` from a user gesture.
|
|
9623
|
+
*/
|
|
9624
|
+
this.blockedAudioElementsSubject = new BehaviorSubject(new Set());
|
|
9625
|
+
/**
|
|
9626
|
+
* Whether the browser's autoplay policy is blocking audio playback.
|
|
9627
|
+
* Will be `true` when the browser blocks autoplay (e.g., no prior user interaction).
|
|
9628
|
+
* Use `resumeAudio()` within a user gesture to unblock.
|
|
9629
|
+
*/
|
|
9630
|
+
this.autoplayBlocked$ = this.blockedAudioElementsSubject.pipe(map((elements) => elements.size > 0), distinctUntilChanged());
|
|
9631
|
+
this.addBlockedAudioElement = (audioElement) => {
|
|
9632
|
+
setCurrentValue(this.blockedAudioElementsSubject, (elements) => {
|
|
9633
|
+
const next = new Set(elements);
|
|
9634
|
+
next.add(audioElement);
|
|
9635
|
+
return next;
|
|
9636
|
+
});
|
|
9637
|
+
};
|
|
9638
|
+
this.removeBlockedAudioElement = (audioElement) => {
|
|
9639
|
+
setCurrentValue(this.blockedAudioElementsSubject, (elements) => {
|
|
9640
|
+
const nextElements = new Set(elements);
|
|
9641
|
+
nextElements.delete(audioElement);
|
|
9642
|
+
return nextElements;
|
|
9643
|
+
});
|
|
9644
|
+
};
|
|
9615
9645
|
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
9616
9646
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
9617
9647
|
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(map((overrides) => {
|
|
@@ -9643,6 +9673,7 @@ class DynascaleManager {
|
|
|
9643
9673
|
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
9644
9674
|
}
|
|
9645
9675
|
this.audioBindingsWatchdog?.dispose();
|
|
9676
|
+
setCurrentValue(this.blockedAudioElementsSubject, new Set());
|
|
9646
9677
|
const context = this.audioContext;
|
|
9647
9678
|
if (context && context.state !== 'closed') {
|
|
9648
9679
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
@@ -9940,8 +9971,10 @@ class DynascaleManager {
|
|
|
9940
9971
|
return;
|
|
9941
9972
|
setTimeout(() => {
|
|
9942
9973
|
audioElement.srcObject = source ?? null;
|
|
9943
|
-
if (!source)
|
|
9974
|
+
if (!source) {
|
|
9975
|
+
this.removeBlockedAudioElement(audioElement);
|
|
9944
9976
|
return;
|
|
9977
|
+
}
|
|
9945
9978
|
// Safari has a special quirk that prevents playing audio until the user
|
|
9946
9979
|
// interacts with the page or focuses on the tab where the call happens.
|
|
9947
9980
|
// This is a workaround for the issue where:
|
|
@@ -9965,6 +9998,10 @@ class DynascaleManager {
|
|
|
9965
9998
|
audioElement.muted = false;
|
|
9966
9999
|
audioElement.play().catch((e) => {
|
|
9967
10000
|
this.tracer.trace('audioPlaybackError', e.message);
|
|
10001
|
+
if (e.name === 'NotAllowedError') {
|
|
10002
|
+
this.tracer.trace('audioPlaybackBlocked', null);
|
|
10003
|
+
this.addBlockedAudioElement(audioElement);
|
|
10004
|
+
}
|
|
9968
10005
|
this.logger.warn(`Failed to play audio stream`, e);
|
|
9969
10006
|
});
|
|
9970
10007
|
}
|
|
@@ -9991,6 +10028,7 @@ class DynascaleManager {
|
|
|
9991
10028
|
audioElement.autoplay = true;
|
|
9992
10029
|
return () => {
|
|
9993
10030
|
this.audioBindingsWatchdog?.unregister(sessionId, trackType);
|
|
10031
|
+
this.removeBlockedAudioElement(audioElement);
|
|
9994
10032
|
sinkIdSubscription?.unsubscribe();
|
|
9995
10033
|
volumeSubscription.unsubscribe();
|
|
9996
10034
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -9999,6 +10037,28 @@ class DynascaleManager {
|
|
|
9999
10037
|
gainNode?.disconnect();
|
|
10000
10038
|
};
|
|
10001
10039
|
};
|
|
10040
|
+
/**
|
|
10041
|
+
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
10042
|
+
* Must be called from within a user gesture (e.g., click handler).
|
|
10043
|
+
*
|
|
10044
|
+
* @returns a promise that resolves when all blocked elements have been retried.
|
|
10045
|
+
*/
|
|
10046
|
+
this.resumeAudio = async () => {
|
|
10047
|
+
this.tracer.trace('resumeAudio', null);
|
|
10048
|
+
const blocked = new Set();
|
|
10049
|
+
await Promise.all(Array.from(getCurrentValue(this.blockedAudioElementsSubject), async (el) => {
|
|
10050
|
+
try {
|
|
10051
|
+
if (el.srcObject) {
|
|
10052
|
+
await el.play();
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
catch {
|
|
10056
|
+
this.logger.warn(`Can't resume audio for element: `, el);
|
|
10057
|
+
blocked.add(el);
|
|
10058
|
+
}
|
|
10059
|
+
}));
|
|
10060
|
+
setCurrentValue(this.blockedAudioElementsSubject, blocked);
|
|
10061
|
+
};
|
|
10002
10062
|
this.getOrCreateAudioContext = () => {
|
|
10003
10063
|
if (!this.useWebAudio)
|
|
10004
10064
|
return;
|
|
@@ -11879,11 +11939,15 @@ class RNSpeechDetector {
|
|
|
11879
11939
|
? this.externalAudioStream
|
|
11880
11940
|
: await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
11881
11941
|
this.audioStream = audioStream;
|
|
11882
|
-
this.pc1.addEventListener('icecandidate',
|
|
11883
|
-
|
|
11942
|
+
this.pc1.addEventListener('icecandidate', (e) => {
|
|
11943
|
+
this.pc2.addIceCandidate(e.candidate).catch(() => {
|
|
11944
|
+
// do nothing
|
|
11945
|
+
});
|
|
11884
11946
|
});
|
|
11885
11947
|
this.pc2.addEventListener('icecandidate', async (e) => {
|
|
11886
|
-
|
|
11948
|
+
this.pc1.addIceCandidate(e.candidate).catch(() => {
|
|
11949
|
+
// do nothing
|
|
11950
|
+
});
|
|
11887
11951
|
});
|
|
11888
11952
|
this.pc2.addEventListener('track', (e) => {
|
|
11889
11953
|
e.streams[0].getTracks().forEach((track) => {
|
|
@@ -12112,6 +12176,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12112
12176
|
const deviceId = this.state.selectedDevice;
|
|
12113
12177
|
const devices = await firstValueFrom(this.listDevices());
|
|
12114
12178
|
const label = devices.find((d) => d.deviceId === deviceId)?.label;
|
|
12179
|
+
let lastCapturesAudio;
|
|
12115
12180
|
this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
|
|
12116
12181
|
noAudioThresholdMs: this.silenceThresholdMs,
|
|
12117
12182
|
emitIntervalMs: this.silenceThresholdMs,
|
|
@@ -12123,7 +12188,10 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12123
12188
|
deviceId,
|
|
12124
12189
|
label,
|
|
12125
12190
|
};
|
|
12126
|
-
|
|
12191
|
+
if (capturesAudio !== lastCapturesAudio) {
|
|
12192
|
+
lastCapturesAudio = capturesAudio;
|
|
12193
|
+
this.call.tracer.trace('mic.capture_report', event);
|
|
12194
|
+
}
|
|
12127
12195
|
this.call.streamClient.dispatchEvent(event);
|
|
12128
12196
|
},
|
|
12129
12197
|
});
|
|
@@ -12645,6 +12713,7 @@ class SpeakerManager {
|
|
|
12645
12713
|
this.defaultDevice = defaultDevice;
|
|
12646
12714
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
12647
12715
|
defaultDevice,
|
|
12716
|
+
isRingingTypeCall: this.call.ringing,
|
|
12648
12717
|
});
|
|
12649
12718
|
}
|
|
12650
12719
|
}
|
|
@@ -12844,6 +12913,7 @@ class Call {
|
|
|
12844
12913
|
const currentUserId = this.currentUserId;
|
|
12845
12914
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
12846
12915
|
this.logger.info('Leaving call because of being blocked');
|
|
12916
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
|
|
12847
12917
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
12848
12918
|
this.logger.error('Error leaving call after being blocked', err);
|
|
12849
12919
|
});
|
|
@@ -12880,6 +12950,7 @@ class Call {
|
|
|
12880
12950
|
const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === CallingState.RINGING;
|
|
12881
12951
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
12882
12952
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
12953
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
|
|
12883
12954
|
this.leave().catch(() => {
|
|
12884
12955
|
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
12885
12956
|
});
|
|
@@ -12891,6 +12962,9 @@ class Call {
|
|
|
12891
12962
|
const receiver_id = this.clientStore.connectedUser?.id;
|
|
12892
12963
|
const ended_at = callSession?.ended_at;
|
|
12893
12964
|
const created_by_id = this.state.createdBy?.id;
|
|
12965
|
+
if (this.currentUserId && created_by_id === this.currentUserId) {
|
|
12966
|
+
globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
|
|
12967
|
+
}
|
|
12894
12968
|
const rejected_by = callSession?.rejected_by;
|
|
12895
12969
|
const accepted_by = callSession?.accepted_by;
|
|
12896
12970
|
let leaveCallIdle = false;
|
|
@@ -13029,7 +13103,16 @@ class Call {
|
|
|
13029
13103
|
}
|
|
13030
13104
|
if (callingState === CallingState.RINGING && reject !== false) {
|
|
13031
13105
|
if (reject) {
|
|
13032
|
-
|
|
13106
|
+
const reasonToEndCallReason = {
|
|
13107
|
+
timeout: 'missed',
|
|
13108
|
+
cancel: 'canceled',
|
|
13109
|
+
busy: 'busy',
|
|
13110
|
+
decline: 'rejected',
|
|
13111
|
+
};
|
|
13112
|
+
const rejectReason = reason ?? 'decline';
|
|
13113
|
+
const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
|
|
13114
|
+
await this.reject(rejectReason);
|
|
13115
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
|
|
13033
13116
|
}
|
|
13034
13117
|
else {
|
|
13035
13118
|
// if reject was undefined, we still have to cancel the call automatically
|
|
@@ -13037,9 +13120,11 @@ class Call {
|
|
|
13037
13120
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
13038
13121
|
if (this.isCreatedByMe && !hasOtherParticipants) {
|
|
13039
13122
|
await this.reject('cancel');
|
|
13123
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
|
|
13040
13124
|
}
|
|
13041
13125
|
}
|
|
13042
13126
|
}
|
|
13127
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this);
|
|
13043
13128
|
this.statsReporter?.stop();
|
|
13044
13129
|
this.statsReporter = undefined;
|
|
13045
13130
|
const leaveReason = message ?? reason ?? 'user is leaving the call';
|
|
@@ -13066,7 +13151,9 @@ class Call {
|
|
|
13066
13151
|
this.ringingSubject.next(false);
|
|
13067
13152
|
this.cancelAutoDrop();
|
|
13068
13153
|
this.clientStore.unregisterCall(this);
|
|
13069
|
-
globalThis.streamRNVideoSDK?.callManager.stop(
|
|
13154
|
+
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
13155
|
+
isRingingTypeCall: this.ringing,
|
|
13156
|
+
});
|
|
13070
13157
|
this.camera.dispose();
|
|
13071
13158
|
this.microphone.dispose();
|
|
13072
13159
|
this.screenShare.dispose();
|
|
@@ -13232,11 +13319,19 @@ class Call {
|
|
|
13232
13319
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
13233
13320
|
*/
|
|
13234
13321
|
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
13235
|
-
await this.setup();
|
|
13236
13322
|
const callingState = this.state.callingState;
|
|
13237
13323
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
13238
13324
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
13239
13325
|
}
|
|
13326
|
+
if (data?.ring) {
|
|
13327
|
+
this.ringingSubject.next(true);
|
|
13328
|
+
}
|
|
13329
|
+
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
13330
|
+
if (callingX) {
|
|
13331
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
13332
|
+
await callingX.joinCall(this, this.clientStore.calls);
|
|
13333
|
+
}
|
|
13334
|
+
await this.setup();
|
|
13240
13335
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
13241
13336
|
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
13242
13337
|
// we will count the number of join failures per SFU.
|
|
@@ -13245,38 +13340,44 @@ class Call {
|
|
|
13245
13340
|
const sfuJoinFailures = new Map();
|
|
13246
13341
|
const joinData = data;
|
|
13247
13342
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
catch (err) {
|
|
13257
|
-
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13258
|
-
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13259
|
-
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13260
|
-
// if the error is unrecoverable, we should not retry as that signals
|
|
13261
|
-
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13262
|
-
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13263
|
-
throw err;
|
|
13264
|
-
}
|
|
13265
|
-
// immediately switch to a different SFU in case of recoverable join error
|
|
13266
|
-
const switchSfu = err instanceof SfuJoinError &&
|
|
13267
|
-
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13268
|
-
const sfuId = this.credentials?.server.edge_name || '';
|
|
13269
|
-
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13270
|
-
sfuJoinFailures.set(sfuId, failures);
|
|
13271
|
-
if (switchSfu || failures >= 2) {
|
|
13272
|
-
joinData.migrating_from = sfuId;
|
|
13273
|
-
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13343
|
+
try {
|
|
13344
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
13345
|
+
try {
|
|
13346
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
13347
|
+
await this.doJoin(data);
|
|
13348
|
+
delete joinData.migrating_from;
|
|
13349
|
+
delete joinData.migrating_from_list;
|
|
13350
|
+
break;
|
|
13274
13351
|
}
|
|
13275
|
-
|
|
13276
|
-
|
|
13352
|
+
catch (err) {
|
|
13353
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
13354
|
+
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
13355
|
+
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
13356
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
13357
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
13358
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
13359
|
+
throw err;
|
|
13360
|
+
}
|
|
13361
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
13362
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
13363
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
13364
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
13365
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
13366
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
13367
|
+
if (switchSfu || failures >= 2) {
|
|
13368
|
+
joinData.migrating_from = sfuId;
|
|
13369
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
13370
|
+
}
|
|
13371
|
+
if (attempt === maxJoinRetries - 1) {
|
|
13372
|
+
throw err;
|
|
13373
|
+
}
|
|
13277
13374
|
}
|
|
13375
|
+
await sleep(retryInterval(attempt));
|
|
13278
13376
|
}
|
|
13279
|
-
|
|
13377
|
+
}
|
|
13378
|
+
catch (error) {
|
|
13379
|
+
callingX?.endCall(this, 'error');
|
|
13380
|
+
throw error;
|
|
13280
13381
|
}
|
|
13281
13382
|
};
|
|
13282
13383
|
/**
|
|
@@ -13423,7 +13524,9 @@ class Call {
|
|
|
13423
13524
|
// re-apply them on later reconnections or server-side data fetches
|
|
13424
13525
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
13425
13526
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
13426
|
-
globalThis.streamRNVideoSDK?.callManager.start(
|
|
13527
|
+
globalThis.streamRNVideoSDK?.callManager.start({
|
|
13528
|
+
isRingingTypeCall: this.ringing,
|
|
13529
|
+
});
|
|
13427
13530
|
this.deviceSettingsAppliedOnce = true;
|
|
13428
13531
|
}
|
|
13429
13532
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
@@ -13851,6 +13954,7 @@ class Call {
|
|
|
13851
13954
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13852
13955
|
return;
|
|
13853
13956
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
13957
|
+
globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
|
|
13854
13958
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
13855
13959
|
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
13856
13960
|
});
|
|
@@ -14755,6 +14859,12 @@ class Call {
|
|
|
14755
14859
|
unbind();
|
|
14756
14860
|
};
|
|
14757
14861
|
};
|
|
14862
|
+
/**
|
|
14863
|
+
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
14864
|
+
*/
|
|
14865
|
+
this.resumeAudio = () => {
|
|
14866
|
+
return this.dynascaleManager.resumeAudio();
|
|
14867
|
+
};
|
|
14758
14868
|
/**
|
|
14759
14869
|
* Binds a DOM <img> element to this call's thumbnail (if enabled in settings).
|
|
14760
14870
|
*
|
|
@@ -14872,7 +14982,7 @@ class Call {
|
|
|
14872
14982
|
* A flag indicating whether the call was created by the current user.
|
|
14873
14983
|
*/
|
|
14874
14984
|
get isCreatedByMe() {
|
|
14875
|
-
return this.state.createdBy?.id === this.currentUserId;
|
|
14985
|
+
return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
|
|
14876
14986
|
}
|
|
14877
14987
|
}
|
|
14878
14988
|
|
|
@@ -15996,7 +16106,7 @@ class StreamClient {
|
|
|
15996
16106
|
this.getUserAgent = () => {
|
|
15997
16107
|
if (!this.cachedUserAgent) {
|
|
15998
16108
|
const { clientAppIdentifier = {} } = this.options;
|
|
15999
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
16109
|
+
const { sdkName = 'js', sdkVersion = "1.46.0", ...extras } = clientAppIdentifier;
|
|
16000
16110
|
this.cachedUserAgent = [
|
|
16001
16111
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16002
16112
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|