@creativeorange/azure-text-to-speech 1.0.0 → 1.1.0
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/.eslintrc.js +29 -0
- package/dist/co-azure-tts.es.js +104 -42
- package/dist/co-azure-tts.umd.js +5 -5
- package/package.json +9 -7
- package/src/main.ts +130 -56
- package/test.xml +9 -0
- package/vite.config.ts +6 -0
package/package.json
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
{
|
2
2
|
"name": "@creativeorange/azure-text-to-speech",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.0",
|
4
4
|
"main": "dist/co-azure-tts.umd.js",
|
5
5
|
"browser": "dist/co-azure-tts.es.js",
|
6
6
|
"scripts": {
|
7
|
-
"
|
8
|
-
"build-vite": "vite build --watch",
|
9
|
-
"preview-vite": "vite preview"
|
7
|
+
"build": "vite build --watch"
|
10
8
|
},
|
11
9
|
"license": "MIT",
|
12
10
|
"author": "Edsardio",
|
13
11
|
"devDependencies": {
|
12
|
+
"@originjs/vite-plugin-commonjs": "^1.0.1",
|
13
|
+
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
14
14
|
"build-esm": "^4.2.2",
|
15
|
+
"eslint": "^8.21.0",
|
16
|
+
"eslint-config-google": "^0.14.0",
|
15
17
|
"microsoft-cognitiveservices-speech-sdk": "^1.22.0",
|
16
18
|
"typescript": "^4.7.4",
|
17
|
-
"vite-plugin-env-compatible": "^1.1.1",
|
18
19
|
"vite": "^2.7.2",
|
19
|
-
"
|
20
|
+
"vite-plugin-env-compatible": "^1.1.1",
|
21
|
+
"vite-plugin-eslint": "^1.7.0"
|
20
22
|
}
|
21
|
-
}
|
23
|
+
}
|
package/src/main.ts
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
SpeakerAudioDestination,
|
3
|
+
AudioConfig,
|
4
|
+
SpeechConfig,
|
5
|
+
SpeechSynthesizer,
|
6
|
+
SpeechSynthesisOutputFormat,
|
7
|
+
} from 'microsoft-cognitiveservices-speech-sdk';
|
2
8
|
|
3
9
|
export default class TextToSpeech {
|
4
|
-
|
5
10
|
key: string;
|
6
11
|
region: string;
|
7
12
|
voice: string;
|
@@ -20,55 +25,32 @@ export default class TextToSpeech {
|
|
20
25
|
|
21
26
|
previousWordBoundary: any;
|
22
27
|
|
28
|
+
interval: any;
|
29
|
+
|
30
|
+
wordEncounters: number[] = [];
|
31
|
+
originalHighlightDivInnerHTML: string = '';
|
32
|
+
currentWord: string = '';
|
33
|
+
currentOffset: number = 0;
|
34
|
+
|
23
35
|
|
24
|
-
constructor(key: string, region: string, voice: string
|
36
|
+
constructor(key: string, region: string, voice: string) {
|
25
37
|
this.key = key;
|
26
38
|
this.region = region;
|
27
39
|
this.voice = voice;
|
28
40
|
}
|
29
41
|
|
30
42
|
async start() {
|
31
|
-
setInterval(() => {
|
32
|
-
if (this.player !== undefined && this.highlightDiv) {
|
33
|
-
const currentTime = this.player.currentTime;
|
34
|
-
let wordBoundary;
|
35
|
-
for (const e of this.wordBoundryList) {
|
36
|
-
if (currentTime * 1000 > e.audioOffset / 10000) {
|
37
|
-
wordBoundary = e;
|
38
|
-
} else {
|
39
|
-
break;
|
40
|
-
}
|
41
|
-
}
|
42
|
-
if (~['.', ',', '!', '?'].indexOf(wordBoundary.text)) {
|
43
|
-
wordBoundary = this.previousWordBoundary ?? undefined;
|
44
|
-
}
|
45
|
-
|
46
|
-
if (wordBoundary !== undefined) {
|
47
|
-
this.previousWordBoundary = wordBoundary;
|
48
|
-
this.highlightDiv.innerHTML = this.textToRead.substr(0, wordBoundary.textOffset) +
|
49
|
-
"<span class='co-tts-highlight'>" + wordBoundary.text + "</span>" +
|
50
|
-
this.textToRead.substr(wordBoundary.textOffset + wordBoundary.wordLength);
|
51
|
-
} else {
|
52
|
-
this.highlightDiv.innerHTML = this.textToRead;
|
53
|
-
}
|
54
|
-
}
|
55
|
-
}, 50);
|
56
|
-
|
57
43
|
await this.registerBindings(document);
|
58
44
|
}
|
59
45
|
|
60
|
-
async synthesis() {
|
61
|
-
|
62
|
-
}
|
63
|
-
|
64
46
|
async registerBindings(node: any) {
|
65
|
-
|
47
|
+
const nodes = node.childNodes;
|
66
48
|
for (let i = 0; i < nodes.length; i++) {
|
67
49
|
if (!nodes[i]) {
|
68
50
|
continue;
|
69
51
|
}
|
70
52
|
|
71
|
-
|
53
|
+
const currentNode = nodes[i];
|
72
54
|
|
73
55
|
if (currentNode.attributes) {
|
74
56
|
if (currentNode.attributes.getNamedItem('co-tts.id')) {
|
@@ -95,8 +77,9 @@ export default class TextToSpeech {
|
|
95
77
|
async handleIdModifier(node: any, attr: Attr) {
|
96
78
|
node.addEventListener('click', async (_: any) => {
|
97
79
|
this.stopPlayer();
|
80
|
+
await this.createInterval();
|
98
81
|
this.clickedNode = node;
|
99
|
-
|
82
|
+
const referenceDiv = document.getElementById(attr.value);
|
100
83
|
|
101
84
|
if (!referenceDiv) {
|
102
85
|
return;
|
@@ -105,11 +88,12 @@ export default class TextToSpeech {
|
|
105
88
|
if (referenceDiv.hasAttribute('co-tts.text') && referenceDiv.getAttribute('co-tts.text') !== '') {
|
106
89
|
this.textToRead = referenceDiv.getAttribute('co-tts.text') ?? '';
|
107
90
|
} else {
|
108
|
-
this.textToRead = referenceDiv.
|
91
|
+
this.textToRead = referenceDiv.innerHTML;
|
109
92
|
}
|
110
93
|
|
111
94
|
if (referenceDiv.hasAttribute('co-tts.highlight')) {
|
112
95
|
this.highlightDiv = referenceDiv;
|
96
|
+
this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
|
113
97
|
}
|
114
98
|
|
115
99
|
this.startSynthesizer(node, attr);
|
@@ -119,26 +103,29 @@ export default class TextToSpeech {
|
|
119
103
|
async handleAjaxModifier(node: any, attr: Attr) {
|
120
104
|
node.addEventListener('click', async (_: any) => {
|
121
105
|
this.stopPlayer();
|
106
|
+
await this.createInterval();
|
122
107
|
this.clickedNode = node;
|
123
|
-
|
108
|
+
const response = await fetch(attr.value, {
|
124
109
|
method: `GET`,
|
125
110
|
});
|
126
111
|
|
127
112
|
this.textToRead = await response.text();
|
128
113
|
|
129
114
|
this.startSynthesizer(node, attr);
|
130
|
-
})
|
115
|
+
});
|
131
116
|
}
|
132
117
|
|
133
118
|
async handleDefault(node: any, attr: Attr) {
|
134
119
|
node.addEventListener('click', async (_: any) => {
|
135
120
|
this.stopPlayer();
|
121
|
+
await this.createInterval();
|
136
122
|
this.clickedNode = node;
|
137
123
|
if (node.hasAttribute('co-tts.highlight')) {
|
138
124
|
this.highlightDiv = node;
|
125
|
+
this.originalHighlightDivInnerHTML = node.innerHTML;
|
139
126
|
}
|
140
|
-
if (attr.value ===
|
141
|
-
this.textToRead = node.
|
127
|
+
if (attr.value === '') {
|
128
|
+
this.textToRead = node.innerHTML;
|
142
129
|
} else {
|
143
130
|
this.textToRead = attr.value;
|
144
131
|
}
|
@@ -147,31 +134,57 @@ export default class TextToSpeech {
|
|
147
134
|
});
|
148
135
|
}
|
149
136
|
|
137
|
+
async handleWithoutClick(node: any, attr: Attr) {
|
138
|
+
this.stopPlayer();
|
139
|
+
await this.createInterval();
|
140
|
+
this.clickedNode = node;
|
141
|
+
if (node.hasAttribute('co-tts.highlight')) {
|
142
|
+
this.highlightDiv = node;
|
143
|
+
this.originalHighlightDivInnerHTML = node.innerHTML;
|
144
|
+
}
|
145
|
+
if (attr.value === '') {
|
146
|
+
this.textToRead = node.innerHTML;
|
147
|
+
} else {
|
148
|
+
this.textToRead = attr.value;
|
149
|
+
}
|
150
|
+
|
151
|
+
this.startSynthesizer(node, attr);
|
152
|
+
}
|
153
|
+
|
150
154
|
async handleStopModifier(node: any, attr: Attr) {
|
151
155
|
node.addEventListener('click', async (_: any) => {
|
152
156
|
await this.stopPlayer();
|
157
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSStoppedPlaying', {}));
|
153
158
|
});
|
154
159
|
}
|
155
160
|
|
156
161
|
async handlePauseModifier(node: any, attr: Attr) {
|
157
162
|
node.addEventListener('click', async (_: any) => {
|
163
|
+
await this.clearInterval();
|
158
164
|
await this.player.pause();
|
165
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSPausedPlaying', {}));
|
159
166
|
});
|
160
167
|
}
|
161
168
|
|
162
169
|
async handleResumeModifier(node: any, attr: Attr) {
|
163
170
|
node.addEventListener('click', async (_: any) => {
|
171
|
+
await this.createInterval();
|
164
172
|
await this.player.resume();
|
173
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSResumedPlaying', {}));
|
165
174
|
});
|
166
175
|
}
|
167
176
|
|
168
177
|
async stopPlayer() {
|
178
|
+
await this.clearInterval();
|
169
179
|
if (this.highlightDiv !== undefined) {
|
170
|
-
this.highlightDiv.innerHTML = this.
|
180
|
+
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
|
171
181
|
}
|
172
|
-
|
182
|
+
|
173
183
|
this.textToRead = '';
|
184
|
+
this.currentWord = '';
|
185
|
+
this.originalHighlightDivInnerHTML = '';
|
174
186
|
this.wordBoundryList = [];
|
187
|
+
this.wordEncounters = [];
|
175
188
|
if (this.player !== undefined) {
|
176
189
|
this.player.pause();
|
177
190
|
}
|
@@ -179,15 +192,11 @@ export default class TextToSpeech {
|
|
179
192
|
this.highlightDiv = undefined;
|
180
193
|
}
|
181
194
|
|
182
|
-
async startSynthesizer
|
195
|
+
async startSynthesizer(node: any, attr: Attr) {
|
183
196
|
this.speechConfig = SpeechConfig.fromSubscription(this.key, this.region);
|
184
197
|
|
185
|
-
|
186
|
-
|
187
|
-
} else {
|
188
|
-
this.speechConfig.speechSynthesisVoiceName = 'Microsoft Server Speech Text to Speech Voice (nl-NL, MaartenNeural)';
|
189
|
-
}
|
190
|
-
this.speechConfig.speechSynthesisOutputFormat = 8;
|
198
|
+
this.speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
|
199
|
+
this.speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
|
191
200
|
|
192
201
|
this.player = new SpeakerAudioDestination();
|
193
202
|
|
@@ -198,17 +207,25 @@ export default class TextToSpeech {
|
|
198
207
|
this.wordBoundryList.push(e);
|
199
208
|
};
|
200
209
|
|
201
|
-
this.player.onAudioEnd = () => {
|
210
|
+
this.player.onAudioEnd = async () => {
|
202
211
|
this.stopPlayer();
|
203
|
-
|
212
|
+
|
204
213
|
if (this.clickedNode.hasAttribute('co-tts.next')) {
|
205
|
-
|
206
|
-
if (nextNode) {
|
214
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute('co-tts.next'));
|
215
|
+
if (nextNode && nextNode.attributes.getNamedItem('co-tts.text')) {
|
216
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem('co-tts.text'));
|
217
|
+
} else if (nextNode) {
|
207
218
|
nextNode.dispatchEvent(new Event('click'));
|
208
219
|
}
|
220
|
+
} else {
|
221
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
209
222
|
}
|
210
223
|
};
|
211
|
-
|
224
|
+
|
225
|
+
this.player.onAudioStart = async () => {
|
226
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSStartedPlaying', {}));
|
227
|
+
};
|
228
|
+
|
212
229
|
this.synthesizer.speakTextAsync(this.textToRead,
|
213
230
|
() => {
|
214
231
|
this.synthesizer.close();
|
@@ -220,4 +237,61 @@ export default class TextToSpeech {
|
|
220
237
|
});
|
221
238
|
}
|
222
239
|
|
223
|
-
|
240
|
+
async clearInterval() {
|
241
|
+
clearInterval(this.interval);
|
242
|
+
}
|
243
|
+
|
244
|
+
async createInterval() {
|
245
|
+
this.interval = setInterval(() => {
|
246
|
+
if (this.player !== undefined && this.highlightDiv) {
|
247
|
+
const currentTime = this.player.currentTime;
|
248
|
+
let wordBoundary;
|
249
|
+
for (const e of this.wordBoundryList) {
|
250
|
+
if (currentTime * 1000 > e.audioOffset / 10000) {
|
251
|
+
wordBoundary = e;
|
252
|
+
} else {
|
253
|
+
break;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
if (wordBoundary !== undefined) {
|
258
|
+
if (~['.', ',', '!', '?'].indexOf(wordBoundary.text)) {
|
259
|
+
wordBoundary = this.previousWordBoundary ?? undefined;
|
260
|
+
}
|
261
|
+
|
262
|
+
if (wordBoundary === undefined) {
|
263
|
+
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
|
264
|
+
} else {
|
265
|
+
if (!this.wordEncounters[wordBoundary.text]) {
|
266
|
+
this.wordEncounters[wordBoundary.text] = 0;
|
267
|
+
}
|
268
|
+
|
269
|
+
if (this.currentWord !== wordBoundary.text) {
|
270
|
+
this.wordEncounters[wordBoundary.text]++;
|
271
|
+
console.log(this.wordEncounters);
|
272
|
+
this.currentOffset = this.getPosition(
|
273
|
+
this.originalHighlightDivInnerHTML,
|
274
|
+
wordBoundary.text,
|
275
|
+
this.wordEncounters[wordBoundary.text]
|
276
|
+
);
|
277
|
+
this.currentWord = wordBoundary.text;
|
278
|
+
}
|
279
|
+
|
280
|
+
this.previousWordBoundary = wordBoundary;
|
281
|
+
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML.substring(0, this.currentOffset) +
|
282
|
+
'<mark class=\'co-tts-highlight\'>' + wordBoundary.text + '</mark>' +
|
283
|
+
this.originalHighlightDivInnerHTML.substring(this.currentOffset + wordBoundary.wordLength);
|
284
|
+
}
|
285
|
+
} else {
|
286
|
+
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}, 50);
|
290
|
+
}
|
291
|
+
|
292
|
+
getPosition(string: string, subString: string, index: number) {
|
293
|
+
const regex = new RegExp(`\\b${subString}\\b`, 'g');
|
294
|
+
console.log(string.split(regex, index).join(subString), regex, index);
|
295
|
+
return string.split(regex, index).join(subString).length;
|
296
|
+
}
|
297
|
+
}
|
package/test.xml
ADDED
package/vite.config.ts
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
import { defineConfig } from 'vite';
|
2
2
|
import path from 'path';
|
3
|
+
import eslint from 'vite-plugin-eslint';
|
3
4
|
|
4
5
|
// https://vitejs.dev/config/
|
5
6
|
export default defineConfig({
|
7
|
+
plugins: [
|
8
|
+
eslint({
|
9
|
+
fix: true
|
10
|
+
})
|
11
|
+
],
|
6
12
|
build: {
|
7
13
|
lib: {
|
8
14
|
entry: './src/main.ts',
|