@stream-io/video-client 0.5.0 → 0.5.2

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 CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [0.5.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.5.1...@stream-io/video-client-0.5.2) (2023-12-11)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **ringing:** Auto-Cancel outgoing calls ([#1217](https://github.com/GetStream/stream-video-js/issues/1217)) ([c4d557b](https://github.com/GetStream/stream-video-js/commit/c4d557b736df8ff0a95166d1f9f0a52d4a57a122)), closes [#1215](https://github.com/GetStream/stream-video-js/issues/1215)
11
+
12
+ ### [0.5.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.5.0...@stream-io/video-client-0.5.1) (2023-12-05)
13
+
14
+
15
+ ### Features
16
+
17
+ * **client:** speaking while muted in React Native using temporary peer connection ([#1207](https://github.com/GetStream/stream-video-js/issues/1207)) ([9093006](https://github.com/GetStream/stream-video-js/commit/90930063503b6dfb83572dad8a31e45b16bf1685))
18
+
5
19
  ## [0.5.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.4.10...@stream-io/video-client-0.5.0) (2023-11-29)
6
20
 
7
21
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Official Plain-JS SDK and Low-Level Client for [Stream Video](https://getstream.io/video/docs/)
1
+ # Official JavaScript SDK and Low-Level Client for [Stream Video](https://getstream.io/video/docs/)
2
2
 
3
3
  <img src="../../.readme-assets/Github-Graphic-JS.jpg" alt="Stream Video for JavaScript Header image" style="box-shadow: 0 3px 10px rgb(0 0 0 / 0.2); border-radius: 1rem" />
4
4
 
@@ -7,7 +7,6 @@ Low-level Video SDK client for browser and Node.js integrations.
7
7
  ## **Quick Links**
8
8
 
9
9
  - [Register](https://getstream.io/chat/trial/) to get an API key for Stream Video
10
- TODO: add links to docs and tutorials
11
10
 
12
11
  ## What is Stream?
13
12
 
@@ -15,9 +14,9 @@ Stream allows developers to rapidly deploy scalable feeds, chat messaging and vi
15
14
 
16
15
  With Stream's video components, you can use their SDK to build in-app video calling, audio rooms, audio calls, or live streaming. The best place to get started is with their tutorials:
17
16
 
18
- - [Video & Audio Calling Tutorial](https://getstream.io/video/docs/javascript/tutorials/video-calling/)
19
- - Audio Rooms Tutorial
20
- - [Livestreaming Tutorial](https://getstream.io/video/docs/javascript/tutorials/livestream/)
17
+ - [Video and Audio Calling Tutorial](https://getstream.io/video/sdk/javascript/tutorial/video-calling/)
18
+ - [Audio Rooms Tutorial](https://getstream.io/video/sdk/javascript/tutorial/audio-room/)
19
+ - [Livestream Tutorial](https://getstream.io/video/sdk/javascript/tutorial/livestreaming/)
21
20
 
22
21
  Stream provides UI components and state handling that make it easy to build video calling for your app. All calls run on Stream's network of edge servers around the world, ensuring optimal latency and reliability.
23
22
 
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
4
4
  import axios, { AxiosHeaders } from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
7
- import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, merge, from, Observable, debounceTime, concatMap, pairwise, of, filter, tap, debounce, timer } from 'rxjs';
7
+ import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, merge, from, Observable, debounceTime, concatMap, pairwise, of, filter, debounce, timer } from 'rxjs';
8
8
  import * as SDP from 'sdp-transform';
9
9
  import { UAParser } from 'ua-parser-js';
10
10
  import WebSocket from 'isomorphic-ws';
@@ -6492,7 +6492,7 @@ class Publisher {
6492
6492
  */
6493
6493
  const handleTrackEnded = async () => {
6494
6494
  logger$3('info', `Track ${TrackType[trackType]} has ended, notifying the SFU`);
6495
- await this.notifyTrackMuteStateChanged(mediaStream, track, trackType, true);
6495
+ await this.notifyTrackMuteStateChanged(mediaStream, trackType, true);
6496
6496
  // clean-up, this event listener needs to run only once.
6497
6497
  track.removeEventListener('ended', handleTrackEnded);
6498
6498
  };
@@ -6548,7 +6548,7 @@ class Publisher {
6548
6548
  }
6549
6549
  await transceiver.sender.replaceTrack(track);
6550
6550
  }
6551
- await this.notifyTrackMuteStateChanged(mediaStream, track, trackType, false);
6551
+ await this.notifyTrackMuteStateChanged(mediaStream, trackType, false);
6552
6552
  };
6553
6553
  /**
6554
6554
  * Stops publishing the given track type to the SFU, if it is currently being published.
@@ -6570,7 +6570,7 @@ class Publisher {
6570
6570
  : (transceiver.sender.track.enabled = false);
6571
6571
  // We don't need to notify SFU if unpublishing in response to remote soft mute
6572
6572
  if (this.state.localParticipant?.publishedTracks.includes(trackType)) {
6573
- await this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6573
+ await this.notifyTrackMuteStateChanged(undefined, trackType, true);
6574
6574
  }
6575
6575
  }
6576
6576
  };
@@ -6602,7 +6602,7 @@ class Publisher {
6602
6602
  }
6603
6603
  return false;
6604
6604
  };
6605
- this.notifyTrackMuteStateChanged = async (mediaStream, track, trackType, isMuted) => {
6605
+ this.notifyTrackMuteStateChanged = async (mediaStream, trackType, isMuted) => {
6606
6606
  await this.sfuClient.updateMuteState(trackType, isMuted);
6607
6607
  const audioOrVideoOrScreenShareStream = trackTypeToParticipantStreamKey(trackType);
6608
6608
  if (isMuted) {
@@ -7646,131 +7646,6 @@ var rxUtils = /*#__PURE__*/Object.freeze({
7646
7646
  setCurrentValue: setCurrentValue
7647
7647
  });
7648
7648
 
7649
- class StreamVideoWriteableStateStore {
7650
- constructor() {
7651
- /**
7652
- * A store keeping data of a successfully connected user over WS to the coordinator server.
7653
- */
7654
- this.connectedUserSubject = new BehaviorSubject(undefined);
7655
- /**
7656
- * A list of {@link Call} objects created/tracked by this client.
7657
- */
7658
- this.callsSubject = new BehaviorSubject([]);
7659
- /**
7660
- * Gets the current value of an observable, or undefined if the observable has
7661
- * not emitted a value yet.
7662
- *
7663
- * @param observable$ the observable to get the value from.
7664
- */
7665
- this.getCurrentValue = getCurrentValue;
7666
- /**
7667
- * Updates the value of the provided Subject.
7668
- * An `update` can either be a new value or a function which takes
7669
- * the current value and returns a new value.
7670
- *
7671
- * @param subject the subject to update.
7672
- * @param update the update to apply to the subject.
7673
- * @return the updated value.
7674
- */
7675
- this.setCurrentValue = setCurrentValue;
7676
- /**
7677
- * Sets the currently connected user.
7678
- *
7679
- * @internal
7680
- * @param user the user to set as connected.
7681
- */
7682
- this.setConnectedUser = (user) => {
7683
- return this.setCurrentValue(this.connectedUserSubject, user);
7684
- };
7685
- /**
7686
- * Sets the list of {@link Call} objects created/tracked by this client.
7687
- * @param calls
7688
- */
7689
- this.setCalls = (calls) => {
7690
- return this.setCurrentValue(this.callsSubject, calls);
7691
- };
7692
- /**
7693
- * Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
7694
- *
7695
- * @param call the call to add.
7696
- */
7697
- this.registerCall = (call) => {
7698
- if (!this.calls.find((c) => c.cid === call.cid)) {
7699
- this.setCalls((calls) => [...calls, call]);
7700
- }
7701
- };
7702
- /**
7703
- * Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
7704
- *
7705
- * @param call the call to remove
7706
- */
7707
- this.unregisterCall = (call) => {
7708
- return this.setCalls((calls) => calls.filter((c) => c !== call));
7709
- };
7710
- /**
7711
- * Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
7712
- *
7713
- * @param type the type of call to find.
7714
- * @param id the id of the call to find.
7715
- */
7716
- this.findCall = (type, id) => {
7717
- return this.calls.find((c) => c.type === type && c.id === id);
7718
- };
7719
- this.connectedUserSubject.subscribe(async (user) => {
7720
- // leave all calls when the user disconnects.
7721
- if (!user) {
7722
- for (const call of this.calls) {
7723
- getLogger(['client-state'])('info', `User disconnected, leaving call: ${call.cid}`);
7724
- await call.leave();
7725
- }
7726
- }
7727
- });
7728
- }
7729
- /**
7730
- * The currently connected user.
7731
- */
7732
- get connectedUser() {
7733
- return this.getCurrentValue(this.connectedUserSubject);
7734
- }
7735
- /**
7736
- * A list of {@link Call} objects created/tracked by this client.
7737
- */
7738
- get calls() {
7739
- return this.getCurrentValue(this.callsSubject);
7740
- }
7741
- }
7742
- /**
7743
- * A reactive store that exposes state variables in a reactive manner.
7744
- * You can subscribe to changes of the different state variables.
7745
- * This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
7746
- */
7747
- class StreamVideoReadOnlyStateStore {
7748
- constructor(store) {
7749
- /**
7750
- * This method allows you the get the current value of a state variable.
7751
- *
7752
- * @param observable the observable to get the current value of.
7753
- * @returns the current value of the observable.
7754
- */
7755
- this.getCurrentValue = getCurrentValue;
7756
- // convert and expose subjects as observables
7757
- this.connectedUser$ = store.connectedUserSubject.asObservable();
7758
- this.calls$ = store.callsSubject.asObservable();
7759
- }
7760
- /**
7761
- * The current user connected over WS to the backend.
7762
- */
7763
- get connectedUser() {
7764
- return getCurrentValue(this.connectedUser$);
7765
- }
7766
- /**
7767
- * A list of {@link Call} objects created/tracked by this client.
7768
- */
7769
- get calls() {
7770
- return getCurrentValue(this.calls$);
7771
- }
7772
- }
7773
-
7774
7649
  /**
7775
7650
  * Creates a new combined {@link Comparator<T>} which sorts items by the given comparators.
7776
7651
  * The comparators are applied in the order they are given (left -> right).
@@ -8729,6 +8604,136 @@ class CallState {
8729
8604
  }
8730
8605
  }
8731
8606
 
8607
+ class StreamVideoWriteableStateStore {
8608
+ constructor() {
8609
+ /**
8610
+ * A store keeping data of a successfully connected user over WS to the coordinator server.
8611
+ */
8612
+ this.connectedUserSubject = new BehaviorSubject(undefined);
8613
+ /**
8614
+ * A list of {@link Call} objects created/tracked by this client.
8615
+ */
8616
+ this.callsSubject = new BehaviorSubject([]);
8617
+ /**
8618
+ * Gets the current value of an observable, or undefined if the observable has
8619
+ * not emitted a value yet.
8620
+ *
8621
+ * @param observable$ the observable to get the value from.
8622
+ */
8623
+ this.getCurrentValue = getCurrentValue;
8624
+ /**
8625
+ * Updates the value of the provided Subject.
8626
+ * An `update` can either be a new value or a function which takes
8627
+ * the current value and returns a new value.
8628
+ *
8629
+ * @param subject the subject to update.
8630
+ * @param update the update to apply to the subject.
8631
+ * @return the updated value.
8632
+ */
8633
+ this.setCurrentValue = setCurrentValue;
8634
+ /**
8635
+ * Sets the currently connected user.
8636
+ *
8637
+ * @internal
8638
+ * @param user the user to set as connected.
8639
+ */
8640
+ this.setConnectedUser = (user) => {
8641
+ return this.setCurrentValue(this.connectedUserSubject, user);
8642
+ };
8643
+ /**
8644
+ * Sets the list of {@link Call} objects created/tracked by this client.
8645
+ * @param calls
8646
+ */
8647
+ this.setCalls = (calls) => {
8648
+ return this.setCurrentValue(this.callsSubject, calls);
8649
+ };
8650
+ /**
8651
+ * Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
8652
+ *
8653
+ * @param call the call to add.
8654
+ */
8655
+ this.registerCall = (call) => {
8656
+ if (!this.calls.find((c) => c.cid === call.cid)) {
8657
+ this.setCalls((calls) => [...calls, call]);
8658
+ }
8659
+ };
8660
+ /**
8661
+ * Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
8662
+ *
8663
+ * @param call the call to remove
8664
+ */
8665
+ this.unregisterCall = (call) => {
8666
+ return this.setCalls((calls) => calls.filter((c) => c !== call));
8667
+ };
8668
+ /**
8669
+ * Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
8670
+ *
8671
+ * @param type the type of call to find.
8672
+ * @param id the id of the call to find.
8673
+ */
8674
+ this.findCall = (type, id) => {
8675
+ return this.calls.find((c) => c.type === type && c.id === id);
8676
+ };
8677
+ this.connectedUserSubject.subscribe(async (user) => {
8678
+ // leave all calls when the user disconnects.
8679
+ if (!user) {
8680
+ const logger = getLogger(['client-state']);
8681
+ for (const call of this.calls) {
8682
+ if (call.state.callingState === CallingState.LEFT)
8683
+ continue;
8684
+ logger('info', `User disconnected, leaving call: ${call.cid}`);
8685
+ await call.leave().catch((err) => {
8686
+ logger('error', `Error leaving call: ${call.cid}`, err);
8687
+ });
8688
+ }
8689
+ }
8690
+ });
8691
+ }
8692
+ /**
8693
+ * The currently connected user.
8694
+ */
8695
+ get connectedUser() {
8696
+ return this.getCurrentValue(this.connectedUserSubject);
8697
+ }
8698
+ /**
8699
+ * A list of {@link Call} objects created/tracked by this client.
8700
+ */
8701
+ get calls() {
8702
+ return this.getCurrentValue(this.callsSubject);
8703
+ }
8704
+ }
8705
+ /**
8706
+ * A reactive store that exposes state variables in a reactive manner.
8707
+ * You can subscribe to changes of the different state variables.
8708
+ * This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
8709
+ */
8710
+ class StreamVideoReadOnlyStateStore {
8711
+ constructor(store) {
8712
+ /**
8713
+ * This method allows you the get the current value of a state variable.
8714
+ *
8715
+ * @param observable the observable to get the current value of.
8716
+ * @returns the current value of the observable.
8717
+ */
8718
+ this.getCurrentValue = getCurrentValue;
8719
+ // convert and expose subjects as observables
8720
+ this.connectedUser$ = store.connectedUserSubject.asObservable();
8721
+ this.calls$ = store.callsSubject.asObservable();
8722
+ }
8723
+ /**
8724
+ * The current user connected over WS to the backend.
8725
+ */
8726
+ get connectedUser() {
8727
+ return getCurrentValue(this.connectedUser$);
8728
+ }
8729
+ /**
8730
+ * A list of {@link Call} objects created/tracked by this client.
8731
+ */
8732
+ get calls() {
8733
+ return getCurrentValue(this.calls$);
8734
+ }
8735
+ }
8736
+
8732
8737
  /**
8733
8738
  * Event handler that watched the delivery of `call.accepted`.
8734
8739
  * Once the event is received, the call is joined.
@@ -9318,7 +9323,7 @@ const createStatsReporter = ({ subscriber, publisher, state, pollingIntervalInMs
9318
9323
  const transform = (report, opts) => {
9319
9324
  const { trackKind, kind } = opts;
9320
9325
  const direction = kind === 'subscriber' ? 'inbound-rtp' : 'outbound-rtp';
9321
- const stats = flatten(report);
9326
+ const stats = flatten$1(report);
9322
9327
  const streams = stats
9323
9328
  .filter((stat) => stat.type === direction &&
9324
9329
  stat.kind === trackKind)
@@ -9413,7 +9418,7 @@ const aggregate = (stats) => {
9413
9418
  *
9414
9419
  * @param report the report to flatten.
9415
9420
  */
9416
- const flatten = (report) => {
9421
+ const flatten$1 = (report) => {
9417
9422
  const stats = [];
9418
9423
  report.forEach((s) => {
9419
9424
  stats.push(s);
@@ -10755,7 +10760,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
10755
10760
  }
10756
10761
 
10757
10762
  const DETECTION_FREQUENCY_IN_MS = 500;
10758
- const AUDIO_LEVEL_THRESHOLD = 150;
10763
+ const AUDIO_LEVEL_THRESHOLD$1 = 150;
10759
10764
  const FFT_SIZE = 128;
10760
10765
  /**
10761
10766
  * Creates a new sound detector.
@@ -10766,7 +10771,7 @@ const FFT_SIZE = 128;
10766
10771
  * @returns a clean-up function which once invoked stops the sound detector.
10767
10772
  */
10768
10773
  const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options = {}) => {
10769
- const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
10774
+ const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD$1, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
10770
10775
  const audioContext = new AudioContext();
10771
10776
  const analyser = audioContext.createAnalyser();
10772
10777
  analyser.fftSize = fftSize;
@@ -10805,6 +10810,99 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
10805
10810
  };
10806
10811
  };
10807
10812
 
10813
+ /**
10814
+ * Flatten the stats report into an array of stats objects.
10815
+ *
10816
+ * @param report the report to flatten.
10817
+ */
10818
+ const flatten = (report) => {
10819
+ const stats = [];
10820
+ report.forEach((s) => {
10821
+ stats.push(s);
10822
+ });
10823
+ return stats;
10824
+ };
10825
+ const AUDIO_LEVEL_THRESHOLD = 0.2;
10826
+ class RNSpeechDetector {
10827
+ constructor() {
10828
+ this.pc1 = new RTCPeerConnection({});
10829
+ this.pc2 = new RTCPeerConnection({});
10830
+ }
10831
+ /**
10832
+ * Starts the speech detection.
10833
+ */
10834
+ async start() {
10835
+ try {
10836
+ const audioStream = await navigator.mediaDevices.getUserMedia({
10837
+ audio: true,
10838
+ });
10839
+ this.pc1.addEventListener('icecandidate', async (e) => {
10840
+ await this.pc2.addIceCandidate(e.candidate);
10841
+ });
10842
+ this.pc2.addEventListener('icecandidate', async (e) => {
10843
+ await this.pc1.addIceCandidate(e.candidate);
10844
+ });
10845
+ audioStream
10846
+ .getTracks()
10847
+ .forEach((track) => this.pc1.addTrack(track, audioStream));
10848
+ const offer = await this.pc1.createOffer({});
10849
+ await this.pc2.setRemoteDescription(offer);
10850
+ await this.pc1.setLocalDescription(offer);
10851
+ const answer = await this.pc2.createAnswer();
10852
+ await this.pc1.setRemoteDescription(answer);
10853
+ await this.pc2.setLocalDescription(answer);
10854
+ const audioTracks = audioStream.getAudioTracks();
10855
+ // We need to mute the audio track for this temporary stream, or else you will hear yourself twice while in the call.
10856
+ audioTracks.forEach((track) => (track.enabled = false));
10857
+ }
10858
+ catch (error) {
10859
+ console.error('Error connecting and negotiating between PeerConnections:', error);
10860
+ }
10861
+ }
10862
+ /**
10863
+ * Stops the speech detection and releases all allocated resources.
10864
+ */
10865
+ stop() {
10866
+ this.pc1.close();
10867
+ this.pc2.close();
10868
+ if (this.intervalId) {
10869
+ clearInterval(this.intervalId);
10870
+ }
10871
+ }
10872
+ /**
10873
+ * Public method that detects the audio levels and returns the status.
10874
+ */
10875
+ onSpeakingDetectedStateChange(onSoundDetectedStateChanged) {
10876
+ this.intervalId = setInterval(async () => {
10877
+ const stats = (await this.pc1.getStats());
10878
+ const report = flatten(stats);
10879
+ // Audio levels are present inside stats of type `media-source` and of kind `audio`
10880
+ const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
10881
+ stat.kind === 'audio');
10882
+ if (audioMediaSourceStats) {
10883
+ const { audioLevel } = audioMediaSourceStats;
10884
+ if (audioLevel) {
10885
+ if (audioLevel >= AUDIO_LEVEL_THRESHOLD) {
10886
+ onSoundDetectedStateChanged({
10887
+ isSoundDetected: true,
10888
+ audioLevel,
10889
+ });
10890
+ }
10891
+ else {
10892
+ onSoundDetectedStateChanged({
10893
+ isSoundDetected: false,
10894
+ audioLevel: 0,
10895
+ });
10896
+ }
10897
+ }
10898
+ }
10899
+ }, 1000);
10900
+ return () => {
10901
+ clearInterval(this.intervalId);
10902
+ };
10903
+ }
10904
+ }
10905
+
10808
10906
  class MicrophoneManager extends InputMediaDeviceManager {
10809
10907
  constructor(call) {
10810
10908
  super(call, new MicrophoneManagerState(), TrackType.AUDIO);
@@ -10846,20 +10944,31 @@ class MicrophoneManager extends InputMediaDeviceManager {
10846
10944
  return this.call.stopPublish(TrackType.AUDIO, stopTracks);
10847
10945
  }
10848
10946
  async startSpeakingWhileMutedDetection(deviceId) {
10947
+ await this.stopSpeakingWhileMutedDetection();
10849
10948
  if (isReactNative()) {
10850
- return;
10949
+ this.rnSpeechDetector = new RNSpeechDetector();
10950
+ await this.rnSpeechDetector.start();
10951
+ const unsubscribe = this.rnSpeechDetector?.onSpeakingDetectedStateChange((event) => {
10952
+ this.state.setSpeakingWhileMuted(event.isSoundDetected);
10953
+ });
10954
+ this.soundDetectorCleanup = () => {
10955
+ unsubscribe();
10956
+ this.rnSpeechDetector?.stop();
10957
+ this.rnSpeechDetector = undefined;
10958
+ };
10959
+ }
10960
+ else {
10961
+ // Need to start a new stream that's not connected to publisher
10962
+ const stream = await this.getStream({
10963
+ deviceId,
10964
+ });
10965
+ this.soundDetectorCleanup = createSoundDetector(stream, (event) => {
10966
+ this.state.setSpeakingWhileMuted(event.isSoundDetected);
10967
+ });
10851
10968
  }
10852
- await this.stopSpeakingWhileMutedDetection();
10853
- // Need to start a new stream that's not connected to publisher
10854
- const stream = await this.getStream({
10855
- deviceId,
10856
- });
10857
- this.soundDetectorCleanup = createSoundDetector(stream, (event) => {
10858
- this.state.setSpeakingWhileMuted(event.isSoundDetected);
10859
- });
10860
10969
  }
10861
10970
  async stopSpeakingWhileMutedDetection() {
10862
- if (isReactNative() || !this.soundDetectorCleanup) {
10971
+ if (!this.soundDetectorCleanup) {
10863
10972
  return;
10864
10973
  }
10865
10974
  this.state.setSpeakingWhileMuted(false);
@@ -11811,9 +11920,10 @@ class Call {
11811
11920
  return this.state.setSortParticipantsBy(criteria);
11812
11921
  };
11813
11922
  /**
11923
+ * Updates the list of video layers to publish.
11924
+ *
11814
11925
  * @internal
11815
- * @param enabledRids
11816
- * @returns
11926
+ * @param enabledLayers the list of layers to enable.
11817
11927
  */
11818
11928
  this.updatePublishQuality = async (enabledLayers) => {
11819
11929
  return this.publisher?.updateVideoPublishQuality(enabledLayers);
@@ -12085,35 +12195,29 @@ class Call {
12085
12195
  this.updateCallMembers = async (data) => {
12086
12196
  return this.streamClient.post(`${this.streamClientBasePath}/members`, data);
12087
12197
  };
12198
+ /**
12199
+ * Schedules an auto-drop timeout based on the call settings.
12200
+ * Applicable only for ringing calls.
12201
+ */
12088
12202
  this.scheduleAutoDrop = () => {
12089
- if (this.dropTimeout)
12090
- clearTimeout(this.dropTimeout);
12091
- const subscription = this.state.settings$
12092
- .pipe(pairwise(), tap(([prevSettings, currentSettings]) => {
12093
- if (!currentSettings || !this.clientStore.connectedUser)
12203
+ clearTimeout(this.dropTimeout);
12204
+ this.leaveCallHooks.add(createSubscription(this.state.settings$, (settings) => {
12205
+ if (!settings)
12094
12206
  return;
12095
- const isOutgoingCall = this.currentUserId === this.state.createdBy?.id;
12096
- const [prevTimeoutMs, timeoutMs] = isOutgoingCall
12097
- ? [
12098
- prevSettings?.ring.auto_cancel_timeout_ms,
12099
- currentSettings.ring.auto_cancel_timeout_ms,
12100
- ]
12101
- : [
12102
- prevSettings?.ring.incoming_call_timeout_ms,
12103
- currentSettings.ring.incoming_call_timeout_ms,
12104
- ];
12105
- if (typeof timeoutMs === 'undefined' ||
12106
- timeoutMs === prevTimeoutMs ||
12107
- timeoutMs === 0)
12207
+ // ignore if the call is not ringing
12208
+ if (this.state.callingState !== CallingState.RINGING)
12108
12209
  return;
12109
- if (this.dropTimeout)
12110
- clearTimeout(this.dropTimeout);
12111
- this.dropTimeout = setTimeout(() => this.leave(), timeoutMs);
12112
- }), takeWhile(() => !!this.clientStore.calls.find((call) => call.cid === this.cid)))
12113
- .subscribe();
12114
- this.leaveCallHooks.add(() => {
12115
- !subscription.closed && subscription.unsubscribe();
12116
- });
12210
+ const timeoutInMs = settings.ring.auto_cancel_timeout_ms;
12211
+ // 0 means no auto-drop
12212
+ if (timeoutInMs <= 0)
12213
+ return;
12214
+ clearTimeout(this.dropTimeout);
12215
+ this.dropTimeout = setTimeout(() => {
12216
+ this.leave().catch((err) => {
12217
+ this.logger('error', 'Failed to drop call', err);
12218
+ });
12219
+ }, timeoutInMs);
12220
+ }));
12117
12221
  };
12118
12222
  /**
12119
12223
  * Retrieves the list of recordings for the current call or call session.
@@ -14037,7 +14141,7 @@ class StreamClient {
14037
14141
  });
14038
14142
  };
14039
14143
  this.getUserAgent = () => {
14040
- const version = "0.5.0" ;
14144
+ const version = "0.5.2" ;
14041
14145
  return (this.userAgent ||
14042
14146
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
14043
14147
  };