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