@creativeorange/azure-text-to-speech 2.2.0 → 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 +110 -1
- package/dist/co-azure-tts.umd.js +8 -6
- package/package.json +1 -1
- package/src/TextToSpeech.ts +142 -1
package/package.json
CHANGED
package/src/TextToSpeech.ts
CHANGED
|
@@ -39,6 +39,7 @@ export class TextToSpeech {
|
|
|
39
39
|
prefetchedAudio: Map<string, any> = new Map();
|
|
40
40
|
prefetchPromises: Map<string, Promise<any>> = new Map();
|
|
41
41
|
activePrefetchedAudioUrl: string = '';
|
|
42
|
+
playbackSegments: any[] = [];
|
|
42
43
|
|
|
43
44
|
constructor(key: string, region: string, voice: string, rate: number = 0, pitch: number = 0, url: string = '') {
|
|
44
45
|
this.key = key;
|
|
@@ -240,6 +241,8 @@ export class TextToSpeech {
|
|
|
240
241
|
this.originalHighlightDivInnerHTML = '';
|
|
241
242
|
this.wordBoundryList = [];
|
|
242
243
|
this.wordEncounters = [];
|
|
244
|
+
this.resetPlaybackSegments();
|
|
245
|
+
this.playbackSegments = [];
|
|
243
246
|
if (this.player !== undefined) {
|
|
244
247
|
this.player.pause();
|
|
245
248
|
}
|
|
@@ -267,9 +270,25 @@ export class TextToSpeech {
|
|
|
267
270
|
this.wordBoundryList.push(e);
|
|
268
271
|
};
|
|
269
272
|
|
|
273
|
+
const playbackChain = this.collectPlaybackChain(this.clickedNode);
|
|
274
|
+
const isChainedPlayback = playbackChain.length > 1;
|
|
275
|
+
|
|
276
|
+
if (isChainedPlayback) {
|
|
277
|
+
this.preparePlaybackChain(playbackChain);
|
|
278
|
+
} else {
|
|
279
|
+
this.playbackSegments = [];
|
|
280
|
+
}
|
|
281
|
+
|
|
270
282
|
this.player.onAudioEnd = async () => {
|
|
283
|
+
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
271
284
|
this.stopPlayer();
|
|
272
285
|
|
|
286
|
+
if (wasChainedPlayback) {
|
|
287
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
|
288
|
+
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
273
292
|
if (this.clickedNode.hasAttribute('co-tts.next')) {
|
|
274
293
|
const nextNode = document.getElementById(this.clickedNode.getAttribute('co-tts.next'));
|
|
275
294
|
|
|
@@ -291,7 +310,9 @@ export class TextToSpeech {
|
|
|
291
310
|
document.dispatchEvent(new CustomEvent('COAzureTTSStartedPlaying', {}));
|
|
292
311
|
};
|
|
293
312
|
|
|
294
|
-
|
|
313
|
+
if (!isChainedPlayback) {
|
|
314
|
+
this.prefetchNextNode(this.clickedNode);
|
|
315
|
+
}
|
|
295
316
|
|
|
296
317
|
this.synthesizer.speakSsmlAsync(this.buildSSML(this.textToRead),
|
|
297
318
|
() => {
|
|
@@ -304,6 +325,120 @@ export class TextToSpeech {
|
|
|
304
325
|
});
|
|
305
326
|
}
|
|
306
327
|
|
|
328
|
+
collectPlaybackChain(node: any) {
|
|
329
|
+
const chain = [];
|
|
330
|
+
const seenNodeIds = new Set<string>();
|
|
331
|
+
let currentNode = node;
|
|
332
|
+
let offset = 0;
|
|
333
|
+
|
|
334
|
+
while (currentNode && !seenNodeIds.has(currentNode.id)) {
|
|
335
|
+
seenNodeIds.add(currentNode.id);
|
|
336
|
+
|
|
337
|
+
const attr = currentNode.attributes.getNamedItem('co-tts.text') ?? currentNode.attributes.getNamedItem('co-tts');
|
|
338
|
+
const text = this.getNodeText(currentNode, attr);
|
|
339
|
+
|
|
340
|
+
if (text === '') {
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const highlightDiv = this.getHighlightDivForNode(currentNode);
|
|
345
|
+
|
|
346
|
+
chain.push({
|
|
347
|
+
node: currentNode,
|
|
348
|
+
text,
|
|
349
|
+
start: offset,
|
|
350
|
+
end: offset + text.length,
|
|
351
|
+
highlightDiv,
|
|
352
|
+
originalHighlightDivInnerHTML: highlightDiv?.innerHTML ?? '',
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
offset += text.length + 2;
|
|
356
|
+
|
|
357
|
+
if (!currentNode.hasAttribute('co-tts.next')) {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
currentNode = document.getElementById(currentNode.getAttribute('co-tts.next'));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return chain;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
preparePlaybackChain(chain: any[]) {
|
|
368
|
+
this.playbackSegments = chain;
|
|
369
|
+
this.textToRead = chain.map((segment) => segment.text).join('. ');
|
|
370
|
+
this.highlightDiv = undefined;
|
|
371
|
+
this.originalHighlightDivInnerHTML = '';
|
|
372
|
+
this.wordEncounters = [];
|
|
373
|
+
this.previousWordBoundary = undefined;
|
|
374
|
+
this.prevTextOffset = 0;
|
|
375
|
+
this.currentWord = '';
|
|
376
|
+
this.currentOffset = 0;
|
|
377
|
+
this.wordBoundaryOffset = 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
getHighlightDivForNode(node: any) {
|
|
381
|
+
if (!node.hasAttribute('co-tts.highlight')) {
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (node.attributes.getNamedItem('co-tts.highlight')?.value !== '') {
|
|
386
|
+
return document.getElementById(node.attributes.getNamedItem('co-tts.highlight').value);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return node;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
resetPlaybackSegments() {
|
|
393
|
+
this.playbackSegments.forEach((segment) => {
|
|
394
|
+
if (segment.highlightDiv) {
|
|
395
|
+
segment.highlightDiv.innerHTML = segment.originalHighlightDivInnerHTML;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
updateChainedHighlight(wordBoundary: any) {
|
|
401
|
+
if (~['.', ',', '!', '?', '*', '(', ')', '&', '\\', '/', '^', '[', ']', '<', '>', ':'].indexOf(wordBoundary.text)) {
|
|
402
|
+
wordBoundary = this.previousWordBoundary ?? undefined;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!wordBoundary) {
|
|
406
|
+
this.resetPlaybackSegments();
|
|
407
|
+
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const segment = this.playbackSegments.find((candidate) => (
|
|
412
|
+
wordBoundary.textOffset >= candidate.start && wordBoundary.textOffset < candidate.end
|
|
413
|
+
));
|
|
414
|
+
|
|
415
|
+
this.resetPlaybackSegments();
|
|
416
|
+
|
|
417
|
+
if (!segment?.highlightDiv) {
|
|
418
|
+
this.previousWordBoundary = wordBoundary;
|
|
419
|
+
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const relativeTextOffset = wordBoundary.textOffset - segment.start;
|
|
424
|
+
const currentOffset = this.getPosition(segment.originalHighlightDivInnerHTML, wordBoundary.text, relativeTextOffset);
|
|
425
|
+
|
|
426
|
+
if (currentOffset === Number.MAX_SAFE_INTEGER) {
|
|
427
|
+
this.previousWordBoundary = wordBoundary;
|
|
428
|
+
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const startOfString = segment.originalHighlightDivInnerHTML.substring(0, currentOffset);
|
|
433
|
+
const endOffset = currentOffset + wordBoundary.wordLength;
|
|
434
|
+
const endOfString = segment.originalHighlightDivInnerHTML.substring(endOffset);
|
|
435
|
+
|
|
436
|
+
segment.highlightDiv.innerHTML = `
|
|
437
|
+
${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
|
|
438
|
+
`;
|
|
439
|
+
this.previousWordBoundary = wordBoundary;
|
|
440
|
+
}
|
|
441
|
+
|
|
307
442
|
getNodeText(node: any, attr?: Attr | null) {
|
|
308
443
|
if (attr && attr.value !== '') {
|
|
309
444
|
return attr.value;
|
|
@@ -497,6 +632,12 @@ export class TextToSpeech {
|
|
|
497
632
|
}
|
|
498
633
|
|
|
499
634
|
if (wordBoundary !== undefined) {
|
|
635
|
+
if (this.playbackSegments.length > 0) {
|
|
636
|
+
this.updateChainedHighlight(wordBoundary);
|
|
637
|
+
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
500
641
|
if (~['.', ',', '!', '?', '*', '(', ')', '&', '\\', '/', '^', '[', ']', '<', '>', ':']
|
|
501
642
|
.indexOf(wordBoundary.text)) {
|
|
502
643
|
wordBoundary = this.previousWordBoundary ?? undefined;
|