@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.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
|
+
_stoppedUtterances = /* @__PURE__ */ new WeakSet();
|
|
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,23 +2628,27 @@ ${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
|
-
this._currentUtterance
|
|
2636
|
+
const utterance = this._currentUtterance;
|
|
2637
|
+
utterance.onend = () => {
|
|
2616
2638
|
this._currentUtterance = null;
|
|
2617
2639
|
resolve();
|
|
2618
2640
|
};
|
|
2619
|
-
|
|
2641
|
+
utterance.onerror = (error) => {
|
|
2620
2642
|
this._currentUtterance = null;
|
|
2621
|
-
|
|
2643
|
+
if (this._stoppedUtterances.has(utterance) && error.error === "interrupted") resolve();
|
|
2644
|
+
else reject(error);
|
|
2622
2645
|
};
|
|
2623
|
-
window.speechSynthesis.speak(
|
|
2646
|
+
window.speechSynthesis.speak(utterance);
|
|
2624
2647
|
});
|
|
2625
2648
|
}
|
|
2626
2649
|
/** Stops all TTS */
|
|
2627
2650
|
stop() {
|
|
2651
|
+
if (this._currentUtterance) this._stoppedUtterances.add(this._currentUtterance);
|
|
2628
2652
|
window.speechSynthesis.cancel();
|
|
2629
2653
|
this._currentUtterance = null;
|
|
2630
2654
|
}
|
|
@@ -2642,21 +2666,23 @@ ${err.message || err.toString()}`);
|
|
|
2642
2666
|
*/
|
|
2643
2667
|
speakStream() {
|
|
2644
2668
|
let buffer = "";
|
|
2669
|
+
let streamPromise = Promise.resolve();
|
|
2645
2670
|
const sentenceRegex = /[^.!?\n]+[.!?\n]+/g;
|
|
2646
2671
|
return {
|
|
2647
2672
|
next: (text) => {
|
|
2648
2673
|
buffer += text;
|
|
2649
2674
|
const sentences = buffer.match(sentenceRegex);
|
|
2650
2675
|
if (sentences) {
|
|
2651
|
-
sentences.forEach((sentence) => this.speak(sentence.trim()));
|
|
2676
|
+
sentences.forEach((sentence) => streamPromise = this.speak(sentence.trim()));
|
|
2652
2677
|
buffer = buffer.replace(sentenceRegex, "");
|
|
2653
2678
|
}
|
|
2654
2679
|
},
|
|
2655
2680
|
done: async () => {
|
|
2656
2681
|
if (buffer.trim()) {
|
|
2657
|
-
|
|
2682
|
+
streamPromise = this.speak(buffer.trim());
|
|
2658
2683
|
buffer = "";
|
|
2659
2684
|
}
|
|
2685
|
+
await streamPromise;
|
|
2660
2686
|
}
|
|
2661
2687
|
};
|
|
2662
2688
|
}
|