@ztimson/utils 0.28.9 → 0.28.11
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 +33 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +33 -7
- 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
|
+
_stoppedUtterances = /* @__PURE__ */ new WeakSet();
|
|
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,23 +2624,27 @@ 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
|
-
this._currentUtterance
|
|
2632
|
+
const utterance = this._currentUtterance;
|
|
2633
|
+
utterance.onend = () => {
|
|
2612
2634
|
this._currentUtterance = null;
|
|
2613
2635
|
resolve();
|
|
2614
2636
|
};
|
|
2615
|
-
|
|
2637
|
+
utterance.onerror = (error) => {
|
|
2616
2638
|
this._currentUtterance = null;
|
|
2617
|
-
|
|
2639
|
+
if (this._stoppedUtterances.has(utterance) && error.error === "interrupted") resolve();
|
|
2640
|
+
else reject(error);
|
|
2618
2641
|
};
|
|
2619
|
-
window.speechSynthesis.speak(
|
|
2642
|
+
window.speechSynthesis.speak(utterance);
|
|
2620
2643
|
});
|
|
2621
2644
|
}
|
|
2622
2645
|
/** Stops all TTS */
|
|
2623
2646
|
stop() {
|
|
2647
|
+
if (this._currentUtterance) this._stoppedUtterances.add(this._currentUtterance);
|
|
2624
2648
|
window.speechSynthesis.cancel();
|
|
2625
2649
|
this._currentUtterance = null;
|
|
2626
2650
|
}
|
|
@@ -2638,21 +2662,23 @@ class TTS {
|
|
|
2638
2662
|
*/
|
|
2639
2663
|
speakStream() {
|
|
2640
2664
|
let buffer = "";
|
|
2665
|
+
let streamPromise = Promise.resolve();
|
|
2641
2666
|
const sentenceRegex = /[^.!?\n]+[.!?\n]+/g;
|
|
2642
2667
|
return {
|
|
2643
2668
|
next: (text) => {
|
|
2644
2669
|
buffer += text;
|
|
2645
2670
|
const sentences = buffer.match(sentenceRegex);
|
|
2646
2671
|
if (sentences) {
|
|
2647
|
-
sentences.forEach((sentence) => this.speak(sentence.trim()));
|
|
2672
|
+
sentences.forEach((sentence) => streamPromise = this.speak(sentence.trim()));
|
|
2648
2673
|
buffer = buffer.replace(sentenceRegex, "");
|
|
2649
2674
|
}
|
|
2650
2675
|
},
|
|
2651
2676
|
done: async () => {
|
|
2652
2677
|
if (buffer.trim()) {
|
|
2653
|
-
|
|
2678
|
+
streamPromise = this.speak(buffer.trim());
|
|
2654
2679
|
buffer = "";
|
|
2655
2680
|
}
|
|
2681
|
+
await streamPromise;
|
|
2656
2682
|
}
|
|
2657
2683
|
};
|
|
2658
2684
|
}
|