@creativeorange/azure-text-to-speech 2.1.1 → 2.2.1
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/co-azure-tts.es.js +267 -0
- package/dist/co-azure-tts.umd.js +10 -8
- package/package.json +1 -1
- package/src/TextToSpeech.ts +333 -0
package/dist/co-azure-tts.es.js
CHANGED
|
@@ -8457,6 +8457,10 @@ class TextToSpeech {
|
|
|
8457
8457
|
__publicField(this, "wordBoundaryOffset", 0);
|
|
8458
8458
|
__publicField(this, "prevTextOffset", 0);
|
|
8459
8459
|
__publicField(this, "url", "");
|
|
8460
|
+
__publicField(this, "prefetchedAudio", /* @__PURE__ */ new Map());
|
|
8461
|
+
__publicField(this, "prefetchPromises", /* @__PURE__ */ new Map());
|
|
8462
|
+
__publicField(this, "activePrefetchedAudioUrl", "");
|
|
8463
|
+
__publicField(this, "playbackSegments", []);
|
|
8460
8464
|
this.key = key;
|
|
8461
8465
|
this.region = region;
|
|
8462
8466
|
this.voice = voice;
|
|
@@ -8469,14 +8473,17 @@ class TextToSpeech {
|
|
|
8469
8473
|
}
|
|
8470
8474
|
setVoice(voice) {
|
|
8471
8475
|
this.voice = voice;
|
|
8476
|
+
this.clearPrefetchedAudio();
|
|
8472
8477
|
return this;
|
|
8473
8478
|
}
|
|
8474
8479
|
setRate(rate) {
|
|
8475
8480
|
this.rate = rate;
|
|
8481
|
+
this.clearPrefetchedAudio();
|
|
8476
8482
|
return this;
|
|
8477
8483
|
}
|
|
8478
8484
|
setPitch(pitch) {
|
|
8479
8485
|
this.pitch = pitch;
|
|
8486
|
+
this.clearPrefetchedAudio();
|
|
8480
8487
|
return this;
|
|
8481
8488
|
}
|
|
8482
8489
|
async registerBindings(node) {
|
|
@@ -8624,9 +8631,15 @@ class TextToSpeech {
|
|
|
8624
8631
|
this.originalHighlightDivInnerHTML = "";
|
|
8625
8632
|
this.wordBoundryList = [];
|
|
8626
8633
|
this.wordEncounters = [];
|
|
8634
|
+
this.resetPlaybackSegments();
|
|
8635
|
+
this.playbackSegments = [];
|
|
8627
8636
|
if (this.player !== void 0) {
|
|
8628
8637
|
this.player.pause();
|
|
8629
8638
|
}
|
|
8639
|
+
if (this.activePrefetchedAudioUrl !== "") {
|
|
8640
|
+
URL.revokeObjectURL(this.activePrefetchedAudioUrl);
|
|
8641
|
+
this.activePrefetchedAudioUrl = "";
|
|
8642
|
+
}
|
|
8630
8643
|
this.player = void 0;
|
|
8631
8644
|
this.highlightDiv = void 0;
|
|
8632
8645
|
this.prevTextOffset = 0;
|
|
@@ -8641,10 +8654,25 @@ class TextToSpeech {
|
|
|
8641
8654
|
this.synthesizer.wordBoundary = (s, e) => {
|
|
8642
8655
|
this.wordBoundryList.push(e);
|
|
8643
8656
|
};
|
|
8657
|
+
const playbackChain = this.collectPlaybackChain(this.clickedNode);
|
|
8658
|
+
const isChainedPlayback = playbackChain.length > 1;
|
|
8659
|
+
if (isChainedPlayback) {
|
|
8660
|
+
this.preparePlaybackChain(playbackChain);
|
|
8661
|
+
} else {
|
|
8662
|
+
this.playbackSegments = [];
|
|
8663
|
+
}
|
|
8644
8664
|
this.player.onAudioEnd = async () => {
|
|
8665
|
+
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
8645
8666
|
this.stopPlayer();
|
|
8667
|
+
if (wasChainedPlayback) {
|
|
8668
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8669
|
+
return;
|
|
8670
|
+
}
|
|
8646
8671
|
if (this.clickedNode.hasAttribute("co-tts.next")) {
|
|
8647
8672
|
const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
|
|
8673
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
8674
|
+
return;
|
|
8675
|
+
}
|
|
8648
8676
|
if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
|
|
8649
8677
|
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
|
|
8650
8678
|
} else if (nextNode) {
|
|
@@ -8657,6 +8685,9 @@ class TextToSpeech {
|
|
|
8657
8685
|
this.player.onAudioStart = async () => {
|
|
8658
8686
|
document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
|
|
8659
8687
|
};
|
|
8688
|
+
if (!isChainedPlayback) {
|
|
8689
|
+
this.prefetchNextNode(this.clickedNode);
|
|
8690
|
+
}
|
|
8660
8691
|
this.synthesizer.speakSsmlAsync(
|
|
8661
8692
|
this.buildSSML(this.textToRead),
|
|
8662
8693
|
() => {
|
|
@@ -8669,6 +8700,238 @@ class TextToSpeech {
|
|
|
8669
8700
|
}
|
|
8670
8701
|
);
|
|
8671
8702
|
}
|
|
8703
|
+
collectPlaybackChain(node) {
|
|
8704
|
+
var _a, _b;
|
|
8705
|
+
const chain = [];
|
|
8706
|
+
const seenNodeIds = /* @__PURE__ */ new Set();
|
|
8707
|
+
let currentNode = node;
|
|
8708
|
+
let offset = 0;
|
|
8709
|
+
while (currentNode && !seenNodeIds.has(currentNode.id)) {
|
|
8710
|
+
seenNodeIds.add(currentNode.id);
|
|
8711
|
+
const attr = (_a = currentNode.attributes.getNamedItem("co-tts.text")) != null ? _a : currentNode.attributes.getNamedItem("co-tts");
|
|
8712
|
+
const text = this.getNodeText(currentNode, attr);
|
|
8713
|
+
if (text === "") {
|
|
8714
|
+
break;
|
|
8715
|
+
}
|
|
8716
|
+
const highlightDiv = this.getHighlightDivForNode(currentNode);
|
|
8717
|
+
chain.push({
|
|
8718
|
+
node: currentNode,
|
|
8719
|
+
text,
|
|
8720
|
+
start: offset,
|
|
8721
|
+
end: offset + text.length,
|
|
8722
|
+
highlightDiv,
|
|
8723
|
+
originalHighlightDivInnerHTML: (_b = highlightDiv == null ? void 0 : highlightDiv.innerHTML) != null ? _b : ""
|
|
8724
|
+
});
|
|
8725
|
+
offset += text.length + 2;
|
|
8726
|
+
if (!currentNode.hasAttribute("co-tts.next")) {
|
|
8727
|
+
break;
|
|
8728
|
+
}
|
|
8729
|
+
currentNode = document.getElementById(currentNode.getAttribute("co-tts.next"));
|
|
8730
|
+
}
|
|
8731
|
+
return chain;
|
|
8732
|
+
}
|
|
8733
|
+
preparePlaybackChain(chain) {
|
|
8734
|
+
this.playbackSegments = chain;
|
|
8735
|
+
this.textToRead = chain.map((segment) => segment.text).join(". ");
|
|
8736
|
+
this.highlightDiv = void 0;
|
|
8737
|
+
this.originalHighlightDivInnerHTML = "";
|
|
8738
|
+
this.wordEncounters = [];
|
|
8739
|
+
this.previousWordBoundary = void 0;
|
|
8740
|
+
this.prevTextOffset = 0;
|
|
8741
|
+
this.currentWord = "";
|
|
8742
|
+
this.currentOffset = 0;
|
|
8743
|
+
this.wordBoundaryOffset = 0;
|
|
8744
|
+
}
|
|
8745
|
+
getHighlightDivForNode(node) {
|
|
8746
|
+
var _a;
|
|
8747
|
+
if (!node.hasAttribute("co-tts.highlight")) {
|
|
8748
|
+
return void 0;
|
|
8749
|
+
}
|
|
8750
|
+
if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
|
|
8751
|
+
return document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
|
|
8752
|
+
}
|
|
8753
|
+
return node;
|
|
8754
|
+
}
|
|
8755
|
+
resetPlaybackSegments() {
|
|
8756
|
+
this.playbackSegments.forEach((segment) => {
|
|
8757
|
+
if (segment.highlightDiv) {
|
|
8758
|
+
segment.highlightDiv.innerHTML = segment.originalHighlightDivInnerHTML;
|
|
8759
|
+
}
|
|
8760
|
+
});
|
|
8761
|
+
}
|
|
8762
|
+
updateChainedHighlight(wordBoundary) {
|
|
8763
|
+
var _a;
|
|
8764
|
+
if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
|
|
8765
|
+
wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
|
|
8766
|
+
}
|
|
8767
|
+
if (!wordBoundary) {
|
|
8768
|
+
this.resetPlaybackSegments();
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
const segment = this.playbackSegments.find((candidate) => wordBoundary.textOffset >= candidate.start && wordBoundary.textOffset < candidate.end);
|
|
8772
|
+
this.resetPlaybackSegments();
|
|
8773
|
+
if (!(segment == null ? void 0 : segment.highlightDiv)) {
|
|
8774
|
+
this.previousWordBoundary = wordBoundary;
|
|
8775
|
+
return;
|
|
8776
|
+
}
|
|
8777
|
+
const relativeTextOffset = wordBoundary.textOffset - segment.start;
|
|
8778
|
+
const currentOffset = this.getPosition(segment.originalHighlightDivInnerHTML, wordBoundary.text, relativeTextOffset);
|
|
8779
|
+
if (currentOffset === Number.MAX_SAFE_INTEGER) {
|
|
8780
|
+
this.previousWordBoundary = wordBoundary;
|
|
8781
|
+
return;
|
|
8782
|
+
}
|
|
8783
|
+
const startOfString = segment.originalHighlightDivInnerHTML.substring(0, currentOffset);
|
|
8784
|
+
const endOffset = currentOffset + wordBoundary.wordLength;
|
|
8785
|
+
const endOfString = segment.originalHighlightDivInnerHTML.substring(endOffset);
|
|
8786
|
+
segment.highlightDiv.innerHTML = `
|
|
8787
|
+
${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
|
|
8788
|
+
`;
|
|
8789
|
+
this.previousWordBoundary = wordBoundary;
|
|
8790
|
+
}
|
|
8791
|
+
getNodeText(node, attr) {
|
|
8792
|
+
var _a;
|
|
8793
|
+
if (attr && attr.value !== "") {
|
|
8794
|
+
return attr.value;
|
|
8795
|
+
}
|
|
8796
|
+
if (node.hasAttribute("co-tts.text") && node.getAttribute("co-tts.text") !== "") {
|
|
8797
|
+
return (_a = node.getAttribute("co-tts.text")) != null ? _a : "";
|
|
8798
|
+
}
|
|
8799
|
+
return node.innerText;
|
|
8800
|
+
}
|
|
8801
|
+
getPrefetchKey(node, text) {
|
|
8802
|
+
return [
|
|
8803
|
+
node.id,
|
|
8804
|
+
text,
|
|
8805
|
+
this.voice,
|
|
8806
|
+
this.rate,
|
|
8807
|
+
this.pitch,
|
|
8808
|
+
this.url
|
|
8809
|
+
].join("|");
|
|
8810
|
+
}
|
|
8811
|
+
clearPrefetchedAudio() {
|
|
8812
|
+
this.prefetchedAudio.forEach((prefetch) => {
|
|
8813
|
+
if (prefetch.url) {
|
|
8814
|
+
URL.revokeObjectURL(prefetch.url);
|
|
8815
|
+
}
|
|
8816
|
+
});
|
|
8817
|
+
this.prefetchedAudio.clear();
|
|
8818
|
+
this.prefetchPromises.clear();
|
|
8819
|
+
}
|
|
8820
|
+
async prefetchNextNode(node) {
|
|
8821
|
+
var _a;
|
|
8822
|
+
if (!node || !node.hasAttribute("co-tts.next")) {
|
|
8823
|
+
return;
|
|
8824
|
+
}
|
|
8825
|
+
const nextNode = document.getElementById(node.getAttribute("co-tts.next"));
|
|
8826
|
+
if (!nextNode) {
|
|
8827
|
+
return;
|
|
8828
|
+
}
|
|
8829
|
+
const attr = (_a = nextNode.attributes.getNamedItem("co-tts.text")) != null ? _a : nextNode.attributes.getNamedItem("co-tts");
|
|
8830
|
+
const text = this.getNodeText(nextNode, attr);
|
|
8831
|
+
if (text === "") {
|
|
8832
|
+
return;
|
|
8833
|
+
}
|
|
8834
|
+
const key = this.getPrefetchKey(nextNode, text);
|
|
8835
|
+
if (this.prefetchedAudio.has(key) || this.prefetchPromises.has(key)) {
|
|
8836
|
+
return;
|
|
8837
|
+
}
|
|
8838
|
+
const speechConfig = SpeechConfig.fromSubscription(this.key, this.region);
|
|
8839
|
+
speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
|
|
8840
|
+
speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
|
|
8841
|
+
const synthesizer = new SpeechSynthesizer(speechConfig, null);
|
|
8842
|
+
const wordBoundryList = [];
|
|
8843
|
+
synthesizer.wordBoundary = (s, e) => {
|
|
8844
|
+
wordBoundryList.push(e);
|
|
8845
|
+
};
|
|
8846
|
+
const prefetchPromise = new Promise((resolve) => {
|
|
8847
|
+
synthesizer.speakSsmlAsync(
|
|
8848
|
+
this.buildSSML(text),
|
|
8849
|
+
(result) => {
|
|
8850
|
+
synthesizer.close();
|
|
8851
|
+
if (!(result == null ? void 0 : result.audioData)) {
|
|
8852
|
+
resolve(null);
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8855
|
+
const blob = new Blob([result.audioData], { type: "audio/mpeg" });
|
|
8856
|
+
const url = URL.createObjectURL(blob);
|
|
8857
|
+
const prefetch = {
|
|
8858
|
+
key,
|
|
8859
|
+
nodeId: nextNode.id,
|
|
8860
|
+
text,
|
|
8861
|
+
url,
|
|
8862
|
+
wordBoundryList
|
|
8863
|
+
};
|
|
8864
|
+
this.prefetchedAudio.set(key, prefetch);
|
|
8865
|
+
resolve(prefetch);
|
|
8866
|
+
},
|
|
8867
|
+
() => {
|
|
8868
|
+
synthesizer.close();
|
|
8869
|
+
resolve(null);
|
|
8870
|
+
}
|
|
8871
|
+
);
|
|
8872
|
+
}).finally(() => {
|
|
8873
|
+
this.prefetchPromises.delete(key);
|
|
8874
|
+
});
|
|
8875
|
+
this.prefetchPromises.set(key, prefetchPromise);
|
|
8876
|
+
}
|
|
8877
|
+
async playPrefetchedNode(node) {
|
|
8878
|
+
var _a, _b, _c;
|
|
8879
|
+
const attr = (_a = node.attributes.getNamedItem("co-tts.text")) != null ? _a : node.attributes.getNamedItem("co-tts");
|
|
8880
|
+
const text = this.getNodeText(node, attr);
|
|
8881
|
+
const key = this.getPrefetchKey(node, text);
|
|
8882
|
+
const prefetch = (_b = this.prefetchedAudio.get(key)) != null ? _b : await this.prefetchPromises.get(key);
|
|
8883
|
+
if (!prefetch) {
|
|
8884
|
+
return false;
|
|
8885
|
+
}
|
|
8886
|
+
this.prefetchedAudio.delete(key);
|
|
8887
|
+
this.clickedNode = node;
|
|
8888
|
+
this.textToRead = prefetch.text;
|
|
8889
|
+
this.wordBoundryList = prefetch.wordBoundryList;
|
|
8890
|
+
this.wordEncounters = [];
|
|
8891
|
+
this.previousWordBoundary = void 0;
|
|
8892
|
+
this.prevTextOffset = 0;
|
|
8893
|
+
this.currentWord = "";
|
|
8894
|
+
this.currentOffset = 0;
|
|
8895
|
+
this.wordBoundaryOffset = 0;
|
|
8896
|
+
if (node.hasAttribute("co-tts.highlight")) {
|
|
8897
|
+
if (((_c = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _c.value) !== "") {
|
|
8898
|
+
const newReferenceDiv = document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
|
|
8899
|
+
this.highlightDiv = newReferenceDiv;
|
|
8900
|
+
if (newReferenceDiv !== null) {
|
|
8901
|
+
this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
|
|
8902
|
+
}
|
|
8903
|
+
} else {
|
|
8904
|
+
this.highlightDiv = node;
|
|
8905
|
+
this.originalHighlightDivInnerHTML = node.innerHTML;
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
await this.createInterval();
|
|
8909
|
+
const audio = new Audio(prefetch.url);
|
|
8910
|
+
this.activePrefetchedAudioUrl = prefetch.url;
|
|
8911
|
+
this.player = audio;
|
|
8912
|
+
audio.addEventListener("play", () => {
|
|
8913
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
|
|
8914
|
+
}, { once: true });
|
|
8915
|
+
audio.addEventListener("ended", async () => {
|
|
8916
|
+
this.stopPlayer();
|
|
8917
|
+
if (this.clickedNode.hasAttribute("co-tts.next")) {
|
|
8918
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
|
|
8919
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
8920
|
+
return;
|
|
8921
|
+
}
|
|
8922
|
+
if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
|
|
8923
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
|
|
8924
|
+
} else if (nextNode) {
|
|
8925
|
+
nextNode.dispatchEvent(new Event("click"));
|
|
8926
|
+
}
|
|
8927
|
+
} else {
|
|
8928
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8929
|
+
}
|
|
8930
|
+
}, { once: true });
|
|
8931
|
+
this.prefetchNextNode(node);
|
|
8932
|
+
await audio.play();
|
|
8933
|
+
return true;
|
|
8934
|
+
}
|
|
8672
8935
|
async clearInterval() {
|
|
8673
8936
|
clearInterval(this.interval);
|
|
8674
8937
|
}
|
|
@@ -8686,6 +8949,10 @@ class TextToSpeech {
|
|
|
8686
8949
|
}
|
|
8687
8950
|
}
|
|
8688
8951
|
if (wordBoundary !== void 0) {
|
|
8952
|
+
if (this.playbackSegments.length > 0) {
|
|
8953
|
+
this.updateChainedHighlight(wordBoundary);
|
|
8954
|
+
return;
|
|
8955
|
+
}
|
|
8689
8956
|
if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
|
|
8690
8957
|
wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
|
|
8691
8958
|
}
|