@stream-io/video-client 1.45.0 → 1.46.1

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/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 getCurrentValue(this.connectedUserSubject);
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.45.0";
6307
+ const version = "1.46.1";
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;
@@ -11887,31 +11947,43 @@ class RNSpeechDetector {
11887
11947
  constructor(externalAudioStream) {
11888
11948
  this.pc1 = new RTCPeerConnection({});
11889
11949
  this.pc2 = new RTCPeerConnection({});
11950
+ this.isStopped = false;
11890
11951
  this.externalAudioStream = externalAudioStream;
11891
11952
  }
11892
11953
  /**
11893
11954
  * Starts the speech detection.
11894
11955
  */
11895
11956
  async start(onSoundDetectedStateChanged) {
11957
+ let detachListeners;
11958
+ let unsubscribe;
11896
11959
  try {
11960
+ this.isStopped = false;
11897
11961
  const audioStream = this.externalAudioStream != null
11898
11962
  ? this.externalAudioStream
11899
11963
  : await navigator.mediaDevices.getUserMedia({ audio: true });
11900
11964
  this.audioStream = audioStream;
11901
- this.pc1.addEventListener('icecandidate', async (e) => {
11902
- await this.pc2.addIceCandidate(e.candidate);
11903
- });
11904
- this.pc2.addEventListener('icecandidate', async (e) => {
11905
- await this.pc1.addIceCandidate(e.candidate);
11906
- });
11907
- this.pc2.addEventListener('track', (e) => {
11965
+ const onPc1IceCandidate = (e) => {
11966
+ this.forwardIceCandidate(this.pc2, e.candidate);
11967
+ };
11968
+ const onPc2IceCandidate = (e) => {
11969
+ this.forwardIceCandidate(this.pc1, e.candidate);
11970
+ };
11971
+ const onTrackPc2 = (e) => {
11908
11972
  e.streams[0].getTracks().forEach((track) => {
11909
11973
  // In RN, the remote track is automatically added to the audio output device
11910
11974
  // so we need to mute it to avoid hearing the audio back
11911
11975
  // @ts-expect-error _setVolume is a private method in react-native-webrtc
11912
11976
  track._setVolume(0);
11913
11977
  });
11914
- });
11978
+ };
11979
+ this.pc1.addEventListener('icecandidate', onPc1IceCandidate);
11980
+ this.pc2.addEventListener('icecandidate', onPc2IceCandidate);
11981
+ this.pc2.addEventListener('track', onTrackPc2);
11982
+ detachListeners = () => {
11983
+ this.pc1.removeEventListener('icecandidate', onPc1IceCandidate);
11984
+ this.pc2.removeEventListener('icecandidate', onPc2IceCandidate);
11985
+ this.pc2.removeEventListener('track', onTrackPc2);
11986
+ };
11915
11987
  audioStream
11916
11988
  .getTracks()
11917
11989
  .forEach((track) => this.pc1.addTrack(track, audioStream));
@@ -11921,13 +11993,17 @@ class RNSpeechDetector {
11921
11993
  const answer = await this.pc2.createAnswer();
11922
11994
  await this.pc1.setRemoteDescription(answer);
11923
11995
  await this.pc2.setLocalDescription(answer);
11924
- const unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11996
+ unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11925
11997
  return () => {
11926
- unsubscribe();
11998
+ detachListeners?.();
11999
+ unsubscribe?.();
11927
12000
  this.stop();
11928
12001
  };
11929
12002
  }
11930
12003
  catch (error) {
12004
+ detachListeners?.();
12005
+ unsubscribe?.();
12006
+ this.stop();
11931
12007
  const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
11932
12008
  logger.error('error handling permissions: ', error);
11933
12009
  return () => { };
@@ -11937,6 +12013,9 @@ class RNSpeechDetector {
11937
12013
  * Stops the speech detection and releases all allocated resources.
11938
12014
  */
11939
12015
  stop() {
12016
+ if (this.isStopped)
12017
+ return;
12018
+ this.isStopped = true;
11940
12019
  this.pc1.close();
11941
12020
  this.pc2.close();
11942
12021
  if (this.externalAudioStream != null) {
@@ -12036,6 +12115,18 @@ class RNSpeechDetector {
12036
12115
  this.audioStream.release();
12037
12116
  }
12038
12117
  }
12118
+ forwardIceCandidate(destination, candidate) {
12119
+ if (this.isStopped ||
12120
+ !candidate ||
12121
+ destination.signalingState === 'closed') {
12122
+ return;
12123
+ }
12124
+ destination.addIceCandidate(candidate).catch(() => {
12125
+ // silently ignore the error
12126
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12127
+ logger.info('cannot add ice candidate - ignoring');
12128
+ });
12129
+ }
12039
12130
  }
12040
12131
 
12041
12132
  class MicrophoneManager extends AudioDeviceManager {
@@ -12131,6 +12222,7 @@ class MicrophoneManager extends AudioDeviceManager {
12131
12222
  const deviceId = this.state.selectedDevice;
12132
12223
  const devices = await rxjs.firstValueFrom(this.listDevices());
12133
12224
  const label = devices.find((d) => d.deviceId === deviceId)?.label;
12225
+ let lastCapturesAudio;
12134
12226
  this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
12135
12227
  noAudioThresholdMs: this.silenceThresholdMs,
12136
12228
  emitIntervalMs: this.silenceThresholdMs,
@@ -12142,7 +12234,10 @@ class MicrophoneManager extends AudioDeviceManager {
12142
12234
  deviceId,
12143
12235
  label,
12144
12236
  };
12145
- this.call.tracer.trace('mic.capture_report', event);
12237
+ if (capturesAudio !== lastCapturesAudio) {
12238
+ lastCapturesAudio = capturesAudio;
12239
+ this.call.tracer.trace('mic.capture_report', event);
12240
+ }
12146
12241
  this.call.streamClient.dispatchEvent(event);
12147
12242
  },
12148
12243
  });
@@ -12664,6 +12759,7 @@ class SpeakerManager {
12664
12759
  this.defaultDevice = defaultDevice;
12665
12760
  globalThis.streamRNVideoSDK?.callManager.setup({
12666
12761
  defaultDevice,
12762
+ isRingingTypeCall: this.call.ringing,
12667
12763
  });
12668
12764
  }
12669
12765
  }
@@ -12863,6 +12959,7 @@ class Call {
12863
12959
  const currentUserId = this.currentUserId;
12864
12960
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
12865
12961
  this.logger.info('Leaving call because of being blocked');
12962
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
12866
12963
  await this.leave({ message: 'user blocked' }).catch((err) => {
12867
12964
  this.logger.error('Error leaving call after being blocked', err);
12868
12965
  });
@@ -12899,6 +12996,7 @@ class Call {
12899
12996
  const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === exports.CallingState.RINGING;
12900
12997
  if ((isAcceptedElsewhere || isRejectedByMe) &&
12901
12998
  !hasPending(this.joinLeaveConcurrencyTag)) {
12999
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
12902
13000
  this.leave().catch(() => {
12903
13001
  this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
12904
13002
  });
@@ -12910,6 +13008,9 @@ class Call {
12910
13008
  const receiver_id = this.clientStore.connectedUser?.id;
12911
13009
  const ended_at = callSession?.ended_at;
12912
13010
  const created_by_id = this.state.createdBy?.id;
13011
+ if (this.currentUserId && created_by_id === this.currentUserId) {
13012
+ globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
13013
+ }
12913
13014
  const rejected_by = callSession?.rejected_by;
12914
13015
  const accepted_by = callSession?.accepted_by;
12915
13016
  let leaveCallIdle = false;
@@ -13048,7 +13149,16 @@ class Call {
13048
13149
  }
13049
13150
  if (callingState === exports.CallingState.RINGING && reject !== false) {
13050
13151
  if (reject) {
13051
- await this.reject(reason ?? 'decline');
13152
+ const reasonToEndCallReason = {
13153
+ timeout: 'missed',
13154
+ cancel: 'canceled',
13155
+ busy: 'busy',
13156
+ decline: 'rejected',
13157
+ };
13158
+ const rejectReason = reason ?? 'decline';
13159
+ const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
13160
+ await this.reject(rejectReason);
13161
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
13052
13162
  }
13053
13163
  else {
13054
13164
  // if reject was undefined, we still have to cancel the call automatically
@@ -13056,9 +13166,11 @@ class Call {
13056
13166
  const hasOtherParticipants = this.state.remoteParticipants.length > 0;
13057
13167
  if (this.isCreatedByMe && !hasOtherParticipants) {
13058
13168
  await this.reject('cancel');
13169
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
13059
13170
  }
13060
13171
  }
13061
13172
  }
13173
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this);
13062
13174
  this.statsReporter?.stop();
13063
13175
  this.statsReporter = undefined;
13064
13176
  const leaveReason = message ?? reason ?? 'user is leaving the call';
@@ -13085,7 +13197,9 @@ class Call {
13085
13197
  this.ringingSubject.next(false);
13086
13198
  this.cancelAutoDrop();
13087
13199
  this.clientStore.unregisterCall(this);
13088
- globalThis.streamRNVideoSDK?.callManager.stop();
13200
+ globalThis.streamRNVideoSDK?.callManager.stop({
13201
+ isRingingTypeCall: this.ringing,
13202
+ });
13089
13203
  this.camera.dispose();
13090
13204
  this.microphone.dispose();
13091
13205
  this.screenShare.dispose();
@@ -13251,11 +13365,19 @@ class Call {
13251
13365
  * @returns a promise which resolves once the call join-flow has finished.
13252
13366
  */
13253
13367
  this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
13254
- await this.setup();
13255
13368
  const callingState = this.state.callingState;
13256
13369
  if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
13257
13370
  throw new Error(`Illegal State: call.join() shall be called only once`);
13258
13371
  }
13372
+ if (data?.ring) {
13373
+ this.ringingSubject.next(true);
13374
+ }
13375
+ const callingX = globalThis.streamRNVideoSDK?.callingX;
13376
+ if (callingX) {
13377
+ // for Android/iOS, we need to start the call in the callingx library as soon as possible
13378
+ await callingX.joinCall(this, this.clientStore.calls);
13379
+ }
13380
+ await this.setup();
13259
13381
  this.joinResponseTimeout = joinResponseTimeout;
13260
13382
  this.rpcRequestTimeout = rpcRequestTimeout;
13261
13383
  // we will count the number of join failures per SFU.
@@ -13264,38 +13386,44 @@ class Call {
13264
13386
  const sfuJoinFailures = new Map();
13265
13387
  const joinData = data;
13266
13388
  maxJoinRetries = Math.max(maxJoinRetries, 1);
13267
- for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13268
- try {
13269
- this.logger.trace(`Joining call (${attempt})`, this.cid);
13270
- await this.doJoin(data);
13271
- delete joinData.migrating_from;
13272
- delete joinData.migrating_from_list;
13273
- break;
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());
13389
+ try {
13390
+ for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13391
+ try {
13392
+ this.logger.trace(`Joining call (${attempt})`, this.cid);
13393
+ await this.doJoin(data);
13394
+ delete joinData.migrating_from;
13395
+ delete joinData.migrating_from_list;
13396
+ break;
13293
13397
  }
13294
- if (attempt === maxJoinRetries - 1) {
13295
- throw err;
13398
+ catch (err) {
13399
+ this.logger.warn(`Failed to join call (${attempt})`, this.cid);
13400
+ if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
13401
+ (err instanceof SfuJoinError && err.unrecoverable)) {
13402
+ // if the error is unrecoverable, we should not retry as that signals
13403
+ // that connectivity is good, but the coordinator doesn't allow the user
13404
+ // to join the call due to some reason (e.g., ended call, expired token...)
13405
+ throw err;
13406
+ }
13407
+ // immediately switch to a different SFU in case of recoverable join error
13408
+ const switchSfu = err instanceof SfuJoinError &&
13409
+ SfuJoinError.isJoinErrorCode(err.errorEvent);
13410
+ const sfuId = this.credentials?.server.edge_name || '';
13411
+ const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
13412
+ sfuJoinFailures.set(sfuId, failures);
13413
+ if (switchSfu || failures >= 2) {
13414
+ joinData.migrating_from = sfuId;
13415
+ joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
13416
+ }
13417
+ if (attempt === maxJoinRetries - 1) {
13418
+ throw err;
13419
+ }
13296
13420
  }
13421
+ await sleep(retryInterval(attempt));
13297
13422
  }
13298
- await sleep(retryInterval(attempt));
13423
+ }
13424
+ catch (error) {
13425
+ callingX?.endCall(this, 'error');
13426
+ throw error;
13299
13427
  }
13300
13428
  };
13301
13429
  /**
@@ -13442,7 +13570,9 @@ class Call {
13442
13570
  // re-apply them on later reconnections or server-side data fetches
13443
13571
  if (!this.deviceSettingsAppliedOnce && this.state.settings) {
13444
13572
  await this.applyDeviceConfig(this.state.settings, true, false);
13445
- globalThis.streamRNVideoSDK?.callManager.start();
13573
+ globalThis.streamRNVideoSDK?.callManager.start({
13574
+ isRingingTypeCall: this.ringing,
13575
+ });
13446
13576
  this.deviceSettingsAppliedOnce = true;
13447
13577
  }
13448
13578
  // We shouldn't persist the `ring` and `notify` state after joining the call
@@ -13870,6 +14000,7 @@ class Call {
13870
14000
  if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
13871
14001
  return;
13872
14002
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
14003
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
13873
14004
  this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
13874
14005
  this.logger.warn(`Can't leave call after disconnect request`, err);
13875
14006
  });
@@ -14774,6 +14905,12 @@ class Call {
14774
14905
  unbind();
14775
14906
  };
14776
14907
  };
14908
+ /**
14909
+ * Plays all audio elements blocked by the browser's autoplay policy.
14910
+ */
14911
+ this.resumeAudio = () => {
14912
+ return this.dynascaleManager.resumeAudio();
14913
+ };
14777
14914
  /**
14778
14915
  * Binds a DOM <img> element to this call's thumbnail (if enabled in settings).
14779
14916
  *
@@ -14891,7 +15028,7 @@ class Call {
14891
15028
  * A flag indicating whether the call was created by the current user.
14892
15029
  */
14893
15030
  get isCreatedByMe() {
14894
- return this.state.createdBy?.id === this.currentUserId;
15031
+ return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
14895
15032
  }
14896
15033
  }
14897
15034
 
@@ -16015,7 +16152,7 @@ class StreamClient {
16015
16152
  this.getUserAgent = () => {
16016
16153
  if (!this.cachedUserAgent) {
16017
16154
  const { clientAppIdentifier = {} } = this.options;
16018
- const { sdkName = 'js', sdkVersion = "1.45.0", ...extras } = clientAppIdentifier;
16155
+ const { sdkName = 'js', sdkVersion = "1.46.1", ...extras } = clientAppIdentifier;
16019
16156
  this.cachedUserAgent = [
16020
16157
  `stream-video-${sdkName}-v${sdkVersion}`,
16021
16158
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),