@ztimson/utils 0.28.9 → 0.28.10

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.mjs CHANGED
@@ -2534,6 +2534,8 @@ ${err.message || err.toString()}`);
2534
2534
  class TTS {
2535
2535
  static QUALITY_PATTERNS = ["Google", "Microsoft", "Samantha", "Premium", "Natural", "Neural"];
2536
2536
  _currentUtterance = null;
2537
+ _voicesLoaded;
2538
+ _isStopping = false;
2537
2539
  _rate = 1;
2538
2540
  get rate() {
2539
2541
  return this._rate;
@@ -2568,6 +2570,7 @@ class TTS {
2568
2570
  }
2569
2571
  /** Create a TTS instance with optional configuration */
2570
2572
  constructor(config) {
2573
+ this._voicesLoaded = this.initializeVoices();
2571
2574
  if (config) {
2572
2575
  if (config.rate !== void 0) this._rate = config.rate;
2573
2576
  if (config.pitch !== void 0) this._pitch = config.pitch;
@@ -2575,6 +2578,23 @@ class TTS {
2575
2578
  this._voice = config.voice === null ? void 0 : config.voice || void 0;
2576
2579
  }
2577
2580
  }
2581
+ /** Initializes voice loading and sets default voice if needed */
2582
+ initializeVoices() {
2583
+ return new Promise((resolve) => {
2584
+ const voices = window.speechSynthesis.getVoices();
2585
+ if (voices.length > 0) {
2586
+ if (!this._voice) this._voice = TTS.bestVoice();
2587
+ resolve();
2588
+ } else {
2589
+ const handler = () => {
2590
+ window.speechSynthesis.removeEventListener("voiceschanged", handler);
2591
+ if (!this._voice) this._voice = TTS.bestVoice();
2592
+ resolve();
2593
+ };
2594
+ window.speechSynthesis.addEventListener("voiceschanged", handler);
2595
+ }
2596
+ });
2597
+ }
2578
2598
  /**
2579
2599
  * Selects the best available TTS voice, prioritizing high-quality options
2580
2600
  * @param lang Speaking language
@@ -2604,8 +2624,9 @@ class TTS {
2604
2624
  return utterance;
2605
2625
  }
2606
2626
  /** Speaks text and returns a Promise which resolves once complete */
2607
- speak(text) {
2627
+ async speak(text) {
2608
2628
  if (!text.trim()) return Promise.resolve();
2629
+ await this._voicesLoaded;
2609
2630
  return new Promise((resolve, reject) => {
2610
2631
  this._currentUtterance = this.createUtterance(text);
2611
2632
  this._currentUtterance.onend = () => {
@@ -2614,15 +2635,20 @@ class TTS {
2614
2635
  };
2615
2636
  this._currentUtterance.onerror = (error) => {
2616
2637
  this._currentUtterance = null;
2617
- reject(error);
2638
+ if (this._isStopping && error.error === "interrupted") resolve();
2639
+ else reject(error);
2618
2640
  };
2619
2641
  window.speechSynthesis.speak(this._currentUtterance);
2620
2642
  });
2621
2643
  }
2622
2644
  /** Stops all TTS */
2623
2645
  stop() {
2646
+ this._isStopping = true;
2624
2647
  window.speechSynthesis.cancel();
2625
2648
  this._currentUtterance = null;
2649
+ setTimeout(() => {
2650
+ this._isStopping = false;
2651
+ }, 0);
2626
2652
  }
2627
2653
  /**
2628
2654
  * Initialize a stream that chunks text into sentences and speak them.
@@ -2638,21 +2664,23 @@ class TTS {
2638
2664
  */
2639
2665
  speakStream() {
2640
2666
  let buffer = "";
2667
+ let streamPromise = Promise.resolve();
2641
2668
  const sentenceRegex = /[^.!?\n]+[.!?\n]+/g;
2642
2669
  return {
2643
2670
  next: (text) => {
2644
2671
  buffer += text;
2645
2672
  const sentences = buffer.match(sentenceRegex);
2646
2673
  if (sentences) {
2647
- sentences.forEach((sentence) => this.speak(sentence.trim()));
2674
+ sentences.forEach((sentence) => streamPromise = this.speak(sentence.trim()));
2648
2675
  buffer = buffer.replace(sentenceRegex, "");
2649
2676
  }
2650
2677
  },
2651
2678
  done: async () => {
2652
2679
  if (buffer.trim()) {
2653
- await this.speak(buffer.trim());
2680
+ streamPromise = this.speak(buffer.trim());
2654
2681
  buffer = "";
2655
2682
  }
2683
+ await streamPromise;
2656
2684
  }
2657
2685
  };
2658
2686
  }