@creativeorange/azure-text-to-speech 2.2.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +496 -0
- package/dist/co-azure-tts.es.js +437 -169
- package/dist/co-azure-tts.umd.js +10 -10
- package/package.json +21 -3
- package/src/SpeechToText.ts +106 -40
- package/src/TextToSpeech.ts +243 -132
- package/src/authentication.ts +228 -0
- package/src/main.ts +7 -0
- package/.eslintrc.js +0 -30
- package/index.html +0 -578
- package/nuxt/plugins/azure-speech-to-text.client.js +0 -27
- package/nuxt/plugins/azure-text-to-speech.client.js +0 -28
- package/tsconfig.json +0 -21
- package/vite.config.ts +0 -26
package/dist/co-azure-tts.es.js
CHANGED
|
@@ -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(
|
|
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
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
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
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
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
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
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
|
-
|
|
8430
|
-
|
|
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(
|
|
8438
|
-
__publicField(this, "key");
|
|
8625
|
+
constructor(options) {
|
|
8439
8626
|
__publicField(this, "region");
|
|
8440
8627
|
__publicField(this, "voice");
|
|
8441
8628
|
__publicField(this, "rate");
|
|
@@ -8455,18 +8642,24 @@ class TextToSpeech {
|
|
|
8455
8642
|
__publicField(this, "currentWord", "");
|
|
8456
8643
|
__publicField(this, "currentOffset", 0);
|
|
8457
8644
|
__publicField(this, "wordBoundaryOffset", 0);
|
|
8645
|
+
__publicField(this, "playbackTextOffsetBase");
|
|
8458
8646
|
__publicField(this, "prevTextOffset", 0);
|
|
8459
8647
|
__publicField(this, "url", "");
|
|
8460
8648
|
__publicField(this, "prefetchedAudio", /* @__PURE__ */ new Map());
|
|
8461
8649
|
__publicField(this, "prefetchPromises", /* @__PURE__ */ new Map());
|
|
8462
8650
|
__publicField(this, "activePrefetchedAudioUrl", "");
|
|
8463
8651
|
__publicField(this, "playbackSegments", []);
|
|
8464
|
-
this
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
this.
|
|
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 : "";
|
|
8470
8663
|
}
|
|
8471
8664
|
async start() {
|
|
8472
8665
|
await this.registerBindings(document);
|
|
@@ -8515,7 +8708,7 @@ class TextToSpeech {
|
|
|
8515
8708
|
}
|
|
8516
8709
|
async handleIdModifier(node, attr) {
|
|
8517
8710
|
node.addEventListener("click", async (_) => {
|
|
8518
|
-
var _a, _b;
|
|
8711
|
+
var _a, _b, _c, _d;
|
|
8519
8712
|
this.stopPlayer();
|
|
8520
8713
|
await this.createInterval();
|
|
8521
8714
|
const referenceDiv = document.getElementById(attr.value);
|
|
@@ -8526,10 +8719,10 @@ class TextToSpeech {
|
|
|
8526
8719
|
if (referenceDiv.hasAttribute("co-tts.text") && referenceDiv.getAttribute("co-tts.text") !== "") {
|
|
8527
8720
|
this.textToRead = (_a = referenceDiv.getAttribute("co-tts.text")) != null ? _a : "";
|
|
8528
8721
|
} else {
|
|
8529
|
-
this.textToRead = referenceDiv.innerText;
|
|
8722
|
+
this.textToRead = (_c = (_b = referenceDiv.innerText) != null ? _b : referenceDiv.textContent) != null ? _c : "";
|
|
8530
8723
|
}
|
|
8531
8724
|
if (referenceDiv.hasAttribute("co-tts.highlight")) {
|
|
8532
|
-
if (((
|
|
8725
|
+
if (((_d = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _d.value) !== "") {
|
|
8533
8726
|
const newReferenceDiv = document.getElementById(referenceDiv.attributes.getNamedItem("co-tts.highlight").value);
|
|
8534
8727
|
this.highlightDiv = newReferenceDiv;
|
|
8535
8728
|
this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
|
|
@@ -8538,7 +8731,7 @@ class TextToSpeech {
|
|
|
8538
8731
|
this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
|
|
8539
8732
|
}
|
|
8540
8733
|
}
|
|
8541
|
-
this.startSynthesizer(node, attr);
|
|
8734
|
+
await this.startSynthesizer(node, attr);
|
|
8542
8735
|
});
|
|
8543
8736
|
}
|
|
8544
8737
|
async handleAjaxModifier(node, attr) {
|
|
@@ -8550,12 +8743,12 @@ class TextToSpeech {
|
|
|
8550
8743
|
method: `GET`
|
|
8551
8744
|
});
|
|
8552
8745
|
this.textToRead = await response.text();
|
|
8553
|
-
this.startSynthesizer(node, attr);
|
|
8746
|
+
await this.startSynthesizer(node, attr);
|
|
8554
8747
|
});
|
|
8555
8748
|
}
|
|
8556
8749
|
async handleDefault(node, attr) {
|
|
8557
8750
|
node.addEventListener("click", async (_) => {
|
|
8558
|
-
var _a;
|
|
8751
|
+
var _a, _b, _c;
|
|
8559
8752
|
this.stopPlayer();
|
|
8560
8753
|
await this.createInterval();
|
|
8561
8754
|
this.clickedNode = node;
|
|
@@ -8570,15 +8763,15 @@ class TextToSpeech {
|
|
|
8570
8763
|
}
|
|
8571
8764
|
}
|
|
8572
8765
|
if (attr.value === "") {
|
|
8573
|
-
this.textToRead = node.innerText;
|
|
8766
|
+
this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8574
8767
|
} else {
|
|
8575
8768
|
this.textToRead = attr.value;
|
|
8576
8769
|
}
|
|
8577
|
-
this.startSynthesizer(node, attr);
|
|
8770
|
+
await this.startSynthesizer(node, attr);
|
|
8578
8771
|
});
|
|
8579
8772
|
}
|
|
8580
8773
|
async handleWithoutClick(node, attr) {
|
|
8581
|
-
var _a;
|
|
8774
|
+
var _a, _b, _c;
|
|
8582
8775
|
this.stopPlayer();
|
|
8583
8776
|
await this.createInterval();
|
|
8584
8777
|
this.clickedNode = node;
|
|
@@ -8595,11 +8788,11 @@ class TextToSpeech {
|
|
|
8595
8788
|
}
|
|
8596
8789
|
}
|
|
8597
8790
|
if (attr.value === "") {
|
|
8598
|
-
this.textToRead = node.innerText;
|
|
8791
|
+
this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8599
8792
|
} else {
|
|
8600
8793
|
this.textToRead = attr.value;
|
|
8601
8794
|
}
|
|
8602
|
-
this.startSynthesizer(node, attr);
|
|
8795
|
+
await this.startSynthesizer(node, attr);
|
|
8603
8796
|
}
|
|
8604
8797
|
async handleStopModifier(node, attr) {
|
|
8605
8798
|
node.addEventListener("click", async (_) => {
|
|
@@ -8640,65 +8833,83 @@ class TextToSpeech {
|
|
|
8640
8833
|
URL.revokeObjectURL(this.activePrefetchedAudioUrl);
|
|
8641
8834
|
this.activePrefetchedAudioUrl = "";
|
|
8642
8835
|
}
|
|
8836
|
+
this.closeSynthesizer();
|
|
8643
8837
|
this.player = void 0;
|
|
8838
|
+
this.audioConfig = void 0;
|
|
8839
|
+
this.speechConfig = void 0;
|
|
8644
8840
|
this.highlightDiv = void 0;
|
|
8645
8841
|
this.prevTextOffset = 0;
|
|
8842
|
+
this.playbackTextOffsetBase = void 0;
|
|
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;
|
|
8646
8854
|
}
|
|
8647
8855
|
async startSynthesizer(node, attr) {
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
this.playbackSegments = [];
|
|
8663
|
-
}
|
|
8664
|
-
this.player.onAudioEnd = async () => {
|
|
8665
|
-
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
8666
|
-
this.stopPlayer();
|
|
8667
|
-
if (wasChainedPlayback) {
|
|
8668
|
-
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8669
|
-
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 = [];
|
|
8670
8870
|
}
|
|
8671
|
-
|
|
8672
|
-
const
|
|
8673
|
-
|
|
8871
|
+
this.player.onAudioEnd = async () => {
|
|
8872
|
+
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
8873
|
+
this.stopPlayer();
|
|
8874
|
+
if (wasChainedPlayback) {
|
|
8875
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8674
8876
|
return;
|
|
8675
8877
|
}
|
|
8676
|
-
if (
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
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", {}));
|
|
8680
8890
|
}
|
|
8681
|
-
}
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
};
|
|
8688
|
-
if (!isChainedPlayback) {
|
|
8689
|
-
this.prefetchNextNode(this.clickedNode);
|
|
8690
|
-
}
|
|
8691
|
-
this.synthesizer.speakSsmlAsync(
|
|
8692
|
-
this.buildSSML(this.textToRead),
|
|
8693
|
-
() => {
|
|
8694
|
-
this.synthesizer.close();
|
|
8695
|
-
this.synthesizer = void 0;
|
|
8696
|
-
},
|
|
8697
|
-
() => {
|
|
8698
|
-
this.synthesizer.close();
|
|
8699
|
-
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);
|
|
8700
8897
|
}
|
|
8701
|
-
|
|
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
|
+
}
|
|
8702
8913
|
}
|
|
8703
8914
|
collectPlaybackChain(node) {
|
|
8704
8915
|
var _a, _b;
|
|
@@ -8738,6 +8949,7 @@ class TextToSpeech {
|
|
|
8738
8949
|
this.wordEncounters = [];
|
|
8739
8950
|
this.previousWordBoundary = void 0;
|
|
8740
8951
|
this.prevTextOffset = 0;
|
|
8952
|
+
this.playbackTextOffsetBase = void 0;
|
|
8741
8953
|
this.currentWord = "";
|
|
8742
8954
|
this.currentOffset = 0;
|
|
8743
8955
|
this.wordBoundaryOffset = 0;
|
|
@@ -8768,13 +8980,17 @@ class TextToSpeech {
|
|
|
8768
8980
|
this.resetPlaybackSegments();
|
|
8769
8981
|
return;
|
|
8770
8982
|
}
|
|
8771
|
-
|
|
8983
|
+
if (this.playbackTextOffsetBase === void 0) {
|
|
8984
|
+
this.playbackTextOffsetBase = wordBoundary.textOffset;
|
|
8985
|
+
}
|
|
8986
|
+
const normalizedTextOffset = Math.max(0, wordBoundary.textOffset - this.playbackTextOffsetBase);
|
|
8987
|
+
const segment = this.playbackSegments.find((candidate) => normalizedTextOffset >= candidate.start && normalizedTextOffset < candidate.end);
|
|
8772
8988
|
this.resetPlaybackSegments();
|
|
8773
8989
|
if (!(segment == null ? void 0 : segment.highlightDiv)) {
|
|
8774
8990
|
this.previousWordBoundary = wordBoundary;
|
|
8775
8991
|
return;
|
|
8776
8992
|
}
|
|
8777
|
-
const relativeTextOffset =
|
|
8993
|
+
const relativeTextOffset = normalizedTextOffset - segment.start;
|
|
8778
8994
|
const currentOffset = this.getPosition(segment.originalHighlightDivInnerHTML, wordBoundary.text, relativeTextOffset);
|
|
8779
8995
|
if (currentOffset === Number.MAX_SAFE_INTEGER) {
|
|
8780
8996
|
this.previousWordBoundary = wordBoundary;
|
|
@@ -8789,14 +9005,14 @@ class TextToSpeech {
|
|
|
8789
9005
|
this.previousWordBoundary = wordBoundary;
|
|
8790
9006
|
}
|
|
8791
9007
|
getNodeText(node, attr) {
|
|
8792
|
-
var _a;
|
|
9008
|
+
var _a, _b, _c;
|
|
8793
9009
|
if (attr && attr.value !== "") {
|
|
8794
9010
|
return attr.value;
|
|
8795
9011
|
}
|
|
8796
9012
|
if (node.hasAttribute("co-tts.text") && node.getAttribute("co-tts.text") !== "") {
|
|
8797
9013
|
return (_a = node.getAttribute("co-tts.text")) != null ? _a : "";
|
|
8798
9014
|
}
|
|
8799
|
-
return node.innerText;
|
|
9015
|
+
return (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8800
9016
|
}
|
|
8801
9017
|
getPrefetchKey(node, text) {
|
|
8802
9018
|
return [
|
|
@@ -8835,41 +9051,52 @@ class TextToSpeech {
|
|
|
8835
9051
|
if (this.prefetchedAudio.has(key) || this.prefetchPromises.has(key)) {
|
|
8836
9052
|
return;
|
|
8837
9053
|
}
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
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(() => {
|
|
8873
9100
|
this.prefetchPromises.delete(key);
|
|
8874
9101
|
});
|
|
8875
9102
|
this.prefetchPromises.set(key, prefetchPromise);
|
|
@@ -8906,31 +9133,44 @@ class TextToSpeech {
|
|
|
8906
9133
|
}
|
|
8907
9134
|
}
|
|
8908
9135
|
await this.createInterval();
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
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", {}));
|
|
8926
9157
|
}
|
|
8927
|
-
}
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
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
|
+
}
|
|
8934
9174
|
}
|
|
8935
9175
|
async clearInterval() {
|
|
8936
9176
|
clearInterval(this.interval);
|
|
@@ -8938,7 +9178,7 @@ class TextToSpeech {
|
|
|
8938
9178
|
async createInterval() {
|
|
8939
9179
|
this.interval = setInterval(() => {
|
|
8940
9180
|
var _a;
|
|
8941
|
-
if (this.player !== void 0 && this.highlightDiv) {
|
|
9181
|
+
if (this.player !== void 0 && (this.highlightDiv || this.playbackSegments.length > 0)) {
|
|
8942
9182
|
const currentTime = this.player.currentTime;
|
|
8943
9183
|
let wordBoundary;
|
|
8944
9184
|
for (const e of this.wordBoundryList) {
|
|
@@ -8985,6 +9225,8 @@ class TextToSpeech {
|
|
|
8985
9225
|
`;
|
|
8986
9226
|
}
|
|
8987
9227
|
}
|
|
9228
|
+
} else if (this.playbackSegments.length > 0) {
|
|
9229
|
+
this.resetPlaybackSegments();
|
|
8988
9230
|
} else {
|
|
8989
9231
|
this.highlightDiv.innerHTML = this.originalHighlightDivInnerHTML;
|
|
8990
9232
|
}
|
|
@@ -9031,5 +9273,31 @@ class TextToSpeech {
|
|
|
9031
9273
|
p.textContent = input;
|
|
9032
9274
|
return p.innerHTML;
|
|
9033
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
|
+
}
|
|
9034
9302
|
}
|
|
9035
9303
|
export { SpeechToText, TextToSpeech };
|