@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/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "@creativeorange/azure-text-to-speech",
3
- "version": "1.0.0",
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
- "serve-vite": "vite",
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
- "@originjs/vite-plugin-commonjs": "^1.0.1"
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 { SpeakerAudioDestination, AudioConfig, OutputFormat, Recognizer, SpeechConfig, SpeechRecognizer, SpeechSynthesizer } from 'microsoft-cognitiveservices-speech-sdk';
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 = 'male') {
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
- let nodes = node.childNodes;
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
- let currentNode = nodes[i];
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
- let referenceDiv = document.getElementById(attr.value);
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.innerText;
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
- let response = await fetch(attr.value, {
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.innerText;
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.textToRead;
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 (node: any, attr: Attr) {
195
+ async startSynthesizer(node: any, attr: Attr) {
183
196
  this.speechConfig = SpeechConfig.fromSubscription(this.key, this.region);
184
197
 
185
- if (this.voice === 'female') {
186
- this.speechConfig.speechSynthesisVoiceName = 'Microsoft Server Speech Text to Speech Voice (nl-NL, ColetteNeural)';
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
- let nextNode = document.getElementById(this.clickedNode.getAttribute('co-tts.next'));
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
@@ -0,0 +1,9 @@
1
+ <speak version='1.0' xml:lang='nl-NL'>
2
+ <voice xml:lang='nl-NL' name='nl-NL-ColetteNeural'>
3
+ Tijdens deze opdracht
4
+ <lexeme>
5
+ <grapheme>Scotland MV</grapheme>
6
+ <alias>Scotland Media Wave</alias>
7
+ </lexeme>
8
+ </voice>
9
+ </speak>
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',