@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.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 getCurrentValue(this.connectedUserSubject);
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.45.0";
6288
+ const version = "1.46.1";
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;
@@ -11868,31 +11928,43 @@ class RNSpeechDetector {
11868
11928
  constructor(externalAudioStream) {
11869
11929
  this.pc1 = new RTCPeerConnection({});
11870
11930
  this.pc2 = new RTCPeerConnection({});
11931
+ this.isStopped = false;
11871
11932
  this.externalAudioStream = externalAudioStream;
11872
11933
  }
11873
11934
  /**
11874
11935
  * Starts the speech detection.
11875
11936
  */
11876
11937
  async start(onSoundDetectedStateChanged) {
11938
+ let detachListeners;
11939
+ let unsubscribe;
11877
11940
  try {
11941
+ this.isStopped = false;
11878
11942
  const audioStream = this.externalAudioStream != null
11879
11943
  ? this.externalAudioStream
11880
11944
  : await navigator.mediaDevices.getUserMedia({ audio: true });
11881
11945
  this.audioStream = audioStream;
11882
- this.pc1.addEventListener('icecandidate', async (e) => {
11883
- await this.pc2.addIceCandidate(e.candidate);
11884
- });
11885
- this.pc2.addEventListener('icecandidate', async (e) => {
11886
- await this.pc1.addIceCandidate(e.candidate);
11887
- });
11888
- this.pc2.addEventListener('track', (e) => {
11946
+ const onPc1IceCandidate = (e) => {
11947
+ this.forwardIceCandidate(this.pc2, e.candidate);
11948
+ };
11949
+ const onPc2IceCandidate = (e) => {
11950
+ this.forwardIceCandidate(this.pc1, e.candidate);
11951
+ };
11952
+ const onTrackPc2 = (e) => {
11889
11953
  e.streams[0].getTracks().forEach((track) => {
11890
11954
  // In RN, the remote track is automatically added to the audio output device
11891
11955
  // so we need to mute it to avoid hearing the audio back
11892
11956
  // @ts-expect-error _setVolume is a private method in react-native-webrtc
11893
11957
  track._setVolume(0);
11894
11958
  });
11895
- });
11959
+ };
11960
+ this.pc1.addEventListener('icecandidate', onPc1IceCandidate);
11961
+ this.pc2.addEventListener('icecandidate', onPc2IceCandidate);
11962
+ this.pc2.addEventListener('track', onTrackPc2);
11963
+ detachListeners = () => {
11964
+ this.pc1.removeEventListener('icecandidate', onPc1IceCandidate);
11965
+ this.pc2.removeEventListener('icecandidate', onPc2IceCandidate);
11966
+ this.pc2.removeEventListener('track', onTrackPc2);
11967
+ };
11896
11968
  audioStream
11897
11969
  .getTracks()
11898
11970
  .forEach((track) => this.pc1.addTrack(track, audioStream));
@@ -11902,13 +11974,17 @@ class RNSpeechDetector {
11902
11974
  const answer = await this.pc2.createAnswer();
11903
11975
  await this.pc1.setRemoteDescription(answer);
11904
11976
  await this.pc2.setLocalDescription(answer);
11905
- const unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11977
+ unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11906
11978
  return () => {
11907
- unsubscribe();
11979
+ detachListeners?.();
11980
+ unsubscribe?.();
11908
11981
  this.stop();
11909
11982
  };
11910
11983
  }
11911
11984
  catch (error) {
11985
+ detachListeners?.();
11986
+ unsubscribe?.();
11987
+ this.stop();
11912
11988
  const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
11913
11989
  logger.error('error handling permissions: ', error);
11914
11990
  return () => { };
@@ -11918,6 +11994,9 @@ class RNSpeechDetector {
11918
11994
  * Stops the speech detection and releases all allocated resources.
11919
11995
  */
11920
11996
  stop() {
11997
+ if (this.isStopped)
11998
+ return;
11999
+ this.isStopped = true;
11921
12000
  this.pc1.close();
11922
12001
  this.pc2.close();
11923
12002
  if (this.externalAudioStream != null) {
@@ -12017,6 +12096,18 @@ class RNSpeechDetector {
12017
12096
  this.audioStream.release();
12018
12097
  }
12019
12098
  }
12099
+ forwardIceCandidate(destination, candidate) {
12100
+ if (this.isStopped ||
12101
+ !candidate ||
12102
+ destination.signalingState === 'closed') {
12103
+ return;
12104
+ }
12105
+ destination.addIceCandidate(candidate).catch(() => {
12106
+ // silently ignore the error
12107
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12108
+ logger.info('cannot add ice candidate - ignoring');
12109
+ });
12110
+ }
12020
12111
  }
12021
12112
 
12022
12113
  class MicrophoneManager extends AudioDeviceManager {
@@ -12112,6 +12203,7 @@ class MicrophoneManager extends AudioDeviceManager {
12112
12203
  const deviceId = this.state.selectedDevice;
12113
12204
  const devices = await firstValueFrom(this.listDevices());
12114
12205
  const label = devices.find((d) => d.deviceId === deviceId)?.label;
12206
+ let lastCapturesAudio;
12115
12207
  this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
12116
12208
  noAudioThresholdMs: this.silenceThresholdMs,
12117
12209
  emitIntervalMs: this.silenceThresholdMs,
@@ -12123,7 +12215,10 @@ class MicrophoneManager extends AudioDeviceManager {
12123
12215
  deviceId,
12124
12216
  label,
12125
12217
  };
12126
- this.call.tracer.trace('mic.capture_report', event);
12218
+ if (capturesAudio !== lastCapturesAudio) {
12219
+ lastCapturesAudio = capturesAudio;
12220
+ this.call.tracer.trace('mic.capture_report', event);
12221
+ }
12127
12222
  this.call.streamClient.dispatchEvent(event);
12128
12223
  },
12129
12224
  });
@@ -12645,6 +12740,7 @@ class SpeakerManager {
12645
12740
  this.defaultDevice = defaultDevice;
12646
12741
  globalThis.streamRNVideoSDK?.callManager.setup({
12647
12742
  defaultDevice,
12743
+ isRingingTypeCall: this.call.ringing,
12648
12744
  });
12649
12745
  }
12650
12746
  }
@@ -12844,6 +12940,7 @@ class Call {
12844
12940
  const currentUserId = this.currentUserId;
12845
12941
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
12846
12942
  this.logger.info('Leaving call because of being blocked');
12943
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
12847
12944
  await this.leave({ message: 'user blocked' }).catch((err) => {
12848
12945
  this.logger.error('Error leaving call after being blocked', err);
12849
12946
  });
@@ -12880,6 +12977,7 @@ class Call {
12880
12977
  const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === CallingState.RINGING;
12881
12978
  if ((isAcceptedElsewhere || isRejectedByMe) &&
12882
12979
  !hasPending(this.joinLeaveConcurrencyTag)) {
12980
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
12883
12981
  this.leave().catch(() => {
12884
12982
  this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
12885
12983
  });
@@ -12891,6 +12989,9 @@ class Call {
12891
12989
  const receiver_id = this.clientStore.connectedUser?.id;
12892
12990
  const ended_at = callSession?.ended_at;
12893
12991
  const created_by_id = this.state.createdBy?.id;
12992
+ if (this.currentUserId && created_by_id === this.currentUserId) {
12993
+ globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
12994
+ }
12894
12995
  const rejected_by = callSession?.rejected_by;
12895
12996
  const accepted_by = callSession?.accepted_by;
12896
12997
  let leaveCallIdle = false;
@@ -13029,7 +13130,16 @@ class Call {
13029
13130
  }
13030
13131
  if (callingState === CallingState.RINGING && reject !== false) {
13031
13132
  if (reject) {
13032
- await this.reject(reason ?? 'decline');
13133
+ const reasonToEndCallReason = {
13134
+ timeout: 'missed',
13135
+ cancel: 'canceled',
13136
+ busy: 'busy',
13137
+ decline: 'rejected',
13138
+ };
13139
+ const rejectReason = reason ?? 'decline';
13140
+ const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
13141
+ await this.reject(rejectReason);
13142
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
13033
13143
  }
13034
13144
  else {
13035
13145
  // if reject was undefined, we still have to cancel the call automatically
@@ -13037,9 +13147,11 @@ class Call {
13037
13147
  const hasOtherParticipants = this.state.remoteParticipants.length > 0;
13038
13148
  if (this.isCreatedByMe && !hasOtherParticipants) {
13039
13149
  await this.reject('cancel');
13150
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
13040
13151
  }
13041
13152
  }
13042
13153
  }
13154
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this);
13043
13155
  this.statsReporter?.stop();
13044
13156
  this.statsReporter = undefined;
13045
13157
  const leaveReason = message ?? reason ?? 'user is leaving the call';
@@ -13066,7 +13178,9 @@ class Call {
13066
13178
  this.ringingSubject.next(false);
13067
13179
  this.cancelAutoDrop();
13068
13180
  this.clientStore.unregisterCall(this);
13069
- globalThis.streamRNVideoSDK?.callManager.stop();
13181
+ globalThis.streamRNVideoSDK?.callManager.stop({
13182
+ isRingingTypeCall: this.ringing,
13183
+ });
13070
13184
  this.camera.dispose();
13071
13185
  this.microphone.dispose();
13072
13186
  this.screenShare.dispose();
@@ -13232,11 +13346,19 @@ class Call {
13232
13346
  * @returns a promise which resolves once the call join-flow has finished.
13233
13347
  */
13234
13348
  this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
13235
- await this.setup();
13236
13349
  const callingState = this.state.callingState;
13237
13350
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
13238
13351
  throw new Error(`Illegal State: call.join() shall be called only once`);
13239
13352
  }
13353
+ if (data?.ring) {
13354
+ this.ringingSubject.next(true);
13355
+ }
13356
+ const callingX = globalThis.streamRNVideoSDK?.callingX;
13357
+ if (callingX) {
13358
+ // for Android/iOS, we need to start the call in the callingx library as soon as possible
13359
+ await callingX.joinCall(this, this.clientStore.calls);
13360
+ }
13361
+ await this.setup();
13240
13362
  this.joinResponseTimeout = joinResponseTimeout;
13241
13363
  this.rpcRequestTimeout = rpcRequestTimeout;
13242
13364
  // we will count the number of join failures per SFU.
@@ -13245,38 +13367,44 @@ class Call {
13245
13367
  const sfuJoinFailures = new Map();
13246
13368
  const joinData = data;
13247
13369
  maxJoinRetries = Math.max(maxJoinRetries, 1);
13248
- for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13249
- try {
13250
- this.logger.trace(`Joining call (${attempt})`, this.cid);
13251
- await this.doJoin(data);
13252
- delete joinData.migrating_from;
13253
- delete joinData.migrating_from_list;
13254
- break;
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());
13370
+ try {
13371
+ for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13372
+ try {
13373
+ this.logger.trace(`Joining call (${attempt})`, this.cid);
13374
+ await this.doJoin(data);
13375
+ delete joinData.migrating_from;
13376
+ delete joinData.migrating_from_list;
13377
+ break;
13274
13378
  }
13275
- if (attempt === maxJoinRetries - 1) {
13276
- throw err;
13379
+ catch (err) {
13380
+ this.logger.warn(`Failed to join call (${attempt})`, this.cid);
13381
+ if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
13382
+ (err instanceof SfuJoinError && err.unrecoverable)) {
13383
+ // if the error is unrecoverable, we should not retry as that signals
13384
+ // that connectivity is good, but the coordinator doesn't allow the user
13385
+ // to join the call due to some reason (e.g., ended call, expired token...)
13386
+ throw err;
13387
+ }
13388
+ // immediately switch to a different SFU in case of recoverable join error
13389
+ const switchSfu = err instanceof SfuJoinError &&
13390
+ SfuJoinError.isJoinErrorCode(err.errorEvent);
13391
+ const sfuId = this.credentials?.server.edge_name || '';
13392
+ const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
13393
+ sfuJoinFailures.set(sfuId, failures);
13394
+ if (switchSfu || failures >= 2) {
13395
+ joinData.migrating_from = sfuId;
13396
+ joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
13397
+ }
13398
+ if (attempt === maxJoinRetries - 1) {
13399
+ throw err;
13400
+ }
13277
13401
  }
13402
+ await sleep(retryInterval(attempt));
13278
13403
  }
13279
- await sleep(retryInterval(attempt));
13404
+ }
13405
+ catch (error) {
13406
+ callingX?.endCall(this, 'error');
13407
+ throw error;
13280
13408
  }
13281
13409
  };
13282
13410
  /**
@@ -13423,7 +13551,9 @@ class Call {
13423
13551
  // re-apply them on later reconnections or server-side data fetches
13424
13552
  if (!this.deviceSettingsAppliedOnce && this.state.settings) {
13425
13553
  await this.applyDeviceConfig(this.state.settings, true, false);
13426
- globalThis.streamRNVideoSDK?.callManager.start();
13554
+ globalThis.streamRNVideoSDK?.callManager.start({
13555
+ isRingingTypeCall: this.ringing,
13556
+ });
13427
13557
  this.deviceSettingsAppliedOnce = true;
13428
13558
  }
13429
13559
  // We shouldn't persist the `ring` and `notify` state after joining the call
@@ -13851,6 +13981,7 @@ class Call {
13851
13981
  if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
13852
13982
  return;
13853
13983
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
13984
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
13854
13985
  this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
13855
13986
  this.logger.warn(`Can't leave call after disconnect request`, err);
13856
13987
  });
@@ -14755,6 +14886,12 @@ class Call {
14755
14886
  unbind();
14756
14887
  };
14757
14888
  };
14889
+ /**
14890
+ * Plays all audio elements blocked by the browser's autoplay policy.
14891
+ */
14892
+ this.resumeAudio = () => {
14893
+ return this.dynascaleManager.resumeAudio();
14894
+ };
14758
14895
  /**
14759
14896
  * Binds a DOM <img> element to this call's thumbnail (if enabled in settings).
14760
14897
  *
@@ -14872,7 +15009,7 @@ class Call {
14872
15009
  * A flag indicating whether the call was created by the current user.
14873
15010
  */
14874
15011
  get isCreatedByMe() {
14875
- return this.state.createdBy?.id === this.currentUserId;
15012
+ return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
14876
15013
  }
14877
15014
  }
14878
15015
 
@@ -15996,7 +16133,7 @@ class StreamClient {
15996
16133
  this.getUserAgent = () => {
15997
16134
  if (!this.cachedUserAgent) {
15998
16135
  const { clientAppIdentifier = {} } = this.options;
15999
- const { sdkName = 'js', sdkVersion = "1.45.0", ...extras } = clientAppIdentifier;
16136
+ const { sdkName = 'js', sdkVersion = "1.46.1", ...extras } = clientAppIdentifier;
16000
16137
  this.cachedUserAgent = [
16001
16138
  `stream-video-${sdkName}-v${sdkVersion}`,
16002
16139
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),