@livekit/agents-plugin-elevenlabs 1.0.40 → 1.0.41
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/dist/tts.cjs +55 -28
- package/dist/tts.cjs.map +1 -1
- package/dist/tts.d.cts +1 -2
- package/dist/tts.d.ts +1 -2
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +56 -28
- package/dist/tts.js.map +1 -1
- package/package.json +5 -5
- package/src/tts.ts +70 -26
package/dist/tts.cjs
CHANGED
|
@@ -72,7 +72,7 @@ function stripUndefined(obj) {
|
|
|
72
72
|
}
|
|
73
73
|
return result;
|
|
74
74
|
}
|
|
75
|
-
function toTimedWords(text, startTimesMs, durationsMs, flush = false) {
|
|
75
|
+
function toTimedWords(text, startTimesMs, durationsMs, flush = false, firstWordOffsetMs = 0) {
|
|
76
76
|
if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {
|
|
77
77
|
return [[], text || ""];
|
|
78
78
|
}
|
|
@@ -90,23 +90,27 @@ function toTimedWords(text, startTimesMs, durationsMs, flush = false) {
|
|
|
90
90
|
const start = startIndices[i];
|
|
91
91
|
const nextStart = startIndices[i + 1];
|
|
92
92
|
end = nextStart;
|
|
93
|
-
const startT = (timestamps[start] ?? 0) / 1e3;
|
|
94
|
-
const endT = (timestamps[nextStart] ?? 0) / 1e3;
|
|
95
|
-
timedWords.push(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
const startT = Math.max(0, (timestamps[start] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
94
|
+
const endT = Math.max(0, (timestamps[nextStart] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
95
|
+
timedWords.push(
|
|
96
|
+
(0, import_agents.createTimedString)({
|
|
97
|
+
text: text.slice(start, nextStart),
|
|
98
|
+
startTime: startT,
|
|
99
|
+
endTime: endT
|
|
100
|
+
})
|
|
101
|
+
);
|
|
100
102
|
}
|
|
101
103
|
if (flush && words.length > 0) {
|
|
102
104
|
const lastWordStart = startIndices[startIndices.length - 1];
|
|
103
|
-
const startT = (timestamps[lastWordStart] ?? 0) / 1e3;
|
|
104
|
-
const endT = (timestamps[timestamps.length - 1] ?? 0) / 1e3;
|
|
105
|
-
timedWords.push(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
const startT = Math.max(0, (timestamps[lastWordStart] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
106
|
+
const endT = Math.max(0, (timestamps[timestamps.length - 1] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
107
|
+
timedWords.push(
|
|
108
|
+
(0, import_agents.createTimedString)({
|
|
109
|
+
text: text.slice(lastWordStart),
|
|
110
|
+
startTime: startT,
|
|
111
|
+
endTime: endT
|
|
112
|
+
})
|
|
113
|
+
);
|
|
110
114
|
end = text.length;
|
|
111
115
|
} else if (words.length > 0) {
|
|
112
116
|
end = startIndices[startIndices.length - 1];
|
|
@@ -166,7 +170,8 @@ class Connection {
|
|
|
166
170
|
waiter,
|
|
167
171
|
textBuffer: "",
|
|
168
172
|
startTimesMs: [],
|
|
169
|
-
durationsMs: []
|
|
173
|
+
durationsMs: [],
|
|
174
|
+
firstWordOffsetMs: null
|
|
170
175
|
});
|
|
171
176
|
}
|
|
172
177
|
sendContent(content) {
|
|
@@ -321,6 +326,9 @@ class Connection {
|
|
|
321
326
|
const char = chars[i];
|
|
322
327
|
const start = starts[i];
|
|
323
328
|
const dur = durs[i];
|
|
329
|
+
if (ctx.firstWordOffsetMs === null && start > 0) {
|
|
330
|
+
ctx.firstWordOffsetMs = start;
|
|
331
|
+
}
|
|
324
332
|
if (char.length > 1) {
|
|
325
333
|
for (let j = 0; j < char.length - 1; j++) {
|
|
326
334
|
ctx.startTimesMs.push(start);
|
|
@@ -333,7 +341,9 @@ class Connection {
|
|
|
333
341
|
const [timedWords, remainingText] = toTimedWords(
|
|
334
342
|
ctx.textBuffer,
|
|
335
343
|
ctx.startTimesMs,
|
|
336
|
-
ctx.durationsMs
|
|
344
|
+
ctx.durationsMs,
|
|
345
|
+
false,
|
|
346
|
+
ctx.firstWordOffsetMs ?? 0
|
|
337
347
|
);
|
|
338
348
|
if (timedWords.length > 0) {
|
|
339
349
|
stream2.pushTimedTranscript(timedWords);
|
|
@@ -353,7 +363,8 @@ class Connection {
|
|
|
353
363
|
ctx.textBuffer,
|
|
354
364
|
ctx.startTimesMs,
|
|
355
365
|
ctx.durationsMs,
|
|
356
|
-
true
|
|
366
|
+
true,
|
|
367
|
+
ctx.firstWordOffsetMs ?? 0
|
|
357
368
|
);
|
|
358
369
|
if (timedWords.length > 0) {
|
|
359
370
|
stream2.pushTimedTranscript(timedWords);
|
|
@@ -428,8 +439,10 @@ class TTS extends import_agents.tts.TTS {
|
|
|
428
439
|
const autoMode = opts.autoMode ?? true;
|
|
429
440
|
const encoding = opts.encoding ?? DEFAULT_ENCODING;
|
|
430
441
|
const sampleRate = sampleRateFromFormat(encoding);
|
|
442
|
+
const syncAlignment = opts.syncAlignment ?? true;
|
|
431
443
|
super(sampleRate, 1, {
|
|
432
|
-
streaming: true
|
|
444
|
+
streaming: true,
|
|
445
|
+
alignedTranscript: syncAlignment
|
|
433
446
|
});
|
|
434
447
|
const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;
|
|
435
448
|
if (!apiKey) {
|
|
@@ -720,13 +733,28 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
720
733
|
};
|
|
721
734
|
const audioProcessTask = async () => {
|
|
722
735
|
let lastFrame;
|
|
736
|
+
let pendingTimedTranscripts = [];
|
|
737
|
+
const sendLastFrame = (final) => {
|
|
738
|
+
if (lastFrame) {
|
|
739
|
+
this.queue.put({
|
|
740
|
+
requestId,
|
|
741
|
+
segmentId,
|
|
742
|
+
frame: lastFrame,
|
|
743
|
+
final,
|
|
744
|
+
timedTranscripts: pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : void 0
|
|
745
|
+
});
|
|
746
|
+
lastFrame = void 0;
|
|
747
|
+
pendingTimedTranscripts = [];
|
|
748
|
+
}
|
|
749
|
+
};
|
|
723
750
|
while (!this.abortController.signal.aborted) {
|
|
751
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
752
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift());
|
|
753
|
+
}
|
|
724
754
|
while (this.#audioQueue.length > 0) {
|
|
725
755
|
const audioData = this.#audioQueue.shift();
|
|
726
756
|
for (const frame of bstream.write(audioData.buffer)) {
|
|
727
|
-
|
|
728
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
729
|
-
}
|
|
757
|
+
sendLastFrame(false);
|
|
730
758
|
lastFrame = frame;
|
|
731
759
|
}
|
|
732
760
|
}
|
|
@@ -735,15 +763,14 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
735
763
|
}
|
|
736
764
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
737
765
|
}
|
|
766
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
767
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift());
|
|
768
|
+
}
|
|
738
769
|
for (const frame of bstream.flush()) {
|
|
739
|
-
|
|
740
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
741
|
-
}
|
|
770
|
+
sendLastFrame(false);
|
|
742
771
|
lastFrame = frame;
|
|
743
772
|
}
|
|
744
|
-
|
|
745
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: true });
|
|
746
|
-
}
|
|
773
|
+
sendLastFrame(true);
|
|
747
774
|
};
|
|
748
775
|
try {
|
|
749
776
|
await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);
|
package/dist/tts.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n Future,\n log,\n shortuuid,\n stream,\n tokenize,\n tts,\n type voice,\n} from '@livekit/agents';\nimport { Mutex } from '@livekit/mutex';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\ntype TimedString = voice.TimedString;\n\nconst DEFAULT_VOICE_ID = 'bIHbv24MWmeRgasZH58o';\nconst API_BASE_URL_V1 = 'https://api.elevenlabs.io/v1';\nconst AUTHORIZATION_HEADER = 'xi-api-key';\nconst WS_INACTIVITY_TIMEOUT = 180;\nconst DEFAULT_ENCODING: TTSEncoding = 'pcm_22050';\n\nexport interface VoiceSettings {\n stability: number; // [0.0 - 1.0]\n similarity_boost: number; // [0.0 - 1.0]\n style?: number; // [0.0 - 1.0]\n speed?: number; // [0.8 - 1.2]\n use_speaker_boost?: boolean;\n}\n\nexport interface Voice {\n id: string;\n name: string;\n category: string;\n settings?: VoiceSettings;\n}\n\nexport interface PronunciationDictionaryLocator {\n pronunciation_dictionary_id: string;\n version_id: string;\n}\n\nexport interface TTSOptions {\n apiKey?: string;\n // New interface\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n // Legacy interface (backward compatibility)\n voice?: Voice;\n modelID?: TTSModels | string;\n languageCode?: string;\n // Common options\n baseURL?: string;\n encoding?: TTSEncoding;\n streamingLatency?: number;\n wordTokenizer?: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing?: boolean;\n enableLogging?: boolean;\n inactivityTimeout?: number;\n syncAlignment?: boolean;\n applyTextNormalization?: 'auto' | 'on' | 'off';\n preferredAlignment?: 'normalized' | 'original';\n autoMode?: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal options type with resolved defaults\ninterface ResolvedTTSOptions {\n apiKey: string;\n voiceId: string;\n voiceSettings?: VoiceSettings;\n model: TTSModels | string;\n language?: string;\n baseURL: string;\n encoding: TTSEncoding;\n sampleRate: number;\n streamingLatency?: number;\n wordTokenizer: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing: boolean;\n enableLogging: boolean;\n inactivityTimeout: number;\n syncAlignment: boolean;\n applyTextNormalization: 'auto' | 'on' | 'off';\n preferredAlignment: 'normalized' | 'original';\n autoMode: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal types for connection management\ninterface SynthesizeContent {\n contextId: string;\n text: string;\n flush: boolean;\n}\n\ninterface CloseContext {\n contextId: string;\n}\n\ninterface StreamData {\n stream: SynthesizeStream;\n waiter: {\n resolve: (value: void) => void;\n reject: (error: Error) => void;\n };\n textBuffer: string;\n startTimesMs: number[];\n durationsMs: number[];\n}\n\ntype ConnectionMessage = SynthesizeContent | CloseContext;\n\n// Helper Functions\n\nfunction sampleRateFromFormat(encoding: TTSEncoding): number {\n const split = encoding.split('_');\n return parseInt(split[1]!, 10);\n}\n\nfunction synthesizeUrl(opts: ResolvedTTSOptions): string {\n const { baseURL, voiceId, model, encoding, streamingLatency } = opts;\n let url = `${baseURL}/text-to-speech/${voiceId}/stream?model_id=${model}&output_format=${encoding}`;\n if (streamingLatency !== undefined) {\n url += `&optimize_streaming_latency=${streamingLatency}`;\n }\n return url;\n}\n\nfunction multiStreamUrl(opts: ResolvedTTSOptions): string {\n const baseURL = opts.baseURL.replace('https://', 'wss://').replace('http://', 'ws://');\n const params: string[] = [];\n params.push(`model_id=${opts.model}`);\n params.push(`output_format=${opts.encoding}`);\n if (opts.language) {\n params.push(`language_code=${opts.language}`);\n }\n params.push(`enable_ssml_parsing=${opts.enableSsmlParsing}`);\n params.push(`enable_logging=${opts.enableLogging}`);\n params.push(`inactivity_timeout=${opts.inactivityTimeout}`);\n params.push(`apply_text_normalization=${opts.applyTextNormalization}`);\n if (opts.syncAlignment) {\n params.push('sync_alignment=true');\n }\n if (opts.autoMode !== undefined) {\n params.push(`auto_mode=${opts.autoMode}`);\n }\n return `${baseURL}/text-to-speech/${opts.voiceId}/multi-stream-input?${params.join('&')}`;\n}\n\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key in obj) {\n if (obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Convert alignment data to timed words.\n * Returns the timed words and remaining text buffer.\n */\nfunction toTimedWords(\n text: string,\n startTimesMs: number[],\n durationsMs: number[],\n flush: boolean = false,\n): [TimedString[], string] {\n if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {\n return [[], text || ''];\n }\n\n const lastStartTime = startTimesMs[startTimesMs.length - 1]!;\n const lastDuration = durationsMs[durationsMs.length - 1]!;\n const timestamps = [...startTimesMs, lastStartTime + lastDuration];\n\n const words = tokenize.basic.splitWords(text, false);\n const timedWords: TimedString[] = [];\n\n if (words.length === 0) {\n return [[], text];\n }\n\n const startIndices = words.map((w) => w[1]);\n let end = 0;\n\n // We don't know if the last word is complete, always leave it as remaining\n for (let i = 0; i < startIndices.length - 1; i++) {\n const start = startIndices[i]!;\n const nextStart = startIndices[i + 1]!;\n end = nextStart;\n const startT = (timestamps[start] ?? 0) / 1000;\n const endT = (timestamps[nextStart] ?? 0) / 1000;\n timedWords.push({\n text: text.slice(start, nextStart),\n startTime: startT,\n endTime: endT,\n });\n }\n\n if (flush && words.length > 0) {\n const lastWordStart = startIndices[startIndices.length - 1]!;\n const startT = (timestamps[lastWordStart] ?? 0) / 1000;\n const endT = (timestamps[timestamps.length - 1] ?? 0) / 1000;\n timedWords.push({\n text: text.slice(lastWordStart),\n startTime: startT,\n endTime: endT,\n });\n end = text.length;\n } else if (words.length > 0) {\n end = startIndices[startIndices.length - 1]!;\n }\n\n return [timedWords, text.slice(end)];\n}\n\nclass Connection {\n #opts: ResolvedTTSOptions;\n #ws: WebSocket | null = null;\n #isCurrent = true;\n #activeContexts = new Set<string>();\n #inputQueue: ConnectionMessage[] = [];\n #contextData = new Map<string, StreamData>();\n #sendTask: Promise<void> | null = null;\n #recvTask: Promise<void> | null = null;\n #closed = false;\n #logger = log();\n #inputQueueResolver: (() => void) | null = null;\n\n constructor(opts: ResolvedTTSOptions) {\n this.#opts = opts;\n }\n\n get voiceId(): string {\n return this.#opts.voiceId;\n }\n\n get isCurrent(): boolean {\n return this.#isCurrent;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n markNonCurrent(): void {\n this.#isCurrent = false;\n }\n\n async connect(): Promise<void> {\n if (this.#ws || this.#closed) {\n return;\n }\n\n const url = multiStreamUrl(this.#opts);\n const headers = { [AUTHORIZATION_HEADER]: this.#opts.apiKey };\n\n return new Promise((resolve, reject) => {\n this.#ws = new WebSocket(url, { headers });\n\n this.#ws.on('open', () => {\n this.#sendTask = this.#sendLoop();\n this.#recvTask = this.#recvLoop();\n resolve();\n });\n\n this.#ws.on('error', (error) => {\n this.#logger.error({ error }, 'WebSocket connection error');\n reject(new APIConnectionError({ message: `WebSocket error: ${error.message}` }));\n });\n });\n }\n\n registerStream(\n stream: SynthesizeStream,\n waiter: { resolve: (value: void) => void; reject: (error: Error) => void },\n ): void {\n const contextId = stream.contextId;\n this.#contextData.set(contextId, {\n stream,\n waiter,\n textBuffer: '',\n startTimesMs: [],\n durationsMs: [],\n });\n }\n\n sendContent(content: SynthesizeContent): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push(content);\n this.#inputQueueResolver?.();\n }\n\n closeContext(contextId: string): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push({ contextId });\n this.#inputQueueResolver?.();\n }\n\n async #sendLoop(): Promise<void> {\n try {\n while (!this.#closed) {\n // Wait for messages in queue\n if (this.#inputQueue.length === 0) {\n await new Promise<void>((resolve) => {\n this.#inputQueueResolver = resolve;\n });\n this.#inputQueueResolver = null;\n }\n\n if (this.#closed) break;\n\n const msg = this.#inputQueue.shift();\n if (!msg) continue;\n\n if (!this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n break;\n }\n\n if ('text' in msg) {\n // SynthesizeContent\n const content = msg as SynthesizeContent;\n const isNewContext = !this.#activeContexts.has(content.contextId);\n\n // If not current and this is a new context, ignore it\n if (!this.#isCurrent && isNewContext) {\n continue;\n }\n\n if (isNewContext) {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : {};\n\n const initPkt: Record<string, unknown> = {\n text: ' ',\n voice_settings: voiceSettings,\n context_id: content.contextId,\n };\n\n if (this.#opts.pronunciationDictionaryLocators) {\n initPkt.pronunciation_dictionary_locators =\n this.#opts.pronunciationDictionaryLocators.map((locator) => ({\n pronunciation_dictionary_id: locator.pronunciation_dictionary_id,\n version_id: locator.version_id,\n }));\n }\n\n const initPktStr = JSON.stringify(initPkt);\n this.#ws.send(initPktStr);\n this.#activeContexts.add(content.contextId);\n }\n\n const pkt: Record<string, unknown> = {\n text: content.text,\n context_id: content.contextId,\n };\n if (content.flush) {\n pkt.flush = true;\n }\n\n const pktStr = JSON.stringify(pkt);\n this.#ws.send(pktStr);\n } else {\n // CloseContext\n const closeMsg = msg as CloseContext;\n if (this.#activeContexts.has(closeMsg.contextId)) {\n const closePkt = {\n context_id: closeMsg.contextId,\n close_context: true,\n };\n const closePktStr = JSON.stringify(closePkt);\n this.#ws.send(closePktStr);\n }\n }\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'send loop error');\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n async #recvLoop(): Promise<void> {\n try {\n const messageChannel = stream.createStreamChannel<Record<string, unknown>>();\n const errorFuture = new Future<Error>();\n\n const onMessage = (rawData: Buffer) => {\n try {\n const parsed = JSON.parse(rawData.toString());\n messageChannel.write(parsed);\n } catch (e) {\n this.#logger.warn({ error: e }, 'failed to parse WebSocket message');\n }\n };\n\n const onClose = () => {\n if (!this.#closed && this.#contextData.size > 0) {\n this.#logger.warn('websocket closed unexpectedly');\n }\n messageChannel.close();\n };\n\n const onError = (error: Error) => {\n errorFuture.resolve(error);\n messageChannel.close();\n };\n\n // Set up persistent listeners\n if (!this.#ws) return;\n this.#ws.on('message', onMessage);\n this.#ws.on('close', onClose);\n this.#ws.on('error', onError);\n\n const cleanup = () => {\n this.#ws?.off('message', onMessage);\n this.#ws?.off('close', onClose);\n this.#ws?.off('error', onError);\n };\n\n const reader = messageChannel.stream().getReader();\n try {\n while (!this.#closed) {\n const result = await reader.read();\n if (result.done || this.#closed) break;\n\n const data = result.value;\n const contextId = data.contextId as string | undefined;\n const ctx = contextId ? this.#contextData.get(contextId) : undefined;\n\n if (data.error) {\n this.#logger.error(\n { context_id: contextId, error: data.error, data },\n 'elevenlabs tts returned error',\n );\n if (contextId) {\n if (ctx) {\n ctx.waiter.reject(new APIError(data.error as string));\n }\n this.#cleanupContext(contextId);\n }\n continue;\n }\n\n if (!ctx) {\n this.#logger.warn({ data }, 'unexpected message received from elevenlabs tts');\n continue;\n }\n\n const stream = ctx.stream;\n\n // Process alignment data\n const alignment =\n this.#opts.preferredAlignment === 'normalized'\n ? (data.normalizedAlignment as Record<string, unknown>)\n : (data.alignment as Record<string, unknown>);\n\n if (alignment && stream) {\n const chars = alignment.chars as string[] | undefined;\n const starts = (alignment.charStartTimesMs || alignment.charsStartTimesMs) as\n | number[]\n | undefined;\n const durs = (alignment.charDurationsMs || alignment.charsDurationsMs) as\n | number[]\n | undefined;\n\n if (\n chars &&\n starts &&\n durs &&\n chars.length === durs.length &&\n starts.length === durs.length\n ) {\n ctx.textBuffer += chars.join('');\n\n // Handle chars with multiple characters\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!;\n const start = starts[i]!;\n const dur = durs[i]!;\n\n if (char.length > 1) {\n for (let j = 0; j < char.length - 1; j++) {\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(0);\n }\n }\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(dur);\n }\n\n const [timedWords, remainingText] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n );\n\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n\n ctx.textBuffer = remainingText;\n ctx.startTimesMs = ctx.startTimesMs.slice(-remainingText.length);\n ctx.durationsMs = ctx.durationsMs.slice(-remainingText.length);\n }\n }\n\n if (data.audio) {\n const audioData = Buffer.from(data.audio as string, 'base64');\n stream.pushAudio(audioData);\n }\n\n if (data.isFinal) {\n // Flush remaining alignment data\n if (ctx.textBuffer) {\n const [timedWords] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n true,\n );\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n }\n\n stream.markDone();\n ctx.waiter.resolve();\n this.#cleanupContext(contextId!);\n\n if (!this.#isCurrent && this.#activeContexts.size === 0) {\n this.#logger.debug('no active contexts, shutting down connection');\n break;\n }\n }\n }\n\n // Throw any error that occurred\n if (errorFuture.done) {\n throw await errorFuture.await;\n }\n } finally {\n reader.releaseLock();\n cleanup();\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'recv loop error');\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(e instanceof Error ? e : new Error(String(e)));\n }\n this.#contextData.clear();\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n #cleanupContext(contextId: string): void {\n this.#contextData.delete(contextId);\n this.#activeContexts.delete(contextId);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n this.#closed = true;\n this.#inputQueueResolver?.();\n\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(new APIStatusError({ message: 'connection closed' }));\n }\n this.#contextData.clear();\n\n if (this.#ws) {\n this.#ws.close();\n this.#ws = null;\n }\n\n if (this.#sendTask) {\n await this.#sendTask.catch(() => {});\n }\n if (this.#recvTask) {\n await this.#recvTask.catch(() => {});\n }\n }\n}\n\nexport class TTS extends tts.TTS {\n #opts: ResolvedTTSOptions;\n #streams = new Set<SynthesizeStream>();\n #currentConnection: Connection | null = null;\n #connectionLock = new Mutex();\n #logger = log();\n\n label = 'elevenlabs.TTS';\n\n constructor(opts: TTSOptions = {}) {\n const autoMode = opts.autoMode ?? true;\n const encoding = opts.encoding ?? DEFAULT_ENCODING;\n const sampleRate = sampleRateFromFormat(encoding);\n\n super(sampleRate, 1, {\n streaming: true,\n });\n\n const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ElevenLabs API key is required, either as argument or set ELEVEN_API_KEY environmental variable',\n );\n }\n\n let wordTokenizer = opts.wordTokenizer;\n if (!wordTokenizer) {\n wordTokenizer = autoMode\n ? new tokenize.basic.SentenceTokenizer()\n : new tokenize.basic.WordTokenizer(false);\n } else if (autoMode && !(wordTokenizer instanceof tokenize.SentenceTokenizer)) {\n this.#logger.warn(\n 'autoMode is enabled, it expects full sentences or phrases, ' +\n 'please provide a SentenceTokenizer instead of a WordTokenizer.',\n );\n }\n\n // Handle legacy options for backward compatibility\n const voiceId = opts.voiceId ?? opts.voice?.id ?? DEFAULT_VOICE_ID;\n const voiceSettings = opts.voiceSettings ?? opts.voice?.settings;\n const model = opts.model ?? opts.modelID ?? 'eleven_turbo_v2_5';\n const language = opts.language ?? opts.languageCode;\n\n this.#opts = {\n apiKey,\n voiceId,\n voiceSettings,\n model,\n language,\n baseURL: opts.baseURL ?? API_BASE_URL_V1,\n encoding,\n sampleRate,\n streamingLatency: opts.streamingLatency,\n wordTokenizer,\n chunkLengthSchedule: opts.chunkLengthSchedule,\n enableSsmlParsing: opts.enableSsmlParsing ?? false,\n enableLogging: opts.enableLogging ?? true,\n inactivityTimeout: opts.inactivityTimeout ?? WS_INACTIVITY_TIMEOUT,\n syncAlignment: opts.syncAlignment ?? true,\n applyTextNormalization: opts.applyTextNormalization ?? 'auto',\n preferredAlignment: opts.preferredAlignment ?? 'normalized',\n autoMode,\n pronunciationDictionaryLocators: opts.pronunciationDictionaryLocators,\n };\n }\n\n get model(): string {\n return this.#opts.model;\n }\n\n get provider(): string {\n return 'ElevenLabs';\n }\n\n async listVoices(): Promise<Voice[]> {\n const response = await fetch(`${this.#opts.baseURL}/voices`, {\n headers: { [AUTHORIZATION_HEADER]: this.#opts.apiKey },\n });\n const data = (await response.json()) as {\n voices: { voice_id: string; name: string; category: string }[];\n };\n return data.voices.map((v) => ({\n id: v.voice_id,\n name: v.name,\n category: v.category,\n }));\n }\n\n updateOptions(opts: {\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n }): void {\n let changed = false;\n\n if (opts.model !== undefined && opts.model !== this.#opts.model) {\n this.#opts.model = opts.model;\n changed = true;\n }\n\n if (opts.voiceId !== undefined && opts.voiceId !== this.#opts.voiceId) {\n this.#opts.voiceId = opts.voiceId;\n changed = true;\n }\n\n if (opts.voiceSettings !== undefined) {\n this.#opts.voiceSettings = opts.voiceSettings;\n changed = true;\n }\n\n if (opts.language !== undefined && opts.language !== this.#opts.language) {\n this.#opts.language = opts.language;\n changed = true;\n }\n\n if (opts.pronunciationDictionaryLocators !== undefined) {\n this.#opts.pronunciationDictionaryLocators = opts.pronunciationDictionaryLocators;\n changed = true;\n }\n\n if (changed && this.#currentConnection) {\n this.#currentConnection.markNonCurrent();\n this.#currentConnection = null;\n }\n }\n\n async currentConnection(): Promise<Connection> {\n const unlock = await this.#connectionLock.lock();\n try {\n if (\n this.#currentConnection &&\n this.#currentConnection.isCurrent &&\n !this.#currentConnection.closed\n ) {\n return this.#currentConnection;\n }\n\n const conn = new Connection({ ...this.#opts });\n await conn.connect();\n this.#currentConnection = conn;\n return conn;\n } finally {\n unlock();\n }\n }\n\n synthesize(text: string): ChunkedStream {\n return new ChunkedStream(this, text, { ...this.#opts });\n }\n\n stream(): SynthesizeStream {\n const stream = new SynthesizeStream(this, { ...this.#opts });\n this.#streams.add(stream);\n return stream;\n }\n\n async close(): Promise<void> {\n for (const stream of this.#streams) {\n stream.close();\n }\n this.#streams.clear();\n\n if (this.#currentConnection) {\n await this.#currentConnection.close();\n this.#currentConnection = null;\n }\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #logger = log();\n\n label = 'elevenlabs.ChunkedStream';\n\n constructor(tts: TTS, text: string, opts: ResolvedTTSOptions) {\n super(text, tts);\n this.#tts = tts;\n this.#opts = opts;\n }\n\n protected async run(): Promise<void> {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : undefined;\n\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n try {\n const response = await fetch(synthesizeUrl(this.#opts), {\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n text: this.inputText,\n model_id: this.#opts.model,\n voice_settings: voiceSettings,\n }),\n signal: this.abortSignal,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new APIStatusError({\n message: `ElevenLabs API error: ${errorText}`,\n options: { statusCode: response.status },\n });\n }\n\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.startsWith('audio/')) {\n const content = await response.text();\n throw new APIError(`ElevenLabs returned non-audio data: ${content}`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new APIError('No response body');\n }\n\n let lastFrame: AudioFrame | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n for (const frame of bstream.write(value.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Flush remaining data\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: true });\n }\n } catch (e) {\n if (e instanceof APIError) {\n throw e;\n }\n if (e instanceof Error && e.name === 'AbortError') {\n return;\n }\n throw new APIConnectionError({ message: `Connection error: ${e}` });\n }\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #contextId: string;\n #sentTokenizerStream: tokenize.SentenceStream | tokenize.WordStream;\n #logger = log();\n #audioQueue: Buffer[] = [];\n #timedTranscriptQueue: TimedString[] = [];\n #streamDone = false;\n\n label = 'elevenlabs.SynthesizeStream';\n\n constructor(tts: TTS, opts: ResolvedTTSOptions) {\n super(tts);\n this.#tts = tts;\n this.#opts = opts;\n this.#contextId = shortuuid();\n this.#sentTokenizerStream = this.#opts.wordTokenizer.stream();\n }\n\n get contextId(): string {\n return this.#contextId;\n }\n\n pushAudio(data: Buffer): void {\n // Don't push if stream is closed/aborted\n if (this.closed || this.abortController.signal.aborted) {\n return;\n }\n this.#audioQueue.push(data);\n }\n\n pushTimedTranscript(timedWords: TimedString[]): void {\n this.#timedTranscriptQueue.push(...timedWords);\n }\n\n markDone(): void {\n this.#streamDone = true;\n }\n\n protected async run(): Promise<void> {\n const requestId = this.#contextId;\n const segmentId = this.#contextId;\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n let connection: Connection;\n try {\n connection = await this.#tts.currentConnection();\n } catch (e) {\n throw new APIConnectionError({ message: 'could not connect to ElevenLabs' });\n }\n\n let waiterReject: ((reason: Error) => void) | undefined;\n const waiterPromise = new Promise<void>((resolve, reject) => {\n waiterReject = reject;\n connection.registerStream(this, { resolve, reject });\n });\n\n // Handle abort - reject the waiter so Promise.all can complete\n const abortHandler = () => {\n if (waiterReject) {\n waiterReject(new Error('Stream aborted'));\n }\n };\n this.abortController.signal.addEventListener('abort', abortHandler, { once: true });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (this.abortController.signal.aborted) break;\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.#sentTokenizerStream.flush();\n continue;\n }\n this.#sentTokenizerStream.pushText(data);\n }\n this.#sentTokenizerStream.endInput();\n };\n\n const sentenceStreamTask = async () => {\n const flushOnChunk =\n this.#opts.wordTokenizer instanceof tokenize.SentenceTokenizer && this.#opts.autoMode;\n\n let xmlContent: string[] = [];\n\n for await (const data of this.#sentTokenizerStream) {\n if (this.abortController.signal.aborted) break;\n\n let text = data.token;\n const xmlStartTokens = ['<phoneme', '<break'];\n const xmlEndTokens = ['</phoneme>', '/>'];\n\n if (\n (this.#opts.enableSsmlParsing &&\n xmlStartTokens.some((start) => text.startsWith(start))) ||\n xmlContent.length > 0\n ) {\n xmlContent.push(text);\n\n if (xmlEndTokens.some((end) => text.includes(end))) {\n text = xmlContent.join(' ');\n xmlContent = [];\n } else {\n continue;\n }\n }\n\n const formattedText = `${text} `; // must always end with a space\n connection.sendContent({\n contextId: this.#contextId,\n text: formattedText,\n flush: flushOnChunk,\n });\n }\n\n if (xmlContent.length > 0) {\n this.#logger.warn('ElevenLabs stream ended with incomplete xml content');\n }\n\n // Send final empty text to signal end of input\n connection.sendContent({ contextId: this.#contextId, text: '', flush: true });\n connection.closeContext(this.#contextId);\n };\n\n const audioProcessTask = async () => {\n let lastFrame: AudioFrame | undefined;\n\n while (!this.abortController.signal.aborted) {\n // Process audio queue\n while (this.#audioQueue.length > 0) {\n const audioData = this.#audioQueue.shift()!;\n for (const frame of bstream.write(audioData.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Exit when stream is done and queue is empty\n if (this.#streamDone && this.#audioQueue.length === 0) {\n break;\n }\n\n // Small delay to avoid busy waiting\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n\n // Flush remaining\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: true });\n }\n };\n\n try {\n await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);\n } catch (e) {\n // If aborted, this is a normal termination - don't throw\n if (this.abortController.signal.aborted) {\n return;\n }\n\n if (e instanceof APITimeoutError) {\n throw e;\n }\n if (e instanceof APIStatusError) {\n throw e;\n }\n throw new APIStatusError({ message: 'Could not synthesize' });\n } finally {\n // Clean up abort listener\n this.abortController.signal.removeEventListener('abort', abortHandler);\n }\n }\n\n close(): void {\n // Clear audio buffers to prevent memory leak\n this.#audioQueue.length = 0;\n this.#timedTranscriptQueue.length = 0;\n this.#streamDone = true;\n this.#sentTokenizerStream.close();\n super.close();\n }\n}\n"],"mappings":"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","names":["stream","tts"]}
|
|
1
|
+
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n Future,\n type TimedString,\n createTimedString,\n log,\n shortuuid,\n stream,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport { Mutex } from '@livekit/mutex';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst DEFAULT_VOICE_ID = 'bIHbv24MWmeRgasZH58o';\nconst API_BASE_URL_V1 = 'https://api.elevenlabs.io/v1';\nconst AUTHORIZATION_HEADER = 'xi-api-key';\nconst WS_INACTIVITY_TIMEOUT = 180;\nconst DEFAULT_ENCODING: TTSEncoding = 'pcm_22050';\n\nexport interface VoiceSettings {\n stability: number; // [0.0 - 1.0]\n similarity_boost: number; // [0.0 - 1.0]\n style?: number; // [0.0 - 1.0]\n speed?: number; // [0.8 - 1.2]\n use_speaker_boost?: boolean;\n}\n\nexport interface Voice {\n id: string;\n name: string;\n category: string;\n settings?: VoiceSettings;\n}\n\nexport interface PronunciationDictionaryLocator {\n pronunciation_dictionary_id: string;\n version_id: string;\n}\n\nexport interface TTSOptions {\n apiKey?: string;\n // New interface\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n // Legacy interface (backward compatibility)\n voice?: Voice;\n modelID?: TTSModels | string;\n languageCode?: string;\n // Common options\n baseURL?: string;\n encoding?: TTSEncoding;\n streamingLatency?: number;\n wordTokenizer?: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing?: boolean;\n enableLogging?: boolean;\n inactivityTimeout?: number;\n syncAlignment?: boolean;\n applyTextNormalization?: 'auto' | 'on' | 'off';\n preferredAlignment?: 'normalized' | 'original';\n autoMode?: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal options type with resolved defaults\ninterface ResolvedTTSOptions {\n apiKey: string;\n voiceId: string;\n voiceSettings?: VoiceSettings;\n model: TTSModels | string;\n language?: string;\n baseURL: string;\n encoding: TTSEncoding;\n sampleRate: number;\n streamingLatency?: number;\n wordTokenizer: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing: boolean;\n enableLogging: boolean;\n inactivityTimeout: number;\n syncAlignment: boolean;\n applyTextNormalization: 'auto' | 'on' | 'off';\n preferredAlignment: 'normalized' | 'original';\n autoMode: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal types for connection management\ninterface SynthesizeContent {\n contextId: string;\n text: string;\n flush: boolean;\n}\n\ninterface CloseContext {\n contextId: string;\n}\n\ninterface StreamData {\n stream: SynthesizeStream;\n waiter: {\n resolve: (value: void) => void;\n reject: (error: Error) => void;\n };\n textBuffer: string;\n startTimesMs: number[];\n durationsMs: number[];\n /** First word offset for timestamp normalization (removes leading silence) */\n firstWordOffsetMs: number | null;\n}\n\ntype ConnectionMessage = SynthesizeContent | CloseContext;\n\n// Helper Functions\n\nfunction sampleRateFromFormat(encoding: TTSEncoding): number {\n const split = encoding.split('_');\n return parseInt(split[1]!, 10);\n}\n\nfunction synthesizeUrl(opts: ResolvedTTSOptions): string {\n const { baseURL, voiceId, model, encoding, streamingLatency } = opts;\n let url = `${baseURL}/text-to-speech/${voiceId}/stream?model_id=${model}&output_format=${encoding}`;\n if (streamingLatency !== undefined) {\n url += `&optimize_streaming_latency=${streamingLatency}`;\n }\n return url;\n}\n\nfunction multiStreamUrl(opts: ResolvedTTSOptions): string {\n const baseURL = opts.baseURL.replace('https://', 'wss://').replace('http://', 'ws://');\n const params: string[] = [];\n params.push(`model_id=${opts.model}`);\n params.push(`output_format=${opts.encoding}`);\n if (opts.language) {\n params.push(`language_code=${opts.language}`);\n }\n params.push(`enable_ssml_parsing=${opts.enableSsmlParsing}`);\n params.push(`enable_logging=${opts.enableLogging}`);\n params.push(`inactivity_timeout=${opts.inactivityTimeout}`);\n params.push(`apply_text_normalization=${opts.applyTextNormalization}`);\n if (opts.syncAlignment) {\n params.push('sync_alignment=true');\n }\n if (opts.autoMode !== undefined) {\n params.push(`auto_mode=${opts.autoMode}`);\n }\n return `${baseURL}/text-to-speech/${opts.voiceId}/multi-stream-input?${params.join('&')}`;\n}\n\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key in obj) {\n if (obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Convert alignment data to timed words.\n * Returns the timed words and remaining text buffer.\n *\n * @param firstWordOffsetMs - Optional offset to normalize timestamps (subtract from all).\n * ElevenLabs returns absolute timestamps from the start of TTS audio, which may include\n * leading silence. By normalizing to 0, we ensure proper sync with the synchronizer.\n */\nfunction toTimedWords(\n text: string,\n startTimesMs: number[],\n durationsMs: number[],\n flush: boolean = false,\n firstWordOffsetMs: number = 0,\n): [TimedString[], string] {\n if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {\n return [[], text || ''];\n }\n\n const lastStartTime = startTimesMs[startTimesMs.length - 1]!;\n const lastDuration = durationsMs[durationsMs.length - 1]!;\n const timestamps = [...startTimesMs, lastStartTime + lastDuration];\n\n const words = tokenize.basic.splitWords(text, false);\n const timedWords: TimedString[] = [];\n\n if (words.length === 0) {\n return [[], text];\n }\n\n const startIndices = words.map((w) => w[1]);\n let end = 0;\n\n // We don't know if the last word is complete, always leave it as remaining\n for (let i = 0; i < startIndices.length - 1; i++) {\n const start = startIndices[i]!;\n const nextStart = startIndices[i + 1]!;\n end = nextStart;\n // Normalize timestamps by subtracting the first word offset\n const startT = Math.max(0, (timestamps[start] ?? 0) - firstWordOffsetMs) / 1000;\n const endT = Math.max(0, (timestamps[nextStart] ?? 0) - firstWordOffsetMs) / 1000;\n timedWords.push(\n createTimedString({\n text: text.slice(start, nextStart),\n startTime: startT,\n endTime: endT,\n }),\n );\n }\n\n if (flush && words.length > 0) {\n const lastWordStart = startIndices[startIndices.length - 1]!;\n const startT = Math.max(0, (timestamps[lastWordStart] ?? 0) - firstWordOffsetMs) / 1000;\n const endT = Math.max(0, (timestamps[timestamps.length - 1] ?? 0) - firstWordOffsetMs) / 1000;\n timedWords.push(\n createTimedString({\n text: text.slice(lastWordStart),\n startTime: startT,\n endTime: endT,\n }),\n );\n end = text.length;\n } else if (words.length > 0) {\n end = startIndices[startIndices.length - 1]!;\n }\n\n return [timedWords, text.slice(end)];\n}\n\nclass Connection {\n #opts: ResolvedTTSOptions;\n #ws: WebSocket | null = null;\n #isCurrent = true;\n #activeContexts = new Set<string>();\n #inputQueue: ConnectionMessage[] = [];\n #contextData = new Map<string, StreamData>();\n #sendTask: Promise<void> | null = null;\n #recvTask: Promise<void> | null = null;\n #closed = false;\n #logger = log();\n #inputQueueResolver: (() => void) | null = null;\n\n constructor(opts: ResolvedTTSOptions) {\n this.#opts = opts;\n }\n\n get voiceId(): string {\n return this.#opts.voiceId;\n }\n\n get isCurrent(): boolean {\n return this.#isCurrent;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n markNonCurrent(): void {\n this.#isCurrent = false;\n }\n\n async connect(): Promise<void> {\n if (this.#ws || this.#closed) {\n return;\n }\n\n const url = multiStreamUrl(this.#opts);\n const headers = { [AUTHORIZATION_HEADER]: this.#opts.apiKey };\n\n return new Promise((resolve, reject) => {\n this.#ws = new WebSocket(url, { headers });\n\n this.#ws.on('open', () => {\n this.#sendTask = this.#sendLoop();\n this.#recvTask = this.#recvLoop();\n resolve();\n });\n\n this.#ws.on('error', (error) => {\n this.#logger.error({ error }, 'WebSocket connection error');\n reject(new APIConnectionError({ message: `WebSocket error: ${error.message}` }));\n });\n });\n }\n\n registerStream(\n stream: SynthesizeStream,\n waiter: { resolve: (value: void) => void; reject: (error: Error) => void },\n ): void {\n const contextId = stream.contextId;\n this.#contextData.set(contextId, {\n stream,\n waiter,\n textBuffer: '',\n startTimesMs: [],\n durationsMs: [],\n firstWordOffsetMs: null,\n });\n }\n\n sendContent(content: SynthesizeContent): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push(content);\n this.#inputQueueResolver?.();\n }\n\n closeContext(contextId: string): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push({ contextId });\n this.#inputQueueResolver?.();\n }\n\n async #sendLoop(): Promise<void> {\n try {\n while (!this.#closed) {\n // Wait for messages in queue\n if (this.#inputQueue.length === 0) {\n await new Promise<void>((resolve) => {\n this.#inputQueueResolver = resolve;\n });\n this.#inputQueueResolver = null;\n }\n\n if (this.#closed) break;\n\n const msg = this.#inputQueue.shift();\n if (!msg) continue;\n\n if (!this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n break;\n }\n\n if ('text' in msg) {\n // SynthesizeContent\n const content = msg as SynthesizeContent;\n const isNewContext = !this.#activeContexts.has(content.contextId);\n\n // If not current and this is a new context, ignore it\n if (!this.#isCurrent && isNewContext) {\n continue;\n }\n\n if (isNewContext) {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : {};\n\n const initPkt: Record<string, unknown> = {\n text: ' ',\n voice_settings: voiceSettings,\n context_id: content.contextId,\n };\n\n if (this.#opts.pronunciationDictionaryLocators) {\n initPkt.pronunciation_dictionary_locators =\n this.#opts.pronunciationDictionaryLocators.map((locator) => ({\n pronunciation_dictionary_id: locator.pronunciation_dictionary_id,\n version_id: locator.version_id,\n }));\n }\n\n const initPktStr = JSON.stringify(initPkt);\n this.#ws.send(initPktStr);\n this.#activeContexts.add(content.contextId);\n }\n\n const pkt: Record<string, unknown> = {\n text: content.text,\n context_id: content.contextId,\n };\n if (content.flush) {\n pkt.flush = true;\n }\n\n const pktStr = JSON.stringify(pkt);\n this.#ws.send(pktStr);\n } else {\n // CloseContext\n const closeMsg = msg as CloseContext;\n if (this.#activeContexts.has(closeMsg.contextId)) {\n const closePkt = {\n context_id: closeMsg.contextId,\n close_context: true,\n };\n const closePktStr = JSON.stringify(closePkt);\n this.#ws.send(closePktStr);\n }\n }\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'send loop error');\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n async #recvLoop(): Promise<void> {\n try {\n const messageChannel = stream.createStreamChannel<Record<string, unknown>>();\n const errorFuture = new Future<Error>();\n\n const onMessage = (rawData: Buffer) => {\n try {\n const parsed = JSON.parse(rawData.toString());\n messageChannel.write(parsed);\n } catch (e) {\n this.#logger.warn({ error: e }, 'failed to parse WebSocket message');\n }\n };\n\n const onClose = () => {\n if (!this.#closed && this.#contextData.size > 0) {\n this.#logger.warn('websocket closed unexpectedly');\n }\n messageChannel.close();\n };\n\n const onError = (error: Error) => {\n errorFuture.resolve(error);\n messageChannel.close();\n };\n\n // Set up persistent listeners\n if (!this.#ws) return;\n this.#ws.on('message', onMessage);\n this.#ws.on('close', onClose);\n this.#ws.on('error', onError);\n\n const cleanup = () => {\n this.#ws?.off('message', onMessage);\n this.#ws?.off('close', onClose);\n this.#ws?.off('error', onError);\n };\n\n const reader = messageChannel.stream().getReader();\n try {\n while (!this.#closed) {\n const result = await reader.read();\n if (result.done || this.#closed) break;\n\n const data = result.value;\n const contextId = data.contextId as string | undefined;\n const ctx = contextId ? this.#contextData.get(contextId) : undefined;\n\n if (data.error) {\n this.#logger.error(\n { context_id: contextId, error: data.error, data },\n 'elevenlabs tts returned error',\n );\n if (contextId) {\n if (ctx) {\n ctx.waiter.reject(new APIError(data.error as string));\n }\n this.#cleanupContext(contextId);\n }\n continue;\n }\n\n if (!ctx) {\n this.#logger.warn({ data }, 'unexpected message received from elevenlabs tts');\n continue;\n }\n\n const stream = ctx.stream;\n\n // Process alignment data\n const alignment =\n this.#opts.preferredAlignment === 'normalized'\n ? (data.normalizedAlignment as Record<string, unknown>)\n : (data.alignment as Record<string, unknown>);\n\n if (alignment && stream) {\n const chars = alignment.chars as string[] | undefined;\n const starts = (alignment.charStartTimesMs || alignment.charsStartTimesMs) as\n | number[]\n | undefined;\n const durs = (alignment.charDurationsMs || alignment.charsDurationsMs) as\n | number[]\n | undefined;\n\n if (\n chars &&\n starts &&\n durs &&\n chars.length === durs.length &&\n starts.length === durs.length\n ) {\n ctx.textBuffer += chars.join('');\n\n // Handle chars with multiple characters\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!;\n const start = starts[i]!;\n const dur = durs[i]!;\n\n // Capture the first word's start time for normalization\n // This removes leading silence from timestamps\n if (ctx.firstWordOffsetMs === null && start > 0) {\n ctx.firstWordOffsetMs = start;\n }\n\n if (char.length > 1) {\n for (let j = 0; j < char.length - 1; j++) {\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(0);\n }\n }\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(dur);\n }\n\n const [timedWords, remainingText] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n false,\n ctx.firstWordOffsetMs ?? 0,\n );\n\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n\n ctx.textBuffer = remainingText;\n ctx.startTimesMs = ctx.startTimesMs.slice(-remainingText.length);\n ctx.durationsMs = ctx.durationsMs.slice(-remainingText.length);\n }\n }\n\n if (data.audio) {\n const audioData = Buffer.from(data.audio as string, 'base64');\n stream.pushAudio(audioData);\n }\n\n if (data.isFinal) {\n // Flush remaining alignment data\n if (ctx.textBuffer) {\n const [timedWords] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n true,\n ctx.firstWordOffsetMs ?? 0,\n );\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n }\n\n stream.markDone();\n ctx.waiter.resolve();\n this.#cleanupContext(contextId!);\n\n if (!this.#isCurrent && this.#activeContexts.size === 0) {\n this.#logger.debug('no active contexts, shutting down connection');\n break;\n }\n }\n }\n\n // Throw any error that occurred\n if (errorFuture.done) {\n throw await errorFuture.await;\n }\n } finally {\n reader.releaseLock();\n cleanup();\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'recv loop error');\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(e instanceof Error ? e : new Error(String(e)));\n }\n this.#contextData.clear();\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n #cleanupContext(contextId: string): void {\n this.#contextData.delete(contextId);\n this.#activeContexts.delete(contextId);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n this.#closed = true;\n this.#inputQueueResolver?.();\n\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(new APIStatusError({ message: 'connection closed' }));\n }\n this.#contextData.clear();\n\n if (this.#ws) {\n this.#ws.close();\n this.#ws = null;\n }\n\n if (this.#sendTask) {\n await this.#sendTask.catch(() => {});\n }\n if (this.#recvTask) {\n await this.#recvTask.catch(() => {});\n }\n }\n}\n\nexport class TTS extends tts.TTS {\n #opts: ResolvedTTSOptions;\n #streams = new Set<SynthesizeStream>();\n #currentConnection: Connection | null = null;\n #connectionLock = new Mutex();\n #logger = log();\n\n label = 'elevenlabs.TTS';\n\n constructor(opts: TTSOptions = {}) {\n const autoMode = opts.autoMode ?? true;\n const encoding = opts.encoding ?? DEFAULT_ENCODING;\n const sampleRate = sampleRateFromFormat(encoding);\n const syncAlignment = opts.syncAlignment ?? true;\n\n super(sampleRate, 1, {\n streaming: true,\n alignedTranscript: syncAlignment,\n });\n\n const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ElevenLabs API key is required, either as argument or set ELEVEN_API_KEY environmental variable',\n );\n }\n\n let wordTokenizer = opts.wordTokenizer;\n if (!wordTokenizer) {\n wordTokenizer = autoMode\n ? new tokenize.basic.SentenceTokenizer()\n : new tokenize.basic.WordTokenizer(false);\n } else if (autoMode && !(wordTokenizer instanceof tokenize.SentenceTokenizer)) {\n this.#logger.warn(\n 'autoMode is enabled, it expects full sentences or phrases, ' +\n 'please provide a SentenceTokenizer instead of a WordTokenizer.',\n );\n }\n\n // Handle legacy options for backward compatibility\n const voiceId = opts.voiceId ?? opts.voice?.id ?? DEFAULT_VOICE_ID;\n const voiceSettings = opts.voiceSettings ?? opts.voice?.settings;\n const model = opts.model ?? opts.modelID ?? 'eleven_turbo_v2_5';\n const language = opts.language ?? opts.languageCode;\n\n this.#opts = {\n apiKey,\n voiceId,\n voiceSettings,\n model,\n language,\n baseURL: opts.baseURL ?? API_BASE_URL_V1,\n encoding,\n sampleRate,\n streamingLatency: opts.streamingLatency,\n wordTokenizer,\n chunkLengthSchedule: opts.chunkLengthSchedule,\n enableSsmlParsing: opts.enableSsmlParsing ?? false,\n enableLogging: opts.enableLogging ?? true,\n inactivityTimeout: opts.inactivityTimeout ?? WS_INACTIVITY_TIMEOUT,\n syncAlignment: opts.syncAlignment ?? true,\n applyTextNormalization: opts.applyTextNormalization ?? 'auto',\n preferredAlignment: opts.preferredAlignment ?? 'normalized',\n autoMode,\n pronunciationDictionaryLocators: opts.pronunciationDictionaryLocators,\n };\n }\n\n get model(): string {\n return this.#opts.model;\n }\n\n get provider(): string {\n return 'ElevenLabs';\n }\n\n async listVoices(): Promise<Voice[]> {\n const response = await fetch(`${this.#opts.baseURL}/voices`, {\n headers: { [AUTHORIZATION_HEADER]: this.#opts.apiKey },\n });\n const data = (await response.json()) as {\n voices: { voice_id: string; name: string; category: string }[];\n };\n return data.voices.map((v) => ({\n id: v.voice_id,\n name: v.name,\n category: v.category,\n }));\n }\n\n updateOptions(opts: {\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n }): void {\n let changed = false;\n\n if (opts.model !== undefined && opts.model !== this.#opts.model) {\n this.#opts.model = opts.model;\n changed = true;\n }\n\n if (opts.voiceId !== undefined && opts.voiceId !== this.#opts.voiceId) {\n this.#opts.voiceId = opts.voiceId;\n changed = true;\n }\n\n if (opts.voiceSettings !== undefined) {\n this.#opts.voiceSettings = opts.voiceSettings;\n changed = true;\n }\n\n if (opts.language !== undefined && opts.language !== this.#opts.language) {\n this.#opts.language = opts.language;\n changed = true;\n }\n\n if (opts.pronunciationDictionaryLocators !== undefined) {\n this.#opts.pronunciationDictionaryLocators = opts.pronunciationDictionaryLocators;\n changed = true;\n }\n\n if (changed && this.#currentConnection) {\n this.#currentConnection.markNonCurrent();\n this.#currentConnection = null;\n }\n }\n\n async currentConnection(): Promise<Connection> {\n const unlock = await this.#connectionLock.lock();\n try {\n if (\n this.#currentConnection &&\n this.#currentConnection.isCurrent &&\n !this.#currentConnection.closed\n ) {\n return this.#currentConnection;\n }\n\n const conn = new Connection({ ...this.#opts });\n await conn.connect();\n this.#currentConnection = conn;\n return conn;\n } finally {\n unlock();\n }\n }\n\n synthesize(text: string): ChunkedStream {\n return new ChunkedStream(this, text, { ...this.#opts });\n }\n\n stream(): SynthesizeStream {\n const stream = new SynthesizeStream(this, { ...this.#opts });\n this.#streams.add(stream);\n return stream;\n }\n\n async close(): Promise<void> {\n for (const stream of this.#streams) {\n stream.close();\n }\n this.#streams.clear();\n\n if (this.#currentConnection) {\n await this.#currentConnection.close();\n this.#currentConnection = null;\n }\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #logger = log();\n\n label = 'elevenlabs.ChunkedStream';\n\n constructor(tts: TTS, text: string, opts: ResolvedTTSOptions) {\n super(text, tts);\n this.#tts = tts;\n this.#opts = opts;\n }\n\n protected async run(): Promise<void> {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : undefined;\n\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n try {\n const response = await fetch(synthesizeUrl(this.#opts), {\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n text: this.inputText,\n model_id: this.#opts.model,\n voice_settings: voiceSettings,\n }),\n signal: this.abortSignal,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new APIStatusError({\n message: `ElevenLabs API error: ${errorText}`,\n options: { statusCode: response.status },\n });\n }\n\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.startsWith('audio/')) {\n const content = await response.text();\n throw new APIError(`ElevenLabs returned non-audio data: ${content}`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new APIError('No response body');\n }\n\n let lastFrame: AudioFrame | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n for (const frame of bstream.write(value.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Flush remaining data\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: true });\n }\n } catch (e) {\n if (e instanceof APIError) {\n throw e;\n }\n if (e instanceof Error && e.name === 'AbortError') {\n return;\n }\n throw new APIConnectionError({ message: `Connection error: ${e}` });\n }\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #contextId: string;\n #sentTokenizerStream: tokenize.SentenceStream | tokenize.WordStream;\n #logger = log();\n #audioQueue: Buffer[] = [];\n #timedTranscriptQueue: TimedString[] = [];\n #streamDone = false;\n\n label = 'elevenlabs.SynthesizeStream';\n\n constructor(tts: TTS, opts: ResolvedTTSOptions) {\n super(tts);\n this.#tts = tts;\n this.#opts = opts;\n this.#contextId = shortuuid();\n this.#sentTokenizerStream = this.#opts.wordTokenizer.stream();\n }\n\n get contextId(): string {\n return this.#contextId;\n }\n\n pushAudio(data: Buffer): void {\n // Don't push if stream is closed/aborted\n if (this.closed || this.abortController.signal.aborted) {\n return;\n }\n this.#audioQueue.push(data);\n }\n\n pushTimedTranscript(timedWords: TimedString[]): void {\n this.#timedTranscriptQueue.push(...timedWords);\n }\n\n markDone(): void {\n this.#streamDone = true;\n }\n\n protected async run(): Promise<void> {\n const requestId = this.#contextId;\n const segmentId = this.#contextId;\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n let connection: Connection;\n try {\n connection = await this.#tts.currentConnection();\n } catch (e) {\n throw new APIConnectionError({ message: 'could not connect to ElevenLabs' });\n }\n\n let waiterReject: ((reason: Error) => void) | undefined;\n const waiterPromise = new Promise<void>((resolve, reject) => {\n waiterReject = reject;\n connection.registerStream(this, { resolve, reject });\n });\n\n // Handle abort - reject the waiter so Promise.all can complete\n const abortHandler = () => {\n if (waiterReject) {\n waiterReject(new Error('Stream aborted'));\n }\n };\n this.abortController.signal.addEventListener('abort', abortHandler, { once: true });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (this.abortController.signal.aborted) break;\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.#sentTokenizerStream.flush();\n continue;\n }\n this.#sentTokenizerStream.pushText(data);\n }\n this.#sentTokenizerStream.endInput();\n };\n\n const sentenceStreamTask = async () => {\n const flushOnChunk =\n this.#opts.wordTokenizer instanceof tokenize.SentenceTokenizer && this.#opts.autoMode;\n\n let xmlContent: string[] = [];\n\n for await (const data of this.#sentTokenizerStream) {\n if (this.abortController.signal.aborted) break;\n\n let text = data.token;\n const xmlStartTokens = ['<phoneme', '<break'];\n const xmlEndTokens = ['</phoneme>', '/>'];\n\n if (\n (this.#opts.enableSsmlParsing &&\n xmlStartTokens.some((start) => text.startsWith(start))) ||\n xmlContent.length > 0\n ) {\n xmlContent.push(text);\n\n if (xmlEndTokens.some((end) => text.includes(end))) {\n text = xmlContent.join(' ');\n xmlContent = [];\n } else {\n continue;\n }\n }\n\n const formattedText = `${text} `; // must always end with a space\n connection.sendContent({\n contextId: this.#contextId,\n text: formattedText,\n flush: flushOnChunk,\n });\n }\n\n if (xmlContent.length > 0) {\n this.#logger.warn('ElevenLabs stream ended with incomplete xml content');\n }\n\n // Send final empty text to signal end of input\n connection.sendContent({ contextId: this.#contextId, text: '', flush: true });\n connection.closeContext(this.#contextId);\n };\n\n const audioProcessTask = async () => {\n let lastFrame: AudioFrame | undefined;\n let pendingTimedTranscripts: TimedString[] = [];\n\n const sendLastFrame = (final: boolean) => {\n if (lastFrame) {\n // Include timedTranscripts with the audio frame\n this.queue.put({\n requestId,\n segmentId,\n frame: lastFrame,\n final,\n timedTranscripts:\n pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : undefined,\n });\n lastFrame = undefined;\n pendingTimedTranscripts = [];\n }\n };\n\n while (!this.abortController.signal.aborted) {\n // Drain timed transcript queue\n while (this.#timedTranscriptQueue.length > 0) {\n pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);\n }\n\n // Process audio queue\n while (this.#audioQueue.length > 0) {\n const audioData = this.#audioQueue.shift()!;\n for (const frame of bstream.write(audioData.buffer)) {\n sendLastFrame(false);\n lastFrame = frame;\n }\n }\n\n // Exit when stream is done and queue is empty\n if (this.#streamDone && this.#audioQueue.length === 0) {\n break;\n }\n\n // Small delay to avoid busy waiting\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n\n // Drain any remaining timed transcripts\n while (this.#timedTranscriptQueue.length > 0) {\n pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);\n }\n\n // Flush remaining\n for (const frame of bstream.flush()) {\n sendLastFrame(false);\n lastFrame = frame;\n }\n\n sendLastFrame(true);\n };\n\n try {\n await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);\n } catch (e) {\n // If aborted, this is a normal termination - don't throw\n if (this.abortController.signal.aborted) {\n return;\n }\n\n if (e instanceof APITimeoutError) {\n throw e;\n }\n if (e instanceof APIStatusError) {\n throw e;\n }\n throw new APIStatusError({ message: 'Could not synthesize' });\n } finally {\n // Clean up abort listener\n this.abortController.signal.removeEventListener('abort', abortHandler);\n }\n }\n\n close(): void {\n // Clear audio buffers to prevent memory leak\n this.#audioQueue.length = 0;\n this.#timedTranscriptQueue.length = 0;\n this.#streamDone = true;\n this.#sentTokenizerStream.close();\n super.close();\n }\n}\n"],"mappings":"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","names":["stream","tts"]}
|
package/dist/tts.d.cts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
-
import { tokenize, tts
|
|
3
|
+
import { type TimedString, tokenize, tts } from '@livekit/agents';
|
|
4
4
|
import type { TTSEncoding, TTSModels } from './models.js';
|
|
5
|
-
type TimedString = voice.TimedString;
|
|
6
5
|
export interface VoiceSettings {
|
|
7
6
|
stability: number;
|
|
8
7
|
similarity_boost: number;
|
package/dist/tts.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
-
import { tokenize, tts
|
|
3
|
+
import { type TimedString, tokenize, tts } from '@livekit/agents';
|
|
4
4
|
import type { TTSEncoding, TTSModels } from './models.js';
|
|
5
|
-
type TimedString = voice.TimedString;
|
|
6
5
|
export interface VoiceSettings {
|
|
7
6
|
stability: number;
|
|
8
7
|
similarity_boost: number;
|
package/dist/tts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":";;AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"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"}
|
package/dist/tts.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
APITimeoutError,
|
|
6
6
|
AudioByteStream,
|
|
7
7
|
Future,
|
|
8
|
+
createTimedString,
|
|
8
9
|
log,
|
|
9
10
|
shortuuid,
|
|
10
11
|
stream,
|
|
@@ -59,7 +60,7 @@ function stripUndefined(obj) {
|
|
|
59
60
|
}
|
|
60
61
|
return result;
|
|
61
62
|
}
|
|
62
|
-
function toTimedWords(text, startTimesMs, durationsMs, flush = false) {
|
|
63
|
+
function toTimedWords(text, startTimesMs, durationsMs, flush = false, firstWordOffsetMs = 0) {
|
|
63
64
|
if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {
|
|
64
65
|
return [[], text || ""];
|
|
65
66
|
}
|
|
@@ -77,23 +78,27 @@ function toTimedWords(text, startTimesMs, durationsMs, flush = false) {
|
|
|
77
78
|
const start = startIndices[i];
|
|
78
79
|
const nextStart = startIndices[i + 1];
|
|
79
80
|
end = nextStart;
|
|
80
|
-
const startT = (timestamps[start] ?? 0) / 1e3;
|
|
81
|
-
const endT = (timestamps[nextStart] ?? 0) / 1e3;
|
|
82
|
-
timedWords.push(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const startT = Math.max(0, (timestamps[start] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
82
|
+
const endT = Math.max(0, (timestamps[nextStart] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
83
|
+
timedWords.push(
|
|
84
|
+
createTimedString({
|
|
85
|
+
text: text.slice(start, nextStart),
|
|
86
|
+
startTime: startT,
|
|
87
|
+
endTime: endT
|
|
88
|
+
})
|
|
89
|
+
);
|
|
87
90
|
}
|
|
88
91
|
if (flush && words.length > 0) {
|
|
89
92
|
const lastWordStart = startIndices[startIndices.length - 1];
|
|
90
|
-
const startT = (timestamps[lastWordStart] ?? 0) / 1e3;
|
|
91
|
-
const endT = (timestamps[timestamps.length - 1] ?? 0) / 1e3;
|
|
92
|
-
timedWords.push(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const startT = Math.max(0, (timestamps[lastWordStart] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
94
|
+
const endT = Math.max(0, (timestamps[timestamps.length - 1] ?? 0) - firstWordOffsetMs) / 1e3;
|
|
95
|
+
timedWords.push(
|
|
96
|
+
createTimedString({
|
|
97
|
+
text: text.slice(lastWordStart),
|
|
98
|
+
startTime: startT,
|
|
99
|
+
endTime: endT
|
|
100
|
+
})
|
|
101
|
+
);
|
|
97
102
|
end = text.length;
|
|
98
103
|
} else if (words.length > 0) {
|
|
99
104
|
end = startIndices[startIndices.length - 1];
|
|
@@ -153,7 +158,8 @@ class Connection {
|
|
|
153
158
|
waiter,
|
|
154
159
|
textBuffer: "",
|
|
155
160
|
startTimesMs: [],
|
|
156
|
-
durationsMs: []
|
|
161
|
+
durationsMs: [],
|
|
162
|
+
firstWordOffsetMs: null
|
|
157
163
|
});
|
|
158
164
|
}
|
|
159
165
|
sendContent(content) {
|
|
@@ -308,6 +314,9 @@ class Connection {
|
|
|
308
314
|
const char = chars[i];
|
|
309
315
|
const start = starts[i];
|
|
310
316
|
const dur = durs[i];
|
|
317
|
+
if (ctx.firstWordOffsetMs === null && start > 0) {
|
|
318
|
+
ctx.firstWordOffsetMs = start;
|
|
319
|
+
}
|
|
311
320
|
if (char.length > 1) {
|
|
312
321
|
for (let j = 0; j < char.length - 1; j++) {
|
|
313
322
|
ctx.startTimesMs.push(start);
|
|
@@ -320,7 +329,9 @@ class Connection {
|
|
|
320
329
|
const [timedWords, remainingText] = toTimedWords(
|
|
321
330
|
ctx.textBuffer,
|
|
322
331
|
ctx.startTimesMs,
|
|
323
|
-
ctx.durationsMs
|
|
332
|
+
ctx.durationsMs,
|
|
333
|
+
false,
|
|
334
|
+
ctx.firstWordOffsetMs ?? 0
|
|
324
335
|
);
|
|
325
336
|
if (timedWords.length > 0) {
|
|
326
337
|
stream2.pushTimedTranscript(timedWords);
|
|
@@ -340,7 +351,8 @@ class Connection {
|
|
|
340
351
|
ctx.textBuffer,
|
|
341
352
|
ctx.startTimesMs,
|
|
342
353
|
ctx.durationsMs,
|
|
343
|
-
true
|
|
354
|
+
true,
|
|
355
|
+
ctx.firstWordOffsetMs ?? 0
|
|
344
356
|
);
|
|
345
357
|
if (timedWords.length > 0) {
|
|
346
358
|
stream2.pushTimedTranscript(timedWords);
|
|
@@ -415,8 +427,10 @@ class TTS extends tts.TTS {
|
|
|
415
427
|
const autoMode = opts.autoMode ?? true;
|
|
416
428
|
const encoding = opts.encoding ?? DEFAULT_ENCODING;
|
|
417
429
|
const sampleRate = sampleRateFromFormat(encoding);
|
|
430
|
+
const syncAlignment = opts.syncAlignment ?? true;
|
|
418
431
|
super(sampleRate, 1, {
|
|
419
|
-
streaming: true
|
|
432
|
+
streaming: true,
|
|
433
|
+
alignedTranscript: syncAlignment
|
|
420
434
|
});
|
|
421
435
|
const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;
|
|
422
436
|
if (!apiKey) {
|
|
@@ -707,13 +721,28 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
707
721
|
};
|
|
708
722
|
const audioProcessTask = async () => {
|
|
709
723
|
let lastFrame;
|
|
724
|
+
let pendingTimedTranscripts = [];
|
|
725
|
+
const sendLastFrame = (final) => {
|
|
726
|
+
if (lastFrame) {
|
|
727
|
+
this.queue.put({
|
|
728
|
+
requestId,
|
|
729
|
+
segmentId,
|
|
730
|
+
frame: lastFrame,
|
|
731
|
+
final,
|
|
732
|
+
timedTranscripts: pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : void 0
|
|
733
|
+
});
|
|
734
|
+
lastFrame = void 0;
|
|
735
|
+
pendingTimedTranscripts = [];
|
|
736
|
+
}
|
|
737
|
+
};
|
|
710
738
|
while (!this.abortController.signal.aborted) {
|
|
739
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
740
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift());
|
|
741
|
+
}
|
|
711
742
|
while (this.#audioQueue.length > 0) {
|
|
712
743
|
const audioData = this.#audioQueue.shift();
|
|
713
744
|
for (const frame of bstream.write(audioData.buffer)) {
|
|
714
|
-
|
|
715
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
716
|
-
}
|
|
745
|
+
sendLastFrame(false);
|
|
717
746
|
lastFrame = frame;
|
|
718
747
|
}
|
|
719
748
|
}
|
|
@@ -722,15 +751,14 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
722
751
|
}
|
|
723
752
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
724
753
|
}
|
|
754
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
755
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift());
|
|
756
|
+
}
|
|
725
757
|
for (const frame of bstream.flush()) {
|
|
726
|
-
|
|
727
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
728
|
-
}
|
|
758
|
+
sendLastFrame(false);
|
|
729
759
|
lastFrame = frame;
|
|
730
760
|
}
|
|
731
|
-
|
|
732
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: true });
|
|
733
|
-
}
|
|
761
|
+
sendLastFrame(true);
|
|
734
762
|
};
|
|
735
763
|
try {
|
|
736
764
|
await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);
|
package/dist/tts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n Future,\n log,\n shortuuid,\n stream,\n tokenize,\n tts,\n type voice,\n} from '@livekit/agents';\nimport { Mutex } from '@livekit/mutex';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\ntype TimedString = voice.TimedString;\n\nconst DEFAULT_VOICE_ID = 'bIHbv24MWmeRgasZH58o';\nconst API_BASE_URL_V1 = 'https://api.elevenlabs.io/v1';\nconst AUTHORIZATION_HEADER = 'xi-api-key';\nconst WS_INACTIVITY_TIMEOUT = 180;\nconst DEFAULT_ENCODING: TTSEncoding = 'pcm_22050';\n\nexport interface VoiceSettings {\n stability: number; // [0.0 - 1.0]\n similarity_boost: number; // [0.0 - 1.0]\n style?: number; // [0.0 - 1.0]\n speed?: number; // [0.8 - 1.2]\n use_speaker_boost?: boolean;\n}\n\nexport interface Voice {\n id: string;\n name: string;\n category: string;\n settings?: VoiceSettings;\n}\n\nexport interface PronunciationDictionaryLocator {\n pronunciation_dictionary_id: string;\n version_id: string;\n}\n\nexport interface TTSOptions {\n apiKey?: string;\n // New interface\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n // Legacy interface (backward compatibility)\n voice?: Voice;\n modelID?: TTSModels | string;\n languageCode?: string;\n // Common options\n baseURL?: string;\n encoding?: TTSEncoding;\n streamingLatency?: number;\n wordTokenizer?: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing?: boolean;\n enableLogging?: boolean;\n inactivityTimeout?: number;\n syncAlignment?: boolean;\n applyTextNormalization?: 'auto' | 'on' | 'off';\n preferredAlignment?: 'normalized' | 'original';\n autoMode?: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal options type with resolved defaults\ninterface ResolvedTTSOptions {\n apiKey: string;\n voiceId: string;\n voiceSettings?: VoiceSettings;\n model: TTSModels | string;\n language?: string;\n baseURL: string;\n encoding: TTSEncoding;\n sampleRate: number;\n streamingLatency?: number;\n wordTokenizer: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing: boolean;\n enableLogging: boolean;\n inactivityTimeout: number;\n syncAlignment: boolean;\n applyTextNormalization: 'auto' | 'on' | 'off';\n preferredAlignment: 'normalized' | 'original';\n autoMode: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal types for connection management\ninterface SynthesizeContent {\n contextId: string;\n text: string;\n flush: boolean;\n}\n\ninterface CloseContext {\n contextId: string;\n}\n\ninterface StreamData {\n stream: SynthesizeStream;\n waiter: {\n resolve: (value: void) => void;\n reject: (error: Error) => void;\n };\n textBuffer: string;\n startTimesMs: number[];\n durationsMs: number[];\n}\n\ntype ConnectionMessage = SynthesizeContent | CloseContext;\n\n// Helper Functions\n\nfunction sampleRateFromFormat(encoding: TTSEncoding): number {\n const split = encoding.split('_');\n return parseInt(split[1]!, 10);\n}\n\nfunction synthesizeUrl(opts: ResolvedTTSOptions): string {\n const { baseURL, voiceId, model, encoding, streamingLatency } = opts;\n let url = `${baseURL}/text-to-speech/${voiceId}/stream?model_id=${model}&output_format=${encoding}`;\n if (streamingLatency !== undefined) {\n url += `&optimize_streaming_latency=${streamingLatency}`;\n }\n return url;\n}\n\nfunction multiStreamUrl(opts: ResolvedTTSOptions): string {\n const baseURL = opts.baseURL.replace('https://', 'wss://').replace('http://', 'ws://');\n const params: string[] = [];\n params.push(`model_id=${opts.model}`);\n params.push(`output_format=${opts.encoding}`);\n if (opts.language) {\n params.push(`language_code=${opts.language}`);\n }\n params.push(`enable_ssml_parsing=${opts.enableSsmlParsing}`);\n params.push(`enable_logging=${opts.enableLogging}`);\n params.push(`inactivity_timeout=${opts.inactivityTimeout}`);\n params.push(`apply_text_normalization=${opts.applyTextNormalization}`);\n if (opts.syncAlignment) {\n params.push('sync_alignment=true');\n }\n if (opts.autoMode !== undefined) {\n params.push(`auto_mode=${opts.autoMode}`);\n }\n return `${baseURL}/text-to-speech/${opts.voiceId}/multi-stream-input?${params.join('&')}`;\n}\n\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key in obj) {\n if (obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Convert alignment data to timed words.\n * Returns the timed words and remaining text buffer.\n */\nfunction toTimedWords(\n text: string,\n startTimesMs: number[],\n durationsMs: number[],\n flush: boolean = false,\n): [TimedString[], string] {\n if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {\n return [[], text || ''];\n }\n\n const lastStartTime = startTimesMs[startTimesMs.length - 1]!;\n const lastDuration = durationsMs[durationsMs.length - 1]!;\n const timestamps = [...startTimesMs, lastStartTime + lastDuration];\n\n const words = tokenize.basic.splitWords(text, false);\n const timedWords: TimedString[] = [];\n\n if (words.length === 0) {\n return [[], text];\n }\n\n const startIndices = words.map((w) => w[1]);\n let end = 0;\n\n // We don't know if the last word is complete, always leave it as remaining\n for (let i = 0; i < startIndices.length - 1; i++) {\n const start = startIndices[i]!;\n const nextStart = startIndices[i + 1]!;\n end = nextStart;\n const startT = (timestamps[start] ?? 0) / 1000;\n const endT = (timestamps[nextStart] ?? 0) / 1000;\n timedWords.push({\n text: text.slice(start, nextStart),\n startTime: startT,\n endTime: endT,\n });\n }\n\n if (flush && words.length > 0) {\n const lastWordStart = startIndices[startIndices.length - 1]!;\n const startT = (timestamps[lastWordStart] ?? 0) / 1000;\n const endT = (timestamps[timestamps.length - 1] ?? 0) / 1000;\n timedWords.push({\n text: text.slice(lastWordStart),\n startTime: startT,\n endTime: endT,\n });\n end = text.length;\n } else if (words.length > 0) {\n end = startIndices[startIndices.length - 1]!;\n }\n\n return [timedWords, text.slice(end)];\n}\n\nclass Connection {\n #opts: ResolvedTTSOptions;\n #ws: WebSocket | null = null;\n #isCurrent = true;\n #activeContexts = new Set<string>();\n #inputQueue: ConnectionMessage[] = [];\n #contextData = new Map<string, StreamData>();\n #sendTask: Promise<void> | null = null;\n #recvTask: Promise<void> | null = null;\n #closed = false;\n #logger = log();\n #inputQueueResolver: (() => void) | null = null;\n\n constructor(opts: ResolvedTTSOptions) {\n this.#opts = opts;\n }\n\n get voiceId(): string {\n return this.#opts.voiceId;\n }\n\n get isCurrent(): boolean {\n return this.#isCurrent;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n markNonCurrent(): void {\n this.#isCurrent = false;\n }\n\n async connect(): Promise<void> {\n if (this.#ws || this.#closed) {\n return;\n }\n\n const url = multiStreamUrl(this.#opts);\n const headers = { [AUTHORIZATION_HEADER]: this.#opts.apiKey };\n\n return new Promise((resolve, reject) => {\n this.#ws = new WebSocket(url, { headers });\n\n this.#ws.on('open', () => {\n this.#sendTask = this.#sendLoop();\n this.#recvTask = this.#recvLoop();\n resolve();\n });\n\n this.#ws.on('error', (error) => {\n this.#logger.error({ error }, 'WebSocket connection error');\n reject(new APIConnectionError({ message: `WebSocket error: ${error.message}` }));\n });\n });\n }\n\n registerStream(\n stream: SynthesizeStream,\n waiter: { resolve: (value: void) => void; reject: (error: Error) => void },\n ): void {\n const contextId = stream.contextId;\n this.#contextData.set(contextId, {\n stream,\n waiter,\n textBuffer: '',\n startTimesMs: [],\n durationsMs: [],\n });\n }\n\n sendContent(content: SynthesizeContent): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push(content);\n this.#inputQueueResolver?.();\n }\n\n closeContext(contextId: string): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push({ contextId });\n this.#inputQueueResolver?.();\n }\n\n async #sendLoop(): Promise<void> {\n try {\n while (!this.#closed) {\n // Wait for messages in queue\n if (this.#inputQueue.length === 0) {\n await new Promise<void>((resolve) => {\n this.#inputQueueResolver = resolve;\n });\n this.#inputQueueResolver = null;\n }\n\n if (this.#closed) break;\n\n const msg = this.#inputQueue.shift();\n if (!msg) continue;\n\n if (!this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n break;\n }\n\n if ('text' in msg) {\n // SynthesizeContent\n const content = msg as SynthesizeContent;\n const isNewContext = !this.#activeContexts.has(content.contextId);\n\n // If not current and this is a new context, ignore it\n if (!this.#isCurrent && isNewContext) {\n continue;\n }\n\n if (isNewContext) {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : {};\n\n const initPkt: Record<string, unknown> = {\n text: ' ',\n voice_settings: voiceSettings,\n context_id: content.contextId,\n };\n\n if (this.#opts.pronunciationDictionaryLocators) {\n initPkt.pronunciation_dictionary_locators =\n this.#opts.pronunciationDictionaryLocators.map((locator) => ({\n pronunciation_dictionary_id: locator.pronunciation_dictionary_id,\n version_id: locator.version_id,\n }));\n }\n\n const initPktStr = JSON.stringify(initPkt);\n this.#ws.send(initPktStr);\n this.#activeContexts.add(content.contextId);\n }\n\n const pkt: Record<string, unknown> = {\n text: content.text,\n context_id: content.contextId,\n };\n if (content.flush) {\n pkt.flush = true;\n }\n\n const pktStr = JSON.stringify(pkt);\n this.#ws.send(pktStr);\n } else {\n // CloseContext\n const closeMsg = msg as CloseContext;\n if (this.#activeContexts.has(closeMsg.contextId)) {\n const closePkt = {\n context_id: closeMsg.contextId,\n close_context: true,\n };\n const closePktStr = JSON.stringify(closePkt);\n this.#ws.send(closePktStr);\n }\n }\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'send loop error');\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n async #recvLoop(): Promise<void> {\n try {\n const messageChannel = stream.createStreamChannel<Record<string, unknown>>();\n const errorFuture = new Future<Error>();\n\n const onMessage = (rawData: Buffer) => {\n try {\n const parsed = JSON.parse(rawData.toString());\n messageChannel.write(parsed);\n } catch (e) {\n this.#logger.warn({ error: e }, 'failed to parse WebSocket message');\n }\n };\n\n const onClose = () => {\n if (!this.#closed && this.#contextData.size > 0) {\n this.#logger.warn('websocket closed unexpectedly');\n }\n messageChannel.close();\n };\n\n const onError = (error: Error) => {\n errorFuture.resolve(error);\n messageChannel.close();\n };\n\n // Set up persistent listeners\n if (!this.#ws) return;\n this.#ws.on('message', onMessage);\n this.#ws.on('close', onClose);\n this.#ws.on('error', onError);\n\n const cleanup = () => {\n this.#ws?.off('message', onMessage);\n this.#ws?.off('close', onClose);\n this.#ws?.off('error', onError);\n };\n\n const reader = messageChannel.stream().getReader();\n try {\n while (!this.#closed) {\n const result = await reader.read();\n if (result.done || this.#closed) break;\n\n const data = result.value;\n const contextId = data.contextId as string | undefined;\n const ctx = contextId ? this.#contextData.get(contextId) : undefined;\n\n if (data.error) {\n this.#logger.error(\n { context_id: contextId, error: data.error, data },\n 'elevenlabs tts returned error',\n );\n if (contextId) {\n if (ctx) {\n ctx.waiter.reject(new APIError(data.error as string));\n }\n this.#cleanupContext(contextId);\n }\n continue;\n }\n\n if (!ctx) {\n this.#logger.warn({ data }, 'unexpected message received from elevenlabs tts');\n continue;\n }\n\n const stream = ctx.stream;\n\n // Process alignment data\n const alignment =\n this.#opts.preferredAlignment === 'normalized'\n ? (data.normalizedAlignment as Record<string, unknown>)\n : (data.alignment as Record<string, unknown>);\n\n if (alignment && stream) {\n const chars = alignment.chars as string[] | undefined;\n const starts = (alignment.charStartTimesMs || alignment.charsStartTimesMs) as\n | number[]\n | undefined;\n const durs = (alignment.charDurationsMs || alignment.charsDurationsMs) as\n | number[]\n | undefined;\n\n if (\n chars &&\n starts &&\n durs &&\n chars.length === durs.length &&\n starts.length === durs.length\n ) {\n ctx.textBuffer += chars.join('');\n\n // Handle chars with multiple characters\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!;\n const start = starts[i]!;\n const dur = durs[i]!;\n\n if (char.length > 1) {\n for (let j = 0; j < char.length - 1; j++) {\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(0);\n }\n }\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(dur);\n }\n\n const [timedWords, remainingText] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n );\n\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n\n ctx.textBuffer = remainingText;\n ctx.startTimesMs = ctx.startTimesMs.slice(-remainingText.length);\n ctx.durationsMs = ctx.durationsMs.slice(-remainingText.length);\n }\n }\n\n if (data.audio) {\n const audioData = Buffer.from(data.audio as string, 'base64');\n stream.pushAudio(audioData);\n }\n\n if (data.isFinal) {\n // Flush remaining alignment data\n if (ctx.textBuffer) {\n const [timedWords] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n true,\n );\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n }\n\n stream.markDone();\n ctx.waiter.resolve();\n this.#cleanupContext(contextId!);\n\n if (!this.#isCurrent && this.#activeContexts.size === 0) {\n this.#logger.debug('no active contexts, shutting down connection');\n break;\n }\n }\n }\n\n // Throw any error that occurred\n if (errorFuture.done) {\n throw await errorFuture.await;\n }\n } finally {\n reader.releaseLock();\n cleanup();\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'recv loop error');\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(e instanceof Error ? e : new Error(String(e)));\n }\n this.#contextData.clear();\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n #cleanupContext(contextId: string): void {\n this.#contextData.delete(contextId);\n this.#activeContexts.delete(contextId);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n this.#closed = true;\n this.#inputQueueResolver?.();\n\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(new APIStatusError({ message: 'connection closed' }));\n }\n this.#contextData.clear();\n\n if (this.#ws) {\n this.#ws.close();\n this.#ws = null;\n }\n\n if (this.#sendTask) {\n await this.#sendTask.catch(() => {});\n }\n if (this.#recvTask) {\n await this.#recvTask.catch(() => {});\n }\n }\n}\n\nexport class TTS extends tts.TTS {\n #opts: ResolvedTTSOptions;\n #streams = new Set<SynthesizeStream>();\n #currentConnection: Connection | null = null;\n #connectionLock = new Mutex();\n #logger = log();\n\n label = 'elevenlabs.TTS';\n\n constructor(opts: TTSOptions = {}) {\n const autoMode = opts.autoMode ?? true;\n const encoding = opts.encoding ?? DEFAULT_ENCODING;\n const sampleRate = sampleRateFromFormat(encoding);\n\n super(sampleRate, 1, {\n streaming: true,\n });\n\n const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ElevenLabs API key is required, either as argument or set ELEVEN_API_KEY environmental variable',\n );\n }\n\n let wordTokenizer = opts.wordTokenizer;\n if (!wordTokenizer) {\n wordTokenizer = autoMode\n ? new tokenize.basic.SentenceTokenizer()\n : new tokenize.basic.WordTokenizer(false);\n } else if (autoMode && !(wordTokenizer instanceof tokenize.SentenceTokenizer)) {\n this.#logger.warn(\n 'autoMode is enabled, it expects full sentences or phrases, ' +\n 'please provide a SentenceTokenizer instead of a WordTokenizer.',\n );\n }\n\n // Handle legacy options for backward compatibility\n const voiceId = opts.voiceId ?? opts.voice?.id ?? DEFAULT_VOICE_ID;\n const voiceSettings = opts.voiceSettings ?? opts.voice?.settings;\n const model = opts.model ?? opts.modelID ?? 'eleven_turbo_v2_5';\n const language = opts.language ?? opts.languageCode;\n\n this.#opts = {\n apiKey,\n voiceId,\n voiceSettings,\n model,\n language,\n baseURL: opts.baseURL ?? API_BASE_URL_V1,\n encoding,\n sampleRate,\n streamingLatency: opts.streamingLatency,\n wordTokenizer,\n chunkLengthSchedule: opts.chunkLengthSchedule,\n enableSsmlParsing: opts.enableSsmlParsing ?? false,\n enableLogging: opts.enableLogging ?? true,\n inactivityTimeout: opts.inactivityTimeout ?? WS_INACTIVITY_TIMEOUT,\n syncAlignment: opts.syncAlignment ?? true,\n applyTextNormalization: opts.applyTextNormalization ?? 'auto',\n preferredAlignment: opts.preferredAlignment ?? 'normalized',\n autoMode,\n pronunciationDictionaryLocators: opts.pronunciationDictionaryLocators,\n };\n }\n\n get model(): string {\n return this.#opts.model;\n }\n\n get provider(): string {\n return 'ElevenLabs';\n }\n\n async listVoices(): Promise<Voice[]> {\n const response = await fetch(`${this.#opts.baseURL}/voices`, {\n headers: { [AUTHORIZATION_HEADER]: this.#opts.apiKey },\n });\n const data = (await response.json()) as {\n voices: { voice_id: string; name: string; category: string }[];\n };\n return data.voices.map((v) => ({\n id: v.voice_id,\n name: v.name,\n category: v.category,\n }));\n }\n\n updateOptions(opts: {\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n }): void {\n let changed = false;\n\n if (opts.model !== undefined && opts.model !== this.#opts.model) {\n this.#opts.model = opts.model;\n changed = true;\n }\n\n if (opts.voiceId !== undefined && opts.voiceId !== this.#opts.voiceId) {\n this.#opts.voiceId = opts.voiceId;\n changed = true;\n }\n\n if (opts.voiceSettings !== undefined) {\n this.#opts.voiceSettings = opts.voiceSettings;\n changed = true;\n }\n\n if (opts.language !== undefined && opts.language !== this.#opts.language) {\n this.#opts.language = opts.language;\n changed = true;\n }\n\n if (opts.pronunciationDictionaryLocators !== undefined) {\n this.#opts.pronunciationDictionaryLocators = opts.pronunciationDictionaryLocators;\n changed = true;\n }\n\n if (changed && this.#currentConnection) {\n this.#currentConnection.markNonCurrent();\n this.#currentConnection = null;\n }\n }\n\n async currentConnection(): Promise<Connection> {\n const unlock = await this.#connectionLock.lock();\n try {\n if (\n this.#currentConnection &&\n this.#currentConnection.isCurrent &&\n !this.#currentConnection.closed\n ) {\n return this.#currentConnection;\n }\n\n const conn = new Connection({ ...this.#opts });\n await conn.connect();\n this.#currentConnection = conn;\n return conn;\n } finally {\n unlock();\n }\n }\n\n synthesize(text: string): ChunkedStream {\n return new ChunkedStream(this, text, { ...this.#opts });\n }\n\n stream(): SynthesizeStream {\n const stream = new SynthesizeStream(this, { ...this.#opts });\n this.#streams.add(stream);\n return stream;\n }\n\n async close(): Promise<void> {\n for (const stream of this.#streams) {\n stream.close();\n }\n this.#streams.clear();\n\n if (this.#currentConnection) {\n await this.#currentConnection.close();\n this.#currentConnection = null;\n }\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #logger = log();\n\n label = 'elevenlabs.ChunkedStream';\n\n constructor(tts: TTS, text: string, opts: ResolvedTTSOptions) {\n super(text, tts);\n this.#tts = tts;\n this.#opts = opts;\n }\n\n protected async run(): Promise<void> {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : undefined;\n\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n try {\n const response = await fetch(synthesizeUrl(this.#opts), {\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n text: this.inputText,\n model_id: this.#opts.model,\n voice_settings: voiceSettings,\n }),\n signal: this.abortSignal,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new APIStatusError({\n message: `ElevenLabs API error: ${errorText}`,\n options: { statusCode: response.status },\n });\n }\n\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.startsWith('audio/')) {\n const content = await response.text();\n throw new APIError(`ElevenLabs returned non-audio data: ${content}`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new APIError('No response body');\n }\n\n let lastFrame: AudioFrame | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n for (const frame of bstream.write(value.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Flush remaining data\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: true });\n }\n } catch (e) {\n if (e instanceof APIError) {\n throw e;\n }\n if (e instanceof Error && e.name === 'AbortError') {\n return;\n }\n throw new APIConnectionError({ message: `Connection error: ${e}` });\n }\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #contextId: string;\n #sentTokenizerStream: tokenize.SentenceStream | tokenize.WordStream;\n #logger = log();\n #audioQueue: Buffer[] = [];\n #timedTranscriptQueue: TimedString[] = [];\n #streamDone = false;\n\n label = 'elevenlabs.SynthesizeStream';\n\n constructor(tts: TTS, opts: ResolvedTTSOptions) {\n super(tts);\n this.#tts = tts;\n this.#opts = opts;\n this.#contextId = shortuuid();\n this.#sentTokenizerStream = this.#opts.wordTokenizer.stream();\n }\n\n get contextId(): string {\n return this.#contextId;\n }\n\n pushAudio(data: Buffer): void {\n // Don't push if stream is closed/aborted\n if (this.closed || this.abortController.signal.aborted) {\n return;\n }\n this.#audioQueue.push(data);\n }\n\n pushTimedTranscript(timedWords: TimedString[]): void {\n this.#timedTranscriptQueue.push(...timedWords);\n }\n\n markDone(): void {\n this.#streamDone = true;\n }\n\n protected async run(): Promise<void> {\n const requestId = this.#contextId;\n const segmentId = this.#contextId;\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n let connection: Connection;\n try {\n connection = await this.#tts.currentConnection();\n } catch (e) {\n throw new APIConnectionError({ message: 'could not connect to ElevenLabs' });\n }\n\n let waiterReject: ((reason: Error) => void) | undefined;\n const waiterPromise = new Promise<void>((resolve, reject) => {\n waiterReject = reject;\n connection.registerStream(this, { resolve, reject });\n });\n\n // Handle abort - reject the waiter so Promise.all can complete\n const abortHandler = () => {\n if (waiterReject) {\n waiterReject(new Error('Stream aborted'));\n }\n };\n this.abortController.signal.addEventListener('abort', abortHandler, { once: true });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (this.abortController.signal.aborted) break;\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.#sentTokenizerStream.flush();\n continue;\n }\n this.#sentTokenizerStream.pushText(data);\n }\n this.#sentTokenizerStream.endInput();\n };\n\n const sentenceStreamTask = async () => {\n const flushOnChunk =\n this.#opts.wordTokenizer instanceof tokenize.SentenceTokenizer && this.#opts.autoMode;\n\n let xmlContent: string[] = [];\n\n for await (const data of this.#sentTokenizerStream) {\n if (this.abortController.signal.aborted) break;\n\n let text = data.token;\n const xmlStartTokens = ['<phoneme', '<break'];\n const xmlEndTokens = ['</phoneme>', '/>'];\n\n if (\n (this.#opts.enableSsmlParsing &&\n xmlStartTokens.some((start) => text.startsWith(start))) ||\n xmlContent.length > 0\n ) {\n xmlContent.push(text);\n\n if (xmlEndTokens.some((end) => text.includes(end))) {\n text = xmlContent.join(' ');\n xmlContent = [];\n } else {\n continue;\n }\n }\n\n const formattedText = `${text} `; // must always end with a space\n connection.sendContent({\n contextId: this.#contextId,\n text: formattedText,\n flush: flushOnChunk,\n });\n }\n\n if (xmlContent.length > 0) {\n this.#logger.warn('ElevenLabs stream ended with incomplete xml content');\n }\n\n // Send final empty text to signal end of input\n connection.sendContent({ contextId: this.#contextId, text: '', flush: true });\n connection.closeContext(this.#contextId);\n };\n\n const audioProcessTask = async () => {\n let lastFrame: AudioFrame | undefined;\n\n while (!this.abortController.signal.aborted) {\n // Process audio queue\n while (this.#audioQueue.length > 0) {\n const audioData = this.#audioQueue.shift()!;\n for (const frame of bstream.write(audioData.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Exit when stream is done and queue is empty\n if (this.#streamDone && this.#audioQueue.length === 0) {\n break;\n }\n\n // Small delay to avoid busy waiting\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n\n // Flush remaining\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final: true });\n }\n };\n\n try {\n await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);\n } catch (e) {\n // If aborted, this is a normal termination - don't throw\n if (this.abortController.signal.aborted) {\n return;\n }\n\n if (e instanceof APITimeoutError) {\n throw e;\n }\n if (e instanceof APIStatusError) {\n throw e;\n }\n throw new APIStatusError({ message: 'Could not synthesize' });\n } finally {\n // Clean up abort listener\n this.abortController.signal.removeEventListener('abort', abortHandler);\n }\n }\n\n close(): void {\n // Clear audio buffers to prevent memory leak\n this.#audioQueue.length = 0;\n this.#timedTranscriptQueue.length = 0;\n this.#streamDone = true;\n this.#sentTokenizerStream.close();\n super.close();\n }\n}\n"],"mappings":"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","names":["stream","tts"]}
|
|
1
|
+
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n Future,\n type TimedString,\n createTimedString,\n log,\n shortuuid,\n stream,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport { Mutex } from '@livekit/mutex';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst DEFAULT_VOICE_ID = 'bIHbv24MWmeRgasZH58o';\nconst API_BASE_URL_V1 = 'https://api.elevenlabs.io/v1';\nconst AUTHORIZATION_HEADER = 'xi-api-key';\nconst WS_INACTIVITY_TIMEOUT = 180;\nconst DEFAULT_ENCODING: TTSEncoding = 'pcm_22050';\n\nexport interface VoiceSettings {\n stability: number; // [0.0 - 1.0]\n similarity_boost: number; // [0.0 - 1.0]\n style?: number; // [0.0 - 1.0]\n speed?: number; // [0.8 - 1.2]\n use_speaker_boost?: boolean;\n}\n\nexport interface Voice {\n id: string;\n name: string;\n category: string;\n settings?: VoiceSettings;\n}\n\nexport interface PronunciationDictionaryLocator {\n pronunciation_dictionary_id: string;\n version_id: string;\n}\n\nexport interface TTSOptions {\n apiKey?: string;\n // New interface\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n // Legacy interface (backward compatibility)\n voice?: Voice;\n modelID?: TTSModels | string;\n languageCode?: string;\n // Common options\n baseURL?: string;\n encoding?: TTSEncoding;\n streamingLatency?: number;\n wordTokenizer?: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing?: boolean;\n enableLogging?: boolean;\n inactivityTimeout?: number;\n syncAlignment?: boolean;\n applyTextNormalization?: 'auto' | 'on' | 'off';\n preferredAlignment?: 'normalized' | 'original';\n autoMode?: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal options type with resolved defaults\ninterface ResolvedTTSOptions {\n apiKey: string;\n voiceId: string;\n voiceSettings?: VoiceSettings;\n model: TTSModels | string;\n language?: string;\n baseURL: string;\n encoding: TTSEncoding;\n sampleRate: number;\n streamingLatency?: number;\n wordTokenizer: tokenize.WordTokenizer | tokenize.SentenceTokenizer;\n chunkLengthSchedule?: number[];\n enableSsmlParsing: boolean;\n enableLogging: boolean;\n inactivityTimeout: number;\n syncAlignment: boolean;\n applyTextNormalization: 'auto' | 'on' | 'off';\n preferredAlignment: 'normalized' | 'original';\n autoMode: boolean;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n}\n\n// Internal types for connection management\ninterface SynthesizeContent {\n contextId: string;\n text: string;\n flush: boolean;\n}\n\ninterface CloseContext {\n contextId: string;\n}\n\ninterface StreamData {\n stream: SynthesizeStream;\n waiter: {\n resolve: (value: void) => void;\n reject: (error: Error) => void;\n };\n textBuffer: string;\n startTimesMs: number[];\n durationsMs: number[];\n /** First word offset for timestamp normalization (removes leading silence) */\n firstWordOffsetMs: number | null;\n}\n\ntype ConnectionMessage = SynthesizeContent | CloseContext;\n\n// Helper Functions\n\nfunction sampleRateFromFormat(encoding: TTSEncoding): number {\n const split = encoding.split('_');\n return parseInt(split[1]!, 10);\n}\n\nfunction synthesizeUrl(opts: ResolvedTTSOptions): string {\n const { baseURL, voiceId, model, encoding, streamingLatency } = opts;\n let url = `${baseURL}/text-to-speech/${voiceId}/stream?model_id=${model}&output_format=${encoding}`;\n if (streamingLatency !== undefined) {\n url += `&optimize_streaming_latency=${streamingLatency}`;\n }\n return url;\n}\n\nfunction multiStreamUrl(opts: ResolvedTTSOptions): string {\n const baseURL = opts.baseURL.replace('https://', 'wss://').replace('http://', 'ws://');\n const params: string[] = [];\n params.push(`model_id=${opts.model}`);\n params.push(`output_format=${opts.encoding}`);\n if (opts.language) {\n params.push(`language_code=${opts.language}`);\n }\n params.push(`enable_ssml_parsing=${opts.enableSsmlParsing}`);\n params.push(`enable_logging=${opts.enableLogging}`);\n params.push(`inactivity_timeout=${opts.inactivityTimeout}`);\n params.push(`apply_text_normalization=${opts.applyTextNormalization}`);\n if (opts.syncAlignment) {\n params.push('sync_alignment=true');\n }\n if (opts.autoMode !== undefined) {\n params.push(`auto_mode=${opts.autoMode}`);\n }\n return `${baseURL}/text-to-speech/${opts.voiceId}/multi-stream-input?${params.join('&')}`;\n}\n\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key in obj) {\n if (obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Convert alignment data to timed words.\n * Returns the timed words and remaining text buffer.\n *\n * @param firstWordOffsetMs - Optional offset to normalize timestamps (subtract from all).\n * ElevenLabs returns absolute timestamps from the start of TTS audio, which may include\n * leading silence. By normalizing to 0, we ensure proper sync with the synchronizer.\n */\nfunction toTimedWords(\n text: string,\n startTimesMs: number[],\n durationsMs: number[],\n flush: boolean = false,\n firstWordOffsetMs: number = 0,\n): [TimedString[], string] {\n if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {\n return [[], text || ''];\n }\n\n const lastStartTime = startTimesMs[startTimesMs.length - 1]!;\n const lastDuration = durationsMs[durationsMs.length - 1]!;\n const timestamps = [...startTimesMs, lastStartTime + lastDuration];\n\n const words = tokenize.basic.splitWords(text, false);\n const timedWords: TimedString[] = [];\n\n if (words.length === 0) {\n return [[], text];\n }\n\n const startIndices = words.map((w) => w[1]);\n let end = 0;\n\n // We don't know if the last word is complete, always leave it as remaining\n for (let i = 0; i < startIndices.length - 1; i++) {\n const start = startIndices[i]!;\n const nextStart = startIndices[i + 1]!;\n end = nextStart;\n // Normalize timestamps by subtracting the first word offset\n const startT = Math.max(0, (timestamps[start] ?? 0) - firstWordOffsetMs) / 1000;\n const endT = Math.max(0, (timestamps[nextStart] ?? 0) - firstWordOffsetMs) / 1000;\n timedWords.push(\n createTimedString({\n text: text.slice(start, nextStart),\n startTime: startT,\n endTime: endT,\n }),\n );\n }\n\n if (flush && words.length > 0) {\n const lastWordStart = startIndices[startIndices.length - 1]!;\n const startT = Math.max(0, (timestamps[lastWordStart] ?? 0) - firstWordOffsetMs) / 1000;\n const endT = Math.max(0, (timestamps[timestamps.length - 1] ?? 0) - firstWordOffsetMs) / 1000;\n timedWords.push(\n createTimedString({\n text: text.slice(lastWordStart),\n startTime: startT,\n endTime: endT,\n }),\n );\n end = text.length;\n } else if (words.length > 0) {\n end = startIndices[startIndices.length - 1]!;\n }\n\n return [timedWords, text.slice(end)];\n}\n\nclass Connection {\n #opts: ResolvedTTSOptions;\n #ws: WebSocket | null = null;\n #isCurrent = true;\n #activeContexts = new Set<string>();\n #inputQueue: ConnectionMessage[] = [];\n #contextData = new Map<string, StreamData>();\n #sendTask: Promise<void> | null = null;\n #recvTask: Promise<void> | null = null;\n #closed = false;\n #logger = log();\n #inputQueueResolver: (() => void) | null = null;\n\n constructor(opts: ResolvedTTSOptions) {\n this.#opts = opts;\n }\n\n get voiceId(): string {\n return this.#opts.voiceId;\n }\n\n get isCurrent(): boolean {\n return this.#isCurrent;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n markNonCurrent(): void {\n this.#isCurrent = false;\n }\n\n async connect(): Promise<void> {\n if (this.#ws || this.#closed) {\n return;\n }\n\n const url = multiStreamUrl(this.#opts);\n const headers = { [AUTHORIZATION_HEADER]: this.#opts.apiKey };\n\n return new Promise((resolve, reject) => {\n this.#ws = new WebSocket(url, { headers });\n\n this.#ws.on('open', () => {\n this.#sendTask = this.#sendLoop();\n this.#recvTask = this.#recvLoop();\n resolve();\n });\n\n this.#ws.on('error', (error) => {\n this.#logger.error({ error }, 'WebSocket connection error');\n reject(new APIConnectionError({ message: `WebSocket error: ${error.message}` }));\n });\n });\n }\n\n registerStream(\n stream: SynthesizeStream,\n waiter: { resolve: (value: void) => void; reject: (error: Error) => void },\n ): void {\n const contextId = stream.contextId;\n this.#contextData.set(contextId, {\n stream,\n waiter,\n textBuffer: '',\n startTimesMs: [],\n durationsMs: [],\n firstWordOffsetMs: null,\n });\n }\n\n sendContent(content: SynthesizeContent): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push(content);\n this.#inputQueueResolver?.();\n }\n\n closeContext(contextId: string): void {\n if (this.#closed || !this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket connection is closed' });\n }\n this.#inputQueue.push({ contextId });\n this.#inputQueueResolver?.();\n }\n\n async #sendLoop(): Promise<void> {\n try {\n while (!this.#closed) {\n // Wait for messages in queue\n if (this.#inputQueue.length === 0) {\n await new Promise<void>((resolve) => {\n this.#inputQueueResolver = resolve;\n });\n this.#inputQueueResolver = null;\n }\n\n if (this.#closed) break;\n\n const msg = this.#inputQueue.shift();\n if (!msg) continue;\n\n if (!this.#ws || this.#ws.readyState !== WebSocket.OPEN) {\n break;\n }\n\n if ('text' in msg) {\n // SynthesizeContent\n const content = msg as SynthesizeContent;\n const isNewContext = !this.#activeContexts.has(content.contextId);\n\n // If not current and this is a new context, ignore it\n if (!this.#isCurrent && isNewContext) {\n continue;\n }\n\n if (isNewContext) {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : {};\n\n const initPkt: Record<string, unknown> = {\n text: ' ',\n voice_settings: voiceSettings,\n context_id: content.contextId,\n };\n\n if (this.#opts.pronunciationDictionaryLocators) {\n initPkt.pronunciation_dictionary_locators =\n this.#opts.pronunciationDictionaryLocators.map((locator) => ({\n pronunciation_dictionary_id: locator.pronunciation_dictionary_id,\n version_id: locator.version_id,\n }));\n }\n\n const initPktStr = JSON.stringify(initPkt);\n this.#ws.send(initPktStr);\n this.#activeContexts.add(content.contextId);\n }\n\n const pkt: Record<string, unknown> = {\n text: content.text,\n context_id: content.contextId,\n };\n if (content.flush) {\n pkt.flush = true;\n }\n\n const pktStr = JSON.stringify(pkt);\n this.#ws.send(pktStr);\n } else {\n // CloseContext\n const closeMsg = msg as CloseContext;\n if (this.#activeContexts.has(closeMsg.contextId)) {\n const closePkt = {\n context_id: closeMsg.contextId,\n close_context: true,\n };\n const closePktStr = JSON.stringify(closePkt);\n this.#ws.send(closePktStr);\n }\n }\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'send loop error');\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n async #recvLoop(): Promise<void> {\n try {\n const messageChannel = stream.createStreamChannel<Record<string, unknown>>();\n const errorFuture = new Future<Error>();\n\n const onMessage = (rawData: Buffer) => {\n try {\n const parsed = JSON.parse(rawData.toString());\n messageChannel.write(parsed);\n } catch (e) {\n this.#logger.warn({ error: e }, 'failed to parse WebSocket message');\n }\n };\n\n const onClose = () => {\n if (!this.#closed && this.#contextData.size > 0) {\n this.#logger.warn('websocket closed unexpectedly');\n }\n messageChannel.close();\n };\n\n const onError = (error: Error) => {\n errorFuture.resolve(error);\n messageChannel.close();\n };\n\n // Set up persistent listeners\n if (!this.#ws) return;\n this.#ws.on('message', onMessage);\n this.#ws.on('close', onClose);\n this.#ws.on('error', onError);\n\n const cleanup = () => {\n this.#ws?.off('message', onMessage);\n this.#ws?.off('close', onClose);\n this.#ws?.off('error', onError);\n };\n\n const reader = messageChannel.stream().getReader();\n try {\n while (!this.#closed) {\n const result = await reader.read();\n if (result.done || this.#closed) break;\n\n const data = result.value;\n const contextId = data.contextId as string | undefined;\n const ctx = contextId ? this.#contextData.get(contextId) : undefined;\n\n if (data.error) {\n this.#logger.error(\n { context_id: contextId, error: data.error, data },\n 'elevenlabs tts returned error',\n );\n if (contextId) {\n if (ctx) {\n ctx.waiter.reject(new APIError(data.error as string));\n }\n this.#cleanupContext(contextId);\n }\n continue;\n }\n\n if (!ctx) {\n this.#logger.warn({ data }, 'unexpected message received from elevenlabs tts');\n continue;\n }\n\n const stream = ctx.stream;\n\n // Process alignment data\n const alignment =\n this.#opts.preferredAlignment === 'normalized'\n ? (data.normalizedAlignment as Record<string, unknown>)\n : (data.alignment as Record<string, unknown>);\n\n if (alignment && stream) {\n const chars = alignment.chars as string[] | undefined;\n const starts = (alignment.charStartTimesMs || alignment.charsStartTimesMs) as\n | number[]\n | undefined;\n const durs = (alignment.charDurationsMs || alignment.charsDurationsMs) as\n | number[]\n | undefined;\n\n if (\n chars &&\n starts &&\n durs &&\n chars.length === durs.length &&\n starts.length === durs.length\n ) {\n ctx.textBuffer += chars.join('');\n\n // Handle chars with multiple characters\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!;\n const start = starts[i]!;\n const dur = durs[i]!;\n\n // Capture the first word's start time for normalization\n // This removes leading silence from timestamps\n if (ctx.firstWordOffsetMs === null && start > 0) {\n ctx.firstWordOffsetMs = start;\n }\n\n if (char.length > 1) {\n for (let j = 0; j < char.length - 1; j++) {\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(0);\n }\n }\n ctx.startTimesMs.push(start);\n ctx.durationsMs.push(dur);\n }\n\n const [timedWords, remainingText] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n false,\n ctx.firstWordOffsetMs ?? 0,\n );\n\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n\n ctx.textBuffer = remainingText;\n ctx.startTimesMs = ctx.startTimesMs.slice(-remainingText.length);\n ctx.durationsMs = ctx.durationsMs.slice(-remainingText.length);\n }\n }\n\n if (data.audio) {\n const audioData = Buffer.from(data.audio as string, 'base64');\n stream.pushAudio(audioData);\n }\n\n if (data.isFinal) {\n // Flush remaining alignment data\n if (ctx.textBuffer) {\n const [timedWords] = toTimedWords(\n ctx.textBuffer,\n ctx.startTimesMs,\n ctx.durationsMs,\n true,\n ctx.firstWordOffsetMs ?? 0,\n );\n if (timedWords.length > 0) {\n stream.pushTimedTranscript(timedWords);\n }\n }\n\n stream.markDone();\n ctx.waiter.resolve();\n this.#cleanupContext(contextId!);\n\n if (!this.#isCurrent && this.#activeContexts.size === 0) {\n this.#logger.debug('no active contexts, shutting down connection');\n break;\n }\n }\n }\n\n // Throw any error that occurred\n if (errorFuture.done) {\n throw await errorFuture.await;\n }\n } finally {\n reader.releaseLock();\n cleanup();\n }\n } catch (e) {\n this.#logger.warn({ error: e }, 'recv loop error');\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(e instanceof Error ? e : new Error(String(e)));\n }\n this.#contextData.clear();\n } finally {\n if (!this.#closed) {\n await this.close();\n }\n }\n }\n\n #cleanupContext(contextId: string): void {\n this.#contextData.delete(contextId);\n this.#activeContexts.delete(contextId);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n this.#closed = true;\n this.#inputQueueResolver?.();\n\n for (const ctx of this.#contextData.values()) {\n ctx.waiter.reject(new APIStatusError({ message: 'connection closed' }));\n }\n this.#contextData.clear();\n\n if (this.#ws) {\n this.#ws.close();\n this.#ws = null;\n }\n\n if (this.#sendTask) {\n await this.#sendTask.catch(() => {});\n }\n if (this.#recvTask) {\n await this.#recvTask.catch(() => {});\n }\n }\n}\n\nexport class TTS extends tts.TTS {\n #opts: ResolvedTTSOptions;\n #streams = new Set<SynthesizeStream>();\n #currentConnection: Connection | null = null;\n #connectionLock = new Mutex();\n #logger = log();\n\n label = 'elevenlabs.TTS';\n\n constructor(opts: TTSOptions = {}) {\n const autoMode = opts.autoMode ?? true;\n const encoding = opts.encoding ?? DEFAULT_ENCODING;\n const sampleRate = sampleRateFromFormat(encoding);\n const syncAlignment = opts.syncAlignment ?? true;\n\n super(sampleRate, 1, {\n streaming: true,\n alignedTranscript: syncAlignment,\n });\n\n const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ElevenLabs API key is required, either as argument or set ELEVEN_API_KEY environmental variable',\n );\n }\n\n let wordTokenizer = opts.wordTokenizer;\n if (!wordTokenizer) {\n wordTokenizer = autoMode\n ? new tokenize.basic.SentenceTokenizer()\n : new tokenize.basic.WordTokenizer(false);\n } else if (autoMode && !(wordTokenizer instanceof tokenize.SentenceTokenizer)) {\n this.#logger.warn(\n 'autoMode is enabled, it expects full sentences or phrases, ' +\n 'please provide a SentenceTokenizer instead of a WordTokenizer.',\n );\n }\n\n // Handle legacy options for backward compatibility\n const voiceId = opts.voiceId ?? opts.voice?.id ?? DEFAULT_VOICE_ID;\n const voiceSettings = opts.voiceSettings ?? opts.voice?.settings;\n const model = opts.model ?? opts.modelID ?? 'eleven_turbo_v2_5';\n const language = opts.language ?? opts.languageCode;\n\n this.#opts = {\n apiKey,\n voiceId,\n voiceSettings,\n model,\n language,\n baseURL: opts.baseURL ?? API_BASE_URL_V1,\n encoding,\n sampleRate,\n streamingLatency: opts.streamingLatency,\n wordTokenizer,\n chunkLengthSchedule: opts.chunkLengthSchedule,\n enableSsmlParsing: opts.enableSsmlParsing ?? false,\n enableLogging: opts.enableLogging ?? true,\n inactivityTimeout: opts.inactivityTimeout ?? WS_INACTIVITY_TIMEOUT,\n syncAlignment: opts.syncAlignment ?? true,\n applyTextNormalization: opts.applyTextNormalization ?? 'auto',\n preferredAlignment: opts.preferredAlignment ?? 'normalized',\n autoMode,\n pronunciationDictionaryLocators: opts.pronunciationDictionaryLocators,\n };\n }\n\n get model(): string {\n return this.#opts.model;\n }\n\n get provider(): string {\n return 'ElevenLabs';\n }\n\n async listVoices(): Promise<Voice[]> {\n const response = await fetch(`${this.#opts.baseURL}/voices`, {\n headers: { [AUTHORIZATION_HEADER]: this.#opts.apiKey },\n });\n const data = (await response.json()) as {\n voices: { voice_id: string; name: string; category: string }[];\n };\n return data.voices.map((v) => ({\n id: v.voice_id,\n name: v.name,\n category: v.category,\n }));\n }\n\n updateOptions(opts: {\n voiceId?: string;\n voiceSettings?: VoiceSettings;\n model?: TTSModels | string;\n language?: string;\n pronunciationDictionaryLocators?: PronunciationDictionaryLocator[];\n }): void {\n let changed = false;\n\n if (opts.model !== undefined && opts.model !== this.#opts.model) {\n this.#opts.model = opts.model;\n changed = true;\n }\n\n if (opts.voiceId !== undefined && opts.voiceId !== this.#opts.voiceId) {\n this.#opts.voiceId = opts.voiceId;\n changed = true;\n }\n\n if (opts.voiceSettings !== undefined) {\n this.#opts.voiceSettings = opts.voiceSettings;\n changed = true;\n }\n\n if (opts.language !== undefined && opts.language !== this.#opts.language) {\n this.#opts.language = opts.language;\n changed = true;\n }\n\n if (opts.pronunciationDictionaryLocators !== undefined) {\n this.#opts.pronunciationDictionaryLocators = opts.pronunciationDictionaryLocators;\n changed = true;\n }\n\n if (changed && this.#currentConnection) {\n this.#currentConnection.markNonCurrent();\n this.#currentConnection = null;\n }\n }\n\n async currentConnection(): Promise<Connection> {\n const unlock = await this.#connectionLock.lock();\n try {\n if (\n this.#currentConnection &&\n this.#currentConnection.isCurrent &&\n !this.#currentConnection.closed\n ) {\n return this.#currentConnection;\n }\n\n const conn = new Connection({ ...this.#opts });\n await conn.connect();\n this.#currentConnection = conn;\n return conn;\n } finally {\n unlock();\n }\n }\n\n synthesize(text: string): ChunkedStream {\n return new ChunkedStream(this, text, { ...this.#opts });\n }\n\n stream(): SynthesizeStream {\n const stream = new SynthesizeStream(this, { ...this.#opts });\n this.#streams.add(stream);\n return stream;\n }\n\n async close(): Promise<void> {\n for (const stream of this.#streams) {\n stream.close();\n }\n this.#streams.clear();\n\n if (this.#currentConnection) {\n await this.#currentConnection.close();\n this.#currentConnection = null;\n }\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #logger = log();\n\n label = 'elevenlabs.ChunkedStream';\n\n constructor(tts: TTS, text: string, opts: ResolvedTTSOptions) {\n super(text, tts);\n this.#tts = tts;\n this.#opts = opts;\n }\n\n protected async run(): Promise<void> {\n const voiceSettings = this.#opts.voiceSettings\n ? stripUndefined(this.#opts.voiceSettings)\n : undefined;\n\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n try {\n const response = await fetch(synthesizeUrl(this.#opts), {\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n text: this.inputText,\n model_id: this.#opts.model,\n voice_settings: voiceSettings,\n }),\n signal: this.abortSignal,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new APIStatusError({\n message: `ElevenLabs API error: ${errorText}`,\n options: { statusCode: response.status },\n });\n }\n\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.startsWith('audio/')) {\n const content = await response.text();\n throw new APIError(`ElevenLabs returned non-audio data: ${content}`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new APIError('No response body');\n }\n\n let lastFrame: AudioFrame | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n for (const frame of bstream.write(value.buffer)) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n }\n\n // Flush remaining data\n for (const frame of bstream.flush()) {\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: false });\n }\n lastFrame = frame;\n }\n\n if (lastFrame) {\n this.queue.put({ requestId, segmentId: requestId, frame: lastFrame, final: true });\n }\n } catch (e) {\n if (e instanceof APIError) {\n throw e;\n }\n if (e instanceof Error && e.name === 'AbortError') {\n return;\n }\n throw new APIConnectionError({ message: `Connection error: ${e}` });\n }\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #tts: TTS;\n #opts: ResolvedTTSOptions;\n #contextId: string;\n #sentTokenizerStream: tokenize.SentenceStream | tokenize.WordStream;\n #logger = log();\n #audioQueue: Buffer[] = [];\n #timedTranscriptQueue: TimedString[] = [];\n #streamDone = false;\n\n label = 'elevenlabs.SynthesizeStream';\n\n constructor(tts: TTS, opts: ResolvedTTSOptions) {\n super(tts);\n this.#tts = tts;\n this.#opts = opts;\n this.#contextId = shortuuid();\n this.#sentTokenizerStream = this.#opts.wordTokenizer.stream();\n }\n\n get contextId(): string {\n return this.#contextId;\n }\n\n pushAudio(data: Buffer): void {\n // Don't push if stream is closed/aborted\n if (this.closed || this.abortController.signal.aborted) {\n return;\n }\n this.#audioQueue.push(data);\n }\n\n pushTimedTranscript(timedWords: TimedString[]): void {\n this.#timedTranscriptQueue.push(...timedWords);\n }\n\n markDone(): void {\n this.#streamDone = true;\n }\n\n protected async run(): Promise<void> {\n const requestId = this.#contextId;\n const segmentId = this.#contextId;\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1);\n\n let connection: Connection;\n try {\n connection = await this.#tts.currentConnection();\n } catch (e) {\n throw new APIConnectionError({ message: 'could not connect to ElevenLabs' });\n }\n\n let waiterReject: ((reason: Error) => void) | undefined;\n const waiterPromise = new Promise<void>((resolve, reject) => {\n waiterReject = reject;\n connection.registerStream(this, { resolve, reject });\n });\n\n // Handle abort - reject the waiter so Promise.all can complete\n const abortHandler = () => {\n if (waiterReject) {\n waiterReject(new Error('Stream aborted'));\n }\n };\n this.abortController.signal.addEventListener('abort', abortHandler, { once: true });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (this.abortController.signal.aborted) break;\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.#sentTokenizerStream.flush();\n continue;\n }\n this.#sentTokenizerStream.pushText(data);\n }\n this.#sentTokenizerStream.endInput();\n };\n\n const sentenceStreamTask = async () => {\n const flushOnChunk =\n this.#opts.wordTokenizer instanceof tokenize.SentenceTokenizer && this.#opts.autoMode;\n\n let xmlContent: string[] = [];\n\n for await (const data of this.#sentTokenizerStream) {\n if (this.abortController.signal.aborted) break;\n\n let text = data.token;\n const xmlStartTokens = ['<phoneme', '<break'];\n const xmlEndTokens = ['</phoneme>', '/>'];\n\n if (\n (this.#opts.enableSsmlParsing &&\n xmlStartTokens.some((start) => text.startsWith(start))) ||\n xmlContent.length > 0\n ) {\n xmlContent.push(text);\n\n if (xmlEndTokens.some((end) => text.includes(end))) {\n text = xmlContent.join(' ');\n xmlContent = [];\n } else {\n continue;\n }\n }\n\n const formattedText = `${text} `; // must always end with a space\n connection.sendContent({\n contextId: this.#contextId,\n text: formattedText,\n flush: flushOnChunk,\n });\n }\n\n if (xmlContent.length > 0) {\n this.#logger.warn('ElevenLabs stream ended with incomplete xml content');\n }\n\n // Send final empty text to signal end of input\n connection.sendContent({ contextId: this.#contextId, text: '', flush: true });\n connection.closeContext(this.#contextId);\n };\n\n const audioProcessTask = async () => {\n let lastFrame: AudioFrame | undefined;\n let pendingTimedTranscripts: TimedString[] = [];\n\n const sendLastFrame = (final: boolean) => {\n if (lastFrame) {\n // Include timedTranscripts with the audio frame\n this.queue.put({\n requestId,\n segmentId,\n frame: lastFrame,\n final,\n timedTranscripts:\n pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : undefined,\n });\n lastFrame = undefined;\n pendingTimedTranscripts = [];\n }\n };\n\n while (!this.abortController.signal.aborted) {\n // Drain timed transcript queue\n while (this.#timedTranscriptQueue.length > 0) {\n pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);\n }\n\n // Process audio queue\n while (this.#audioQueue.length > 0) {\n const audioData = this.#audioQueue.shift()!;\n for (const frame of bstream.write(audioData.buffer)) {\n sendLastFrame(false);\n lastFrame = frame;\n }\n }\n\n // Exit when stream is done and queue is empty\n if (this.#streamDone && this.#audioQueue.length === 0) {\n break;\n }\n\n // Small delay to avoid busy waiting\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n\n // Drain any remaining timed transcripts\n while (this.#timedTranscriptQueue.length > 0) {\n pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);\n }\n\n // Flush remaining\n for (const frame of bstream.flush()) {\n sendLastFrame(false);\n lastFrame = frame;\n }\n\n sendLastFrame(true);\n };\n\n try {\n await Promise.all([inputTask(), sentenceStreamTask(), audioProcessTask(), waiterPromise]);\n } catch (e) {\n // If aborted, this is a normal termination - don't throw\n if (this.abortController.signal.aborted) {\n return;\n }\n\n if (e instanceof APITimeoutError) {\n throw e;\n }\n if (e instanceof APIStatusError) {\n throw e;\n }\n throw new APIStatusError({ message: 'Could not synthesize' });\n } finally {\n // Clean up abort listener\n this.abortController.signal.removeEventListener('abort', abortHandler);\n }\n }\n\n close(): void {\n // Clear audio buffers to prevent memory leak\n this.#audioQueue.length = 0;\n this.#timedTranscriptQueue.length = 0;\n this.#streamDone = true;\n this.#sentTokenizerStream.close();\n super.close();\n }\n}\n"],"mappings":"AAGA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AAEtB,SAAS,iBAAiB;AAG1B,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAC7B,MAAM,wBAAwB;AAC9B,MAAM,mBAAgC;AAoGtC,SAAS,qBAAqB,UAA+B;AAC3D,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,SAAO,SAAS,MAAM,CAAC,GAAI,EAAE;AAC/B;AAEA,SAAS,cAAc,MAAkC;AACvD,QAAM,EAAE,SAAS,SAAS,OAAO,UAAU,iBAAiB,IAAI;AAChE,MAAI,MAAM,GAAG,OAAO,mBAAmB,OAAO,oBAAoB,KAAK,kBAAkB,QAAQ;AACjG,MAAI,qBAAqB,QAAW;AAClC,WAAO,+BAA+B,gBAAgB;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,eAAe,MAAkC;AACxD,QAAM,UAAU,KAAK,QAAQ,QAAQ,YAAY,QAAQ,EAAE,QAAQ,WAAW,OAAO;AACrF,QAAM,SAAmB,CAAC;AAC1B,SAAO,KAAK,YAAY,KAAK,KAAK,EAAE;AACpC,SAAO,KAAK,iBAAiB,KAAK,QAAQ,EAAE;AAC5C,MAAI,KAAK,UAAU;AACjB,WAAO,KAAK,iBAAiB,KAAK,QAAQ,EAAE;AAAA,EAC9C;AACA,SAAO,KAAK,uBAAuB,KAAK,iBAAiB,EAAE;AAC3D,SAAO,KAAK,kBAAkB,KAAK,aAAa,EAAE;AAClD,SAAO,KAAK,sBAAsB,KAAK,iBAAiB,EAAE;AAC1D,SAAO,KAAK,4BAA4B,KAAK,sBAAsB,EAAE;AACrE,MAAI,KAAK,eAAe;AACtB,WAAO,KAAK,qBAAqB;AAAA,EACnC;AACA,MAAI,KAAK,aAAa,QAAW;AAC/B,WAAO,KAAK,aAAa,KAAK,QAAQ,EAAE;AAAA,EAC1C;AACA,SAAO,GAAG,OAAO,mBAAmB,KAAK,OAAO,uBAAuB,OAAO,KAAK,GAAG,CAAC;AACzF;AAEA,SAAS,eAAiC,KAAoB;AAC5D,QAAM,SAAqB,CAAC;AAC5B,aAAW,OAAO,KAAK;AACrB,QAAI,IAAI,GAAG,MAAM,QAAW;AAC1B,aAAO,GAAG,IAAI,IAAI,GAAG;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;AAUA,SAAS,aACP,MACA,cACA,aACA,QAAiB,OACjB,oBAA4B,GACH;AACzB,MAAI,CAAC,QAAQ,aAAa,WAAW,KAAK,YAAY,WAAW,GAAG;AAClE,WAAO,CAAC,CAAC,GAAG,QAAQ,EAAE;AAAA,EACxB;AAEA,QAAM,gBAAgB,aAAa,aAAa,SAAS,CAAC;AAC1D,QAAM,eAAe,YAAY,YAAY,SAAS,CAAC;AACvD,QAAM,aAAa,CAAC,GAAG,cAAc,gBAAgB,YAAY;AAEjE,QAAM,QAAQ,SAAS,MAAM,WAAW,MAAM,KAAK;AACnD,QAAM,aAA4B,CAAC;AAEnC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,CAAC,CAAC,GAAG,IAAI;AAAA,EAClB;AAEA,QAAM,eAAe,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC1C,MAAI,MAAM;AAGV,WAAS,IAAI,GAAG,IAAI,aAAa,SAAS,GAAG,KAAK;AAChD,UAAM,QAAQ,aAAa,CAAC;AAC5B,UAAM,YAAY,aAAa,IAAI,CAAC;AACpC,UAAM;AAEN,UAAM,SAAS,KAAK,IAAI,IAAI,WAAW,KAAK,KAAK,KAAK,iBAAiB,IAAI;AAC3E,UAAM,OAAO,KAAK,IAAI,IAAI,WAAW,SAAS,KAAK,KAAK,iBAAiB,IAAI;AAC7E,eAAW;AAAA,MACT,kBAAkB;AAAA,QAChB,MAAM,KAAK,MAAM,OAAO,SAAS;AAAA,QACjC,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,UAAM,gBAAgB,aAAa,aAAa,SAAS,CAAC;AAC1D,UAAM,SAAS,KAAK,IAAI,IAAI,WAAW,aAAa,KAAK,KAAK,iBAAiB,IAAI;AACnF,UAAM,OAAO,KAAK,IAAI,IAAI,WAAW,WAAW,SAAS,CAAC,KAAK,KAAK,iBAAiB,IAAI;AACzF,eAAW;AAAA,MACT,kBAAkB;AAAA,QAChB,MAAM,KAAK,MAAM,aAAa;AAAA,QAC9B,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,KAAK;AAAA,EACb,WAAW,MAAM,SAAS,GAAG;AAC3B,UAAM,aAAa,aAAa,SAAS,CAAC;AAAA,EAC5C;AAEA,SAAO,CAAC,YAAY,KAAK,MAAM,GAAG,CAAC;AACrC;AAEA,MAAM,WAAW;AAAA,EACf;AAAA,EACA,MAAwB;AAAA,EACxB,aAAa;AAAA,EACb,kBAAkB,oBAAI,IAAY;AAAA,EAClC,cAAmC,CAAC;AAAA,EACpC,eAAe,oBAAI,IAAwB;AAAA,EAC3C,YAAkC;AAAA,EAClC,YAAkC;AAAA,EAClC,UAAU;AAAA,EACV,UAAU,IAAI;AAAA,EACd,sBAA2C;AAAA,EAE3C,YAAY,MAA0B;AACpC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,iBAAuB;AACrB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,OAAO,KAAK,SAAS;AAC5B;AAAA,IACF;AAEA,UAAM,MAAM,eAAe,KAAK,KAAK;AACrC,UAAM,UAAU,EAAE,CAAC,oBAAoB,GAAG,KAAK,MAAM,OAAO;AAE5D,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,MAAM,IAAI,UAAU,KAAK,EAAE,QAAQ,CAAC;AAEzC,WAAK,IAAI,GAAG,QAAQ,MAAM;AACxB,aAAK,YAAY,KAAK,UAAU;AAChC,aAAK,YAAY,KAAK,UAAU;AAChC,gBAAQ;AAAA,MACV,CAAC;AAED,WAAK,IAAI,GAAG,SAAS,CAAC,UAAU;AAC9B,aAAK,QAAQ,MAAM,EAAE,MAAM,GAAG,4BAA4B;AAC1D,eAAO,IAAI,mBAAmB,EAAE,SAAS,oBAAoB,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,MACjF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,eACEA,SACA,QACM;AACN,UAAM,YAAYA,QAAO;AACzB,SAAK,aAAa,IAAI,WAAW;AAAA,MAC/B,QAAAA;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,cAAc,CAAC;AAAA,MACf,aAAa,CAAC;AAAA,MACd,mBAAmB;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,SAAkC;AAzThD;AA0TI,QAAI,KAAK,WAAW,CAAC,KAAK,OAAO,KAAK,IAAI,eAAe,UAAU,MAAM;AACvE,YAAM,IAAI,mBAAmB,EAAE,SAAS,iCAAiC,CAAC;AAAA,IAC5E;AACA,SAAK,YAAY,KAAK,OAAO;AAC7B,eAAK,wBAAL;AAAA,EACF;AAAA,EAEA,aAAa,WAAyB;AAjUxC;AAkUI,QAAI,KAAK,WAAW,CAAC,KAAK,OAAO,KAAK,IAAI,eAAe,UAAU,MAAM;AACvE,YAAM,IAAI,mBAAmB,EAAE,SAAS,iCAAiC,CAAC;AAAA,IAC5E;AACA,SAAK,YAAY,KAAK,EAAE,UAAU,CAAC;AACnC,eAAK,wBAAL;AAAA,EACF;AAAA,EAEA,MAAM,YAA2B;AAC/B,QAAI;AACF,aAAO,CAAC,KAAK,SAAS;AAEpB,YAAI,KAAK,YAAY,WAAW,GAAG;AACjC,gBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAK,sBAAsB;AAAA,UAC7B,CAAC;AACD,eAAK,sBAAsB;AAAA,QAC7B;AAEA,YAAI,KAAK,QAAS;AAElB,cAAM,MAAM,KAAK,YAAY,MAAM;AACnC,YAAI,CAAC,IAAK;AAEV,YAAI,CAAC,KAAK,OAAO,KAAK,IAAI,eAAe,UAAU,MAAM;AACvD;AAAA,QACF;AAEA,YAAI,UAAU,KAAK;AAEjB,gBAAM,UAAU;AAChB,gBAAM,eAAe,CAAC,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAGhE,cAAI,CAAC,KAAK,cAAc,cAAc;AACpC;AAAA,UACF;AAEA,cAAI,cAAc;AAChB,kBAAM,gBAAgB,KAAK,MAAM,gBAC7B,eAAe,KAAK,MAAM,aAAa,IACvC,CAAC;AAEL,kBAAM,UAAmC;AAAA,cACvC,MAAM;AAAA,cACN,gBAAgB;AAAA,cAChB,YAAY,QAAQ;AAAA,YACtB;AAEA,gBAAI,KAAK,MAAM,iCAAiC;AAC9C,sBAAQ,oCACN,KAAK,MAAM,gCAAgC,IAAI,CAAC,aAAa;AAAA,gBAC3D,6BAA6B,QAAQ;AAAA,gBACrC,YAAY,QAAQ;AAAA,cACtB,EAAE;AAAA,YACN;AAEA,kBAAM,aAAa,KAAK,UAAU,OAAO;AACzC,iBAAK,IAAI,KAAK,UAAU;AACxB,iBAAK,gBAAgB,IAAI,QAAQ,SAAS;AAAA,UAC5C;AAEA,gBAAM,MAA+B;AAAA,YACnC,MAAM,QAAQ;AAAA,YACd,YAAY,QAAQ;AAAA,UACtB;AACA,cAAI,QAAQ,OAAO;AACjB,gBAAI,QAAQ;AAAA,UACd;AAEA,gBAAM,SAAS,KAAK,UAAU,GAAG;AACjC,eAAK,IAAI,KAAK,MAAM;AAAA,QACtB,OAAO;AAEL,gBAAM,WAAW;AACjB,cAAI,KAAK,gBAAgB,IAAI,SAAS,SAAS,GAAG;AAChD,kBAAM,WAAW;AAAA,cACf,YAAY,SAAS;AAAA,cACrB,eAAe;AAAA,YACjB;AACA,kBAAM,cAAc,KAAK,UAAU,QAAQ;AAC3C,iBAAK,IAAI,KAAK,WAAW;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,WAAK,QAAQ,KAAK,EAAE,OAAO,EAAE,GAAG,iBAAiB;AAAA,IACnD,UAAE;AACA,UAAI,CAAC,KAAK,SAAS;AACjB,cAAM,KAAK,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YAA2B;AAC/B,QAAI;AACF,YAAM,iBAAiB,OAAO,oBAA6C;AAC3E,YAAM,cAAc,IAAI,OAAc;AAEtC,YAAM,YAAY,CAAC,YAAoB;AACrC,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,QAAQ,SAAS,CAAC;AAC5C,yBAAe,MAAM,MAAM;AAAA,QAC7B,SAAS,GAAG;AACV,eAAK,QAAQ,KAAK,EAAE,OAAO,EAAE,GAAG,mCAAmC;AAAA,QACrE;AAAA,MACF;AAEA,YAAM,UAAU,MAAM;AACpB,YAAI,CAAC,KAAK,WAAW,KAAK,aAAa,OAAO,GAAG;AAC/C,eAAK,QAAQ,KAAK,+BAA+B;AAAA,QACnD;AACA,uBAAe,MAAM;AAAA,MACvB;AAEA,YAAM,UAAU,CAAC,UAAiB;AAChC,oBAAY,QAAQ,KAAK;AACzB,uBAAe,MAAM;AAAA,MACvB;AAGA,UAAI,CAAC,KAAK,IAAK;AACf,WAAK,IAAI,GAAG,WAAW,SAAS;AAChC,WAAK,IAAI,GAAG,SAAS,OAAO;AAC5B,WAAK,IAAI,GAAG,SAAS,OAAO;AAE5B,YAAM,UAAU,MAAM;AA/b5B;AAgcQ,mBAAK,QAAL,mBAAU,IAAI,WAAW;AACzB,mBAAK,QAAL,mBAAU,IAAI,SAAS;AACvB,mBAAK,QAAL,mBAAU,IAAI,SAAS;AAAA,MACzB;AAEA,YAAM,SAAS,eAAe,OAAO,EAAE,UAAU;AACjD,UAAI;AACF,eAAO,CAAC,KAAK,SAAS;AACpB,gBAAM,SAAS,MAAM,OAAO,KAAK;AACjC,cAAI,OAAO,QAAQ,KAAK,QAAS;AAEjC,gBAAM,OAAO,OAAO;AACpB,gBAAM,YAAY,KAAK;AACvB,gBAAM,MAAM,YAAY,KAAK,aAAa,IAAI,SAAS,IAAI;AAE3D,cAAI,KAAK,OAAO;AACd,iBAAK,QAAQ;AAAA,cACX,EAAE,YAAY,WAAW,OAAO,KAAK,OAAO,KAAK;AAAA,cACjD;AAAA,YACF;AACA,gBAAI,WAAW;AACb,kBAAI,KAAK;AACP,oBAAI,OAAO,OAAO,IAAI,SAAS,KAAK,KAAe,CAAC;AAAA,cACtD;AACA,mBAAK,gBAAgB,SAAS;AAAA,YAChC;AACA;AAAA,UACF;AAEA,cAAI,CAAC,KAAK;AACR,iBAAK,QAAQ,KAAK,EAAE,KAAK,GAAG,iDAAiD;AAC7E;AAAA,UACF;AAEA,gBAAMA,UAAS,IAAI;AAGnB,gBAAM,YACJ,KAAK,MAAM,uBAAuB,eAC7B,KAAK,sBACL,KAAK;AAEZ,cAAI,aAAaA,SAAQ;AACvB,kBAAM,QAAQ,UAAU;AACxB,kBAAM,SAAU,UAAU,oBAAoB,UAAU;AAGxD,kBAAM,OAAQ,UAAU,mBAAmB,UAAU;AAIrD,gBACE,SACA,UACA,QACA,MAAM,WAAW,KAAK,UACtB,OAAO,WAAW,KAAK,QACvB;AACA,kBAAI,cAAc,MAAM,KAAK,EAAE;AAG/B,uBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,sBAAM,OAAO,MAAM,CAAC;AACpB,sBAAM,QAAQ,OAAO,CAAC;AACtB,sBAAM,MAAM,KAAK,CAAC;AAIlB,oBAAI,IAAI,sBAAsB,QAAQ,QAAQ,GAAG;AAC/C,sBAAI,oBAAoB;AAAA,gBAC1B;AAEA,oBAAI,KAAK,SAAS,GAAG;AACnB,2BAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,wBAAI,aAAa,KAAK,KAAK;AAC3B,wBAAI,YAAY,KAAK,CAAC;AAAA,kBACxB;AAAA,gBACF;AACA,oBAAI,aAAa,KAAK,KAAK;AAC3B,oBAAI,YAAY,KAAK,GAAG;AAAA,cAC1B;AAEA,oBAAM,CAAC,YAAY,aAAa,IAAI;AAAA,gBAClC,IAAI;AAAA,gBACJ,IAAI;AAAA,gBACJ,IAAI;AAAA,gBACJ;AAAA,gBACA,IAAI,qBAAqB;AAAA,cAC3B;AAEA,kBAAI,WAAW,SAAS,GAAG;AACzB,gBAAAA,QAAO,oBAAoB,UAAU;AAAA,cACvC;AAEA,kBAAI,aAAa;AACjB,kBAAI,eAAe,IAAI,aAAa,MAAM,CAAC,cAAc,MAAM;AAC/D,kBAAI,cAAc,IAAI,YAAY,MAAM,CAAC,cAAc,MAAM;AAAA,YAC/D;AAAA,UACF;AAEA,cAAI,KAAK,OAAO;AACd,kBAAM,YAAY,OAAO,KAAK,KAAK,OAAiB,QAAQ;AAC5D,YAAAA,QAAO,UAAU,SAAS;AAAA,UAC5B;AAEA,cAAI,KAAK,SAAS;AAEhB,gBAAI,IAAI,YAAY;AAClB,oBAAM,CAAC,UAAU,IAAI;AAAA,gBACnB,IAAI;AAAA,gBACJ,IAAI;AAAA,gBACJ,IAAI;AAAA,gBACJ;AAAA,gBACA,IAAI,qBAAqB;AAAA,cAC3B;AACA,kBAAI,WAAW,SAAS,GAAG;AACzB,gBAAAA,QAAO,oBAAoB,UAAU;AAAA,cACvC;AAAA,YACF;AAEA,YAAAA,QAAO,SAAS;AAChB,gBAAI,OAAO,QAAQ;AACnB,iBAAK,gBAAgB,SAAU;AAE/B,gBAAI,CAAC,KAAK,cAAc,KAAK,gBAAgB,SAAS,GAAG;AACvD,mBAAK,QAAQ,MAAM,8CAA8C;AACjE;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,YAAI,YAAY,MAAM;AACpB,gBAAM,MAAM,YAAY;AAAA,QAC1B;AAAA,MACF,UAAE;AACA,eAAO,YAAY;AACnB,gBAAQ;AAAA,MACV;AAAA,IACF,SAAS,GAAG;AACV,WAAK,QAAQ,KAAK,EAAE,OAAO,EAAE,GAAG,iBAAiB;AACjD,iBAAW,OAAO,KAAK,aAAa,OAAO,GAAG;AAC5C,YAAI,OAAO,OAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACjE;AACA,WAAK,aAAa,MAAM;AAAA,IAC1B,UAAE;AACA,UAAI,CAAC,KAAK,SAAS;AACjB,cAAM,KAAK,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,gBAAgB,WAAyB;AACvC,SAAK,aAAa,OAAO,SAAS;AAClC,SAAK,gBAAgB,OAAO,SAAS;AAAA,EACvC;AAAA,EAEA,MAAM,QAAuB;AA7lB/B;AA8lBI,QAAI,KAAK,SAAS;AAChB;AAAA,IACF;AAEA,SAAK,UAAU;AACf,eAAK,wBAAL;AAEA,eAAW,OAAO,KAAK,aAAa,OAAO,GAAG;AAC5C,UAAI,OAAO,OAAO,IAAI,eAAe,EAAE,SAAS,oBAAoB,CAAC,CAAC;AAAA,IACxE;AACA,SAAK,aAAa,MAAM;AAExB,QAAI,KAAK,KAAK;AACZ,WAAK,IAAI,MAAM;AACf,WAAK,MAAM;AAAA,IACb;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,UAAU,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrC;AACA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,UAAU,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrC;AAAA,EACF;AACF;AAEO,MAAM,YAAY,IAAI,IAAI;AAAA,EAC/B;AAAA,EACA,WAAW,oBAAI,IAAsB;AAAA,EACrC,qBAAwC;AAAA,EACxC,kBAAkB,IAAI,MAAM;AAAA,EAC5B,UAAU,IAAI;AAAA,EAEd,QAAQ;AAAA,EAER,YAAY,OAAmB,CAAC,GAAG;AAjoBrC;AAkoBI,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,aAAa,qBAAqB,QAAQ;AAChD,UAAM,gBAAgB,KAAK,iBAAiB;AAE5C,UAAM,YAAY,GAAG;AAAA,MACnB,WAAW;AAAA,MACX,mBAAmB;AAAA,IACrB,CAAC;AAED,UAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,gBAAgB,KAAK;AACzB,QAAI,CAAC,eAAe;AAClB,sBAAgB,WACZ,IAAI,SAAS,MAAM,kBAAkB,IACrC,IAAI,SAAS,MAAM,cAAc,KAAK;AAAA,IAC5C,WAAW,YAAY,EAAE,yBAAyB,SAAS,oBAAoB;AAC7E,WAAK,QAAQ;AAAA,QACX;AAAA,MAEF;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,aAAW,UAAK,UAAL,mBAAY,OAAM;AAClD,UAAM,gBAAgB,KAAK,mBAAiB,UAAK,UAAL,mBAAY;AACxD,UAAM,QAAQ,KAAK,SAAS,KAAK,WAAW;AAC5C,UAAM,WAAW,KAAK,YAAY,KAAK;AAEvC,SAAK,QAAQ;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,KAAK,WAAW;AAAA,MACzB;AAAA,MACA;AAAA,MACA,kBAAkB,KAAK;AAAA,MACvB;AAAA,MACA,qBAAqB,KAAK;AAAA,MAC1B,mBAAmB,KAAK,qBAAqB;AAAA,MAC7C,eAAe,KAAK,iBAAiB;AAAA,MACrC,mBAAmB,KAAK,qBAAqB;AAAA,MAC7C,eAAe,KAAK,iBAAiB;AAAA,MACrC,wBAAwB,KAAK,0BAA0B;AAAA,MACvD,oBAAoB,KAAK,sBAAsB;AAAA,MAC/C;AAAA,MACA,iCAAiC,KAAK;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAA+B;AACnC,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,MAAM,OAAO,WAAW;AAAA,MAC3D,SAAS,EAAE,CAAC,oBAAoB,GAAG,KAAK,MAAM,OAAO;AAAA,IACvD,CAAC;AACD,UAAM,OAAQ,MAAM,SAAS,KAAK;AAGlC,WAAO,KAAK,OAAO,IAAI,CAAC,OAAO;AAAA,MAC7B,IAAI,EAAE;AAAA,MACN,MAAM,EAAE;AAAA,MACR,UAAU,EAAE;AAAA,IACd,EAAE;AAAA,EACJ;AAAA,EAEA,cAAc,MAML;AACP,QAAI,UAAU;AAEd,QAAI,KAAK,UAAU,UAAa,KAAK,UAAU,KAAK,MAAM,OAAO;AAC/D,WAAK,MAAM,QAAQ,KAAK;AACxB,gBAAU;AAAA,IACZ;AAEA,QAAI,KAAK,YAAY,UAAa,KAAK,YAAY,KAAK,MAAM,SAAS;AACrE,WAAK,MAAM,UAAU,KAAK;AAC1B,gBAAU;AAAA,IACZ;AAEA,QAAI,KAAK,kBAAkB,QAAW;AACpC,WAAK,MAAM,gBAAgB,KAAK;AAChC,gBAAU;AAAA,IACZ;AAEA,QAAI,KAAK,aAAa,UAAa,KAAK,aAAa,KAAK,MAAM,UAAU;AACxE,WAAK,MAAM,WAAW,KAAK;AAC3B,gBAAU;AAAA,IACZ;AAEA,QAAI,KAAK,oCAAoC,QAAW;AACtD,WAAK,MAAM,kCAAkC,KAAK;AAClD,gBAAU;AAAA,IACZ;AAEA,QAAI,WAAW,KAAK,oBAAoB;AACtC,WAAK,mBAAmB,eAAe;AACvC,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,oBAAyC;AAC7C,UAAM,SAAS,MAAM,KAAK,gBAAgB,KAAK;AAC/C,QAAI;AACF,UACE,KAAK,sBACL,KAAK,mBAAmB,aACxB,CAAC,KAAK,mBAAmB,QACzB;AACA,eAAO,KAAK;AAAA,MACd;AAEA,YAAM,OAAO,IAAI,WAAW,EAAE,GAAG,KAAK,MAAM,CAAC;AAC7C,YAAM,KAAK,QAAQ;AACnB,WAAK,qBAAqB;AAC1B,aAAO;AAAA,IACT,UAAE;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,WAAW,MAA6B;AACtC,WAAO,IAAI,cAAc,MAAM,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC;AAAA,EACxD;AAAA,EAEA,SAA2B;AACzB,UAAMA,UAAS,IAAI,iBAAiB,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC;AAC3D,SAAK,SAAS,IAAIA,OAAM;AACxB,WAAOA;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,eAAWA,WAAU,KAAK,UAAU;AAClC,MAAAA,QAAO,MAAM;AAAA,IACf;AACA,SAAK,SAAS,MAAM;AAEpB,QAAI,KAAK,oBAAoB;AAC3B,YAAM,KAAK,mBAAmB,MAAM;AACpC,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AACF;AAEO,MAAM,sBAAsB,IAAI,cAAc;AAAA,EACnD;AAAA,EACA;AAAA,EACA,UAAU,IAAI;AAAA,EAEd,QAAQ;AAAA,EAER,YAAYC,MAAU,MAAc,MAA0B;AAC5D,UAAM,MAAMA,IAAG;AACf,SAAK,OAAOA;AACZ,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAgB,MAAqB;AAlzBvC;AAmzBI,UAAM,gBAAgB,KAAK,MAAM,gBAC7B,eAAe,KAAK,MAAM,aAAa,IACvC;AAEJ,UAAM,YAAY,UAAU;AAC5B,UAAM,UAAU,IAAI,gBAAgB,KAAK,MAAM,YAAY,CAAC;AAE5D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,cAAc,KAAK,KAAK,GAAG;AAAA,QACtD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,CAAC,oBAAoB,GAAG,KAAK,MAAM;AAAA,UACnC,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,MAAM,KAAK;AAAA,UACX,UAAU,KAAK,MAAM;AAAA,UACrB,gBAAgB;AAAA,QAClB,CAAC;AAAA,QACD,QAAQ,KAAK;AAAA,MACf,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI,eAAe;AAAA,UACvB,SAAS,yBAAyB,SAAS;AAAA,UAC3C,SAAS,EAAE,YAAY,SAAS,OAAO;AAAA,QACzC,CAAC;AAAA,MACH;AAEA,YAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,UAAI,CAAC,YAAY,WAAW,QAAQ,GAAG;AACrC,cAAM,UAAU,MAAM,SAAS,KAAK;AACpC,cAAM,IAAI,SAAS,uCAAuC,OAAO,EAAE;AAAA,MACrE;AAEA,YAAM,UAAS,cAAS,SAAT,mBAAe;AAC9B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,SAAS,kBAAkB;AAAA,MACvC;AAEA,UAAI;AAEJ,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AAEV,mBAAW,SAAS,QAAQ,MAAM,MAAM,MAAM,GAAG;AAC/C,cAAI,WAAW;AACb,iBAAK,MAAM,IAAI,EAAE,WAAW,WAAW,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC;AAAA,UACpF;AACA,sBAAY;AAAA,QACd;AAAA,MACF;AAGA,iBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,YAAI,WAAW;AACb,eAAK,MAAM,IAAI,EAAE,WAAW,WAAW,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC;AAAA,QACpF;AACA,oBAAY;AAAA,MACd;AAEA,UAAI,WAAW;AACb,aAAK,MAAM,IAAI,EAAE,WAAW,WAAW,WAAW,OAAO,WAAW,OAAO,KAAK,CAAC;AAAA,MACnF;AAAA,IACF,SAAS,GAAG;AACV,UAAI,aAAa,UAAU;AACzB,cAAM;AAAA,MACR;AACA,UAAI,aAAa,SAAS,EAAE,SAAS,cAAc;AACjD;AAAA,MACF;AACA,YAAM,IAAI,mBAAmB,EAAE,SAAS,qBAAqB,CAAC,GAAG,CAAC;AAAA,IACpE;AAAA,EACF;AACF;AAEO,MAAM,yBAAyB,IAAI,iBAAiB;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU,IAAI;AAAA,EACd,cAAwB,CAAC;AAAA,EACzB,wBAAuC,CAAC;AAAA,EACxC,cAAc;AAAA,EAEd,QAAQ;AAAA,EAER,YAAYA,MAAU,MAA0B;AAC9C,UAAMA,IAAG;AACT,SAAK,OAAOA;AACZ,SAAK,QAAQ;AACb,SAAK,aAAa,UAAU;AAC5B,SAAK,uBAAuB,KAAK,MAAM,cAAc,OAAO;AAAA,EAC9D;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,MAAoB;AAE5B,QAAI,KAAK,UAAU,KAAK,gBAAgB,OAAO,SAAS;AACtD;AAAA,IACF;AACA,SAAK,YAAY,KAAK,IAAI;AAAA,EAC5B;AAAA,EAEA,oBAAoB,YAAiC;AACnD,SAAK,sBAAsB,KAAK,GAAG,UAAU;AAAA,EAC/C;AAAA,EAEA,WAAiB;AACf,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAgB,MAAqB;AACnC,UAAM,YAAY,KAAK;AACvB,UAAM,YAAY,KAAK;AACvB,UAAM,UAAU,IAAI,gBAAgB,KAAK,MAAM,YAAY,CAAC;AAE5D,QAAI;AACJ,QAAI;AACF,mBAAa,MAAM,KAAK,KAAK,kBAAkB;AAAA,IACjD,SAAS,GAAG;AACV,YAAM,IAAI,mBAAmB,EAAE,SAAS,kCAAkC,CAAC;AAAA,IAC7E;AAEA,QAAI;AACJ,UAAM,gBAAgB,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3D,qBAAe;AACf,iBAAW,eAAe,MAAM,EAAE,SAAS,OAAO,CAAC;AAAA,IACrD,CAAC;AAGD,UAAM,eAAe,MAAM;AACzB,UAAI,cAAc;AAChB,qBAAa,IAAI,MAAM,gBAAgB,CAAC;AAAA,MAC1C;AAAA,IACF;AACA,SAAK,gBAAgB,OAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAElF,UAAM,YAAY,YAAY;AAC5B,uBAAiB,QAAQ,KAAK,OAAO;AACnC,YAAI,KAAK,gBAAgB,OAAO,QAAS;AACzC,YAAI,SAAS,iBAAiB,gBAAgB;AAC5C,eAAK,qBAAqB,MAAM;AAChC;AAAA,QACF;AACA,aAAK,qBAAqB,SAAS,IAAI;AAAA,MACzC;AACA,WAAK,qBAAqB,SAAS;AAAA,IACrC;AAEA,UAAM,qBAAqB,YAAY;AACrC,YAAM,eACJ,KAAK,MAAM,yBAAyB,SAAS,qBAAqB,KAAK,MAAM;AAE/E,UAAI,aAAuB,CAAC;AAE5B,uBAAiB,QAAQ,KAAK,sBAAsB;AAClD,YAAI,KAAK,gBAAgB,OAAO,QAAS;AAEzC,YAAI,OAAO,KAAK;AAChB,cAAM,iBAAiB,CAAC,YAAY,QAAQ;AAC5C,cAAM,eAAe,CAAC,cAAc,IAAI;AAExC,YACG,KAAK,MAAM,qBACV,eAAe,KAAK,CAAC,UAAU,KAAK,WAAW,KAAK,CAAC,KACvD,WAAW,SAAS,GACpB;AACA,qBAAW,KAAK,IAAI;AAEpB,cAAI,aAAa,KAAK,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC,GAAG;AAClD,mBAAO,WAAW,KAAK,GAAG;AAC1B,yBAAa,CAAC;AAAA,UAChB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AAEA,cAAM,gBAAgB,GAAG,IAAI;AAC7B,mBAAW,YAAY;AAAA,UACrB,WAAW,KAAK;AAAA,UAChB,MAAM;AAAA,UACN,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,aAAK,QAAQ,KAAK,qDAAqD;AAAA,MACzE;AAGA,iBAAW,YAAY,EAAE,WAAW,KAAK,YAAY,MAAM,IAAI,OAAO,KAAK,CAAC;AAC5E,iBAAW,aAAa,KAAK,UAAU;AAAA,IACzC;AAEA,UAAM,mBAAmB,YAAY;AACnC,UAAI;AACJ,UAAI,0BAAyC,CAAC;AAE9C,YAAM,gBAAgB,CAAC,UAAmB;AACxC,YAAI,WAAW;AAEb,eAAK,MAAM,IAAI;AAAA,YACb;AAAA,YACA;AAAA,YACA,OAAO;AAAA,YACP;AAAA,YACA,kBACE,wBAAwB,SAAS,IAAI,0BAA0B;AAAA,UACnE,CAAC;AACD,sBAAY;AACZ,oCAA0B,CAAC;AAAA,QAC7B;AAAA,MACF;AAEA,aAAO,CAAC,KAAK,gBAAgB,OAAO,SAAS;AAE3C,eAAO,KAAK,sBAAsB,SAAS,GAAG;AAC5C,kCAAwB,KAAK,KAAK,sBAAsB,MAAM,CAAE;AAAA,QAClE;AAGA,eAAO,KAAK,YAAY,SAAS,GAAG;AAClC,gBAAM,YAAY,KAAK,YAAY,MAAM;AACzC,qBAAW,SAAS,QAAQ,MAAM,UAAU,MAAM,GAAG;AACnD,0BAAc,KAAK;AACnB,wBAAY;AAAA,UACd;AAAA,QACF;AAGA,YAAI,KAAK,eAAe,KAAK,YAAY,WAAW,GAAG;AACrD;AAAA,QACF;AAGA,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,MACxD;AAGA,aAAO,KAAK,sBAAsB,SAAS,GAAG;AAC5C,gCAAwB,KAAK,KAAK,sBAAsB,MAAM,CAAE;AAAA,MAClE;AAGA,iBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,sBAAc,KAAK;AACnB,oBAAY;AAAA,MACd;AAEA,oBAAc,IAAI;AAAA,IACpB;AAEA,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,aAAa,CAAC;AAAA,IAC1F,SAAS,GAAG;AAEV,UAAI,KAAK,gBAAgB,OAAO,SAAS;AACvC;AAAA,MACF;AAEA,UAAI,aAAa,iBAAiB;AAChC,cAAM;AAAA,MACR;AACA,UAAI,aAAa,gBAAgB;AAC/B,cAAM;AAAA,MACR;AACA,YAAM,IAAI,eAAe,EAAE,SAAS,uBAAuB,CAAC;AAAA,IAC9D,UAAE;AAEA,WAAK,gBAAgB,OAAO,oBAAoB,SAAS,YAAY;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,QAAc;AAEZ,SAAK,YAAY,SAAS;AAC1B,SAAK,sBAAsB,SAAS;AACpC,SAAK,cAAc;AACnB,SAAK,qBAAqB,MAAM;AAChC,UAAM,MAAM;AAAA,EACd;AACF;","names":["stream","tts"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents-plugin-elevenlabs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.41",
|
|
4
4
|
"description": "ElevenLabs plugin for LiveKit Node Agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@types/ws": "^8.5.10",
|
|
31
31
|
"tsup": "^8.3.5",
|
|
32
32
|
"typescript": "^5.0.0",
|
|
33
|
-
"@livekit/agents
|
|
34
|
-
"@livekit/agents": "1.0.
|
|
35
|
-
"@livekit/agents-plugins-test": "1.0.
|
|
33
|
+
"@livekit/agents": "1.0.41",
|
|
34
|
+
"@livekit/agents-plugin-openai": "1.0.41",
|
|
35
|
+
"@livekit/agents-plugins-test": "1.0.41"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@livekit/mutex": "^1.1.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@livekit/rtc-node": "^0.13.24",
|
|
43
|
-
"@livekit/agents": "1.0.
|
|
43
|
+
"@livekit/agents": "1.0.41"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup --onSuccess \"pnpm build:types\"",
|
package/src/tts.ts
CHANGED
|
@@ -8,20 +8,19 @@ import {
|
|
|
8
8
|
APITimeoutError,
|
|
9
9
|
AudioByteStream,
|
|
10
10
|
Future,
|
|
11
|
+
type TimedString,
|
|
12
|
+
createTimedString,
|
|
11
13
|
log,
|
|
12
14
|
shortuuid,
|
|
13
15
|
stream,
|
|
14
16
|
tokenize,
|
|
15
17
|
tts,
|
|
16
|
-
type voice,
|
|
17
18
|
} from '@livekit/agents';
|
|
18
19
|
import { Mutex } from '@livekit/mutex';
|
|
19
20
|
import type { AudioFrame } from '@livekit/rtc-node';
|
|
20
21
|
import { WebSocket } from 'ws';
|
|
21
22
|
import type { TTSEncoding, TTSModels } from './models.js';
|
|
22
23
|
|
|
23
|
-
type TimedString = voice.TimedString;
|
|
24
|
-
|
|
25
24
|
const DEFAULT_VOICE_ID = 'bIHbv24MWmeRgasZH58o';
|
|
26
25
|
const API_BASE_URL_V1 = 'https://api.elevenlabs.io/v1';
|
|
27
26
|
const AUTHORIZATION_HEADER = 'xi-api-key';
|
|
@@ -118,6 +117,8 @@ interface StreamData {
|
|
|
118
117
|
textBuffer: string;
|
|
119
118
|
startTimesMs: number[];
|
|
120
119
|
durationsMs: number[];
|
|
120
|
+
/** First word offset for timestamp normalization (removes leading silence) */
|
|
121
|
+
firstWordOffsetMs: number | null;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
type ConnectionMessage = SynthesizeContent | CloseContext;
|
|
@@ -172,12 +173,17 @@ function stripUndefined<T extends object>(obj: T): Partial<T> {
|
|
|
172
173
|
/**
|
|
173
174
|
* Convert alignment data to timed words.
|
|
174
175
|
* Returns the timed words and remaining text buffer.
|
|
176
|
+
*
|
|
177
|
+
* @param firstWordOffsetMs - Optional offset to normalize timestamps (subtract from all).
|
|
178
|
+
* ElevenLabs returns absolute timestamps from the start of TTS audio, which may include
|
|
179
|
+
* leading silence. By normalizing to 0, we ensure proper sync with the synchronizer.
|
|
175
180
|
*/
|
|
176
181
|
function toTimedWords(
|
|
177
182
|
text: string,
|
|
178
183
|
startTimesMs: number[],
|
|
179
184
|
durationsMs: number[],
|
|
180
185
|
flush: boolean = false,
|
|
186
|
+
firstWordOffsetMs: number = 0,
|
|
181
187
|
): [TimedString[], string] {
|
|
182
188
|
if (!text || startTimesMs.length === 0 || durationsMs.length === 0) {
|
|
183
189
|
return [[], text || ''];
|
|
@@ -202,24 +208,29 @@ function toTimedWords(
|
|
|
202
208
|
const start = startIndices[i]!;
|
|
203
209
|
const nextStart = startIndices[i + 1]!;
|
|
204
210
|
end = nextStart;
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
// Normalize timestamps by subtracting the first word offset
|
|
212
|
+
const startT = Math.max(0, (timestamps[start] ?? 0) - firstWordOffsetMs) / 1000;
|
|
213
|
+
const endT = Math.max(0, (timestamps[nextStart] ?? 0) - firstWordOffsetMs) / 1000;
|
|
214
|
+
timedWords.push(
|
|
215
|
+
createTimedString({
|
|
216
|
+
text: text.slice(start, nextStart),
|
|
217
|
+
startTime: startT,
|
|
218
|
+
endTime: endT,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
if (flush && words.length > 0) {
|
|
215
224
|
const lastWordStart = startIndices[startIndices.length - 1]!;
|
|
216
|
-
const startT = (timestamps[lastWordStart] ?? 0) / 1000;
|
|
217
|
-
const endT = (timestamps[timestamps.length - 1] ?? 0) / 1000;
|
|
218
|
-
timedWords.push(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
const startT = Math.max(0, (timestamps[lastWordStart] ?? 0) - firstWordOffsetMs) / 1000;
|
|
226
|
+
const endT = Math.max(0, (timestamps[timestamps.length - 1] ?? 0) - firstWordOffsetMs) / 1000;
|
|
227
|
+
timedWords.push(
|
|
228
|
+
createTimedString({
|
|
229
|
+
text: text.slice(lastWordStart),
|
|
230
|
+
startTime: startT,
|
|
231
|
+
endTime: endT,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
223
234
|
end = text.length;
|
|
224
235
|
} else if (words.length > 0) {
|
|
225
236
|
end = startIndices[startIndices.length - 1]!;
|
|
@@ -296,6 +307,7 @@ class Connection {
|
|
|
296
307
|
textBuffer: '',
|
|
297
308
|
startTimesMs: [],
|
|
298
309
|
durationsMs: [],
|
|
310
|
+
firstWordOffsetMs: null,
|
|
299
311
|
});
|
|
300
312
|
}
|
|
301
313
|
|
|
@@ -500,6 +512,12 @@ class Connection {
|
|
|
500
512
|
const start = starts[i]!;
|
|
501
513
|
const dur = durs[i]!;
|
|
502
514
|
|
|
515
|
+
// Capture the first word's start time for normalization
|
|
516
|
+
// This removes leading silence from timestamps
|
|
517
|
+
if (ctx.firstWordOffsetMs === null && start > 0) {
|
|
518
|
+
ctx.firstWordOffsetMs = start;
|
|
519
|
+
}
|
|
520
|
+
|
|
503
521
|
if (char.length > 1) {
|
|
504
522
|
for (let j = 0; j < char.length - 1; j++) {
|
|
505
523
|
ctx.startTimesMs.push(start);
|
|
@@ -514,6 +532,8 @@ class Connection {
|
|
|
514
532
|
ctx.textBuffer,
|
|
515
533
|
ctx.startTimesMs,
|
|
516
534
|
ctx.durationsMs,
|
|
535
|
+
false,
|
|
536
|
+
ctx.firstWordOffsetMs ?? 0,
|
|
517
537
|
);
|
|
518
538
|
|
|
519
539
|
if (timedWords.length > 0) {
|
|
@@ -539,6 +559,7 @@ class Connection {
|
|
|
539
559
|
ctx.startTimesMs,
|
|
540
560
|
ctx.durationsMs,
|
|
541
561
|
true,
|
|
562
|
+
ctx.firstWordOffsetMs ?? 0,
|
|
542
563
|
);
|
|
543
564
|
if (timedWords.length > 0) {
|
|
544
565
|
stream.pushTimedTranscript(timedWords);
|
|
@@ -622,9 +643,11 @@ export class TTS extends tts.TTS {
|
|
|
622
643
|
const autoMode = opts.autoMode ?? true;
|
|
623
644
|
const encoding = opts.encoding ?? DEFAULT_ENCODING;
|
|
624
645
|
const sampleRate = sampleRateFromFormat(encoding);
|
|
646
|
+
const syncAlignment = opts.syncAlignment ?? true;
|
|
625
647
|
|
|
626
648
|
super(sampleRate, 1, {
|
|
627
649
|
streaming: true,
|
|
650
|
+
alignedTranscript: syncAlignment,
|
|
628
651
|
});
|
|
629
652
|
|
|
630
653
|
const apiKey = opts.apiKey ?? process.env.ELEVEN_API_KEY;
|
|
@@ -997,15 +1020,35 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
997
1020
|
|
|
998
1021
|
const audioProcessTask = async () => {
|
|
999
1022
|
let lastFrame: AudioFrame | undefined;
|
|
1023
|
+
let pendingTimedTranscripts: TimedString[] = [];
|
|
1024
|
+
|
|
1025
|
+
const sendLastFrame = (final: boolean) => {
|
|
1026
|
+
if (lastFrame) {
|
|
1027
|
+
// Include timedTranscripts with the audio frame
|
|
1028
|
+
this.queue.put({
|
|
1029
|
+
requestId,
|
|
1030
|
+
segmentId,
|
|
1031
|
+
frame: lastFrame,
|
|
1032
|
+
final,
|
|
1033
|
+
timedTranscripts:
|
|
1034
|
+
pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : undefined,
|
|
1035
|
+
});
|
|
1036
|
+
lastFrame = undefined;
|
|
1037
|
+
pendingTimedTranscripts = [];
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1000
1040
|
|
|
1001
1041
|
while (!this.abortController.signal.aborted) {
|
|
1042
|
+
// Drain timed transcript queue
|
|
1043
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
1044
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1002
1047
|
// Process audio queue
|
|
1003
1048
|
while (this.#audioQueue.length > 0) {
|
|
1004
1049
|
const audioData = this.#audioQueue.shift()!;
|
|
1005
1050
|
for (const frame of bstream.write(audioData.buffer)) {
|
|
1006
|
-
|
|
1007
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
1008
|
-
}
|
|
1051
|
+
sendLastFrame(false);
|
|
1009
1052
|
lastFrame = frame;
|
|
1010
1053
|
}
|
|
1011
1054
|
}
|
|
@@ -1019,17 +1062,18 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
1019
1062
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1020
1063
|
}
|
|
1021
1064
|
|
|
1065
|
+
// Drain any remaining timed transcripts
|
|
1066
|
+
while (this.#timedTranscriptQueue.length > 0) {
|
|
1067
|
+
pendingTimedTranscripts.push(this.#timedTranscriptQueue.shift()!);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1022
1070
|
// Flush remaining
|
|
1023
1071
|
for (const frame of bstream.flush()) {
|
|
1024
|
-
|
|
1025
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: false });
|
|
1026
|
-
}
|
|
1072
|
+
sendLastFrame(false);
|
|
1027
1073
|
lastFrame = frame;
|
|
1028
1074
|
}
|
|
1029
1075
|
|
|
1030
|
-
|
|
1031
|
-
this.queue.put({ requestId, segmentId, frame: lastFrame, final: true });
|
|
1032
|
-
}
|
|
1076
|
+
sendLastFrame(true);
|
|
1033
1077
|
};
|
|
1034
1078
|
|
|
1035
1079
|
try {
|