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