ssml-builder-js 2.4.0 → 2.5.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 +149 -3
- package/dist/{chunk-RUIEMCWP.mjs → chunk-4RHHW33A.mjs} +111 -15
- package/dist/chunk-4RHHW33A.mjs.map +1 -0
- package/dist/{chunk-I3BR2WCQ.mjs → chunk-ALFYHSCR.mjs} +1 -1
- package/dist/chunk-ALFYHSCR.mjs.map +1 -0
- package/dist/core.d.mts +18 -4
- package/dist/core.d.ts +18 -4
- package/dist/core.js +110 -14
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +138 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-I3BR2WCQ.mjs.map +0 -1
- package/dist/chunk-RUIEMCWP.mjs.map +0 -1
package/dist/elements.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { AudioElement, AzureSsmlValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.mjs';
|
|
1
|
+
export { AudioElement, AzureSsmlValidationOptions, AzureValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.mjs';
|
|
2
2
|
|
|
3
3
|
interface TtsConfig {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
timeoutMs?: number;
|
|
4
6
|
endpoint?: string;
|
|
5
7
|
subscriptionKey: string;
|
|
6
8
|
region: string;
|
|
@@ -13,6 +15,8 @@ interface AzureTtsLogger {
|
|
|
13
15
|
error?: (...args: unknown[]) => void;
|
|
14
16
|
}
|
|
15
17
|
interface AzureTtsClientOptions {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
timeoutMs?: number;
|
|
16
20
|
subscriptionKey: string;
|
|
17
21
|
region: string;
|
|
18
22
|
endpoint?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { AudioElement, AzureSsmlValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.js';
|
|
1
|
+
export { AudioElement, AzureSsmlValidationOptions, AzureValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.js';
|
|
2
2
|
|
|
3
3
|
interface TtsConfig {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
timeoutMs?: number;
|
|
4
6
|
endpoint?: string;
|
|
5
7
|
subscriptionKey: string;
|
|
6
8
|
region: string;
|
|
@@ -13,6 +15,8 @@ interface AzureTtsLogger {
|
|
|
13
15
|
error?: (...args: unknown[]) => void;
|
|
14
16
|
}
|
|
15
17
|
interface AzureTtsClientOptions {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
timeoutMs?: number;
|
|
16
20
|
subscriptionKey: string;
|
|
17
21
|
region: string;
|
|
18
22
|
endpoint?: string;
|
package/dist/index.js
CHANGED
|
@@ -911,6 +911,9 @@ function decodeXmlText(value) {
|
|
|
911
911
|
function encodeXmlText(value) {
|
|
912
912
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
913
913
|
}
|
|
914
|
+
function decodeXmlAttribute(value) {
|
|
915
|
+
return decodeXmlText(value);
|
|
916
|
+
}
|
|
914
917
|
function findTagEnd(source, start) {
|
|
915
918
|
let quote = "";
|
|
916
919
|
for (let index = start; index < source.length; index += 1) {
|
|
@@ -929,14 +932,31 @@ function readTagName(tag) {
|
|
|
929
932
|
const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
|
|
930
933
|
return match?.[1];
|
|
931
934
|
}
|
|
935
|
+
function readTagAttributes(tag, name) {
|
|
936
|
+
const attributes = {};
|
|
937
|
+
const nameStart = tag.indexOf(name);
|
|
938
|
+
const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
|
|
939
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
940
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
941
|
+
attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
|
|
942
|
+
}
|
|
943
|
+
return attributes;
|
|
944
|
+
}
|
|
932
945
|
function collectTextNodes(source) {
|
|
933
946
|
const nodes = [];
|
|
934
|
-
const
|
|
947
|
+
const elements = [];
|
|
935
948
|
let index = 0;
|
|
936
949
|
const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
|
|
937
950
|
if (!rawText) return;
|
|
951
|
+
const path = elements.map((element) => element.name);
|
|
952
|
+
const parent = elements[elements.length - 1];
|
|
938
953
|
nodes.push({
|
|
939
|
-
context: {
|
|
954
|
+
context: {
|
|
955
|
+
ancestorTags: path.slice(0, -1),
|
|
956
|
+
parentAttributes: { ...parent?.attributes ?? {} },
|
|
957
|
+
parentTag: parent?.name ?? "",
|
|
958
|
+
path
|
|
959
|
+
},
|
|
940
960
|
decodedText: decodeXmlText(rawText),
|
|
941
961
|
end,
|
|
942
962
|
sourceEnd,
|
|
@@ -978,14 +998,14 @@ function collectTextNodes(source) {
|
|
|
978
998
|
}
|
|
979
999
|
if (source.startsWith("</", index)) {
|
|
980
1000
|
const end2 = findTagEnd(source, index + 2);
|
|
981
|
-
|
|
1001
|
+
elements.pop();
|
|
982
1002
|
index = end2 + 1;
|
|
983
1003
|
continue;
|
|
984
1004
|
}
|
|
985
1005
|
const end = findTagEnd(source, index + 1);
|
|
986
1006
|
const tag = source.slice(index, end + 1);
|
|
987
1007
|
const name = readTagName(tag);
|
|
988
|
-
if (name && !/\/\s*>$/.test(tag))
|
|
1008
|
+
if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
|
|
989
1009
|
index = end + 1;
|
|
990
1010
|
}
|
|
991
1011
|
return nodes;
|
|
@@ -994,15 +1014,21 @@ function extractSsmlText(ssml) {
|
|
|
994
1014
|
parseSsml(ssml);
|
|
995
1015
|
return collectTextNodes(ssml).map((node) => node.decodedText);
|
|
996
1016
|
}
|
|
997
|
-
async function mapSsmlTextNodes(ssml, transform) {
|
|
1017
|
+
async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
998
1018
|
parseSsml(ssml);
|
|
999
1019
|
const nodes = collectTextNodes(ssml);
|
|
1020
|
+
const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
|
|
1000
1021
|
const replacements = await Promise.all(
|
|
1001
1022
|
nodes.map(async (node) => {
|
|
1002
|
-
const
|
|
1023
|
+
const context = {
|
|
1024
|
+
ancestorTags: [...node.context.ancestorTags],
|
|
1025
|
+
parentAttributes: { ...node.context.parentAttributes },
|
|
1003
1026
|
parentTag: node.context.parentTag,
|
|
1004
1027
|
path: [...node.context.path]
|
|
1005
|
-
}
|
|
1028
|
+
};
|
|
1029
|
+
const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
|
|
1030
|
+
if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
|
|
1031
|
+
const transformed = await transform(node.decodedText, context);
|
|
1006
1032
|
if (typeof transformed !== "string") {
|
|
1007
1033
|
throw new TypeError("SSML text node transform must return a string");
|
|
1008
1034
|
}
|
|
@@ -1159,6 +1185,7 @@ function findTagEnd2(source, start) {
|
|
|
1159
1185
|
}
|
|
1160
1186
|
function tokenizeElements(source) {
|
|
1161
1187
|
const tokens = [];
|
|
1188
|
+
const openElements = [];
|
|
1162
1189
|
let index = 0;
|
|
1163
1190
|
while (index < source.length) {
|
|
1164
1191
|
const start = source.indexOf("<", index);
|
|
@@ -1180,8 +1207,13 @@ function tokenizeElements(source) {
|
|
|
1180
1207
|
}
|
|
1181
1208
|
const end = findTagEnd2(source, start + 1);
|
|
1182
1209
|
const raw = source.slice(start, end + 1);
|
|
1210
|
+
if (raw.startsWith("</")) {
|
|
1211
|
+
openElements.pop();
|
|
1212
|
+
index = end + 1;
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1183
1215
|
const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
|
|
1184
|
-
if (!nameMatch?.[1]
|
|
1216
|
+
if (!nameMatch?.[1]) {
|
|
1185
1217
|
index = end + 1;
|
|
1186
1218
|
continue;
|
|
1187
1219
|
}
|
|
@@ -1191,7 +1223,15 @@ function tokenizeElements(source) {
|
|
|
1191
1223
|
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
1192
1224
|
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1193
1225
|
}
|
|
1194
|
-
|
|
1226
|
+
const selfClosing = /\/\s*>$/.test(raw);
|
|
1227
|
+
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1228
|
+
tokens.push({ attributes, end, name: nameMatch[1], parentVoiceName, selfClosing, start });
|
|
1229
|
+
if (!selfClosing) {
|
|
1230
|
+
openElements.push({
|
|
1231
|
+
name: nameMatch[1],
|
|
1232
|
+
voiceName: nameMatch[1].toLowerCase() === "voice" ? attributes.get("name") : parentVoiceName
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1195
1235
|
index = end + 1;
|
|
1196
1236
|
}
|
|
1197
1237
|
return tokens;
|
|
@@ -1207,7 +1247,23 @@ function addDiagnostic(diagnostics, source, offset, message, severity = "error")
|
|
|
1207
1247
|
function attr(token, name) {
|
|
1208
1248
|
return token.attributes.get(name.toLowerCase());
|
|
1209
1249
|
}
|
|
1210
|
-
function
|
|
1250
|
+
function normalizeVoiceStyleMap(customVoiceStyleMap) {
|
|
1251
|
+
const map = new Map(
|
|
1252
|
+
Object.entries(EXPRESS_AS_STYLES).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
|
|
1253
|
+
);
|
|
1254
|
+
for (const [voiceName, styles] of Object.entries(customVoiceStyleMap ?? {})) {
|
|
1255
|
+
map.set(
|
|
1256
|
+
voiceName.toLowerCase(),
|
|
1257
|
+
styles.map((style) => style.toLowerCase())
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return map;
|
|
1261
|
+
}
|
|
1262
|
+
function diagnosticSeverity(policy) {
|
|
1263
|
+
if (policy === "ignore") return void 0;
|
|
1264
|
+
return policy === "error" ? "error" : "warning";
|
|
1265
|
+
}
|
|
1266
|
+
function validateElement(token, source, diagnostics, voiceName, options, voiceStyleMap) {
|
|
1211
1267
|
const name = token.name.toLowerCase();
|
|
1212
1268
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1213
1269
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
@@ -1249,9 +1305,24 @@ function validateElement(token, source, diagnostics, voiceName, options) {
|
|
|
1249
1305
|
const role = attr(token, "role");
|
|
1250
1306
|
if (role && !ALLOWED_ROLES.has(role))
|
|
1251
1307
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
1252
|
-
const supportedStyles = voiceName ?
|
|
1253
|
-
|
|
1254
|
-
|
|
1308
|
+
const supportedStyles = voiceName ? voiceStyleMap.get(voiceName.toLowerCase()) : void 0;
|
|
1309
|
+
const severity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
1310
|
+
if (style && supportedStyles && !supportedStyles.includes(style.toLowerCase()) && severity)
|
|
1311
|
+
addDiagnostic(
|
|
1312
|
+
diagnostics,
|
|
1313
|
+
source,
|
|
1314
|
+
token.start,
|
|
1315
|
+
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
1316
|
+
severity
|
|
1317
|
+
);
|
|
1318
|
+
if (style && voiceName && !supportedStyles && severity)
|
|
1319
|
+
addDiagnostic(
|
|
1320
|
+
diagnostics,
|
|
1321
|
+
source,
|
|
1322
|
+
token.start,
|
|
1323
|
+
`Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
|
|
1324
|
+
severity
|
|
1325
|
+
);
|
|
1255
1326
|
}
|
|
1256
1327
|
if (name === "say-as" || name === "sayas") {
|
|
1257
1328
|
const interpretAs = attr(token, "interpret-as");
|
|
@@ -1311,6 +1382,14 @@ function validateElement(token, source, diagnostics, voiceName, options) {
|
|
|
1311
1382
|
addDiagnostic(diagnostics, source, token.start, "<audio src> must use HTTPS.");
|
|
1312
1383
|
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1313
1384
|
addDiagnostic(diagnostics, source, token.start, `<audio src> origin "${parsed.origin}" is not allowed.`);
|
|
1385
|
+
else if (!options.allowExternalAudio)
|
|
1386
|
+
addDiagnostic(
|
|
1387
|
+
diagnostics,
|
|
1388
|
+
source,
|
|
1389
|
+
token.start,
|
|
1390
|
+
`<audio src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`,
|
|
1391
|
+
"error"
|
|
1392
|
+
);
|
|
1314
1393
|
}
|
|
1315
1394
|
}
|
|
1316
1395
|
}
|
|
@@ -1341,7 +1420,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1341
1420
|
"Azure SSML requires at least one <voice> element under <speak>."
|
|
1342
1421
|
);
|
|
1343
1422
|
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
1344
|
-
|
|
1423
|
+
const voiceStyleMap = normalizeVoiceStyleMap(options.customVoiceStyleMap);
|
|
1424
|
+
const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
1425
|
+
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
1426
|
+
for (const token of voicesToValidate) {
|
|
1427
|
+
const name = attr(token, "name")?.trim();
|
|
1428
|
+
if (name && !voiceStyleMap.has(name.toLowerCase()) && policySeverity)
|
|
1429
|
+
addDiagnostic(
|
|
1430
|
+
diagnostics,
|
|
1431
|
+
ssml,
|
|
1432
|
+
token.start,
|
|
1433
|
+
`Unknown voice "${name}" is not registered in the voice style map.`,
|
|
1434
|
+
policySeverity
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
for (const token of tokens) {
|
|
1438
|
+
const tokenVoiceName = options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1439
|
+
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceStyleMap);
|
|
1440
|
+
}
|
|
1345
1441
|
return diagnostics;
|
|
1346
1442
|
}
|
|
1347
1443
|
|
|
@@ -1452,30 +1548,56 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
1452
1548
|
}
|
|
1453
1549
|
}
|
|
1454
1550
|
async function synthesizeSpeech(ssml, config) {
|
|
1551
|
+
if (config.signal?.aborted) {
|
|
1552
|
+
throw createSpeechSdkError("Speech synthesis was cancelled.");
|
|
1553
|
+
}
|
|
1455
1554
|
const speechConfig = createSpeechConfig(config);
|
|
1456
1555
|
const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
|
|
1457
1556
|
return await new Promise((resolve, reject) => {
|
|
1458
1557
|
let resourcesClosed = false;
|
|
1558
|
+
let settled = false;
|
|
1559
|
+
let timeout;
|
|
1560
|
+
let abortHandler;
|
|
1561
|
+
const cleanup = () => {
|
|
1562
|
+
if (timeout) clearTimeout(timeout);
|
|
1563
|
+
if (abortHandler) config.signal?.removeEventListener("abort", abortHandler);
|
|
1564
|
+
};
|
|
1459
1565
|
const closeResources = () => {
|
|
1460
1566
|
if (resourcesClosed) return;
|
|
1461
1567
|
resourcesClosed = true;
|
|
1462
1568
|
closeSpeechResources(speechConfig, synthesizer);
|
|
1463
1569
|
};
|
|
1464
1570
|
const rejectWithError = (error) => {
|
|
1571
|
+
if (settled) return;
|
|
1572
|
+
settled = true;
|
|
1573
|
+
cleanup();
|
|
1465
1574
|
closeResources();
|
|
1466
1575
|
reject(createSpeechSdkError(error));
|
|
1467
1576
|
};
|
|
1468
1577
|
const cb = (result) => {
|
|
1578
|
+
if (settled) return;
|
|
1469
1579
|
const { reason, errorDetails } = result;
|
|
1470
1580
|
if (reason !== SpeechSDK2.ResultReason.SynthesizingAudioCompleted) {
|
|
1471
1581
|
const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;
|
|
1472
1582
|
rejectWithError(err);
|
|
1473
1583
|
return;
|
|
1474
1584
|
}
|
|
1585
|
+
settled = true;
|
|
1586
|
+
cleanup();
|
|
1475
1587
|
closeResources();
|
|
1476
1588
|
resolve(result.audioData);
|
|
1477
1589
|
};
|
|
1478
1590
|
try {
|
|
1591
|
+
if (config.signal) {
|
|
1592
|
+
abortHandler = () => rejectWithError("Speech synthesis was cancelled.");
|
|
1593
|
+
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1594
|
+
}
|
|
1595
|
+
if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
|
|
1596
|
+
timeout = setTimeout(
|
|
1597
|
+
() => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
|
|
1598
|
+
config.timeoutMs
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1479
1601
|
synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
|
|
1480
1602
|
} catch (error) {
|
|
1481
1603
|
rejectWithError(error);
|
|
@@ -1492,10 +1614,10 @@ var AzureTtsClient = class {
|
|
|
1492
1614
|
__privateSet(this, _options, options);
|
|
1493
1615
|
}
|
|
1494
1616
|
async synthesize(ssml) {
|
|
1495
|
-
const { region, subscriptionKey, outputFormat } = __privateGet(this, _options);
|
|
1617
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
1496
1618
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1497
1619
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1498
|
-
const config = { endpoint, region, subscriptionKey, outputFormat };
|
|
1620
|
+
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
1499
1621
|
return synthesizeSpeech(ssml, config);
|
|
1500
1622
|
}
|
|
1501
1623
|
};
|