@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.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
|
-
|
|
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
|
-
|
|
2684
|
+
streamPromise = this.speak(buffer.trim());
|
|
2658
2685
|
buffer = "";
|
|
2659
2686
|
}
|
|
2687
|
+
await streamPromise;
|
|
2660
2688
|
}
|
|
2661
2689
|
};
|
|
2662
2690
|
}
|