@creativeorange/azure-text-to-speech 3.0.1 → 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.
@@ -8738,6 +8738,24 @@ class SpeechToText {
8738
8738
  );
8739
8739
  }
8740
8740
  }
8741
+ const PUNCTUATION_MARKS = [
8742
+ ".",
8743
+ ",",
8744
+ "!",
8745
+ "?",
8746
+ "*",
8747
+ "(",
8748
+ ")",
8749
+ "&",
8750
+ "\\",
8751
+ "/",
8752
+ "^",
8753
+ "[",
8754
+ "]",
8755
+ "<",
8756
+ ">",
8757
+ ":"
8758
+ ];
8741
8759
  class TextToSpeech {
8742
8760
  constructor(options) {
8743
8761
  __publicField(this, "region", "");
@@ -8753,8 +8771,6 @@ class TextToSpeech {
8753
8771
  __publicField(this, "player");
8754
8772
  __publicField(this, "synthesizer");
8755
8773
  __publicField(this, "previousWordBoundary");
8756
- __publicField(this, "interval");
8757
- __publicField(this, "wordEncounters", []);
8758
8774
  __publicField(this, "originalHighlightDivInnerHTML", "");
8759
8775
  __publicField(this, "currentWord", "");
8760
8776
  __publicField(this, "currentOffset", 0);
@@ -8768,6 +8784,8 @@ class TextToSpeech {
8768
8784
  __publicField(this, "playbackSegments", []);
8769
8785
  __publicField(this, "authorizationManager");
8770
8786
  __publicField(this, "stopPromise");
8787
+ __publicField(this, "playbackSessionId", 0);
8788
+ __publicField(this, "highlightInterval");
8771
8789
  var _a, _b, _c;
8772
8790
  if (!options || typeof options.voice !== "string" || options.voice.trim() === "") {
8773
8791
  throw new Error("A voice is required.");
@@ -8843,9 +8861,8 @@ class TextToSpeech {
8843
8861
  }
8844
8862
  async handleIdModifier(node, attr) {
8845
8863
  node.addEventListener("click", async (_) => {
8846
- var _a, _b, _c, _d, _e, _f;
8864
+ var _a, _b, _c;
8847
8865
  await this.stopPlayer();
8848
- await this.createInterval();
8849
8866
  const referenceDiv = document.getElementById(attr.value);
8850
8867
  this.clickedNode = referenceDiv;
8851
8868
  if (!referenceDiv) {
@@ -8856,51 +8873,30 @@ class TextToSpeech {
8856
8873
  } else {
8857
8874
  this.textToRead = (_c = (_b = referenceDiv.innerText) != null ? _b : referenceDiv.textContent) != null ? _c : "";
8858
8875
  }
8859
- if (referenceDiv.hasAttribute("co-tts.highlight")) {
8860
- const highlightTarget = (_e = (_d = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _d.value) != null ? _e : "";
8861
- if (highlightTarget !== "") {
8862
- const newReferenceDiv = document.getElementById(highlightTarget);
8863
- this.highlightDiv = newReferenceDiv;
8864
- this.originalHighlightDivInnerHTML = (_f = newReferenceDiv == null ? void 0 : newReferenceDiv.innerHTML) != null ? _f : "";
8865
- } else {
8866
- this.highlightDiv = referenceDiv;
8867
- this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
8868
- }
8869
- }
8876
+ this.setHighlightTargetFromNode(referenceDiv);
8870
8877
  await this.startSynthesizer(node, attr);
8871
8878
  });
8872
8879
  }
8873
8880
  async handleAjaxModifier(node, attr) {
8874
8881
  node.addEventListener("click", async (_) => {
8875
8882
  await this.stopPlayer();
8876
- await this.createInterval();
8877
8883
  this.clickedNode = node;
8878
8884
  const response = await fetch(attr.value, {
8879
8885
  method: `GET`
8880
8886
  });
8881
8887
  this.textToRead = await response.text();
8888
+ this.setHighlightTargetFromNode(node);
8882
8889
  await this.startSynthesizer(node, attr);
8883
8890
  });
8884
8891
  }
8885
8892
  async handleDefault(node, attr) {
8886
8893
  node.addEventListener("click", async (_) => {
8887
- var _a, _b, _c, _d, _e;
8894
+ var _a, _b;
8888
8895
  await this.stopPlayer();
8889
- await this.createInterval();
8890
8896
  this.clickedNode = node;
8891
- if (node.hasAttribute("co-tts.highlight")) {
8892
- const highlightTarget = (_b = (_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) != null ? _b : "";
8893
- if (highlightTarget !== "") {
8894
- const newReferenceDiv = document.getElementById(highlightTarget);
8895
- this.highlightDiv = newReferenceDiv;
8896
- this.originalHighlightDivInnerHTML = (_c = newReferenceDiv == null ? void 0 : newReferenceDiv.innerHTML) != null ? _c : "";
8897
- } else {
8898
- this.highlightDiv = node;
8899
- this.originalHighlightDivInnerHTML = node.innerHTML;
8900
- }
8901
- }
8897
+ this.setHighlightTargetFromNode(node);
8902
8898
  if (attr.value === "") {
8903
- this.textToRead = (_e = (_d = node.innerText) != null ? _d : node.textContent) != null ? _e : "";
8899
+ this.textToRead = (_b = (_a = node.innerText) != null ? _a : node.textContent) != null ? _b : "";
8904
8900
  } else {
8905
8901
  this.textToRead = attr.value;
8906
8902
  }
@@ -8908,26 +8904,12 @@ class TextToSpeech {
8908
8904
  });
8909
8905
  }
8910
8906
  async handleWithoutClick(node, attr) {
8911
- var _a, _b, _c;
8907
+ var _a, _b;
8912
8908
  await this.stopPlayer();
8913
- await this.createInterval();
8914
8909
  this.clickedNode = node;
8915
- if (node.hasAttribute("co-tts.highlight")) {
8916
- if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
8917
- const newReferenceDiv = document.getElementById(
8918
- node.attributes.getNamedItem("co-tts.highlight").value
8919
- );
8920
- this.highlightDiv = newReferenceDiv;
8921
- if (newReferenceDiv !== null) {
8922
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
8923
- }
8924
- } else {
8925
- this.highlightDiv = node;
8926
- this.originalHighlightDivInnerHTML = node.innerHTML;
8927
- }
8928
- }
8910
+ this.setHighlightTargetFromNode(node);
8929
8911
  if (attr.value === "") {
8930
- 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 : "";
8931
8913
  } else {
8932
8914
  this.textToRead = attr.value;
8933
8915
  }
@@ -8944,7 +8926,7 @@ class TextToSpeech {
8944
8926
  if (!this.player || typeof this.player.pause !== "function") {
8945
8927
  return;
8946
8928
  }
8947
- await this.clearInterval();
8929
+ this.stopHighlightingInterval();
8948
8930
  await this.player.pause();
8949
8931
  document.dispatchEvent(new CustomEvent("COAzureTTSPausedPlaying", {}));
8950
8932
  });
@@ -8954,7 +8936,9 @@ class TextToSpeech {
8954
8936
  if (!this.player) {
8955
8937
  return;
8956
8938
  }
8957
- await this.createInterval();
8939
+ if (this.shouldHighlight()) {
8940
+ this.startHighlightingInterval(this.playbackSessionId);
8941
+ }
8958
8942
  if (typeof this.player.resume === "function") {
8959
8943
  await this.player.resume();
8960
8944
  } else if (typeof this.player.play === "function") {
@@ -8975,8 +8959,9 @@ class TextToSpeech {
8975
8959
  return this.stopPromise;
8976
8960
  }
8977
8961
  async performStopPlayer() {
8978
- await this.clearInterval();
8979
- if (this.highlightDiv !== void 0) {
8962
+ this.invalidatePlaybackSession();
8963
+ this.stopHighlightingInterval();
8964
+ if (this.highlightDiv) {
8980
8965
  this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
8981
8966
  }
8982
8967
  this.resetPlaybackSegments();
@@ -8985,10 +8970,12 @@ class TextToSpeech {
8985
8970
  const synthesizer = this.synthesizer;
8986
8971
  this.textToRead = "";
8987
8972
  this.currentWord = "";
8973
+ this.currentOffset = 0;
8974
+ this.wordBoundaryOffset = 0;
8988
8975
  this.originalHighlightDivInnerHTML = "";
8989
8976
  this.wordBoundryList = [];
8990
- this.wordEncounters = [];
8991
8977
  this.playbackSegments = [];
8978
+ this.previousWordBoundary = void 0;
8992
8979
  this.highlightDiv = void 0;
8993
8980
  this.prevTextOffset = 0;
8994
8981
  this.playbackTextOffsetBase = void 0;
@@ -9028,10 +9015,18 @@ class TextToSpeech {
9028
9015
  async startSynthesizer(_node, _attr) {
9029
9016
  try {
9030
9017
  this.speechConfig = await this.createSpeechConfig();
9031
- this.player = new SpeakerAudioDestination();
9032
- this.audioConfig = AudioConfig.fromSpeakerOutput(this.player);
9033
- this.synthesizer = new SpeechSynthesizer(this.speechConfig, this.audioConfig);
9034
- 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
+ }
9035
9030
  this.wordBoundryList.push(e);
9036
9031
  };
9037
9032
  const playbackChain = this.collectPlaybackChain(this.clickedNode);
@@ -9041,8 +9036,11 @@ class TextToSpeech {
9041
9036
  } else {
9042
9037
  this.playbackSegments = [];
9043
9038
  }
9044
- this.player.onAudioEnd = async () => {
9039
+ player.onAudioEnd = async () => {
9045
9040
  var _a;
9041
+ if (!this.isActivePlaybackSession(sessionId)) {
9042
+ return;
9043
+ }
9046
9044
  const clickedNode = this.clickedNode;
9047
9045
  const wasChainedPlayback = this.playbackSegments.length > 0;
9048
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;
@@ -9066,23 +9064,39 @@ class TextToSpeech {
9066
9064
  document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
9067
9065
  }
9068
9066
  };
9069
- this.player.onAudioStart = async () => {
9067
+ player.onAudioStart = async () => {
9068
+ if (!this.isActivePlaybackSession(sessionId)) {
9069
+ return;
9070
+ }
9070
9071
  document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
9071
9072
  };
9073
+ if (this.shouldHighlight()) {
9074
+ this.startHighlightingInterval(sessionId);
9075
+ }
9072
9076
  if (!isChainedPlayback) {
9073
9077
  void this.prefetchNextNode(this.clickedNode);
9074
9078
  }
9075
- this.synthesizer.speakSsmlAsync(
9076
- this.buildSSML(this.textToRead),
9079
+ const ssml = this.buildSSML(this.textToRead);
9080
+ synthesizer.speakSsmlAsync(
9081
+ ssml,
9077
9082
  () => {
9078
- this.closeSynthesizer();
9083
+ this.closeResource(synthesizer);
9084
+ if (this.isActivePlaybackSession(sessionId) && this.synthesizer === synthesizer) {
9085
+ this.synthesizer = void 0;
9086
+ }
9079
9087
  },
9080
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
+ }
9081
9096
  if (isLikelyAuthorizationError(error)) {
9082
9097
  this.authorizationManager.clearAuthorization();
9083
9098
  }
9084
9099
  this.dispatchError(error, "SYNTHESIS_ERROR");
9085
- this.closeSynthesizer();
9086
9100
  void this.stopPlayer();
9087
9101
  }
9088
9102
  );
@@ -9129,23 +9143,16 @@ class TextToSpeech {
9129
9143
  this.textToRead = chain.map((segment) => segment.text).join(" ");
9130
9144
  this.highlightDiv = void 0;
9131
9145
  this.originalHighlightDivInnerHTML = "";
9132
- this.wordEncounters = [];
9133
- this.previousWordBoundary = void 0;
9134
- this.prevTextOffset = 0;
9135
- this.playbackTextOffsetBase = void 0;
9136
- this.currentWord = "";
9137
- this.currentOffset = 0;
9138
- this.wordBoundaryOffset = 0;
9146
+ this.resetHighlightState();
9139
9147
  }
9140
9148
  getHighlightDivForNode(node) {
9141
- var _a;
9142
- 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"))) {
9143
9151
  return void 0;
9144
9152
  }
9145
- if (((_a = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _a.value) !== "") {
9146
- return document.getElementById(
9147
- node.attributes.getNamedItem("co-tts.highlight").value
9148
- );
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;
9149
9156
  }
9150
9157
  return node;
9151
9158
  }
@@ -9158,16 +9165,14 @@ class TextToSpeech {
9158
9165
  }
9159
9166
  updateChainedHighlight(wordBoundary) {
9160
9167
  var _a, _b;
9161
- if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
9168
+ if (~PUNCTUATION_MARKS.indexOf(wordBoundary.text)) {
9162
9169
  wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
9163
9170
  }
9164
9171
  if (!wordBoundary) {
9165
9172
  this.resetPlaybackSegments();
9166
9173
  return;
9167
9174
  }
9168
- if (this.playbackTextOffsetBase === void 0) {
9169
- this.playbackTextOffsetBase = wordBoundary.textOffset;
9170
- }
9175
+ this.ensurePlaybackTextOffsetBase();
9171
9176
  const normalizedTextOffset = Math.max(
9172
9177
  0,
9173
9178
  wordBoundary.textOffset - ((_b = this.playbackTextOffsetBase) != null ? _b : 0)
@@ -9179,21 +9184,12 @@ class TextToSpeech {
9179
9184
  return;
9180
9185
  }
9181
9186
  const relativeTextOffset = normalizedTextOffset - segment.start;
9182
- const currentOffset = this.getPosition(
9187
+ this.highlightTextRange(
9188
+ segment.highlightDiv,
9183
9189
  segment.originalHighlightDivInnerHTML,
9184
- wordBoundary.text,
9185
- relativeTextOffset
9190
+ relativeTextOffset,
9191
+ wordBoundary.wordLength
9186
9192
  );
9187
- if (currentOffset === Number.MAX_SAFE_INTEGER) {
9188
- this.previousWordBoundary = wordBoundary;
9189
- return;
9190
- }
9191
- const startOfString = segment.originalHighlightDivInnerHTML.substring(0, currentOffset);
9192
- const endOffset = currentOffset + wordBoundary.wordLength;
9193
- const endOfString = segment.originalHighlightDivInnerHTML.substring(endOffset);
9194
- segment.highlightDiv.innerHTML = `
9195
- ${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
9196
- `;
9197
9193
  this.previousWordBoundary = wordBoundary;
9198
9194
  }
9199
9195
  getNodeText(node, attr) {
@@ -9300,7 +9296,7 @@ class TextToSpeech {
9300
9296
  this.prefetchPromises.set(key, prefetchPromise);
9301
9297
  }
9302
9298
  async playPrefetchedNode(node) {
9303
- var _a, _b, _c;
9299
+ var _a, _b;
9304
9300
  const attr = (_a = node.attributes.getNamedItem("co-tts.text")) != null ? _a : node.attributes.getNamedItem("co-tts");
9305
9301
  const text = this.getNodeText(node, attr);
9306
9302
  const key = this.getPrefetchKey(node, text);
@@ -9309,39 +9305,28 @@ class TextToSpeech {
9309
9305
  return false;
9310
9306
  }
9311
9307
  this.prefetchedAudio.delete(key);
9308
+ const sessionId = this.createPlaybackSession();
9309
+ this.resetHighlightState();
9312
9310
  this.clickedNode = node;
9313
9311
  this.textToRead = prefetch.text;
9314
9312
  this.wordBoundryList = prefetch.wordBoundryList;
9315
- this.wordEncounters = [];
9316
- this.previousWordBoundary = void 0;
9317
- this.prevTextOffset = 0;
9318
- this.currentWord = "";
9319
- this.currentOffset = 0;
9320
- this.wordBoundaryOffset = 0;
9321
- if (node.hasAttribute("co-tts.highlight")) {
9322
- if (((_c = node.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _c.value) !== "") {
9323
- const newReferenceDiv = document.getElementById(
9324
- node.attributes.getNamedItem("co-tts.highlight").value
9325
- );
9326
- this.highlightDiv = newReferenceDiv;
9327
- if (newReferenceDiv !== null) {
9328
- this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
9329
- }
9330
- } else {
9331
- this.highlightDiv = node;
9332
- this.originalHighlightDivInnerHTML = node.innerHTML;
9333
- }
9334
- }
9335
- await this.createInterval();
9313
+ this.playbackSegments = [];
9314
+ this.setHighlightTargetFromNode(node);
9336
9315
  try {
9337
9316
  const audio = new Audio(prefetch.url);
9338
9317
  this.activePrefetchedAudioUrl = prefetch.url;
9339
9318
  this.player = audio;
9340
9319
  audio.addEventListener("play", () => {
9320
+ if (!this.isActivePlaybackSession(sessionId)) {
9321
+ return;
9322
+ }
9341
9323
  document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
9342
9324
  }, { once: true });
9343
9325
  audio.addEventListener("ended", async () => {
9344
9326
  var _a2;
9327
+ if (!this.isActivePlaybackSession(sessionId)) {
9328
+ return;
9329
+ }
9345
9330
  const clickedNode = this.clickedNode;
9346
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;
9347
9332
  await this.stopPlayer();
@@ -9361,101 +9346,186 @@ class TextToSpeech {
9361
9346
  }
9362
9347
  }, { once: true });
9363
9348
  audio.addEventListener("error", () => {
9349
+ if (!this.isActivePlaybackSession(sessionId)) {
9350
+ return;
9351
+ }
9364
9352
  this.dispatchError(
9365
9353
  new Error("Prefetched audio resource closed unexpectedly."),
9366
9354
  "AUDIO_RESOURCE_ERROR"
9367
9355
  );
9368
9356
  void this.stopPlayer();
9369
9357
  }, { once: true });
9358
+ if (this.shouldHighlight()) {
9359
+ this.startHighlightingInterval(sessionId);
9360
+ }
9370
9361
  void this.prefetchNextNode(node);
9371
9362
  await audio.play();
9372
9363
  return true;
9373
9364
  } catch (error) {
9365
+ this.stopHighlightingInterval();
9374
9366
  this.dispatchError(error, "AUDIO_RESOURCE_ERROR");
9375
9367
  await this.stopPlayer();
9376
9368
  return false;
9377
9369
  }
9378
9370
  }
9379
- async clearInterval() {
9380
- clearInterval(this.interval);
9371
+ createPlaybackSession() {
9372
+ this.playbackSessionId += 1;
9373
+ return this.playbackSessionId;
9381
9374
  }
9382
- async createInterval() {
9383
- this.interval = setInterval(() => {
9384
- var _a;
9385
- if (this.player !== void 0 && (this.highlightDiv || this.playbackSegments.length > 0)) {
9386
- const currentTime = this.player.currentTime;
9387
- let wordBoundary;
9388
- for (const e of this.wordBoundryList) {
9389
- if (currentTime * 1e3 > e.audioOffset / 1e4) {
9390
- wordBoundary = e;
9391
- } else {
9392
- break;
9393
- }
9394
- }
9395
- if (wordBoundary !== void 0) {
9396
- if (this.playbackSegments.length > 0) {
9397
- this.updateChainedHighlight(wordBoundary);
9398
- return;
9399
- }
9400
- if (~[".", ",", "!", "?", "*", "(", ")", "&", "\\", "/", "^", "[", "]", "<", ">", ":"].indexOf(wordBoundary.text)) {
9401
- wordBoundary = (_a = this.previousWordBoundary) != null ? _a : void 0;
9402
- }
9403
- if (wordBoundary === void 0 || this.prevTextOffset > wordBoundary.prevTextOffset) {
9404
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9405
- } else {
9406
- if (!this.wordEncounters[wordBoundary.text]) {
9407
- this.wordEncounters[wordBoundary.text] = 0;
9408
- }
9409
- this.prevTextOffset = wordBoundary.prevTextOffset;
9410
- if (this.currentWord !== wordBoundary.text || this.wordBoundaryOffset !== wordBoundary.textOffset) {
9411
- this.currentOffset = this.getPosition(
9412
- this.originalHighlightDivInnerHTML,
9413
- wordBoundary.text,
9414
- wordBoundary.textOffset
9415
- );
9416
- this.wordEncounters[wordBoundary.text] = this.currentOffset + wordBoundary.wordLength;
9417
- this.currentWord = wordBoundary.text;
9418
- this.wordBoundaryOffset = wordBoundary.textOffset;
9419
- }
9420
- if (this.currentOffset === Number.MAX_SAFE_INTEGER) {
9421
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9422
- } else {
9423
- this.previousWordBoundary = wordBoundary;
9424
- const startOfString = this.originalHighlightDivInnerHTML.substring(0, this.currentOffset);
9425
- const endOffset = this.currentOffset + wordBoundary.wordLength;
9426
- const endOfString = this.originalHighlightDivInnerHTML.substring(endOffset);
9427
- this.highlightDiv.innerHTML = `
9428
- ${startOfString}<mark class='co-tts-highlight'>${wordBoundary.text}</mark>${endOfString}
9429
- `;
9430
- }
9431
- }
9432
- } else if (this.playbackSegments.length > 0) {
9433
- this.resetPlaybackSegments();
9434
- } else {
9435
- this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
9436
- }
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;
9437
9394
  }
9395
+ this.updateHighlightForCurrentTime(sessionId);
9438
9396
  }, 50);
9439
9397
  }
9440
- getPosition(string, subString, textOffset) {
9441
- let visibleOffset = 0;
9442
- let insideTag = false;
9443
- for (let htmlOffset = 0; htmlOffset < string.length; htmlOffset++) {
9444
- const character = string[htmlOffset];
9445
- if (character === "<") {
9446
- insideTag = true;
9447
- }
9448
- if (!insideTag) {
9449
- if (visibleOffset === textOffset) {
9450
- return htmlOffset;
9451
- }
9452
- 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;
9453
9413
  }
9454
- if (character === ">") {
9455
- 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;
9456
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;
9457
9528
  }
9458
- return string.indexOf(subString);
9459
9529
  }
9460
9530
  buildSSML(text) {
9461
9531
  let ssml = `<speak xmlns="http://www.w3.org/2001/10/synthesis"
@@ -9477,10 +9547,6 @@ class TextToSpeech {
9477
9547
  p.textContent = input;
9478
9548
  return p.innerHTML;
9479
9549
  }
9480
- closeSynthesizer() {
9481
- this.closeResource(this.synthesizer);
9482
- this.synthesizer = void 0;
9483
- }
9484
9550
  closeResource(resource) {
9485
9551
  if (!resource || typeof resource.close !== "function") {
9486
9552
  return;