@creativeorange/azure-text-to-speech 2.2.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +496 -0
- package/dist/co-azure-tts.es.js +425 -166
- package/dist/co-azure-tts.umd.js +8 -8
- package/package.json +21 -3
- package/src/SpeechToText.ts +106 -40
- package/src/TextToSpeech.ts +230 -129
- 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");
|
|
@@ -8462,12 +8649,17 @@ class TextToSpeech {
|
|
|
8462
8649
|
__publicField(this, "prefetchPromises", /* @__PURE__ */ new Map());
|
|
8463
8650
|
__publicField(this, "activePrefetchedAudioUrl", "");
|
|
8464
8651
|
__publicField(this, "playbackSegments", []);
|
|
8465
|
-
this
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
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 : "";
|
|
8471
8663
|
}
|
|
8472
8664
|
async start() {
|
|
8473
8665
|
await this.registerBindings(document);
|
|
@@ -8516,7 +8708,7 @@ class TextToSpeech {
|
|
|
8516
8708
|
}
|
|
8517
8709
|
async handleIdModifier(node, attr) {
|
|
8518
8710
|
node.addEventListener("click", async (_) => {
|
|
8519
|
-
var _a, _b;
|
|
8711
|
+
var _a, _b, _c, _d;
|
|
8520
8712
|
this.stopPlayer();
|
|
8521
8713
|
await this.createInterval();
|
|
8522
8714
|
const referenceDiv = document.getElementById(attr.value);
|
|
@@ -8527,10 +8719,10 @@ class TextToSpeech {
|
|
|
8527
8719
|
if (referenceDiv.hasAttribute("co-tts.text") && referenceDiv.getAttribute("co-tts.text") !== "") {
|
|
8528
8720
|
this.textToRead = (_a = referenceDiv.getAttribute("co-tts.text")) != null ? _a : "";
|
|
8529
8721
|
} else {
|
|
8530
|
-
this.textToRead = referenceDiv.innerText;
|
|
8722
|
+
this.textToRead = (_c = (_b = referenceDiv.innerText) != null ? _b : referenceDiv.textContent) != null ? _c : "";
|
|
8531
8723
|
}
|
|
8532
8724
|
if (referenceDiv.hasAttribute("co-tts.highlight")) {
|
|
8533
|
-
if (((
|
|
8725
|
+
if (((_d = referenceDiv.attributes.getNamedItem("co-tts.highlight")) == null ? void 0 : _d.value) !== "") {
|
|
8534
8726
|
const newReferenceDiv = document.getElementById(referenceDiv.attributes.getNamedItem("co-tts.highlight").value);
|
|
8535
8727
|
this.highlightDiv = newReferenceDiv;
|
|
8536
8728
|
this.originalHighlightDivInnerHTML = newReferenceDiv.innerHTML;
|
|
@@ -8539,7 +8731,7 @@ class TextToSpeech {
|
|
|
8539
8731
|
this.originalHighlightDivInnerHTML = referenceDiv.innerHTML;
|
|
8540
8732
|
}
|
|
8541
8733
|
}
|
|
8542
|
-
this.startSynthesizer(node, attr);
|
|
8734
|
+
await this.startSynthesizer(node, attr);
|
|
8543
8735
|
});
|
|
8544
8736
|
}
|
|
8545
8737
|
async handleAjaxModifier(node, attr) {
|
|
@@ -8551,12 +8743,12 @@ class TextToSpeech {
|
|
|
8551
8743
|
method: `GET`
|
|
8552
8744
|
});
|
|
8553
8745
|
this.textToRead = await response.text();
|
|
8554
|
-
this.startSynthesizer(node, attr);
|
|
8746
|
+
await this.startSynthesizer(node, attr);
|
|
8555
8747
|
});
|
|
8556
8748
|
}
|
|
8557
8749
|
async handleDefault(node, attr) {
|
|
8558
8750
|
node.addEventListener("click", async (_) => {
|
|
8559
|
-
var _a;
|
|
8751
|
+
var _a, _b, _c;
|
|
8560
8752
|
this.stopPlayer();
|
|
8561
8753
|
await this.createInterval();
|
|
8562
8754
|
this.clickedNode = node;
|
|
@@ -8571,15 +8763,15 @@ class TextToSpeech {
|
|
|
8571
8763
|
}
|
|
8572
8764
|
}
|
|
8573
8765
|
if (attr.value === "") {
|
|
8574
|
-
this.textToRead = node.innerText;
|
|
8766
|
+
this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8575
8767
|
} else {
|
|
8576
8768
|
this.textToRead = attr.value;
|
|
8577
8769
|
}
|
|
8578
|
-
this.startSynthesizer(node, attr);
|
|
8770
|
+
await this.startSynthesizer(node, attr);
|
|
8579
8771
|
});
|
|
8580
8772
|
}
|
|
8581
8773
|
async handleWithoutClick(node, attr) {
|
|
8582
|
-
var _a;
|
|
8774
|
+
var _a, _b, _c;
|
|
8583
8775
|
this.stopPlayer();
|
|
8584
8776
|
await this.createInterval();
|
|
8585
8777
|
this.clickedNode = node;
|
|
@@ -8596,11 +8788,11 @@ class TextToSpeech {
|
|
|
8596
8788
|
}
|
|
8597
8789
|
}
|
|
8598
8790
|
if (attr.value === "") {
|
|
8599
|
-
this.textToRead = node.innerText;
|
|
8791
|
+
this.textToRead = (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8600
8792
|
} else {
|
|
8601
8793
|
this.textToRead = attr.value;
|
|
8602
8794
|
}
|
|
8603
|
-
this.startSynthesizer(node, attr);
|
|
8795
|
+
await this.startSynthesizer(node, attr);
|
|
8604
8796
|
}
|
|
8605
8797
|
async handleStopModifier(node, attr) {
|
|
8606
8798
|
node.addEventListener("click", async (_) => {
|
|
@@ -8641,66 +8833,83 @@ class TextToSpeech {
|
|
|
8641
8833
|
URL.revokeObjectURL(this.activePrefetchedAudioUrl);
|
|
8642
8834
|
this.activePrefetchedAudioUrl = "";
|
|
8643
8835
|
}
|
|
8836
|
+
this.closeSynthesizer();
|
|
8644
8837
|
this.player = void 0;
|
|
8838
|
+
this.audioConfig = void 0;
|
|
8839
|
+
this.speechConfig = void 0;
|
|
8645
8840
|
this.highlightDiv = void 0;
|
|
8646
8841
|
this.prevTextOffset = 0;
|
|
8647
8842
|
this.playbackTextOffsetBase = void 0;
|
|
8648
8843
|
}
|
|
8844
|
+
async createSpeechConfig() {
|
|
8845
|
+
const authorization = await this.authorizationManager.getAuthorization();
|
|
8846
|
+
const speechConfig = SpeechConfig.fromAuthorizationToken(
|
|
8847
|
+
authorization.token,
|
|
8848
|
+
authorization.region
|
|
8849
|
+
);
|
|
8850
|
+
speechConfig.speechSynthesisVoiceName = `Microsoft Server Speech Text to Speech Voice (${this.voice})`;
|
|
8851
|
+
speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3;
|
|
8852
|
+
this.region = authorization.region;
|
|
8853
|
+
return speechConfig;
|
|
8854
|
+
}
|
|
8649
8855
|
async startSynthesizer(node, attr) {
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
this.playbackSegments = [];
|
|
8665
|
-
}
|
|
8666
|
-
this.player.onAudioEnd = async () => {
|
|
8667
|
-
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
8668
|
-
this.stopPlayer();
|
|
8669
|
-
if (wasChainedPlayback) {
|
|
8670
|
-
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8671
|
-
return;
|
|
8856
|
+
try {
|
|
8857
|
+
this.speechConfig = await this.createSpeechConfig();
|
|
8858
|
+
this.player = new SpeakerAudioDestination();
|
|
8859
|
+
this.audioConfig = AudioConfig.fromSpeakerOutput(this.player);
|
|
8860
|
+
this.synthesizer = new SpeechSynthesizer(this.speechConfig, this.audioConfig);
|
|
8861
|
+
this.synthesizer.wordBoundary = (s, e) => {
|
|
8862
|
+
this.wordBoundryList.push(e);
|
|
8863
|
+
};
|
|
8864
|
+
const playbackChain = this.collectPlaybackChain(this.clickedNode);
|
|
8865
|
+
const isChainedPlayback = playbackChain.length > 1;
|
|
8866
|
+
if (isChainedPlayback) {
|
|
8867
|
+
this.preparePlaybackChain(playbackChain);
|
|
8868
|
+
} else {
|
|
8869
|
+
this.playbackSegments = [];
|
|
8672
8870
|
}
|
|
8673
|
-
|
|
8674
|
-
const
|
|
8675
|
-
|
|
8871
|
+
this.player.onAudioEnd = async () => {
|
|
8872
|
+
const wasChainedPlayback = this.playbackSegments.length > 0;
|
|
8873
|
+
this.stopPlayer();
|
|
8874
|
+
if (wasChainedPlayback) {
|
|
8875
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8676
8876
|
return;
|
|
8677
8877
|
}
|
|
8678
|
-
if (
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8878
|
+
if (this.clickedNode.hasAttribute("co-tts.next")) {
|
|
8879
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
|
|
8880
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
8881
|
+
return;
|
|
8882
|
+
}
|
|
8883
|
+
if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
|
|
8884
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
|
|
8885
|
+
} else if (nextNode) {
|
|
8886
|
+
nextNode.dispatchEvent(new Event("click"));
|
|
8887
|
+
}
|
|
8888
|
+
} else {
|
|
8889
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8682
8890
|
}
|
|
8683
|
-
}
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
};
|
|
8690
|
-
if (!isChainedPlayback) {
|
|
8691
|
-
this.prefetchNextNode(this.clickedNode);
|
|
8692
|
-
}
|
|
8693
|
-
this.synthesizer.speakSsmlAsync(
|
|
8694
|
-
this.buildSSML(this.textToRead),
|
|
8695
|
-
() => {
|
|
8696
|
-
this.synthesizer.close();
|
|
8697
|
-
this.synthesizer = void 0;
|
|
8698
|
-
},
|
|
8699
|
-
() => {
|
|
8700
|
-
this.synthesizer.close();
|
|
8701
|
-
this.synthesizer = void 0;
|
|
8891
|
+
};
|
|
8892
|
+
this.player.onAudioStart = async () => {
|
|
8893
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
|
|
8894
|
+
};
|
|
8895
|
+
if (!isChainedPlayback) {
|
|
8896
|
+
this.prefetchNextNode(this.clickedNode);
|
|
8702
8897
|
}
|
|
8703
|
-
|
|
8898
|
+
this.synthesizer.speakSsmlAsync(
|
|
8899
|
+
this.buildSSML(this.textToRead),
|
|
8900
|
+
() => {
|
|
8901
|
+
this.closeSynthesizer();
|
|
8902
|
+
},
|
|
8903
|
+
(error) => {
|
|
8904
|
+
this.dispatchError(error, "SYNTHESIS_ERROR");
|
|
8905
|
+
this.closeSynthesizer();
|
|
8906
|
+
this.stopPlayer();
|
|
8907
|
+
}
|
|
8908
|
+
);
|
|
8909
|
+
} catch (error) {
|
|
8910
|
+
this.dispatchError(error);
|
|
8911
|
+
await this.stopPlayer();
|
|
8912
|
+
}
|
|
8704
8913
|
}
|
|
8705
8914
|
collectPlaybackChain(node) {
|
|
8706
8915
|
var _a, _b;
|
|
@@ -8796,14 +9005,14 @@ class TextToSpeech {
|
|
|
8796
9005
|
this.previousWordBoundary = wordBoundary;
|
|
8797
9006
|
}
|
|
8798
9007
|
getNodeText(node, attr) {
|
|
8799
|
-
var _a;
|
|
9008
|
+
var _a, _b, _c;
|
|
8800
9009
|
if (attr && attr.value !== "") {
|
|
8801
9010
|
return attr.value;
|
|
8802
9011
|
}
|
|
8803
9012
|
if (node.hasAttribute("co-tts.text") && node.getAttribute("co-tts.text") !== "") {
|
|
8804
9013
|
return (_a = node.getAttribute("co-tts.text")) != null ? _a : "";
|
|
8805
9014
|
}
|
|
8806
|
-
return node.innerText;
|
|
9015
|
+
return (_c = (_b = node.innerText) != null ? _b : node.textContent) != null ? _c : "";
|
|
8807
9016
|
}
|
|
8808
9017
|
getPrefetchKey(node, text) {
|
|
8809
9018
|
return [
|
|
@@ -8842,41 +9051,52 @@ class TextToSpeech {
|
|
|
8842
9051
|
if (this.prefetchedAudio.has(key) || this.prefetchPromises.has(key)) {
|
|
8843
9052
|
return;
|
|
8844
9053
|
}
|
|
8845
|
-
|
|
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
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
9054
|
+
let synthesizer;
|
|
9055
|
+
const prefetchPromise = (async () => {
|
|
9056
|
+
try {
|
|
9057
|
+
const speechConfig = await this.createSpeechConfig();
|
|
9058
|
+
synthesizer = new SpeechSynthesizer(speechConfig, null);
|
|
9059
|
+
const wordBoundryList = [];
|
|
9060
|
+
synthesizer.wordBoundary = (s, e) => {
|
|
9061
|
+
wordBoundryList.push(e);
|
|
9062
|
+
};
|
|
9063
|
+
return await new Promise((resolve) => {
|
|
9064
|
+
synthesizer == null ? void 0 : synthesizer.speakSsmlAsync(
|
|
9065
|
+
this.buildSSML(text),
|
|
9066
|
+
(result) => {
|
|
9067
|
+
this.closeResource(synthesizer);
|
|
9068
|
+
synthesizer = void 0;
|
|
9069
|
+
if (!(result == null ? void 0 : result.audioData)) {
|
|
9070
|
+
resolve(null);
|
|
9071
|
+
return;
|
|
9072
|
+
}
|
|
9073
|
+
const blob = new Blob([result.audioData], { type: "audio/mpeg" });
|
|
9074
|
+
const url = URL.createObjectURL(blob);
|
|
9075
|
+
const prefetch = {
|
|
9076
|
+
key,
|
|
9077
|
+
nodeId: nextNode.id,
|
|
9078
|
+
text,
|
|
9079
|
+
url,
|
|
9080
|
+
wordBoundryList
|
|
9081
|
+
};
|
|
9082
|
+
this.prefetchedAudio.set(key, prefetch);
|
|
9083
|
+
resolve(prefetch);
|
|
9084
|
+
},
|
|
9085
|
+
(error) => {
|
|
9086
|
+
this.closeResource(synthesizer);
|
|
9087
|
+
synthesizer = void 0;
|
|
9088
|
+
this.dispatchError(error, "PREFETCH_ERROR");
|
|
9089
|
+
resolve(null);
|
|
9090
|
+
}
|
|
9091
|
+
);
|
|
9092
|
+
});
|
|
9093
|
+
} catch (error) {
|
|
9094
|
+
this.closeResource(synthesizer);
|
|
9095
|
+
synthesizer = void 0;
|
|
9096
|
+
this.dispatchError(error);
|
|
9097
|
+
return null;
|
|
9098
|
+
}
|
|
9099
|
+
})().finally(() => {
|
|
8880
9100
|
this.prefetchPromises.delete(key);
|
|
8881
9101
|
});
|
|
8882
9102
|
this.prefetchPromises.set(key, prefetchPromise);
|
|
@@ -8913,31 +9133,44 @@ class TextToSpeech {
|
|
|
8913
9133
|
}
|
|
8914
9134
|
}
|
|
8915
9135
|
await this.createInterval();
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
9136
|
+
try {
|
|
9137
|
+
const audio = new Audio(prefetch.url);
|
|
9138
|
+
this.activePrefetchedAudioUrl = prefetch.url;
|
|
9139
|
+
this.player = audio;
|
|
9140
|
+
audio.addEventListener("play", () => {
|
|
9141
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSStartedPlaying", {}));
|
|
9142
|
+
}, { once: true });
|
|
9143
|
+
audio.addEventListener("ended", async () => {
|
|
9144
|
+
this.stopPlayer();
|
|
9145
|
+
if (this.clickedNode.hasAttribute("co-tts.next")) {
|
|
9146
|
+
const nextNode = document.getElementById(this.clickedNode.getAttribute("co-tts.next"));
|
|
9147
|
+
if (nextNode && await this.playPrefetchedNode(nextNode)) {
|
|
9148
|
+
return;
|
|
9149
|
+
}
|
|
9150
|
+
if (nextNode && nextNode.attributes.getNamedItem("co-tts.text")) {
|
|
9151
|
+
this.handleWithoutClick(nextNode, nextNode.attributes.getNamedItem("co-tts.text"));
|
|
9152
|
+
} else if (nextNode) {
|
|
9153
|
+
nextNode.dispatchEvent(new Event("click"));
|
|
9154
|
+
}
|
|
9155
|
+
} else {
|
|
9156
|
+
document.dispatchEvent(new CustomEvent("COAzureTTSFinishedPlaying", {}));
|
|
8933
9157
|
}
|
|
8934
|
-
}
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
9158
|
+
}, { once: true });
|
|
9159
|
+
audio.addEventListener("error", () => {
|
|
9160
|
+
this.dispatchError(
|
|
9161
|
+
new Error("Prefetched audio resource closed unexpectedly."),
|
|
9162
|
+
"AUDIO_RESOURCE_ERROR"
|
|
9163
|
+
);
|
|
9164
|
+
this.stopPlayer();
|
|
9165
|
+
}, { once: true });
|
|
9166
|
+
this.prefetchNextNode(node);
|
|
9167
|
+
await audio.play();
|
|
9168
|
+
return true;
|
|
9169
|
+
} catch (error) {
|
|
9170
|
+
this.dispatchError(error, "AUDIO_RESOURCE_ERROR");
|
|
9171
|
+
await this.stopPlayer();
|
|
9172
|
+
return false;
|
|
9173
|
+
}
|
|
8941
9174
|
}
|
|
8942
9175
|
async clearInterval() {
|
|
8943
9176
|
clearInterval(this.interval);
|
|
@@ -9040,5 +9273,31 @@ class TextToSpeech {
|
|
|
9040
9273
|
p.textContent = input;
|
|
9041
9274
|
return p.innerHTML;
|
|
9042
9275
|
}
|
|
9276
|
+
closeSynthesizer() {
|
|
9277
|
+
this.closeResource(this.synthesizer);
|
|
9278
|
+
this.synthesizer = void 0;
|
|
9279
|
+
}
|
|
9280
|
+
closeResource(resource) {
|
|
9281
|
+
if (!resource || typeof resource.close !== "function") {
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
try {
|
|
9285
|
+
resource.close();
|
|
9286
|
+
} catch {
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
dispatchError(error, code) {
|
|
9290
|
+
const detail = toSafeErrorDetail(error);
|
|
9291
|
+
if (code && !detail.code) {
|
|
9292
|
+
detail.code = code;
|
|
9293
|
+
}
|
|
9294
|
+
document.dispatchEvent(
|
|
9295
|
+
new CustomEvent("COAzureTTSError", {
|
|
9296
|
+
detail: {
|
|
9297
|
+
error: detail
|
|
9298
|
+
}
|
|
9299
|
+
})
|
|
9300
|
+
);
|
|
9301
|
+
}
|
|
9043
9302
|
}
|
|
9044
9303
|
export { SpeechToText, TextToSpeech };
|