@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 +32 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +32 -4
- package/dist/index.mjs.map +1 -1
- package/dist/tts.d.ts +5 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
2680
|
+
streamPromise = this.speak(buffer.trim());
|
|
2654
2681
|
buffer = "";
|
|
2655
2682
|
}
|
|
2683
|
+
await streamPromise;
|
|
2656
2684
|
}
|
|
2657
2685
|
};
|
|
2658
2686
|
}
|