@creativeorange/azure-text-to-speech 2.2.2 → 3.0.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/README.md +496 -0
- package/dist/co-azure-tts.es.js +437 -169
- package/dist/co-azure-tts.umd.js +10 -10
- package/package.json +21 -3
- package/src/SpeechToText.ts +106 -40
- package/src/TextToSpeech.ts +243 -132
- package/src/authentication.ts +228 -0
- package/src/main.ts +7 -0
- package/.eslintrc.js +0 -30
- package/index.html +0 -578
- package/nuxt/plugins/azure-speech-to-text.client.js +0 -27
- package/nuxt/plugins/azure-text-to-speech.client.js +0 -28
- package/tsconfig.json +0 -21
- package/vite.config.ts +0 -26
package/src/TextToSpeech.ts
CHANGED
|
@@ -5,9 +5,21 @@ import {
|
|
|
5
5
|
SpeechSynthesizer,
|
|
6
6
|
SpeechSynthesisOutputFormat,
|
|
7
7
|
} from 'microsoft-cognitiveservices-speech-sdk';
|
|
8
|
+
import {
|
|
9
|
+
SpeechAuthenticationOptions,
|
|
10
|
+
SpeechAuthorizationManager,
|
|
11
|
+
toSafeErrorDetail,
|
|
12
|
+
} from './authentication';
|
|
13
|
+
|
|
14
|
+
export type TextToSpeechOptions = SpeechAuthenticationOptions & {
|
|
15
|
+
region?: string;
|
|
16
|
+
voice: string;
|
|
17
|
+
rate?: number;
|
|
18
|
+
pitch?: number;
|
|
19
|
+
url?: string;
|
|
20
|
+
};
|
|
8
21
|
|
|
9
22
|
export class TextToSpeech {
|
|
10
|
-
key: string;
|
|
11
23
|
region: string;
|
|
12
24
|
voice: string;
|
|
13
25
|
rate: number;
|
|
@@ -34,6 +46,7 @@ export class TextToSpeech {
|
|
|
34
46
|
currentWord: string = '';
|
|
35
47
|
currentOffset: number = 0;
|
|
36
48
|
wordBoundaryOffset: number = 0;
|
|
49
|
+
playbackTextOffsetBase: number | undefined;
|
|
37
50
|
prevTextOffset: number = 0;
|
|
38
51
|
url: string = '';
|
|
39
52
|
prefetchedAudio: Map<string, any> = new Map();
|
|
@@ -41,13 +54,19 @@ export class TextToSpeech {
|
|
|
41
54
|
activePrefetchedAudioUrl: string = '';
|
|
42
55
|
playbackSegments: any[] = [];
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
private readonly authorizationManager: SpeechAuthorizationManager;
|
|
58
|
+
|
|
59
|
+
constructor(options: TextToSpeechOptions) {
|
|
60
|
+
if (!options || typeof options.voice !== 'string' || options.voice.trim() === '') {
|
|
61
|
+
throw new Error('A voice is required.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.authorizationManager = new SpeechAuthorizationManager(options);
|
|
65
|
+
this.region = options.region?.trim() ?? '';
|
|
66
|
+
this.voice = options.voice;
|
|
67
|
+
this.rate = options.rate ?? 0;
|
|
68
|
+
this.pitch = options.pitch ?? 0;
|
|
69
|
+
this.url = options.url ?? '';
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
async start() {
|
|
@@ -120,7 +139,7 @@ export class TextToSpeech {
|
|
|
120
139
|
if (referenceDiv.hasAttribute('co-tts.text') && referenceDiv.getAttribute('co-tts.text') !== '') {
|
|
121
140
|
this.textToRead = referenceDiv.getAttribute('co-tts.text') ?? '';
|
|
122
141
|
} else {
|
|
123
|
-
this.textToRead = referenceDiv.innerText;
|
|
142
|
+
this.textToRead = referenceDiv.innerText ?? referenceDiv.textContent ?? '';
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
if (referenceDiv.hasAttribute('co-tts.highlight')) {
|
|
@@ -136,7 +155,7 @@ export class TextToSpeech {
|
|
|
136
155
|
}
|
|
137
156
|
}
|
|
138
157
|
|
|
139
|
-
this.startSynthesizer(node, attr);
|
|
158
|
+
await this.startSynthesizer(node, attr);
|
|
140
159
|
});
|
|
141
160
|
}
|
|
142
161
|
|
|
@@ -151,7 +170,7 @@ export class TextToSpeech {
|
|
|
151
170
|
|
|
152
171
|
this.textToRead = await response.text();
|
|
153
172
|
|
|
154
|
-
this.startSynthesizer(node, attr);
|
|
173
|
+
await this.startSynthesizer(node, attr);
|
|
155
174
|
});
|
|
156
175
|
}
|
|
157
176
|
|
|
@@ -172,12 +191,12 @@ export class TextToSpeech {
|
|
|
172
191
|
}
|
|
173
192
|
}
|
|
174
193
|
if (attr.value === '') {
|
|
175
|
-
this.textToRead = node.innerText;
|
|
194
|
+
this.textToRead = node.innerText ?? node.textContent ?? '';
|
|
176
195
|
} else {
|
|
177
196
|
this.textToRead = attr.value;
|
|
178
197
|
}
|
|
179
198
|
|
|
180
|
-
this.startSynthesizer(node, attr);
|
|
199
|
+
await this.startSynthesizer(node, attr);
|
|
181
200
|
});
|
|
182
201
|
}
|
|
183
202
|
|
|
@@ -199,12 +218,12 @@ export class TextToSpeech {
|
|
|
199
218
|
}
|
|
200
219
|
}
|
|
201
220
|
if (attr.value === '') {
|
|
202
|
-
this.textToRead = node.innerText;
|
|
221
|
+
this.textToRead = node.innerText ?? node.textContent ?? '';
|
|
203
222
|
} else {
|
|
204
223
|
this.textToRead = attr.value;
|
|
205
224
|
}
|
|
206
225
|
|
|
207
|
-
this.startSynthesizer(node, attr);
|
|
226
|
+
await this.startSynthesizer(node, attr);
|
|
208
227
|
}
|
|
209
228
|
|
|
210
229
|
async handleStopModifier(node: any, attr: Attr) {
|
|
@@ -250,79 +269,105 @@ export class TextToSpeech {
|
|
|
250
269
|
URL.revokeObjectURL(this.activePrefetchedAudioUrl);
|
|
251
270
|
this.activePrefetchedAudioUrl = '';
|
|
252
271
|
}
|
|
272
|
+
this.closeSynthesizer();
|
|
253
273
|
this.player = undefined;
|
|
274
|
+
this.audioConfig = undefined;
|
|
275
|
+
this.speechConfig = undefined;
|
|
254
276
|
this.highlightDiv = undefined;
|
|
255
277
|
this.prevTextOffset = 0;
|
|
278
|
+
this.playbackTextOffsetBase = undefined;
|
|
256
279
|
}
|
|
257
280
|
|
|
258
|
-
async
|
|
259
|
-
|
|
281
|
+
private async createSpeechConfig(): Promise<SpeechConfig> {
|
|
282
|
+
const authorization =
|
|
283
|
+
await this.authorizationManager.getAuthorization();
|
|
260
284
|
|
|
261
|
-
|
|
262
|
-
|
|
285
|
+
const speechConfig = SpeechConfig.fromAuthorizationToken(
|
|
286
|
+
authorization.token,
|
|
287
|
+
authorization.region,
|
|
288
|
+
);
|
|
263
289
|
|
|
264
|
-
|
|
290
|
+
speechConfig.speechSynthesisVoiceName =
|
|
291
|
+
`Microsoft Server Speech Text to Speech Voice (${this.voice})`;
|
|
265
292
|
|
|
266
|
-
|
|
267
|
-
|
|
293
|
+
speechConfig.speechSynthesisOutputFormat =
|
|
294
|
+
SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
|
|
268
295
|
|
|
269
|
-
this.
|
|
270
|
-
this.wordBoundryList.push(e);
|
|
271
|
-
};
|
|
296
|
+
this.region = authorization.region;
|
|
272
297
|
|
|
273
|
-
|
|
274
|
-
|
|
298
|
+
return speechConfig;
|
|
299
|
+
}
|
|
275
300
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.playbackSegments = [];
|
|
280
|
-
}
|
|
301
|
+
async startSynthesizer(node: any, attr: Attr) {
|
|
302
|
+
try {
|
|
303
|
+
this.speechConfig = await this.createSpeechConfig();
|
|
281
304
|
|
|
282
|
-
|
|
283
|
-
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
284
|
-
this.stopPlayer();
|
|
305
|
+
this.player = new SpeakerAudioDestination();
|
|
285
306
|
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
this.audioConfig = AudioConfig.fromSpeakerOutput(this.player);
|
|
308
|
+
this.synthesizer = new SpeechSynthesizer(this.speechConfig, this.audioConfig);
|
|
288
309
|
|
|
289
|
-
|
|
310
|
+
this.synthesizer.wordBoundary = (s: any, e: any) => {
|
|
311
|
+
this.wordBoundryList.push(e);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const playbackChain = this.collectPlaybackChain(this.clickedNode);
|
|
315
|
+
const isChainedPlayback = playbackChain.length > 1;
|
|
316
|
+
|
|
317
|
+
if (isChainedPlayback) {
|
|
318
|
+
this.preparePlaybackChain(playbackChain);
|
|
319
|
+
} else {
|
|
320
|
+
this.playbackSegments = [];
|
|
290
321
|
}
|
|
291
322
|
|
|
292
|
-
|
|
293
|
-
const
|
|
323
|
+
this.player.onAudioEnd = async () => {
|
|
324
|
+
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
325
|
+
this.stopPlayer();
|
|
326
|
+
|
|
327
|
+
if (wasChainedPlayback) {
|
|
328
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
|
294
329
|
|
|
295
|
-
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
296
330
|
return;
|
|
297
331
|
}
|
|
298
332
|
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
nextNode.
|
|
333
|
+
if (this.clickedNode.hasAttribute('co-tts.next')) {
|
|
334
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute('co-tts.next'));
|
|
335
|
+
|
|
336
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (nextNode && nextNode.attributes.getNamedItem('co-tts.text')) {
|
|
341
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem('co-tts.text'));
|
|
342
|
+
} else if (nextNode) {
|
|
343
|
+
nextNode.dispatchEvent(new Event('click'));
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
|
303
347
|
}
|
|
304
|
-
}
|
|
305
|
-
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
|
306
|
-
}
|
|
307
|
-
};
|
|
348
|
+
};
|
|
308
349
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
350
|
+
this.player.onAudioStart = async () => {
|
|
351
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSStartedPlaying', {}));
|
|
352
|
+
};
|
|
312
353
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
354
|
+
if (!isChainedPlayback) {
|
|
355
|
+
this.prefetchNextNode(this.clickedNode);
|
|
356
|
+
}
|
|
316
357
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
this.synthesizer.speakSsmlAsync(this.buildSSML(this.textToRead),
|
|
359
|
+
() => {
|
|
360
|
+
this.closeSynthesizer();
|
|
361
|
+
},
|
|
362
|
+
(error: unknown) => {
|
|
363
|
+
this.dispatchError(error, 'SYNTHESIS_ERROR');
|
|
364
|
+
this.closeSynthesizer();
|
|
365
|
+
this.stopPlayer();
|
|
366
|
+
});
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.dispatchError(error);
|
|
369
|
+
await this.stopPlayer();
|
|
370
|
+
}
|
|
326
371
|
}
|
|
327
372
|
|
|
328
373
|
collectPlaybackChain(node: any) {
|
|
@@ -372,6 +417,7 @@ export class TextToSpeech {
|
|
|
372
417
|
this.wordEncounters = [];
|
|
373
418
|
this.previousWordBoundary = undefined;
|
|
374
419
|
this.prevTextOffset = 0;
|
|
420
|
+
this.playbackTextOffsetBase = undefined;
|
|
375
421
|
this.currentWord = '';
|
|
376
422
|
this.currentOffset = 0;
|
|
377
423
|
this.wordBoundaryOffset = 0;
|
|
@@ -408,8 +454,13 @@ export class TextToSpeech {
|
|
|
408
454
|
return;
|
|
409
455
|
}
|
|
410
456
|
|
|
457
|
+
if (this.playbackTextOffsetBase === undefined) {
|
|
458
|
+
this.playbackTextOffsetBase = wordBoundary.textOffset;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const normalizedTextOffset = Math.max(0, wordBoundary.textOffset - this.playbackTextOffsetBase);
|
|
411
462
|
const segment = this.playbackSegments.find((candidate) => (
|
|
412
|
-
|
|
463
|
+
normalizedTextOffset >= candidate.start && normalizedTextOffset < candidate.end
|
|
413
464
|
));
|
|
414
465
|
|
|
415
466
|
this.resetPlaybackSegments();
|
|
@@ -420,7 +471,7 @@ export class TextToSpeech {
|
|
|
420
471
|
return;
|
|
421
472
|
}
|
|
422
473
|
|
|
423
|
-
const relativeTextOffset =
|
|
474
|
+
const relativeTextOffset = normalizedTextOffset - segment.start;
|
|
424
475
|
const currentOffset = this.getPosition(segment.originalHighlightDivInnerHTML, wordBoundary.text, relativeTextOffset);
|
|
425
476
|
|
|
426
477
|
if (currentOffset === Number.MAX_SAFE_INTEGER) {
|
|
@@ -448,7 +499,7 @@ export class TextToSpeech {
|
|
|
448
499
|
return node.getAttribute('co-tts.text') ?? '';
|
|
449
500
|
}
|
|
450
501
|
|
|
451
|
-
return node.innerText;
|
|
502
|
+
return node.innerText ?? node.textContent ?? '';
|
|
452
503
|
}
|
|
453
504
|
|
|
454
505
|
getPrefetchKey(node: any, text: string) {
|
|
@@ -497,46 +548,57 @@ export class TextToSpeech {
|
|
|
497
548
|
return;
|
|
498
549
|
}
|
|
499
550
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
551
|
+
let synthesizer: SpeechSynthesizer | undefined;
|
|
552
|
+
|
|
553
|
+
const prefetchPromise = (async () => {
|
|
554
|
+
try {
|
|
555
|
+
const speechConfig = await this.createSpeechConfig();
|
|
556
|
+
synthesizer = new SpeechSynthesizer(speechConfig, null);
|
|
557
|
+
const wordBoundryList: any[] = [];
|
|
558
|
+
|
|
559
|
+
synthesizer.wordBoundary = (s: any, e: any) => {
|
|
560
|
+
wordBoundryList.push(e);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
return await new Promise((resolve) => {
|
|
564
|
+
synthesizer?.speakSsmlAsync(this.buildSSML(text),
|
|
565
|
+
(result: any) => {
|
|
566
|
+
this.closeResource(synthesizer);
|
|
567
|
+
synthesizer = undefined;
|
|
568
|
+
|
|
569
|
+
if (!result?.audioData) {
|
|
570
|
+
resolve(null);
|
|
571
|
+
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const blob = new Blob([result.audioData], {type: 'audio/mpeg'});
|
|
576
|
+
const url = URL.createObjectURL(blob);
|
|
577
|
+
const prefetch = {
|
|
578
|
+
key,
|
|
579
|
+
nodeId: nextNode.id,
|
|
580
|
+
text,
|
|
581
|
+
url,
|
|
582
|
+
wordBoundryList,
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
this.prefetchedAudio.set(key, prefetch);
|
|
586
|
+
resolve(prefetch);
|
|
587
|
+
},
|
|
588
|
+
(error: unknown) => {
|
|
589
|
+
this.closeResource(synthesizer);
|
|
590
|
+
synthesizer = undefined;
|
|
591
|
+
this.dispatchError(error, 'PREFETCH_ERROR');
|
|
592
|
+
resolve(null);
|
|
593
|
+
});
|
|
538
594
|
});
|
|
539
|
-
|
|
595
|
+
} catch (error) {
|
|
596
|
+
this.closeResource(synthesizer);
|
|
597
|
+
synthesizer = undefined;
|
|
598
|
+
this.dispatchError(error);
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
})().finally(() => {
|
|
540
602
|
this.prefetchPromises.delete(key);
|
|
541
603
|
});
|
|
542
604
|
|
|
@@ -580,38 +642,53 @@ export class TextToSpeech {
|
|
|
580
642
|
|
|
581
643
|
await this.createInterval();
|
|
582
644
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
645
|
+
try {
|
|
646
|
+
const audio = new Audio(prefetch.url);
|
|
647
|
+
this.activePrefetchedAudioUrl = prefetch.url;
|
|
648
|
+
this.player = audio;
|
|
586
649
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
650
|
+
audio.addEventListener('play', () => {
|
|
651
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSStartedPlaying', {}));
|
|
652
|
+
}, {once: true});
|
|
590
653
|
|
|
591
|
-
|
|
592
|
-
|
|
654
|
+
audio.addEventListener('ended', async () => {
|
|
655
|
+
this.stopPlayer();
|
|
593
656
|
|
|
594
|
-
|
|
595
|
-
|
|
657
|
+
if (this.clickedNode.hasAttribute('co-tts.next')) {
|
|
658
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute('co-tts.next'));
|
|
596
659
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
660
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
600
663
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
664
|
+
if (nextNode && nextNode.attributes.getNamedItem('co-tts.text')) {
|
|
665
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem('co-tts.text'));
|
|
666
|
+
} else if (nextNode) {
|
|
667
|
+
nextNode.dispatchEvent(new Event('click'));
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
document.dispatchEvent(new CustomEvent('COAzureTTSFinishedPlaying', {}));
|
|
605
671
|
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
672
|
+
}, {once: true});
|
|
673
|
+
|
|
674
|
+
audio.addEventListener('error', () => {
|
|
675
|
+
this.dispatchError(
|
|
676
|
+
new Error('Prefetched audio resource closed unexpectedly.'),
|
|
677
|
+
'AUDIO_RESOURCE_ERROR',
|
|
678
|
+
);
|
|
679
|
+
this.stopPlayer();
|
|
680
|
+
}, {once: true});
|
|
681
|
+
|
|
682
|
+
this.prefetchNextNode(node);
|
|
683
|
+
await audio.play();
|
|
684
|
+
|
|
685
|
+
return true;
|
|
686
|
+
} catch (error) {
|
|
687
|
+
this.dispatchError(error, 'AUDIO_RESOURCE_ERROR');
|
|
688
|
+
await this.stopPlayer();
|
|
613
689
|
|
|
614
|
-
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
615
692
|
}
|
|
616
693
|
|
|
617
694
|
async clearInterval() {
|
|
@@ -620,7 +697,7 @@ export class TextToSpeech {
|
|
|
620
697
|
|
|
621
698
|
async createInterval() {
|
|
622
699
|
this.interval = setInterval(() => {
|
|
623
|
-
if (this.player !== undefined && this.highlightDiv) {
|
|
700
|
+
if (this.player !== undefined && (this.highlightDiv || this.playbackSegments.length > 0)) {
|
|
624
701
|
const currentTime = this.player.currentTime;
|
|
625
702
|
let wordBoundary;
|
|
626
703
|
for (const e of this.wordBoundryList) {
|
|
@@ -674,6 +751,8 @@ export class TextToSpeech {
|
|
|
674
751
|
`;
|
|
675
752
|
}
|
|
676
753
|
}
|
|
754
|
+
} else if (this.playbackSegments.length > 0) {
|
|
755
|
+
this.resetPlaybackSegments();
|
|
677
756
|
} else {
|
|
678
757
|
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
|
|
679
758
|
}
|
|
@@ -732,4 +811,36 @@ export class TextToSpeech {
|
|
|
732
811
|
|
|
733
812
|
return p.innerHTML;
|
|
734
813
|
}
|
|
814
|
+
|
|
815
|
+
private closeSynthesizer() {
|
|
816
|
+
this.closeResource(this.synthesizer);
|
|
817
|
+
this.synthesizer = undefined;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private closeResource(resource: {close?: () => void} | undefined) {
|
|
821
|
+
if (!resource || typeof resource.close !== 'function') {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
resource.close();
|
|
827
|
+
} catch {
|
|
828
|
+
// Resource may already be closed.
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
private dispatchError(error: unknown, code?: string) {
|
|
833
|
+
const detail = toSafeErrorDetail(error);
|
|
834
|
+
if (code && !detail.code) {
|
|
835
|
+
detail.code = code;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
document.dispatchEvent(
|
|
839
|
+
new CustomEvent('COAzureTTSError', {
|
|
840
|
+
detail: {
|
|
841
|
+
error: detail,
|
|
842
|
+
},
|
|
843
|
+
}),
|
|
844
|
+
);
|
|
845
|
+
}
|
|
735
846
|
}
|