@ztimson/utils 0.28.10 → 0.28.12

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
@@ -2537,9 +2537,10 @@ ${err.message || err.toString()}`);
2537
2537
  }
2538
2538
  class TTS {
2539
2539
  static QUALITY_PATTERNS = ["Google", "Microsoft", "Samantha", "Premium", "Natural", "Neural"];
2540
+ static _errorHandlerInstalled = false;
2540
2541
  _currentUtterance = null;
2541
2542
  _voicesLoaded;
2542
- _isStopping = false;
2543
+ _stoppedUtterances = /* @__PURE__ */ new WeakSet();
2543
2544
  _rate = 1;
2544
2545
  get rate() {
2545
2546
  return this._rate;
@@ -2572,8 +2573,8 @@ ${err.message || err.toString()}`);
2572
2573
  this._voice = value;
2573
2574
  if (this._currentUtterance && value) this._currentUtterance.voice = value;
2574
2575
  }
2575
- /** Create a TTS instance with optional configuration */
2576
2576
  constructor(config) {
2577
+ TTS.installErrorHandler();
2577
2578
  this._voicesLoaded = this.initializeVoices();
2578
2579
  if (config) {
2579
2580
  if (config.rate !== void 0) this._rate = config.rate;
@@ -2582,7 +2583,13 @@ ${err.message || err.toString()}`);
2582
2583
  this._voice = config.voice === null ? void 0 : config.voice || void 0;
2583
2584
  }
2584
2585
  }
2585
- /** Initializes voice loading and sets default voice if needed */
2586
+ static installErrorHandler() {
2587
+ if (this._errorHandlerInstalled) return;
2588
+ window.addEventListener("unhandledrejection", (event) => {
2589
+ if (event.reason?.error === "interrupted" && event.reason instanceof SpeechSynthesisErrorEvent) event.preventDefault();
2590
+ });
2591
+ this._errorHandlerInstalled = true;
2592
+ }
2586
2593
  initializeVoices() {
2587
2594
  return new Promise((resolve) => {
2588
2595
  const voices = window.speechSynthesis.getVoices();
@@ -2599,11 +2606,6 @@ ${err.message || err.toString()}`);
2599
2606
  }
2600
2607
  });
2601
2608
  }
2602
- /**
2603
- * Selects the best available TTS voice, prioritizing high-quality options
2604
- * @param lang Speaking language
2605
- * @returns Highest quality voice
2606
- */
2607
2609
  static bestVoice(lang = "en") {
2608
2610
  const voices = window.speechSynthesis.getVoices();
2609
2611
  for (const pattern of this.QUALITY_PATTERNS) {
@@ -2612,11 +2614,9 @@ ${err.message || err.toString()}`);
2612
2614
  }
2613
2615
  return voices.find((v) => v.lang.startsWith(lang));
2614
2616
  }
2615
- /** Cleans text for TTS by removing emojis, markdown and code block */
2616
2617
  static cleanText(text) {
2617
2618
  return removeEmojis(text).replace(/```[\s\S]*?```/g, " code block ").replace(/[#*_~`]/g, "");
2618
2619
  }
2619
- /** Creates a speech utterance with current options */
2620
2620
  createUtterance(text) {
2621
2621
  const cleanedText = TTS.cleanText(text);
2622
2622
  const utterance = new SpeechSynthesisUtterance(cleanedText);
@@ -2627,45 +2627,29 @@ ${err.message || err.toString()}`);
2627
2627
  utterance.volume = this._volume;
2628
2628
  return utterance;
2629
2629
  }
2630
- /** Speaks text and returns a Promise which resolves once complete */
2631
2630
  async speak(text) {
2632
2631
  if (!text.trim()) return Promise.resolve();
2633
2632
  await this._voicesLoaded;
2634
2633
  return new Promise((resolve, reject) => {
2635
2634
  this._currentUtterance = this.createUtterance(text);
2636
- this._currentUtterance.onend = () => {
2635
+ const utterance = this._currentUtterance;
2636
+ utterance.onend = () => {
2637
2637
  this._currentUtterance = null;
2638
2638
  resolve();
2639
2639
  };
2640
- this._currentUtterance.onerror = (error) => {
2640
+ utterance.onerror = (error) => {
2641
2641
  this._currentUtterance = null;
2642
- if (this._isStopping && error.error === "interrupted") resolve();
2642
+ if (this._stoppedUtterances.has(utterance) && error.error === "interrupted") resolve();
2643
2643
  else reject(error);
2644
2644
  };
2645
- window.speechSynthesis.speak(this._currentUtterance);
2645
+ window.speechSynthesis.speak(utterance);
2646
2646
  });
2647
2647
  }
2648
- /** Stops all TTS */
2649
2648
  stop() {
2650
- this._isStopping = true;
2649
+ if (this._currentUtterance) this._stoppedUtterances.add(this._currentUtterance);
2651
2650
  window.speechSynthesis.cancel();
2652
2651
  this._currentUtterance = null;
2653
- setTimeout(() => {
2654
- this._isStopping = false;
2655
- }, 0);
2656
2652
  }
2657
- /**
2658
- * Initialize a stream that chunks text into sentences and speak them.
2659
- *
2660
- * @example
2661
- * const stream = tts.speakStream();
2662
- * stream.next("Hello ");
2663
- * stream.next("World. How");
2664
- * stream.next(" are you?");
2665
- * await stream.done();
2666
- *
2667
- * @returns Object with next function for passing chunk of streamed text and done for completing the stream
2668
- */
2669
2653
  speakStream() {
2670
2654
  let buffer = "";
2671
2655
  let streamPromise = Promise.resolve();