@creativeorange/azure-text-to-speech 2.2.3 → 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.
@@ -8350,17 +8350,170 @@ class RestMessageAdapter {
8350
8350
  return Object.keys(params).map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(params[k])).join("&");
8351
8351
  }
8352
8352
  }
8353
+ const DEFAULT_TOKEN_LIFETIME_MS = 8 * 60 * 1e3;
8354
+ function createSpeechAuthorizationProvider(options) {
8355
+ var _a;
8356
+ const hasTokenEndpoint = typeof options.tokenEndpoint === "string" && options.tokenEndpoint.trim() !== "";
8357
+ const hasProvider = typeof options.getAuthorizationToken === "function";
8358
+ if (hasTokenEndpoint && hasProvider) {
8359
+ throw new Error(
8360
+ "Provide either tokenEndpoint or getAuthorizationToken, not both."
8361
+ );
8362
+ }
8363
+ if (!hasTokenEndpoint && !hasProvider) {
8364
+ throw new Error(
8365
+ "A tokenEndpoint or getAuthorizationToken provider is required."
8366
+ );
8367
+ }
8368
+ if (hasProvider) {
8369
+ return options.getAuthorizationToken;
8370
+ }
8371
+ const tokenEndpoint = options.tokenEndpoint.trim();
8372
+ const requestOptions = (_a = options.tokenRequestOptions) != null ? _a : {};
8373
+ return async () => {
8374
+ var _a2, _b;
8375
+ let response;
8376
+ try {
8377
+ response = await fetch(tokenEndpoint, {
8378
+ method: "GET",
8379
+ credentials: (_a2 = requestOptions.credentials) != null ? _a2 : "same-origin",
8380
+ headers: {
8381
+ Accept: "application/json",
8382
+ ...(_b = requestOptions.headers) != null ? _b : {}
8383
+ }
8384
+ });
8385
+ } catch {
8386
+ throw createSafeError(
8387
+ "Could not reach the speech token endpoint.",
8388
+ "TOKEN_ENDPOINT_UNREACHABLE"
8389
+ );
8390
+ }
8391
+ if (!response.ok) {
8392
+ throw createSafeError(
8393
+ `Speech token endpoint returned HTTP ${response.status}.`,
8394
+ "TOKEN_ENDPOINT_HTTP_ERROR"
8395
+ );
8396
+ }
8397
+ let payload;
8398
+ try {
8399
+ payload = await response.json();
8400
+ } catch {
8401
+ throw createSafeError(
8402
+ "Speech token endpoint returned invalid JSON.",
8403
+ "TOKEN_ENDPOINT_INVALID_JSON"
8404
+ );
8405
+ }
8406
+ return validateSpeechAuthorization(payload);
8407
+ };
8408
+ }
8409
+ function validateSpeechAuthorization(value) {
8410
+ if (!value || typeof value !== "object") {
8411
+ throw createSafeError(
8412
+ "Speech authorization response must be an object.",
8413
+ "TOKEN_INVALID_RESPONSE"
8414
+ );
8415
+ }
8416
+ const candidate = value;
8417
+ const token = typeof candidate.token === "string" ? candidate.token.trim() : "";
8418
+ const region = typeof candidate.region === "string" ? candidate.region.trim() : "";
8419
+ if (token === "") {
8420
+ throw createSafeError(
8421
+ "Speech authorization token is missing or empty.",
8422
+ "TOKEN_EMPTY"
8423
+ );
8424
+ }
8425
+ if (region === "") {
8426
+ throw createSafeError(
8427
+ "Speech authorization region is missing or empty.",
8428
+ "TOKEN_REGION_MISSING"
8429
+ );
8430
+ }
8431
+ return { token, region };
8432
+ }
8433
+ function createSafeError(message, code) {
8434
+ const error = new Error(message);
8435
+ error.message = message;
8436
+ if (code) {
8437
+ error.code = code;
8438
+ }
8439
+ return error;
8440
+ }
8441
+ function toSafeErrorDetail(error) {
8442
+ if (error && typeof error === "object") {
8443
+ const candidate = error;
8444
+ const message = typeof candidate.message === "string" && candidate.message.trim() !== "" ? sanitizeErrorText(candidate.message) : "An unexpected speech error occurred.";
8445
+ const detail = { message };
8446
+ if (typeof candidate.code === "string" && candidate.code.trim() !== "") {
8447
+ detail.code = candidate.code;
8448
+ }
8449
+ return detail;
8450
+ }
8451
+ return {
8452
+ message: "An unexpected speech error occurred."
8453
+ };
8454
+ }
8455
+ function sanitizeErrorText(message) {
8456
+ return message.replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/token["']?\s*[:=]\s*["']?[^"',\s}]+/gi, "token=[redacted]");
8457
+ }
8458
+ class SpeechAuthorizationManager {
8459
+ constructor(options) {
8460
+ __publicField(this, "authorization");
8461
+ __publicField(this, "authorizationExpiresAt", 0);
8462
+ __publicField(this, "authorizationRequest");
8463
+ __publicField(this, "provider");
8464
+ __publicField(this, "tokenLifetimeMs");
8465
+ this.provider = createSpeechAuthorizationProvider(options);
8466
+ this.tokenLifetimeMs = options.tokenLifetimeMs && options.tokenLifetimeMs > 0 ? options.tokenLifetimeMs : DEFAULT_TOKEN_LIFETIME_MS;
8467
+ }
8468
+ async getAuthorization() {
8469
+ if (this.authorization && Date.now() < this.authorizationExpiresAt) {
8470
+ return this.authorization;
8471
+ }
8472
+ if (this.authorizationRequest) {
8473
+ return this.authorizationRequest;
8474
+ }
8475
+ this.authorizationRequest = this.fetchAuthorization().then((authorization) => {
8476
+ this.authorization = authorization;
8477
+ this.authorizationExpiresAt = Date.now() + this.tokenLifetimeMs;
8478
+ return authorization;
8479
+ }).finally(() => {
8480
+ this.authorizationRequest = void 0;
8481
+ });
8482
+ return this.authorizationRequest;
8483
+ }
8484
+ clearAuthorization() {
8485
+ this.authorization = void 0;
8486
+ this.authorizationExpiresAt = 0;
8487
+ this.authorizationRequest = void 0;
8488
+ }
8489
+ async fetchAuthorization() {
8490
+ try {
8491
+ const authorization = await this.provider();
8492
+ return validateSpeechAuthorization(authorization);
8493
+ } catch (error) {
8494
+ this.clearAuthorization();
8495
+ throw createSafeError(
8496
+ toSafeErrorDetail(error).message,
8497
+ error == null ? void 0 : error.code
8498
+ );
8499
+ }
8500
+ }
8501
+ }
8353
8502
  class SpeechToText {
8354
- constructor(key, region, sourceLanguage, targetLanguage = null) {
8355
- __publicField(this, "key");
8503
+ constructor(options) {
8356
8504
  __publicField(this, "region");
8357
8505
  __publicField(this, "sourceLanguage");
8358
8506
  __publicField(this, "targetLanguage");
8359
8507
  __publicField(this, "recognizer");
8360
- this.key = key;
8361
- this.region = region;
8362
- this.sourceLanguage = sourceLanguage;
8363
- this.targetLanguage = targetLanguage !== null ? targetLanguage : sourceLanguage;
8508
+ __publicField(this, "authorizationManager");
8509
+ var _a, _b, _c;
8510
+ if (!options || typeof options.sourceLanguage !== "string" || options.sourceLanguage.trim() === "") {
8511
+ throw new Error("A sourceLanguage is required.");
8512
+ }
8513
+ this.authorizationManager = new SpeechAuthorizationManager(options);
8514
+ this.region = (_b = (_a = options.region) == null ? void 0 : _a.trim()) != null ? _b : "";
8515
+ this.sourceLanguage = options.sourceLanguage;
8516
+ this.targetLanguage = (_c = options.targetLanguage) != null ? _c : options.sourceLanguage;
8364
8517
  }
8365
8518
  async start() {
8366
8519
  await this.registerBindings(document);
@@ -8384,39 +8537,55 @@ class SpeechToText {
8384
8537
  }
8385
8538
  }
8386
8539
  }
8540
+ async createSpeechTranslationConfig() {
8541
+ const authorization = await this.authorizationManager.getAuthorization();
8542
+ const speechConfig = SpeechTranslationConfig.fromAuthorizationToken(
8543
+ authorization.token,
8544
+ authorization.region
8545
+ );
8546
+ speechConfig.speechRecognitionLanguage = this.sourceLanguage;
8547
+ speechConfig.addTargetLanguage(
8548
+ this.targetLanguage
8549
+ );
8550
+ this.region = authorization.region;
8551
+ return speechConfig;
8552
+ }
8387
8553
  async handleStartModifier(node, attr) {
8388
8554
  node.addEventListener("click", async (_) => {
8389
- const speechConfig = SpeechTranslationConfig.fromSubscription(this.key, this.region);
8390
- speechConfig.speechRecognitionLanguage = this.sourceLanguage;
8391
- speechConfig.addTargetLanguage(this.targetLanguage);
8392
- const audioConfig = AudioConfig.fromDefaultMicrophoneInput();
8393
- this.recognizer = new TranslationRecognizer(speechConfig, audioConfig);
8394
- document.dispatchEvent(new CustomEvent("COAzureSTTStartedRecording", {}));
8395
- const prevResults = [];
8396
- this.recognizer.recognizing = (sender, event) => {
8397
- const result = event.result;
8398
- if (result && result.reason === ResultReason.TranslatingSpeech) {
8399
- const translation = result.translations.get(this.targetLanguage);
8400
- prevResults["result_" + result.privOffset.toString()] = translation;
8401
- const totalResult = Object.values(prevResults).join(". ");
8402
- const inputElement = document.getElementById(attr.value);
8403
- if (inputElement !== null) {
8404
- if (inputElement instanceof HTMLInputElement) {
8405
- inputElement.value = `${totalResult} `;
8406
- } else {
8407
- inputElement.innerHTML = `${totalResult} `;
8555
+ try {
8556
+ const speechConfig = await this.createSpeechTranslationConfig();
8557
+ const audioConfig = AudioConfig.fromDefaultMicrophoneInput();
8558
+ this.recognizer = new TranslationRecognizer(speechConfig, audioConfig);
8559
+ document.dispatchEvent(new CustomEvent("COAzureSTTStartedRecording", {}));
8560
+ const prevResults = [];
8561
+ this.recognizer.recognizing = (sender, event) => {
8562
+ const result = event.result;
8563
+ if (result && result.reason === ResultReason.TranslatingSpeech) {
8564
+ const translation = result.translations.get(this.targetLanguage);
8565
+ prevResults["result_" + result.privOffset.toString()] = translation;
8566
+ const totalResult = Object.values(prevResults).join(". ");
8567
+ const inputElement = document.getElementById(attr.value);
8568
+ if (inputElement !== null) {
8569
+ if (inputElement instanceof HTMLInputElement) {
8570
+ inputElement.value = `${totalResult} `;
8571
+ } else {
8572
+ inputElement.innerHTML = `${totalResult} `;
8573
+ }
8408
8574
  }
8409
8575
  }
8410
- }
8411
- };
8412
- this.recognizer.startContinuousRecognitionAsync(
8413
- (result) => {
8414
- },
8415
- (err) => {
8416
- console.log(err);
8417
- this.stop();
8418
- }
8419
- );
8576
+ };
8577
+ this.recognizer.startContinuousRecognitionAsync(
8578
+ () => {
8579
+ },
8580
+ (err) => {
8581
+ this.dispatchError(err, "RECOGNITION_ERROR");
8582
+ this.stop();
8583
+ }
8584
+ );
8585
+ } catch (error) {
8586
+ this.dispatchError(error);
8587
+ await this.stop();
8588
+ }
8420
8589
  });
8421
8590
  }
8422
8591
  async handleStopModifier(node, attr) {
@@ -8426,16 +8595,34 @@ class SpeechToText {
8426
8595
  }
8427
8596
  async stop() {
8428
8597
  if (this.recognizer !== void 0) {
8429
- this.recognizer.stopContinuousRecognitionAsync();
8430
- this.recognizer.close();
8598
+ try {
8599
+ this.recognizer.stopContinuousRecognitionAsync();
8600
+ } catch {
8601
+ }
8602
+ try {
8603
+ this.recognizer.close();
8604
+ } catch {
8605
+ }
8431
8606
  this.recognizer = void 0;
8432
8607
  }
8433
8608
  document.dispatchEvent(new CustomEvent("COAzureSTTStoppedRecording", {}));
8434
8609
  }
8610
+ dispatchError(error, code) {
8611
+ const detail = toSafeErrorDetail(error);
8612
+ if (code && !detail.code) {
8613
+ detail.code = code;
8614
+ }
8615
+ document.dispatchEvent(
8616
+ new CustomEvent("COAzureSTTError", {
8617
+ detail: {
8618
+ error: detail
8619
+ }
8620
+ })
8621
+ );
8622
+ }
8435
8623
  }
8436
8624
  class TextToSpeech {
8437
- constructor(key, region, voice, rate = 0, pitch = 0, url = "") {
8438
- __publicField(this, "key");
8625
+ constructor(options) {
8439
8626
  __publicField(this, "region");
8440
8627
  __publicField(this, "voice");
8441
8628
  __publicField(this, "rate");
@@ -8462,12 +8649,17 @@ class TextToSpeech {
8462
8649
  __publicField(this, "prefetchPromises", /* @__PURE__ */ new Map());
8463
8650
  __publicField(this, "activePrefetchedAudioUrl", "");
8464
8651
  __publicField(this, "playbackSegments", []);
8465
- this.key = key;
8466
- this.region = region;
8467
- this.voice = voice;
8468
- this.rate = rate;
8469
- this.pitch = pitch;
8470
- this.url = url;
8652
+ __publicField(this, "authorizationManager");
8653
+ var _a, _b, _c, _d, _e;
8654
+ if (!options || typeof options.voice !== "string" || options.voice.trim() === "") {
8655
+ throw new Error("A voice is required.");
8656
+ }
8657
+ this.authorizationManager = new SpeechAuthorizationManager(options);
8658
+ this.region = (_b = (_a = options.region) == null ? void 0 : _a.trim()) != null ? _b : "";
8659
+ this.voice = options.voice;
8660
+ this.rate = (_c = options.rate) != null ? _c : 0;
8661
+ this.pitch = (_d = options.pitch) != null ? _d : 0;
8662
+ this.url = (_e = options.url) != null ? _e : "";
8471
8663
  }
8472
8664
  async start() {
8473
8665
  await this.registerBindings(document);
@@ -8516,7 +8708,7 @@ class TextToSpeech {
8516
8708
  }
8517
8709
  async handleIdModifier(node, attr) {
8518
8710
  node.addEventListener("click", async (_) => {
8519
- var _a, _b;
8711
+ var _a, _b, _c, _d;
8520
8712
  this.stopPlayer();
8521
8713
  await this.createInterval();
8522
8714
  const referenceDiv = document.getElementById(attr.value);
@@ -8527,10 +8719,10 @@ class TextToSpeech {
8527
8719
  if (referenceDiv.hasAttribute("co-tts.text") && referenceDiv.getAttribute("co-tts.text") !== "") {
8528
8720
  this.textToRead = (_a = referenceDiv.getAttribute("co-tts.text")) != null ? _a : "";
8529
8721
  } else {
8530
- this.textToRead = referenceDiv.innerText;
8722
+ this.textToRead = (_c = (_b = referenceDiv.innerText) != null ? _b : referenceDiv.textContent) != null ? _c : "";
8531
8723
  }
8532
8724
  if (referenceDiv.hasAttribute("co-tts.highlight")) {
8533
- if (((_b = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _b.value) !== "") {
8725
+ if (((_d = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _d.value) !== "") {
8534
8726
  const newReferenceDiv = document.getElementById(referenceDiv.attributes.getNamedItem("co-tts.highlight").value);
8535
8727
  this.highlightDiv = newReferenceDiv;
8536
8728
  this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
@@ -8539,7 +8731,7 @@ class TextToSpeech {
8539
8731
  this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
8540
8732
  }
8541
8733
  }
8542
- this.startSynthesizer(node, attr);
8734
+ await this.startSynthesizer(node, attr);
8543
8735
  });
8544
8736
  }
8545
8737
  async handleAjaxModifier(node, attr) {
@@ -8551,12 +8743,12 @@ class TextToSpeech {
8551
8743
  method: `GET`
8552
8744
  });
8553
8745
  this.textToRead = await response.text();
8554
- this.startSynthesizer(node, attr);
8746
+ await this.startSynthesizer(node, attr);
8555
8747
  });
8556
8748
  }
8557
8749
  async handleDefault(node, attr) {
8558
8750
  node.addEventListener("click", async (_) => {
8559
- var _a;
8751
+ var _a, _b, _c;
8560
8752
  this.stopPlayer();
8561
8753
  await this.createInterval();
8562
8754
  this.clickedNode = node;
@@ -8571,15 +8763,15 @@ class TextToSpeech {
8571
8763
  }
8572
8764
  }
8573
8765
  if (attr.value === "") {
8574
- this.textToRead = node.innerText;
8766
+ this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
8575
8767
  } else {
8576
8768
  this.textToRead = attr.value;
8577
8769
  }
8578
- this.startSynthesizer(node, attr);
8770
+ await this.startSynthesizer(node, attr);
8579
8771
  });
8580
8772
  }
8581
8773
  async handleWithoutClick(node, attr) {
8582
- var _a;
8774
+ var _a, _b, _c;
8583
8775
  this.stopPlayer();
8584
8776
  await this.createInterval();
8585
8777
  this.clickedNode = node;
@@ -8596,11 +8788,11 @@ class TextToSpeech {
8596
8788
  }
8597
8789
  }
8598
8790
  if (attr.value === "") {
8599
- this.textToRead = node.innerText;
8791
+ this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
8600
8792
  } else {
8601
8793
  this.textToRead = attr.value;
8602
8794
  }
8603
- this.startSynthesizer(node, attr);
8795
+ await this.startSynthesizer(node, attr);
8604
8796
  }
8605
8797
  async handleStopModifier(node, attr) {
8606
8798
  node.addEventListener("click", async (_) => {
@@ -8641,66 +8833,83 @@ class TextToSpeech {
8641
8833
  URL.revokeObjectURL(this.activePrefetchedAudioUrl);
8642
8834
  this.activePrefetchedAudioUrl = "";
8643
8835
  }
8836
+ this.closeSynthesizer();
8644
8837
  this.player = void 0;
8838
+ this.audioConfig = void 0;
8839
+ this.speechConfig = void 0;
8645
8840
  this.highlightDiv = void 0;
8646
8841
  this.prevTextOffset = 0;
8647
8842
  this.playbackTextOffsetBase = void 0;
8648
8843
  }
8844
+ async createSpeechConfig() {
8845
+ const authorization = await this.authorizationManager.getAuthorization();
8846
+ const speechConfig = SpeechConfig.fromAuthorizationToken(
8847
+ authorization.token,
8848
+ authorization.region
8849
+ );
8850
+ speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
8851
+ speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
8852
+ this.region = authorization.region;
8853
+ return speechConfig;
8854
+ }
8649
8855
  async startSynthesizer(node, attr) {
8650
- this.speechConfig = SpeechConfig.fromSubscription(this.key, this.region);
8651
- this.speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
8652
- this.speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
8653
- this.player = new SpeakerAudioDestination();
8654
- this.audioConfig = AudioConfig.fromSpeakerOutput(this.player);
8655
- this.synthesizer = new SpeechSynthesizer(this.speechConfig, this.audioConfig);
8656
- this.synthesizer.wordBoundary = (s, e) => {
8657
- this.wordBoundryList.push(e);
8658
- };
8659
- const playbackChain = this.collectPlaybackChain(this.clickedNode);
8660
- const isChainedPlayback = playbackChain.length > 1;
8661
- if (isChainedPlayback) {
8662
- this.preparePlaybackChain(playbackChain);
8663
- } else {
8664
- this.playbackSegments = [];
8665
- }
8666
- this.player.onAudioEnd = async () => {
8667
- const wasChainedPlayback = this.playbackSegments.length > 0;
8668
- this.stopPlayer();
8669
- if (wasChainedPlayback) {
8670
- document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8671
- return;
8856
+ try {
8857
+ this.speechConfig = await this.createSpeechConfig();
8858
+ this.player = new SpeakerAudioDestination();
8859
+ this.audioConfig = AudioConfig.fromSpeakerOutput(this.player);
8860
+ this.synthesizer = new SpeechSynthesizer(this.speechConfig, this.audioConfig);
8861
+ this.synthesizer.wordBoundary = (s, e) => {
8862
+ this.wordBoundryList.push(e);
8863
+ };
8864
+ const playbackChain = this.collectPlaybackChain(this.clickedNode);
8865
+ const isChainedPlayback = playbackChain.length > 1;
8866
+ if (isChainedPlayback) {
8867
+ this.preparePlaybackChain(playbackChain);
8868
+ } else {
8869
+ this.playbackSegments = [];
8672
8870
  }
8673
- if (this.clickedNode.hasAttribute("co-tts.next")) {
8674
- const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
8675
- if (nextNode && await this.playPrefetchedNode(nextNode)) {
8871
+ this.player.onAudioEnd = async () => {
8872
+ const wasChainedPlayback = this.playbackSegments.length > 0;
8873
+ this.stopPlayer();
8874
+ if (wasChainedPlayback) {
8875
+ document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8676
8876
  return;
8677
8877
  }
8678
- if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
8679
- this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
8680
- } else if (nextNode) {
8681
- nextNode.dispatchEvent(new Event("click"));
8878
+ if (this.clickedNode.hasAttribute("co-tts.next")) {
8879
+ const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
8880
+ if (nextNode && await this.playPrefetchedNode(nextNode)) {
8881
+ return;
8882
+ }
8883
+ if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
8884
+ this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
8885
+ } else if (nextNode) {
8886
+ nextNode.dispatchEvent(new Event("click"));
8887
+ }
8888
+ } else {
8889
+ document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8682
8890
  }
8683
- } else {
8684
- document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8685
- }
8686
- };
8687
- this.player.onAudioStart = async () => {
8688
- document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
8689
- };
8690
- if (!isChainedPlayback) {
8691
- this.prefetchNextNode(this.clickedNode);
8692
- }
8693
- this.synthesizer.speakSsmlAsync(
8694
- this.buildSSML(this.textToRead),
8695
- () => {
8696
- this.synthesizer.close();
8697
- this.synthesizer = void 0;
8698
- },
8699
- () => {
8700
- this.synthesizer.close();
8701
- this.synthesizer = void 0;
8891
+ };
8892
+ this.player.onAudioStart = async () => {
8893
+ document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
8894
+ };
8895
+ if (!isChainedPlayback) {
8896
+ this.prefetchNextNode(this.clickedNode);
8702
8897
  }
8703
- );
8898
+ this.synthesizer.speakSsmlAsync(
8899
+ this.buildSSML(this.textToRead),
8900
+ () => {
8901
+ this.closeSynthesizer();
8902
+ },
8903
+ (error) => {
8904
+ this.dispatchError(error, "SYNTHESIS_ERROR");
8905
+ this.closeSynthesizer();
8906
+ this.stopPlayer();
8907
+ }
8908
+ );
8909
+ } catch (error) {
8910
+ this.dispatchError(error);
8911
+ await this.stopPlayer();
8912
+ }
8704
8913
  }
8705
8914
  collectPlaybackChain(node) {
8706
8915
  var _a, _b;
@@ -8796,14 +9005,14 @@ class TextToSpeech {
8796
9005
  this.previousWordBoundary = wordBoundary;
8797
9006
  }
8798
9007
  getNodeText(node, attr) {
8799
- var _a;
9008
+ var _a, _b, _c;
8800
9009
  if (attr && attr.value !== "") {
8801
9010
  return attr.value;
8802
9011
  }
8803
9012
  if (node.hasAttribute("co-tts.text") && node.getAttribute("co-tts.text") !== "") {
8804
9013
  return (_a = node.getAttribute("co-tts.text")) != null ? _a : "";
8805
9014
  }
8806
- return node.innerText;
9015
+ return (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
8807
9016
  }
8808
9017
  getPrefetchKey(node, text) {
8809
9018
  return [
@@ -8842,41 +9051,52 @@ class TextToSpeech {
8842
9051
  if (this.prefetchedAudio.has(key) || this.prefetchPromises.has(key)) {
8843
9052
  return;
8844
9053
  }
8845
- const speechConfig = SpeechConfig.fromSubscription(this.key, this.region);
8846
- speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
8847
- speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
8848
- const synthesizer = new SpeechSynthesizer(speechConfig, null);
8849
- const wordBoundryList = [];
8850
- synthesizer.wordBoundary = (s, e) => {
8851
- wordBoundryList.push(e);
8852
- };
8853
- const prefetchPromise = new Promise((resolve) => {
8854
- synthesizer.speakSsmlAsync(
8855
- this.buildSSML(text),
8856
- (result) => {
8857
- synthesizer.close();
8858
- if (!(result == null ? void 0 : result.audioData)) {
8859
- resolve(null);
8860
- return;
8861
- }
8862
- const blob = new Blob([result.audioData], { type: "audio/mpeg" });
8863
- const url = URL.createObjectURL(blob);
8864
- const prefetch = {
8865
- key,
8866
- nodeId: nextNode.id,
8867
- text,
8868
- url,
8869
- wordBoundryList
8870
- };
8871
- this.prefetchedAudio.set(key, prefetch);
8872
- resolve(prefetch);
8873
- },
8874
- () => {
8875
- synthesizer.close();
8876
- resolve(null);
8877
- }
8878
- );
8879
- }).finally(() => {
9054
+ let synthesizer;
9055
+ const prefetchPromise = (async () => {
9056
+ try {
9057
+ const speechConfig = await this.createSpeechConfig();
9058
+ synthesizer = new SpeechSynthesizer(speechConfig, null);
9059
+ const wordBoundryList = [];
9060
+ synthesizer.wordBoundary = (s, e) => {
9061
+ wordBoundryList.push(e);
9062
+ };
9063
+ return await new Promise((resolve) => {
9064
+ synthesizer == null ? void 0 : synthesizer.speakSsmlAsync(
9065
+ this.buildSSML(text),
9066
+ (result) => {
9067
+ this.closeResource(synthesizer);
9068
+ synthesizer = void 0;
9069
+ if (!(result == null ? void 0 : result.audioData)) {
9070
+ resolve(null);
9071
+ return;
9072
+ }
9073
+ const blob = new Blob([result.audioData], { type: "audio/mpeg" });
9074
+ const url = URL.createObjectURL(blob);
9075
+ const prefetch = {
9076
+ key,
9077
+ nodeId: nextNode.id,
9078
+ text,
9079
+ url,
9080
+ wordBoundryList
9081
+ };
9082
+ this.prefetchedAudio.set(key, prefetch);
9083
+ resolve(prefetch);
9084
+ },
9085
+ (error) => {
9086
+ this.closeResource(synthesizer);
9087
+ synthesizer = void 0;
9088
+ this.dispatchError(error, "PREFETCH_ERROR");
9089
+ resolve(null);
9090
+ }
9091
+ );
9092
+ });
9093
+ } catch (error) {
9094
+ this.closeResource(synthesizer);
9095
+ synthesizer = void 0;
9096
+ this.dispatchError(error);
9097
+ return null;
9098
+ }
9099
+ })().finally(() => {
8880
9100
  this.prefetchPromises.delete(key);
8881
9101
  });
8882
9102
  this.prefetchPromises.set(key, prefetchPromise);
@@ -8913,31 +9133,44 @@ class TextToSpeech {
8913
9133
  }
8914
9134
  }
8915
9135
  await this.createInterval();
8916
- const audio = new Audio(prefetch.url);
8917
- this.activePrefetchedAudioUrl = prefetch.url;
8918
- this.player = audio;
8919
- audio.addEventListener("play", () => {
8920
- document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
8921
- }, { once: true });
8922
- audio.addEventListener("ended", async () => {
8923
- this.stopPlayer();
8924
- if (this.clickedNode.hasAttribute("co-tts.next")) {
8925
- const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
8926
- if (nextNode && await this.playPrefetchedNode(nextNode)) {
8927
- return;
8928
- }
8929
- if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
8930
- this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
8931
- } else if (nextNode) {
8932
- nextNode.dispatchEvent(new Event("click"));
9136
+ try {
9137
+ const audio = new Audio(prefetch.url);
9138
+ this.activePrefetchedAudioUrl = prefetch.url;
9139
+ this.player = audio;
9140
+ audio.addEventListener("play", () => {
9141
+ document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
9142
+ }, { once: true });
9143
+ audio.addEventListener("ended", async () => {
9144
+ this.stopPlayer();
9145
+ if (this.clickedNode.hasAttribute("co-tts.next")) {
9146
+ const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
9147
+ if (nextNode && await this.playPrefetchedNode(nextNode)) {
9148
+ return;
9149
+ }
9150
+ if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
9151
+ this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
9152
+ } else if (nextNode) {
9153
+ nextNode.dispatchEvent(new Event("click"));
9154
+ }
9155
+ } else {
9156
+ document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8933
9157
  }
8934
- } else {
8935
- document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8936
- }
8937
- }, { once: true });
8938
- this.prefetchNextNode(node);
8939
- await audio.play();
8940
- return true;
9158
+ }, { once: true });
9159
+ audio.addEventListener("error", () => {
9160
+ this.dispatchError(
9161
+ new Error("Prefetched audio resource closed unexpectedly."),
9162
+ "AUDIO_RESOURCE_ERROR"
9163
+ );
9164
+ this.stopPlayer();
9165
+ }, { once: true });
9166
+ this.prefetchNextNode(node);
9167
+ await audio.play();
9168
+ return true;
9169
+ } catch (error) {
9170
+ this.dispatchError(error, "AUDIO_RESOURCE_ERROR");
9171
+ await this.stopPlayer();
9172
+ return false;
9173
+ }
8941
9174
  }
8942
9175
  async clearInterval() {
8943
9176
  clearInterval(this.interval);
@@ -9040,5 +9273,31 @@ class TextToSpeech {
9040
9273
  p.textContent = input;
9041
9274
  return p.innerHTML;
9042
9275
  }
9276
+ closeSynthesizer() {
9277
+ this.closeResource(this.synthesizer);
9278
+ this.synthesizer = void 0;
9279
+ }
9280
+ closeResource(resource) {
9281
+ if (!resource || typeof resource.close !== "function") {
9282
+ return;
9283
+ }
9284
+ try {
9285
+ resource.close();
9286
+ } catch {
9287
+ }
9288
+ }
9289
+ dispatchError(error, code) {
9290
+ const detail = toSafeErrorDetail(error);
9291
+ if (code && !detail.code) {
9292
+ detail.code = code;
9293
+ }
9294
+ document.dispatchEvent(
9295
+ new CustomEvent("COAzureTTSError", {
9296
+ detail: {
9297
+ error: detail
9298
+ }
9299
+ })
9300
+ );
9301
+ }
9043
9302
  }
9044
9303
  export { SpeechToText, TextToSpeech };