@creativeorange/azure-text-to-speech 3.0.0 → 3.0.2

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.
@@ -4,6 +4,193 @@ var __publicField = (obj, key, value) => {
4
4
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  return value;
6
6
  };
7
+ const DEFAULT_TOKEN_LIFETIME_MS = 8 * 60 * 1e3;
8
+ function createSpeechAuthorizationProvider(options) {
9
+ var _a;
10
+ const hasTokenEndpoint = typeof options.tokenEndpoint === "string" && options.tokenEndpoint.trim() !== "";
11
+ const hasProvider = typeof options.getAuthorizationToken === "function";
12
+ if (hasTokenEndpoint && hasProvider) {
13
+ throw new Error(
14
+ "Provide either tokenEndpoint or getAuthorizationToken, not both."
15
+ );
16
+ }
17
+ if (!hasTokenEndpoint && !hasProvider) {
18
+ throw new Error(
19
+ "A tokenEndpoint or getAuthorizationToken provider is required."
20
+ );
21
+ }
22
+ if (hasProvider) {
23
+ return options.getAuthorizationToken;
24
+ }
25
+ const tokenEndpoint = options.tokenEndpoint.trim();
26
+ const requestOptions = (_a = options.tokenRequestOptions) != null ? _a : {};
27
+ return async () => {
28
+ var _a2, _b, _c;
29
+ let response;
30
+ try {
31
+ response = await fetch(tokenEndpoint, {
32
+ method: "GET",
33
+ cache: (_a2 = requestOptions.cache) != null ? _a2 : "no-store",
34
+ credentials: (_b = requestOptions.credentials) != null ? _b : "same-origin",
35
+ headers: {
36
+ Accept: "application/json",
37
+ ...(_c = requestOptions.headers) != null ? _c : {}
38
+ }
39
+ });
40
+ } catch {
41
+ throw createSafeError(
42
+ "Could not reach the speech token endpoint.",
43
+ "TOKEN_ENDPOINT_UNREACHABLE"
44
+ );
45
+ }
46
+ if (!response.ok) {
47
+ throw createSafeError(
48
+ `Speech token endpoint returned HTTP ${response.status}.`,
49
+ "TOKEN_ENDPOINT_HTTP_ERROR"
50
+ );
51
+ }
52
+ let payload;
53
+ try {
54
+ payload = await response.json();
55
+ } catch {
56
+ throw createSafeError(
57
+ "Speech token endpoint returned invalid JSON.",
58
+ "TOKEN_ENDPOINT_INVALID_JSON"
59
+ );
60
+ }
61
+ return validateSpeechAuthorization(payload);
62
+ };
63
+ }
64
+ function validateSpeechAuthorization(value) {
65
+ if (!value || typeof value !== "object") {
66
+ throw createSafeError(
67
+ "Speech authorization response must be an object.",
68
+ "TOKEN_INVALID_RESPONSE"
69
+ );
70
+ }
71
+ const candidate = value;
72
+ const token = typeof candidate.token === "string" ? candidate.token.trim() : "";
73
+ const region = typeof candidate.region === "string" ? candidate.region.trim() : "";
74
+ if (token === "") {
75
+ throw createSafeError(
76
+ "Speech authorization token is missing or empty.",
77
+ "TOKEN_EMPTY"
78
+ );
79
+ }
80
+ if (region === "") {
81
+ throw createSafeError(
82
+ "Speech authorization region is missing or empty.",
83
+ "TOKEN_REGION_MISSING"
84
+ );
85
+ }
86
+ return { token, region };
87
+ }
88
+ function createSafeError(message, code) {
89
+ const error = new Error(sanitizeErrorText(message));
90
+ error.message = sanitizeErrorText(message);
91
+ if (code) {
92
+ error.code = code;
93
+ }
94
+ return error;
95
+ }
96
+ function toSafeErrorDetail(error) {
97
+ if (typeof error === "string") {
98
+ const message = error.trim() !== "" ? sanitizeErrorText(error) : "An unexpected speech error occurred.";
99
+ return { message };
100
+ }
101
+ if (error && typeof error === "object") {
102
+ const candidate = error;
103
+ const rawMessage = [
104
+ typeof candidate.message === "string" ? candidate.message : "",
105
+ typeof candidate.errorDetails === "string" ? candidate.errorDetails : "",
106
+ typeof candidate.reason === "string" ? candidate.reason : ""
107
+ ].filter((part) => part.trim() !== "").join(" ");
108
+ const message = rawMessage !== "" ? sanitizeErrorText(rawMessage) : "An unexpected speech error occurred.";
109
+ const detail = { message };
110
+ if (typeof candidate.code === "string" && candidate.code.trim() !== "") {
111
+ detail.code = candidate.code;
112
+ } else if (typeof candidate.code === "number") {
113
+ detail.code = String(candidate.code);
114
+ }
115
+ return detail;
116
+ }
117
+ return {
118
+ message: "An unexpected speech error occurred."
119
+ };
120
+ }
121
+ function sanitizeErrorText(message) {
122
+ return message.replace(/Authorization\s*:\s*Bearer\s+\S+/gi, "Authorization: Bearer [redacted]").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(
123
+ /authorization[_-]?token["']?\s*[:=]\s*["']?[^"',\s}]+/gi,
124
+ "authorizationToken=[redacted]"
125
+ ).replace(
126
+ /access[_-]?token["']?\s*[:=]\s*["']?[^"',\s}]+/gi,
127
+ "accessToken=[redacted]"
128
+ ).replace(
129
+ /["']token["']\s*:\s*["'][^"']+["']/gi,
130
+ '"token":"[redacted]"'
131
+ ).replace(
132
+ /token["']?\s*[:=]\s*["']?[^"',\s}]+/gi,
133
+ "token=[redacted]"
134
+ );
135
+ }
136
+ function isLikelyAuthorizationError(error) {
137
+ var _a;
138
+ const detail = toSafeErrorDetail(error);
139
+ const haystack = `${detail.message} ${(_a = detail.code) != null ? _a : ""}`.toLowerCase();
140
+ return haystack.includes("unauthorized") || haystack.includes("authentication") || haystack.includes("authorization") || haystack.includes("invalid token") || haystack.includes("token expired") || haystack.includes("expired token") || haystack.includes("403") || haystack.includes("401") || detail.code === "TOKEN_REFRESH_ERROR";
141
+ }
142
+ class SpeechAuthorizationManager {
143
+ constructor(options) {
144
+ __publicField(this, "authorization");
145
+ __publicField(this, "authorizationExpiresAt", 0);
146
+ __publicField(this, "authorizationRequest");
147
+ __publicField(this, "provider");
148
+ __publicField(this, "tokenLifetimeMs");
149
+ this.provider = createSpeechAuthorizationProvider(options);
150
+ this.tokenLifetimeMs = options.tokenLifetimeMs && options.tokenLifetimeMs > 0 ? options.tokenLifetimeMs : DEFAULT_TOKEN_LIFETIME_MS;
151
+ }
152
+ getTokenLifetimeMs() {
153
+ return this.tokenLifetimeMs;
154
+ }
155
+ async getAuthorization() {
156
+ if (this.authorization && Date.now() < this.authorizationExpiresAt) {
157
+ return this.authorization;
158
+ }
159
+ if (this.authorizationRequest) {
160
+ return this.authorizationRequest;
161
+ }
162
+ this.authorizationRequest = this.fetchAuthorization().then((authorization) => {
163
+ this.authorization = authorization;
164
+ this.authorizationExpiresAt = Date.now() + this.tokenLifetimeMs;
165
+ return authorization;
166
+ }).finally(() => {
167
+ this.authorizationRequest = void 0;
168
+ });
169
+ return this.authorizationRequest;
170
+ }
171
+ async refreshAuthorization() {
172
+ this.authorization = void 0;
173
+ this.authorizationExpiresAt = 0;
174
+ if (this.authorizationRequest) {
175
+ return this.authorizationRequest;
176
+ }
177
+ return this.getAuthorization();
178
+ }
179
+ clearAuthorization() {
180
+ this.authorization = void 0;
181
+ this.authorizationExpiresAt = 0;
182
+ }
183
+ async fetchAuthorization() {
184
+ try {
185
+ const authorization = await this.provider();
186
+ return validateSpeechAuthorization(authorization);
187
+ } catch (error) {
188
+ this.clearAuthorization();
189
+ const detail = toSafeErrorDetail(error);
190
+ throw createSafeError(detail.message, detail.code);
191
+ }
192
+ }
193
+ }
7
194
  var ws = {};
8
195
  var getRandomValues;
9
196
  var rnds8 = new Uint8Array(16);
@@ -1079,7 +1266,7 @@ class Timeout {
1079
1266
  throw new Error(message);
1080
1267
  }
1081
1268
  });
1082
- const clearTimeout = (timerId) => {
1269
+ const clearTimeout2 = (timerId) => {
1083
1270
  const id = Math.random();
1084
1271
  unhandledRequests.set(id, timerId);
1085
1272
  scheduledTimeoutFunctions.set(timerId, id);
@@ -1104,7 +1291,7 @@ class Timeout {
1104
1291
  return timerId;
1105
1292
  };
1106
1293
  return {
1107
- clearTimeout,
1294
+ clearTimeout: clearTimeout2,
1108
1295
  setTimeout: setTimeout2
1109
1296
  };
1110
1297
  }
@@ -8350,170 +8537,24 @@ class RestMessageAdapter {
8350
8537
  return Object.keys(params).map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(params[k])).join("&");
8351
8538
  }
8352
8539
  }
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
- }
8502
8540
  class SpeechToText {
8503
8541
  constructor(options) {
8504
- __publicField(this, "region");
8542
+ __publicField(this, "region", "");
8505
8543
  __publicField(this, "sourceLanguage");
8506
8544
  __publicField(this, "targetLanguage");
8507
8545
  __publicField(this, "recognizer");
8508
8546
  __publicField(this, "authorizationManager");
8509
- var _a, _b, _c;
8547
+ __publicField(this, "authorizationRefreshIntervalMs");
8548
+ __publicField(this, "authorizationRefreshTimer");
8549
+ __publicField(this, "stopPromise");
8550
+ var _a;
8510
8551
  if (!options || typeof options.sourceLanguage !== "string" || options.sourceLanguage.trim() === "") {
8511
8552
  throw new Error("A sourceLanguage is required.");
8512
8553
  }
8513
8554
  this.authorizationManager = new SpeechAuthorizationManager(options);
8514
- this.region = (_b = (_a = options.region) == null ? void 0 : _a.trim()) != null ? _b : "";
8555
+ this.authorizationRefreshIntervalMs = options.tokenLifetimeMs && options.tokenLifetimeMs > 0 ? options.tokenLifetimeMs : DEFAULT_TOKEN_LIFETIME_MS;
8515
8556
  this.sourceLanguage = options.sourceLanguage;
8516
- this.targetLanguage = (_c = options.targetLanguage) != null ? _c : options.sourceLanguage;
8557
+ this.targetLanguage = (_a = options.targetLanguage) != null ? _a : options.sourceLanguage;
8517
8558
  }
8518
8559
  async start() {
8519
8560
  await this.registerBindings(document);
@@ -8527,9 +8568,15 @@ class SpeechToText {
8527
8568
  const currentNode = nodes[i];
8528
8569
  if (currentNode.attributes) {
8529
8570
  if (currentNode.attributes.getNamedItem("co-stt.start")) {
8530
- await this.handleStartModifier(currentNode, currentNode.attributes.getNamedItem("co-stt.start"));
8571
+ await this.handleStartModifier(
8572
+ currentNode,
8573
+ currentNode.attributes.getNamedItem("co-stt.start")
8574
+ );
8531
8575
  } else if (currentNode.attributes.getNamedItem("co-stt.stop")) {
8532
- await this.handleStopModifier(currentNode, currentNode.attributes.getNamedItem("co-stt.stop"));
8576
+ await this.handleStopModifier(
8577
+ currentNode,
8578
+ currentNode.attributes.getNamedItem("co-stt.stop")
8579
+ );
8533
8580
  }
8534
8581
  }
8535
8582
  if (currentNode.childNodes.length > 0) {
@@ -8553,12 +8600,13 @@ class SpeechToText {
8553
8600
  async handleStartModifier(node, attr) {
8554
8601
  node.addEventListener("click", async (_) => {
8555
8602
  try {
8603
+ await this.stop();
8556
8604
  const speechConfig = await this.createSpeechTranslationConfig();
8557
8605
  const audioConfig = AudioConfig.fromDefaultMicrophoneInput();
8558
8606
  this.recognizer = new TranslationRecognizer(speechConfig, audioConfig);
8559
8607
  document.dispatchEvent(new CustomEvent("COAzureSTTStartedRecording", {}));
8560
- const prevResults = [];
8561
- this.recognizer.recognizing = (sender, event) => {
8608
+ const prevResults = {};
8609
+ this.recognizer.recognizing = (_sender, event) => {
8562
8610
  const result = event.result;
8563
8611
  if (result && result.reason === ResultReason.TranslatingSpeech) {
8564
8612
  const translation = result.translations.get(this.targetLanguage);
@@ -8576,36 +8624,105 @@ class SpeechToText {
8576
8624
  };
8577
8625
  this.recognizer.startContinuousRecognitionAsync(
8578
8626
  () => {
8627
+ this.scheduleAuthorizationRefresh();
8579
8628
  },
8580
8629
  (err) => {
8630
+ if (isLikelyAuthorizationError(err)) {
8631
+ this.authorizationManager.clearAuthorization();
8632
+ }
8581
8633
  this.dispatchError(err, "RECOGNITION_ERROR");
8582
- this.stop();
8634
+ void this.stop();
8583
8635
  }
8584
8636
  );
8585
8637
  } catch (error) {
8638
+ this.clearAuthorizationRefreshTimer();
8639
+ if (isLikelyAuthorizationError(error)) {
8640
+ this.authorizationManager.clearAuthorization();
8641
+ }
8586
8642
  this.dispatchError(error);
8587
8643
  await this.stop();
8588
8644
  }
8589
8645
  });
8590
8646
  }
8591
- async handleStopModifier(node, attr) {
8647
+ async handleStopModifier(node, _attr) {
8592
8648
  node.addEventListener("click", async (_) => {
8593
8649
  await this.stop();
8594
8650
  });
8595
8651
  }
8596
8652
  async stop() {
8597
- if (this.recognizer !== void 0) {
8653
+ if (this.stopPromise) {
8654
+ return this.stopPromise;
8655
+ }
8656
+ this.clearAuthorizationRefreshTimer();
8657
+ const recognizer = this.recognizer;
8658
+ if (!recognizer) {
8659
+ return;
8660
+ }
8661
+ this.recognizer = void 0;
8662
+ this.stopPromise = this.stopRecognizer(recognizer).finally(() => {
8663
+ this.stopPromise = void 0;
8664
+ document.dispatchEvent(
8665
+ new CustomEvent("COAzureSTTStoppedRecording", {})
8666
+ );
8667
+ });
8668
+ return this.stopPromise;
8669
+ }
8670
+ scheduleAuthorizationRefresh() {
8671
+ this.clearAuthorizationRefreshTimer();
8672
+ this.authorizationRefreshTimer = setTimeout(async () => {
8673
+ if (!this.recognizer) {
8674
+ return;
8675
+ }
8598
8676
  try {
8599
- this.recognizer.stopContinuousRecognitionAsync();
8600
- } catch {
8677
+ const authorization = await this.authorizationManager.refreshAuthorization();
8678
+ if (!this.recognizer) {
8679
+ return;
8680
+ }
8681
+ this.recognizer.authorizationToken = authorization.token;
8682
+ this.region = authorization.region;
8683
+ this.scheduleAuthorizationRefresh();
8684
+ } catch (error) {
8685
+ this.dispatchError(error, "TOKEN_REFRESH_ERROR");
8686
+ await this.stop();
8601
8687
  }
8688
+ }, this.authorizationRefreshIntervalMs);
8689
+ }
8690
+ clearAuthorizationRefreshTimer() {
8691
+ if (!this.authorizationRefreshTimer) {
8692
+ return;
8693
+ }
8694
+ clearTimeout(this.authorizationRefreshTimer);
8695
+ this.authorizationRefreshTimer = void 0;
8696
+ }
8697
+ stopRecognizer(recognizer) {
8698
+ return new Promise((resolve) => {
8699
+ let closed = false;
8700
+ const closeOnce = () => {
8701
+ if (closed) {
8702
+ resolve();
8703
+ return;
8704
+ }
8705
+ closed = true;
8706
+ try {
8707
+ recognizer.close();
8708
+ } catch {
8709
+ } finally {
8710
+ resolve();
8711
+ }
8712
+ };
8602
8713
  try {
8603
- this.recognizer.close();
8714
+ recognizer.stopContinuousRecognitionAsync(
8715
+ () => {
8716
+ closeOnce();
8717
+ },
8718
+ () => {
8719
+ closeOnce();
8720
+ }
8721
+ );
8604
8722
  } catch {
8723
+ closeOnce();
8605
8724
  }
8606
- this.recognizer = void 0;
8607
- }
8608
- document.dispatchEvent(new CustomEvent("COAzureSTTStoppedRecording", {}));
8725
+ });
8609
8726
  }
8610
8727
  dispatchError(error, code) {
8611
8728
  const detail = toSafeErrorDetail(error);
@@ -8621,9 +8738,27 @@ class SpeechToText {
8621
8738
  );
8622
8739
  }
8623
8740
  }
8741
+ const PUNCTUATION_MARKS = [
8742
+ ".",
8743
+ ",",
8744
+ "!",
8745
+ "?",
8746
+ "*",
8747
+ "(",
8748
+ ")",
8749
+ "&",
8750
+ "\\",
8751
+ "/",
8752
+ "^",
8753
+ "[",
8754
+ "]",
8755
+ "<",
8756
+ ">",
8757
+ ":"
8758
+ ];
8624
8759
  class TextToSpeech {
8625
8760
  constructor(options) {
8626
- __publicField(this, "region");
8761
+ __publicField(this, "region", "");
8627
8762
  __publicField(this, "voice");
8628
8763
  __publicField(this, "rate");
8629
8764
  __publicField(this, "pitch");
@@ -8636,8 +8771,6 @@ class TextToSpeech {
8636
8771
  __publicField(this, "player");
8637
8772
  __publicField(this, "synthesizer");
8638
8773
  __publicField(this, "previousWordBoundary");
8639
- __publicField(this, "interval");
8640
- __publicField(this, "wordEncounters", []);
8641
8774
  __publicField(this, "originalHighlightDivInnerHTML", "");
8642
8775
  __publicField(this, "currentWord", "");
8643
8776
  __publicField(this, "currentOffset", 0);
@@ -8650,16 +8783,18 @@ class TextToSpeech {
8650
8783
  __publicField(this, "activePrefetchedAudioUrl", "");
8651
8784
  __publicField(this, "playbackSegments", []);
8652
8785
  __publicField(this, "authorizationManager");
8653
- var _a, _b, _c, _d, _e;
8786
+ __publicField(this, "stopPromise");
8787
+ __publicField(this, "playbackSessionId", 0);
8788
+ __publicField(this, "highlightInterval");
8789
+ var _a, _b, _c;
8654
8790
  if (!options || typeof options.voice !== "string" || options.voice.trim() === "") {
8655
8791
  throw new Error("A voice is required.");
8656
8792
  }
8657
8793
  this.authorizationManager = new SpeechAuthorizationManager(options);
8658
- this.region = (_b = (_a = options.region) == null ? void 0 : _a.trim()) != null ? _b : "";
8659
8794
  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 : "";
8795
+ this.rate = (_a = options.rate) != null ? _a : 0;
8796
+ this.pitch = (_b = options.pitch) != null ? _b : 0;
8797
+ this.url = (_c = options.url) != null ? _c : "";
8663
8798
  }
8664
8799
  async start() {
8665
8800
  await this.registerBindings(document);
@@ -8688,17 +8823,35 @@ class TextToSpeech {
8688
8823
  const currentNode = nodes[i];
8689
8824
  if (currentNode.attributes) {
8690
8825
  if (currentNode.attributes.getNamedItem("co-tts.id")) {
8691
- await this.handleIdModifier(currentNode, currentNode.attributes.getNamedItem("co-tts.id"));
8826
+ await this.handleIdModifier(
8827
+ currentNode,
8828
+ currentNode.attributes.getNamedItem("co-tts.id")
8829
+ );
8692
8830
  } else if (currentNode.attributes.getNamedItem("co-tts.ajax")) {
8693
- await this.handleAjaxModifier(currentNode, currentNode.attributes.getNamedItem("co-tts.ajax"));
8831
+ await this.handleAjaxModifier(
8832
+ currentNode,
8833
+ currentNode.attributes.getNamedItem("co-tts.ajax")
8834
+ );
8694
8835
  } else if (currentNode.attributes.getNamedItem("co-tts")) {
8695
- await this.handleDefault(currentNode, currentNode.attributes.getNamedItem("co-tts"));
8836
+ await this.handleDefault(
8837
+ currentNode,
8838
+ currentNode.attributes.getNamedItem("co-tts")
8839
+ );
8696
8840
  } else if (currentNode.attributes.getNamedItem("co-tts.stop")) {
8697
- await this.handleStopModifier(currentNode, currentNode.attributes.getNamedItem("co-tts.stop"));
8841
+ await this.handleStopModifier(
8842
+ currentNode,
8843
+ currentNode.attributes.getNamedItem("co-tts.stop")
8844
+ );
8698
8845
  } else if (currentNode.attributes.getNamedItem("co-tts.resume")) {
8699
- await this.handleResumeModifier(currentNode, currentNode.attributes.getNamedItem("co-tts.resume"));
8846
+ await this.handleResumeModifier(
8847
+ currentNode,
8848
+ currentNode.attributes.getNamedItem("co-tts.resume")
8849
+ );
8700
8850
  } else if (currentNode.attributes.getNamedItem("co-tts.pause")) {
8701
- await this.handlePauseModifier(currentNode, currentNode.attributes.getNamedItem("co-tts.pause"));
8851
+ await this.handlePauseModifier(
8852
+ currentNode,
8853
+ currentNode.attributes.getNamedItem("co-tts.pause")
8854
+ );
8702
8855
  }
8703
8856
  }
8704
8857
  if (currentNode.childNodes.length > 0) {
@@ -8708,9 +8861,8 @@ class TextToSpeech {
8708
8861
  }
8709
8862
  async handleIdModifier(node, attr) {
8710
8863
  node.addEventListener("click", async (_) => {
8711
- var _a, _b, _c, _d;
8712
- this.stopPlayer();
8713
- await this.createInterval();
8864
+ var _a, _b, _c;
8865
+ await this.stopPlayer();
8714
8866
  const referenceDiv = document.getElementById(attr.value);
8715
8867
  this.clickedNode = referenceDiv;
8716
8868
  if (!referenceDiv) {
@@ -8721,49 +8873,30 @@ class TextToSpeech {
8721
8873
  } else {
8722
8874
  this.textToRead = (_c = (_b = referenceDiv.innerText) != null ? _b : referenceDiv.textContent) != null ? _c : "";
8723
8875
  }
8724
- if (referenceDiv.hasAttribute("co-tts.highlight")) {
8725
- if (((_d = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _d.value) !== "") {
8726
- const newReferenceDiv = document.getElementById(referenceDiv.attributes.getNamedItem("co-tts.highlight").value);
8727
- this.highlightDiv = newReferenceDiv;
8728
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
8729
- } else {
8730
- this.highlightDiv = referenceDiv;
8731
- this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
8732
- }
8733
- }
8876
+ this.setHighlightTargetFromNode(referenceDiv);
8734
8877
  await this.startSynthesizer(node, attr);
8735
8878
  });
8736
8879
  }
8737
8880
  async handleAjaxModifier(node, attr) {
8738
8881
  node.addEventListener("click", async (_) => {
8739
- this.stopPlayer();
8740
- await this.createInterval();
8882
+ await this.stopPlayer();
8741
8883
  this.clickedNode = node;
8742
8884
  const response = await fetch(attr.value, {
8743
8885
  method: `GET`
8744
8886
  });
8745
8887
  this.textToRead = await response.text();
8888
+ this.setHighlightTargetFromNode(node);
8746
8889
  await this.startSynthesizer(node, attr);
8747
8890
  });
8748
8891
  }
8749
8892
  async handleDefault(node, attr) {
8750
8893
  node.addEventListener("click", async (_) => {
8751
- var _a, _b, _c;
8752
- this.stopPlayer();
8753
- await this.createInterval();
8894
+ var _a, _b;
8895
+ await this.stopPlayer();
8754
8896
  this.clickedNode = node;
8755
- if (node.hasAttribute("co-tts.highlight")) {
8756
- if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
8757
- const newReferenceDiv = document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
8758
- this.highlightDiv = newReferenceDiv;
8759
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
8760
- } else {
8761
- this.highlightDiv = node;
8762
- this.originalHighlightDivInnerHTML = node.innerHTML;
8763
- }
8764
- }
8897
+ this.setHighlightTargetFromNode(node);
8765
8898
  if (attr.value === "") {
8766
- this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
8899
+ this.textToRead = (_b = (_a = node.innerText) != null ? _a : node.textContent) != null ? _b : "";
8767
8900
  } else {
8768
8901
  this.textToRead = attr.value;
8769
8902
  }
@@ -8771,75 +8904,102 @@ class TextToSpeech {
8771
8904
  });
8772
8905
  }
8773
8906
  async handleWithoutClick(node, attr) {
8774
- var _a, _b, _c;
8775
- this.stopPlayer();
8776
- await this.createInterval();
8907
+ var _a, _b;
8908
+ await this.stopPlayer();
8777
8909
  this.clickedNode = node;
8778
- if (node.hasAttribute("co-tts.highlight")) {
8779
- if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
8780
- const newReferenceDiv = document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
8781
- this.highlightDiv = newReferenceDiv;
8782
- if (newReferenceDiv !== null) {
8783
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
8784
- }
8785
- } else {
8786
- this.highlightDiv = node;
8787
- this.originalHighlightDivInnerHTML = node.innerHTML;
8788
- }
8789
- }
8910
+ this.setHighlightTargetFromNode(node);
8790
8911
  if (attr.value === "") {
8791
- this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
8912
+ this.textToRead = (_b = (_a = node.innerText) != null ? _a : node.textContent) != null ? _b : "";
8792
8913
  } else {
8793
8914
  this.textToRead = attr.value;
8794
8915
  }
8795
8916
  await this.startSynthesizer(node, attr);
8796
8917
  }
8797
- async handleStopModifier(node, attr) {
8918
+ async handleStopModifier(node, _attr) {
8798
8919
  node.addEventListener("click", async (_) => {
8799
8920
  await this.stopPlayer();
8800
8921
  document.dispatchEvent(new CustomEvent("COAzureTTSStoppedPlaying", {}));
8801
8922
  });
8802
8923
  }
8803
- async handlePauseModifier(node, attr) {
8924
+ async handlePauseModifier(node, _attr) {
8804
8925
  node.addEventListener("click", async (_) => {
8805
- await this.clearInterval();
8926
+ if (!this.player || typeof this.player.pause !== "function") {
8927
+ return;
8928
+ }
8929
+ this.stopHighlightingInterval();
8806
8930
  await this.player.pause();
8807
8931
  document.dispatchEvent(new CustomEvent("COAzureTTSPausedPlaying", {}));
8808
8932
  });
8809
8933
  }
8810
- async handleResumeModifier(node, attr) {
8934
+ async handleResumeModifier(node, _attr) {
8811
8935
  node.addEventListener("click", async (_) => {
8812
- await this.createInterval();
8813
- await this.player.resume();
8936
+ if (!this.player) {
8937
+ return;
8938
+ }
8939
+ if (this.shouldHighlight()) {
8940
+ this.startHighlightingInterval(this.playbackSessionId);
8941
+ }
8942
+ if (typeof this.player.resume === "function") {
8943
+ await this.player.resume();
8944
+ } else if (typeof this.player.play === "function") {
8945
+ await this.player.play();
8946
+ } else {
8947
+ return;
8948
+ }
8814
8949
  document.dispatchEvent(new CustomEvent("COAzureTTSResumedPlaying", {}));
8815
8950
  });
8816
8951
  }
8817
8952
  async stopPlayer() {
8818
- await this.clearInterval();
8819
- if (this.highlightDiv !== void 0) {
8953
+ if (this.stopPromise) {
8954
+ return this.stopPromise;
8955
+ }
8956
+ this.stopPromise = this.performStopPlayer().finally(() => {
8957
+ this.stopPromise = void 0;
8958
+ });
8959
+ return this.stopPromise;
8960
+ }
8961
+ async performStopPlayer() {
8962
+ this.invalidatePlaybackSession();
8963
+ this.stopHighlightingInterval();
8964
+ if (this.highlightDiv) {
8820
8965
  this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
8821
8966
  }
8967
+ this.resetPlaybackSegments();
8968
+ const player = this.player;
8969
+ const activePrefetchedAudioUrl = this.activePrefetchedAudioUrl;
8970
+ const synthesizer = this.synthesizer;
8822
8971
  this.textToRead = "";
8823
8972
  this.currentWord = "";
8973
+ this.currentOffset = 0;
8974
+ this.wordBoundaryOffset = 0;
8824
8975
  this.originalHighlightDivInnerHTML = "";
8825
8976
  this.wordBoundryList = [];
8826
- this.wordEncounters = [];
8827
- this.resetPlaybackSegments();
8828
8977
  this.playbackSegments = [];
8829
- if (this.player !== void 0) {
8830
- this.player.pause();
8831
- }
8832
- if (this.activePrefetchedAudioUrl !== "") {
8833
- URL.revokeObjectURL(this.activePrefetchedAudioUrl);
8834
- this.activePrefetchedAudioUrl = "";
8835
- }
8836
- this.closeSynthesizer();
8837
- this.player = void 0;
8838
- this.audioConfig = void 0;
8839
- this.speechConfig = void 0;
8978
+ this.previousWordBoundary = void 0;
8840
8979
  this.highlightDiv = void 0;
8841
8980
  this.prevTextOffset = 0;
8842
8981
  this.playbackTextOffsetBase = void 0;
8982
+ this.audioConfig = void 0;
8983
+ this.speechConfig = void 0;
8984
+ if (this.player === player) {
8985
+ this.player = void 0;
8986
+ }
8987
+ if (this.activePrefetchedAudioUrl === activePrefetchedAudioUrl) {
8988
+ this.activePrefetchedAudioUrl = "";
8989
+ }
8990
+ if (this.synthesizer === synthesizer) {
8991
+ this.synthesizer = void 0;
8992
+ }
8993
+ if (player !== void 0 && typeof player.pause === "function") {
8994
+ try {
8995
+ player.pause();
8996
+ } catch {
8997
+ }
8998
+ }
8999
+ if (activePrefetchedAudioUrl !== "") {
9000
+ URL.revokeObjectURL(activePrefetchedAudioUrl);
9001
+ }
9002
+ this.closeResource(synthesizer);
8843
9003
  }
8844
9004
  async createSpeechConfig() {
8845
9005
  const authorization = await this.authorizationManager.getAuthorization();
@@ -8852,13 +9012,21 @@ class TextToSpeech {
8852
9012
  this.region = authorization.region;
8853
9013
  return speechConfig;
8854
9014
  }
8855
- async startSynthesizer(node, attr) {
9015
+ async startSynthesizer(_node, _attr) {
8856
9016
  try {
8857
9017
  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) => {
9018
+ const sessionId = this.createPlaybackSession();
9019
+ this.resetHighlightState();
9020
+ const player = new SpeakerAudioDestination();
9021
+ const audioConfig = AudioConfig.fromSpeakerOutput(player);
9022
+ const synthesizer = new SpeechSynthesizer(this.speechConfig, audioConfig);
9023
+ this.player = player;
9024
+ this.audioConfig = audioConfig;
9025
+ this.synthesizer = synthesizer;
9026
+ synthesizer.wordBoundary = (_s, e) => {
9027
+ if (!this.isActivePlaybackSession(sessionId)) {
9028
+ return;
9029
+ }
8862
9030
  this.wordBoundryList.push(e);
8863
9031
  };
8864
9032
  const playbackChain = this.collectPlaybackChain(this.clickedNode);
@@ -8868,20 +9036,27 @@ class TextToSpeech {
8868
9036
  } else {
8869
9037
  this.playbackSegments = [];
8870
9038
  }
8871
- this.player.onAudioEnd = async () => {
9039
+ player.onAudioEnd = async () => {
9040
+ var _a;
9041
+ if (!this.isActivePlaybackSession(sessionId)) {
9042
+ return;
9043
+ }
9044
+ const clickedNode = this.clickedNode;
8872
9045
  const wasChainedPlayback = this.playbackSegments.length > 0;
8873
- this.stopPlayer();
9046
+ const nextNodeId = ((_a = clickedNode == null ? void 0 : clickedNode.hasAttribute) == null ? void 0 : _a.call(clickedNode, "co-tts.next")) ? clickedNode.getAttribute("co-tts.next") : null;
9047
+ await this.stopPlayer();
8874
9048
  if (wasChainedPlayback) {
8875
9049
  document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8876
9050
  return;
8877
9051
  }
8878
- if (this.clickedNode.hasAttribute("co-tts.next")) {
8879
- const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
9052
+ if (nextNodeId) {
9053
+ const nextNode = document.getElementById(nextNodeId);
8880
9054
  if (nextNode && await this.playPrefetchedNode(nextNode)) {
8881
9055
  return;
8882
9056
  }
8883
- if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
8884
- this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
9057
+ const nextTextAttr = nextNode == null ? void 0 : nextNode.attributes.getNamedItem("co-tts.text");
9058
+ if (nextNode && nextTextAttr) {
9059
+ await this.handleWithoutClick(nextNode, nextTextAttr);
8885
9060
  } else if (nextNode) {
8886
9061
  nextNode.dispatchEvent(new Event("click"));
8887
9062
  }
@@ -8889,24 +9064,46 @@ class TextToSpeech {
8889
9064
  document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
8890
9065
  }
8891
9066
  };
8892
- this.player.onAudioStart = async () => {
9067
+ player.onAudioStart = async () => {
9068
+ if (!this.isActivePlaybackSession(sessionId)) {
9069
+ return;
9070
+ }
8893
9071
  document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
8894
9072
  };
9073
+ if (this.shouldHighlight()) {
9074
+ this.startHighlightingInterval(sessionId);
9075
+ }
8895
9076
  if (!isChainedPlayback) {
8896
- this.prefetchNextNode(this.clickedNode);
9077
+ void this.prefetchNextNode(this.clickedNode);
8897
9078
  }
8898
- this.synthesizer.speakSsmlAsync(
8899
- this.buildSSML(this.textToRead),
9079
+ const ssml = this.buildSSML(this.textToRead);
9080
+ synthesizer.speakSsmlAsync(
9081
+ ssml,
8900
9082
  () => {
8901
- this.closeSynthesizer();
9083
+ this.closeResource(synthesizer);
9084
+ if (this.isActivePlaybackSession(sessionId) && this.synthesizer === synthesizer) {
9085
+ this.synthesizer = void 0;
9086
+ }
8902
9087
  },
8903
9088
  (error) => {
9089
+ this.closeResource(synthesizer);
9090
+ if (!this.isActivePlaybackSession(sessionId)) {
9091
+ return;
9092
+ }
9093
+ if (this.synthesizer === synthesizer) {
9094
+ this.synthesizer = void 0;
9095
+ }
9096
+ if (isLikelyAuthorizationError(error)) {
9097
+ this.authorizationManager.clearAuthorization();
9098
+ }
8904
9099
  this.dispatchError(error, "SYNTHESIS_ERROR");
8905
- this.closeSynthesizer();
8906
- this.stopPlayer();
9100
+ void this.stopPlayer();
8907
9101
  }
8908
9102
  );
8909
9103
  } catch (error) {
9104
+ if (isLikelyAuthorizationError(error)) {
9105
+ this.authorizationManager.clearAuthorization();
9106
+ }
8910
9107
  this.dispatchError(error);
8911
9108
  await this.stopPlayer();
8912
9109
  }
@@ -8946,21 +9143,16 @@ class TextToSpeech {
8946
9143
  this.textToRead = chain.map((segment) => segment.text).join(" ");
8947
9144
  this.highlightDiv = void 0;
8948
9145
  this.originalHighlightDivInnerHTML = "";
8949
- this.wordEncounters = [];
8950
- this.previousWordBoundary = void 0;
8951
- this.prevTextOffset = 0;
8952
- this.playbackTextOffsetBase = void 0;
8953
- this.currentWord = "";
8954
- this.currentOffset = 0;
8955
- this.wordBoundaryOffset = 0;
9146
+ this.resetHighlightState();
8956
9147
  }
8957
9148
  getHighlightDivForNode(node) {
8958
- var _a;
8959
- if (!node.hasAttribute("co-tts.highlight")) {
9149
+ var _a, _b, _c, _d;
9150
+ if (!((_a = node == null ? void 0 : node.hasAttribute) == null ? void 0 : _a.call(node, "co-tts.highlight"))) {
8960
9151
  return void 0;
8961
9152
  }
8962
- if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
8963
- return document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
9153
+ const highlightTarget = (_c = (_b = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _b.value) != null ? _c : "";
9154
+ if (highlightTarget !== "") {
9155
+ return (_d = document.getElementById(highlightTarget)) != null ? _d : void 0;
8964
9156
  }
8965
9157
  return node;
8966
9158
  }
@@ -8972,18 +9164,19 @@ class TextToSpeech {
8972
9164
  });
8973
9165
  }
8974
9166
  updateChainedHighlight(wordBoundary) {
8975
- var _a;
8976
- if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
9167
+ var _a, _b;
9168
+ if (~PUNCTUATION_MARKS.indexOf(wordBoundary.text)) {
8977
9169
  wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
8978
9170
  }
8979
9171
  if (!wordBoundary) {
8980
9172
  this.resetPlaybackSegments();
8981
9173
  return;
8982
9174
  }
8983
- if (this.playbackTextOffsetBase === void 0) {
8984
- this.playbackTextOffsetBase = wordBoundary.textOffset;
8985
- }
8986
- const normalizedTextOffset = Math.max(0, wordBoundary.textOffset - this.playbackTextOffsetBase);
9175
+ this.ensurePlaybackTextOffsetBase();
9176
+ const normalizedTextOffset = Math.max(
9177
+ 0,
9178
+ wordBoundary.textOffset - ((_b = this.playbackTextOffsetBase) != null ? _b : 0)
9179
+ );
8987
9180
  const segment = this.playbackSegments.find((candidate) => normalizedTextOffset >= candidate.start && normalizedTextOffset < candidate.end);
8988
9181
  this.resetPlaybackSegments();
8989
9182
  if (!(segment == null ? void 0 : segment.highlightDiv)) {
@@ -8991,17 +9184,12 @@ class TextToSpeech {
8991
9184
  return;
8992
9185
  }
8993
9186
  const relativeTextOffset = normalizedTextOffset - segment.start;
8994
- const currentOffset = this.getPosition(segment.originalHighlightDivInnerHTML, wordBoundary.text, relativeTextOffset);
8995
- if (currentOffset === Number.MAX_SAFE_INTEGER) {
8996
- this.previousWordBoundary = wordBoundary;
8997
- return;
8998
- }
8999
- const startOfString = segment.originalHighlightDivInnerHTML.substring(0, currentOffset);
9000
- const endOffset = currentOffset + wordBoundary.wordLength;
9001
- const endOfString = segment.originalHighlightDivInnerHTML.substring(endOffset);
9002
- segment.highlightDiv.innerHTML = `
9003
- ${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
9004
- `;
9187
+ this.highlightTextRange(
9188
+ segment.highlightDiv,
9189
+ segment.originalHighlightDivInnerHTML,
9190
+ relativeTextOffset,
9191
+ wordBoundary.wordLength
9192
+ );
9005
9193
  this.previousWordBoundary = wordBoundary;
9006
9194
  }
9007
9195
  getNodeText(node, attr) {
@@ -9055,9 +9243,9 @@ class TextToSpeech {
9055
9243
  const prefetchPromise = (async () => {
9056
9244
  try {
9057
9245
  const speechConfig = await this.createSpeechConfig();
9058
- synthesizer = new SpeechSynthesizer(speechConfig, null);
9246
+ synthesizer = new SpeechSynthesizer(speechConfig, void 0);
9059
9247
  const wordBoundryList = [];
9060
- synthesizer.wordBoundary = (s, e) => {
9248
+ synthesizer.wordBoundary = (_s, e) => {
9061
9249
  wordBoundryList.push(e);
9062
9250
  };
9063
9251
  return await new Promise((resolve) => {
@@ -9085,6 +9273,9 @@ class TextToSpeech {
9085
9273
  (error) => {
9086
9274
  this.closeResource(synthesizer);
9087
9275
  synthesizer = void 0;
9276
+ if (isLikelyAuthorizationError(error)) {
9277
+ this.authorizationManager.clearAuthorization();
9278
+ }
9088
9279
  this.dispatchError(error, "PREFETCH_ERROR");
9089
9280
  resolve(null);
9090
9281
  }
@@ -9093,6 +9284,9 @@ class TextToSpeech {
9093
9284
  } catch (error) {
9094
9285
  this.closeResource(synthesizer);
9095
9286
  synthesizer = void 0;
9287
+ if (isLikelyAuthorizationError(error)) {
9288
+ this.authorizationManager.clearAuthorization();
9289
+ }
9096
9290
  this.dispatchError(error);
9097
9291
  return null;
9098
9292
  }
@@ -9102,7 +9296,7 @@ class TextToSpeech {
9102
9296
  this.prefetchPromises.set(key, prefetchPromise);
9103
9297
  }
9104
9298
  async playPrefetchedNode(node) {
9105
- var _a, _b, _c;
9299
+ var _a, _b;
9106
9300
  const attr = (_a = node.attributes.getNamedItem("co-tts.text")) != null ? _a : node.attributes.getNamedItem("co-tts");
9107
9301
  const text = this.getNodeText(node, attr);
9108
9302
  const key = this.getPrefetchKey(node, text);
@@ -9111,44 +9305,39 @@ class TextToSpeech {
9111
9305
  return false;
9112
9306
  }
9113
9307
  this.prefetchedAudio.delete(key);
9308
+ const sessionId = this.createPlaybackSession();
9309
+ this.resetHighlightState();
9114
9310
  this.clickedNode = node;
9115
9311
  this.textToRead = prefetch.text;
9116
9312
  this.wordBoundryList = prefetch.wordBoundryList;
9117
- this.wordEncounters = [];
9118
- this.previousWordBoundary = void 0;
9119
- this.prevTextOffset = 0;
9120
- this.currentWord = "";
9121
- this.currentOffset = 0;
9122
- this.wordBoundaryOffset = 0;
9123
- if (node.hasAttribute("co-tts.highlight")) {
9124
- if (((_c = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _c.value) !== "") {
9125
- const newReferenceDiv = document.getElementById(node.attributes.getNamedItem("co-tts.highlight").value);
9126
- this.highlightDiv = newReferenceDiv;
9127
- if (newReferenceDiv !== null) {
9128
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
9129
- }
9130
- } else {
9131
- this.highlightDiv = node;
9132
- this.originalHighlightDivInnerHTML = node.innerHTML;
9133
- }
9134
- }
9135
- await this.createInterval();
9313
+ this.playbackSegments = [];
9314
+ this.setHighlightTargetFromNode(node);
9136
9315
  try {
9137
9316
  const audio = new Audio(prefetch.url);
9138
9317
  this.activePrefetchedAudioUrl = prefetch.url;
9139
9318
  this.player = audio;
9140
9319
  audio.addEventListener("play", () => {
9320
+ if (!this.isActivePlaybackSession(sessionId)) {
9321
+ return;
9322
+ }
9141
9323
  document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
9142
9324
  }, { once: true });
9143
9325
  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"));
9326
+ var _a2;
9327
+ if (!this.isActivePlaybackSession(sessionId)) {
9328
+ return;
9329
+ }
9330
+ const clickedNode = this.clickedNode;
9331
+ const nextNodeId = ((_a2 = clickedNode == null ? void 0 : clickedNode.hasAttribute) == null ? void 0 : _a2.call(clickedNode, "co-tts.next")) ? clickedNode.getAttribute("co-tts.next") : null;
9332
+ await this.stopPlayer();
9333
+ if (nextNodeId) {
9334
+ const nextNode = document.getElementById(nextNodeId);
9147
9335
  if (nextNode && await this.playPrefetchedNode(nextNode)) {
9148
9336
  return;
9149
9337
  }
9150
- if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
9151
- this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
9338
+ const nextTextAttr = nextNode == null ? void 0 : nextNode.attributes.getNamedItem("co-tts.text");
9339
+ if (nextNode && nextTextAttr) {
9340
+ await this.handleWithoutClick(nextNode, nextTextAttr);
9152
9341
  } else if (nextNode) {
9153
9342
  nextNode.dispatchEvent(new Event("click"));
9154
9343
  }
@@ -9157,101 +9346,186 @@ class TextToSpeech {
9157
9346
  }
9158
9347
  }, { once: true });
9159
9348
  audio.addEventListener("error", () => {
9349
+ if (!this.isActivePlaybackSession(sessionId)) {
9350
+ return;
9351
+ }
9160
9352
  this.dispatchError(
9161
9353
  new Error("Prefetched audio resource closed unexpectedly."),
9162
9354
  "AUDIO_RESOURCE_ERROR"
9163
9355
  );
9164
- this.stopPlayer();
9356
+ void this.stopPlayer();
9165
9357
  }, { once: true });
9166
- this.prefetchNextNode(node);
9358
+ if (this.shouldHighlight()) {
9359
+ this.startHighlightingInterval(sessionId);
9360
+ }
9361
+ void this.prefetchNextNode(node);
9167
9362
  await audio.play();
9168
9363
  return true;
9169
9364
  } catch (error) {
9365
+ this.stopHighlightingInterval();
9170
9366
  this.dispatchError(error, "AUDIO_RESOURCE_ERROR");
9171
9367
  await this.stopPlayer();
9172
9368
  return false;
9173
9369
  }
9174
9370
  }
9175
- async clearInterval() {
9176
- clearInterval(this.interval);
9371
+ createPlaybackSession() {
9372
+ this.playbackSessionId += 1;
9373
+ return this.playbackSessionId;
9177
9374
  }
9178
- async createInterval() {
9179
- this.interval = setInterval(() => {
9180
- var _a;
9181
- if (this.player !== void 0 && (this.highlightDiv || this.playbackSegments.length > 0)) {
9182
- const currentTime = this.player.currentTime;
9183
- let wordBoundary;
9184
- for (const e of this.wordBoundryList) {
9185
- if (currentTime * 1e3 > e.audioOffset / 1e4) {
9186
- wordBoundary = e;
9187
- } else {
9188
- break;
9189
- }
9190
- }
9191
- if (wordBoundary !== void 0) {
9192
- if (this.playbackSegments.length > 0) {
9193
- this.updateChainedHighlight(wordBoundary);
9194
- return;
9195
- }
9196
- if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
9197
- wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
9198
- }
9199
- if (wordBoundary === void 0 || this.prevTextOffset > wordBoundary.prevTextOffset) {
9200
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9201
- } else {
9202
- if (!this.wordEncounters[wordBoundary.text]) {
9203
- this.wordEncounters[wordBoundary.text] = 0;
9204
- }
9205
- this.prevTextOffset = wordBoundary.prevTextOffset;
9206
- if (this.currentWord !== wordBoundary.text || this.wordBoundaryOffset !== wordBoundary.textOffset) {
9207
- this.currentOffset = this.getPosition(
9208
- this.originalHighlightDivInnerHTML,
9209
- wordBoundary.text,
9210
- wordBoundary.textOffset
9211
- );
9212
- this.wordEncounters[wordBoundary.text] = this.currentOffset + wordBoundary.wordLength;
9213
- this.currentWord = wordBoundary.text;
9214
- this.wordBoundaryOffset = wordBoundary.textOffset;
9215
- }
9216
- if (this.currentOffset === Number.MAX_SAFE_INTEGER) {
9217
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9218
- } else {
9219
- this.previousWordBoundary = wordBoundary;
9220
- const startOfString = this.originalHighlightDivInnerHTML.substring(0, this.currentOffset);
9221
- const endOffset = this.currentOffset + wordBoundary.wordLength;
9222
- const endOfString = this.originalHighlightDivInnerHTML.substring(endOffset);
9223
- this.highlightDiv.innerHTML = `
9224
- ${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
9225
- `;
9226
- }
9227
- }
9228
- } else if (this.playbackSegments.length > 0) {
9229
- this.resetPlaybackSegments();
9230
- } else {
9231
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9232
- }
9375
+ isActivePlaybackSession(sessionId) {
9376
+ return sessionId === this.playbackSessionId;
9377
+ }
9378
+ invalidatePlaybackSession() {
9379
+ this.playbackSessionId += 1;
9380
+ }
9381
+ stopHighlightingInterval() {
9382
+ if (!this.highlightInterval) {
9383
+ return;
9384
+ }
9385
+ clearInterval(this.highlightInterval);
9386
+ this.highlightInterval = void 0;
9387
+ }
9388
+ startHighlightingInterval(sessionId) {
9389
+ this.stopHighlightingInterval();
9390
+ this.highlightInterval = setInterval(() => {
9391
+ if (!this.isActivePlaybackSession(sessionId)) {
9392
+ this.stopHighlightingInterval();
9393
+ return;
9233
9394
  }
9395
+ this.updateHighlightForCurrentTime(sessionId);
9234
9396
  }, 50);
9235
9397
  }
9236
- getPosition(string, subString, textOffset) {
9237
- let visibleOffset = 0;
9238
- let insideTag = false;
9239
- for (let htmlOffset = 0; htmlOffset < string.length; htmlOffset++) {
9240
- const character = string[htmlOffset];
9241
- if (character === "<") {
9242
- insideTag = true;
9243
- }
9244
- if (!insideTag) {
9245
- if (visibleOffset === textOffset) {
9246
- return htmlOffset;
9247
- }
9248
- visibleOffset++;
9398
+ updateHighlightForCurrentTime(sessionId) {
9399
+ var _a, _b;
9400
+ if (!this.isActivePlaybackSession(sessionId) || this.player === void 0) {
9401
+ return;
9402
+ }
9403
+ if (!this.shouldHighlight()) {
9404
+ return;
9405
+ }
9406
+ const currentTime = this.player.currentTime;
9407
+ let wordBoundary;
9408
+ for (const e of this.wordBoundryList) {
9409
+ if (currentTime * 1e3 > e.audioOffset / 1e4) {
9410
+ wordBoundary = e;
9411
+ } else {
9412
+ break;
9249
9413
  }
9250
- if (character === ">") {
9251
- insideTag = false;
9414
+ }
9415
+ if (wordBoundary === void 0) {
9416
+ this.resetCurrentHighlight();
9417
+ return;
9418
+ }
9419
+ if (this.playbackSegments.length > 0) {
9420
+ this.updateChainedHighlight(wordBoundary);
9421
+ return;
9422
+ }
9423
+ if (~PUNCTUATION_MARKS.indexOf(wordBoundary.text)) {
9424
+ wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
9425
+ }
9426
+ if (wordBoundary === void 0 || this.prevTextOffset > wordBoundary.textOffset) {
9427
+ this.resetCurrentHighlight();
9428
+ return;
9429
+ }
9430
+ this.prevTextOffset = wordBoundary.textOffset;
9431
+ this.ensurePlaybackTextOffsetBase();
9432
+ const normalizedTextOffset = Math.max(
9433
+ 0,
9434
+ wordBoundary.textOffset - ((_b = this.playbackTextOffsetBase) != null ? _b : 0)
9435
+ );
9436
+ this.highlightTextRange(
9437
+ this.highlightDiv,
9438
+ this.originalHighlightDivInnerHTML,
9439
+ normalizedTextOffset,
9440
+ wordBoundary.wordLength
9441
+ );
9442
+ this.previousWordBoundary = wordBoundary;
9443
+ this.currentWord = wordBoundary.text;
9444
+ this.wordBoundaryOffset = wordBoundary.textOffset;
9445
+ }
9446
+ highlightTextRange(target, originalHtml, textOffset, wordLength) {
9447
+ if (!target || wordLength <= 0) {
9448
+ return;
9449
+ }
9450
+ target.innerHTML = originalHtml;
9451
+ const walker = document.createTreeWalker(
9452
+ target,
9453
+ NodeFilter.SHOW_TEXT
9454
+ );
9455
+ let currentOffset = 0;
9456
+ let startNode;
9457
+ let endNode;
9458
+ let startOffset = 0;
9459
+ let endOffset = 0;
9460
+ const requestedEnd = textOffset + wordLength;
9461
+ while (walker.nextNode()) {
9462
+ const node = walker.currentNode;
9463
+ const nodeLength = node.data.length;
9464
+ const nodeStart = currentOffset;
9465
+ const nodeEnd = currentOffset + nodeLength;
9466
+ if (!startNode && textOffset >= nodeStart && textOffset < nodeEnd) {
9467
+ startNode = node;
9468
+ startOffset = textOffset - nodeStart;
9469
+ }
9470
+ if (requestedEnd > nodeStart && requestedEnd <= nodeEnd) {
9471
+ endNode = node;
9472
+ endOffset = requestedEnd - nodeStart;
9473
+ break;
9252
9474
  }
9475
+ currentOffset = nodeEnd;
9476
+ }
9477
+ if (!startNode || !endNode) {
9478
+ return;
9479
+ }
9480
+ const range = document.createRange();
9481
+ try {
9482
+ range.setStart(startNode, startOffset);
9483
+ range.setEnd(endNode, endOffset);
9484
+ const mark = document.createElement("mark");
9485
+ mark.className = "co-tts-highlight";
9486
+ mark.appendChild(range.extractContents());
9487
+ range.insertNode(mark);
9488
+ } catch {
9489
+ target.innerHTML = originalHtml;
9490
+ }
9491
+ }
9492
+ setHighlightTargetFromNode(node) {
9493
+ var _a;
9494
+ const highlightDiv = this.getHighlightDivForNode(node);
9495
+ this.highlightDiv = highlightDiv;
9496
+ this.originalHighlightDivInnerHTML = (_a = highlightDiv == null ? void 0 : highlightDiv.innerHTML) != null ? _a : "";
9497
+ }
9498
+ shouldHighlight() {
9499
+ if (this.highlightDiv) {
9500
+ return true;
9501
+ }
9502
+ return this.playbackSegments.some((segment) => Boolean(segment.highlightDiv));
9503
+ }
9504
+ resetHighlightState() {
9505
+ this.prevTextOffset = 0;
9506
+ this.previousWordBoundary = void 0;
9507
+ this.currentWord = "";
9508
+ this.currentOffset = 0;
9509
+ this.wordBoundaryOffset = 0;
9510
+ this.playbackTextOffsetBase = void 0;
9511
+ }
9512
+ ensurePlaybackTextOffsetBase() {
9513
+ if (this.playbackTextOffsetBase !== void 0 || this.wordBoundryList.length === 0) {
9514
+ return;
9515
+ }
9516
+ this.playbackTextOffsetBase = this.wordBoundryList.reduce(
9517
+ (minimum, boundary) => Math.min(minimum, boundary.textOffset),
9518
+ this.wordBoundryList[0].textOffset
9519
+ );
9520
+ }
9521
+ resetCurrentHighlight() {
9522
+ if (this.playbackSegments.length > 0) {
9523
+ this.resetPlaybackSegments();
9524
+ return;
9525
+ }
9526
+ if (this.highlightDiv) {
9527
+ this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9253
9528
  }
9254
- return string.indexOf(subString);
9255
9529
  }
9256
9530
  buildSSML(text) {
9257
9531
  let ssml = `<speak xmlns="http://www.w3.org/2001/10/synthesis"
@@ -9273,10 +9547,6 @@ class TextToSpeech {
9273
9547
  p.textContent = input;
9274
9548
  return p.innerHTML;
9275
9549
  }
9276
- closeSynthesizer() {
9277
- this.closeResource(this.synthesizer);
9278
- this.synthesizer = void 0;
9279
- }
9280
9550
  closeResource(resource) {
9281
9551
  if (!resource || typeof resource.close !== "function") {
9282
9552
  return;
@@ -9300,4 +9570,4 @@ class TextToSpeech {
9300
9570
  );
9301
9571
  }
9302
9572
  }
9303
- export { SpeechToText, TextToSpeech };
9573
+ export { DEFAULT_TOKEN_LIFETIME_MS, SpeechAuthorizationManager, SpeechToText, TextToSpeech, createSpeechAuthorizationProvider, isLikelyAuthorizationError, sanitizeErrorText, toSafeErrorDetail, validateSpeechAuthorization };