@stream-io/video-client 1.41.0 → 1.41.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
@@ -6189,7 +6189,7 @@ const getSdkVersion = (sdk) => {
6189
6189
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6190
6190
  };
6191
6191
 
6192
- const version = "1.41.0";
6192
+ const version = "1.41.1";
6193
6193
  const [major, minor, patch] = version.split('.');
6194
6194
  let sdkInfo = {
6195
6195
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -9373,12 +9373,13 @@ class DynascaleManager {
9373
9373
  /**
9374
9374
  * Creates a new DynascaleManager instance.
9375
9375
  */
9376
- constructor(callState, speaker) {
9376
+ constructor(callState, speaker, tracer) {
9377
9377
  /**
9378
9378
  * The viewport tracker instance.
9379
9379
  */
9380
9380
  this.viewportTracker = new ViewportTracker();
9381
9381
  this.logger = videoLoggerSystem.getLogger('DynascaleManager');
9382
+ this.useWebAudio = isSafari();
9382
9383
  this.pendingSubscriptionsUpdate = null;
9383
9384
  this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
9384
9385
  this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
@@ -9418,6 +9419,10 @@ class DynascaleManager {
9418
9419
  }
9419
9420
  };
9420
9421
  this.setVideoTrackSubscriptionOverrides = (override, sessionIds) => {
9422
+ this.tracer.trace('setVideoTrackSubscriptionOverrides', [
9423
+ override,
9424
+ sessionIds,
9425
+ ]);
9421
9426
  if (!sessionIds) {
9422
9427
  return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, override ? { [globalOverrideKey]: override } : {});
9423
9428
  }
@@ -9499,6 +9504,18 @@ class DynascaleManager {
9499
9504
  this.setViewport = (element) => {
9500
9505
  return this.viewportTracker.setViewport(element);
9501
9506
  };
9507
+ /**
9508
+ * Sets whether to use WebAudio API for audio playback.
9509
+ * Must be set before joining the call.
9510
+ *
9511
+ * @internal
9512
+ *
9513
+ * @param useWebAudio whether to use WebAudio API.
9514
+ */
9515
+ this.setUseWebAudio = (useWebAudio) => {
9516
+ this.tracer.trace('setUseWebAudio', useWebAudio);
9517
+ this.useWebAudio = useWebAudio;
9518
+ };
9502
9519
  /**
9503
9520
  * Binds a DOM <video> element to the given session id.
9504
9521
  * This method will make sure that the video element will play
@@ -9714,6 +9731,7 @@ class DynascaleManager {
9714
9731
  // we will play audio directly through the audio element in other browsers
9715
9732
  audioElement.muted = false;
9716
9733
  audioElement.play().catch((e) => {
9734
+ this.tracer.trace('audioPlaybackError', e.message);
9717
9735
  this.logger.warn(`Failed to play audio stream`, e);
9718
9736
  });
9719
9737
  }
@@ -9748,32 +9766,57 @@ class DynascaleManager {
9748
9766
  };
9749
9767
  };
9750
9768
  this.getOrCreateAudioContext = () => {
9751
- if (this.audioContext || !isSafari())
9769
+ if (!this.useWebAudio)
9770
+ return;
9771
+ if (this.audioContext)
9752
9772
  return this.audioContext;
9753
9773
  const context = new AudioContext();
9774
+ this.tracer.trace('audioContext.create', context.state);
9754
9775
  if (context.state === 'suspended') {
9755
9776
  document.addEventListener('click', this.resumeAudioContext);
9756
9777
  }
9757
- // @ts-expect-error audioSession is available in Safari only
9778
+ context.addEventListener('statechange', () => {
9779
+ this.tracer.trace('audioContext.state', context.state);
9780
+ if (context.state === 'interrupted') {
9781
+ this.resumeAudioContext();
9782
+ }
9783
+ });
9758
9784
  const audioSession = navigator.audioSession;
9759
9785
  if (audioSession) {
9760
9786
  // https://github.com/w3c/audio-session/blob/main/explainer.md
9761
9787
  audioSession.type = 'play-and-record';
9788
+ let isSessionInterrupted = false;
9789
+ audioSession.addEventListener('statechange', () => {
9790
+ this.tracer.trace('audioSession.state', audioSession.state);
9791
+ if (audioSession.state === 'interrupted') {
9792
+ isSessionInterrupted = true;
9793
+ }
9794
+ else if (isSessionInterrupted) {
9795
+ this.resumeAudioContext();
9796
+ isSessionInterrupted = false;
9797
+ }
9798
+ });
9762
9799
  }
9763
9800
  return (this.audioContext = context);
9764
9801
  };
9765
9802
  this.resumeAudioContext = () => {
9766
- if (this.audioContext?.state === 'suspended') {
9767
- this.audioContext
9768
- .resume()
9769
- .catch((err) => this.logger.warn(`Can't resume audio context`, err))
9770
- .then(() => {
9803
+ if (!this.audioContext)
9804
+ return;
9805
+ const { state } = this.audioContext;
9806
+ if (state === 'suspended' || state === 'interrupted') {
9807
+ const tag = 'audioContext.resume';
9808
+ this.audioContext.resume().then(() => {
9809
+ this.tracer.trace(tag, this.audioContext?.state);
9771
9810
  document.removeEventListener('click', this.resumeAudioContext);
9811
+ }, (err) => {
9812
+ this.tracer.trace(`${tag}Error`, this.audioContext?.state);
9813
+ this.logger.warn(`Can't resume audio context`, err);
9772
9814
  });
9773
9815
  }
9774
9816
  };
9775
9817
  this.callState = callState;
9776
9818
  this.speaker = speaker;
9819
+ this.tracer = tracer;
9777
9820
  }
9778
9821
  setSfuClient(sfuClient) {
9779
9822
  this.sfuClient = sfuClient;
@@ -11993,6 +12036,37 @@ class SpeakerManager {
11993
12036
  this.state = new SpeakerState(call.tracer);
11994
12037
  this.setup();
11995
12038
  }
12039
+ apply(settings) {
12040
+ if (!isReactNative()) {
12041
+ return;
12042
+ }
12043
+ /// Determines if the speaker should be enabled based on a priority hierarchy of
12044
+ /// settings.
12045
+ ///
12046
+ /// The priority order is as follows:
12047
+ /// 1. If video camera is set to be on by default, speaker is enabled
12048
+ /// 2. If audio speaker is set to be on by default, speaker is enabled
12049
+ /// 3. If the default audio device is set to speaker, speaker is enabled
12050
+ ///
12051
+ /// This ensures that the speaker state aligns with the most important user
12052
+ /// preference or system requirement.
12053
+ const speakerOnWithSettingsPriority = settings.video.camera_default_on ||
12054
+ settings.audio.speaker_default_on ||
12055
+ settings.audio.default_device ===
12056
+ AudioSettingsRequestDefaultDeviceEnum.SPEAKER;
12057
+ const defaultDevice = speakerOnWithSettingsPriority
12058
+ ? AudioSettingsRequestDefaultDeviceEnum.SPEAKER
12059
+ : AudioSettingsRequestDefaultDeviceEnum.EARPIECE;
12060
+ if (this.defaultDevice !== defaultDevice) {
12061
+ this.call.logger.debug('SpeakerManager: setting default device', {
12062
+ defaultDevice,
12063
+ });
12064
+ this.defaultDevice = defaultDevice;
12065
+ globalThis.streamRNVideoSDK?.callManager.setup({
12066
+ defaultDevice,
12067
+ });
12068
+ }
12069
+ }
11996
12070
  setup() {
11997
12071
  if (this.areSubscriptionsSetUp) {
11998
12072
  return;
@@ -13934,9 +14008,7 @@ class Call {
13934
14008
  * @internal
13935
14009
  */
13936
14010
  this.applyDeviceConfig = async (settings, publish) => {
13937
- globalThis.streamRNVideoSDK?.callManager.setup({
13938
- default_device: settings.audio.default_device,
13939
- });
14011
+ this.speaker.apply(settings);
13940
14012
  await this.camera.apply(settings.video, publish).catch((err) => {
13941
14013
  this.logger.warn('Camera init failed', err);
13942
14014
  });
@@ -14106,7 +14178,7 @@ class Call {
14106
14178
  this.microphone = new MicrophoneManager(this);
14107
14179
  this.speaker = new SpeakerManager(this);
14108
14180
  this.screenShare = new ScreenShareManager(this);
14109
- this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
14181
+ this.dynascaleManager = new DynascaleManager(this.state, this.speaker, this.tracer);
14110
14182
  }
14111
14183
  /**
14112
14184
  * A flag indicating whether the call is "ringing" type of call.
@@ -15248,7 +15320,7 @@ class StreamClient {
15248
15320
  this.getUserAgent = () => {
15249
15321
  if (!this.cachedUserAgent) {
15250
15322
  const { clientAppIdentifier = {} } = this.options;
15251
- const { sdkName = 'js', sdkVersion = "1.41.0", ...extras } = clientAppIdentifier;
15323
+ const { sdkName = 'js', sdkVersion = "1.41.1", ...extras } = clientAppIdentifier;
15252
15324
  this.cachedUserAgent = [
15253
15325
  `stream-video-${sdkName}-v${sdkVersion}`,
15254
15326
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),