From 97f2ff2cac7bfaeef8ee77dd860ba513a5112abb Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 20 Oct 2025 16:19:34 +0200 Subject: [PATCH] refactor: enhance TTS playback management with abort signal support - Update TTSManager to accept an optional abort signal, allowing for graceful cancellation of TTS playback. - Implement abort handling in the generateAndWaitForPlayback method to clean up pending playbacks and reject promises when playback is aborted. - Modify Session class to pass the abort signal during TTS generation, improving control over playback interruptions. These changes improve the robustness of the voice assistant's TTS functionality and enhance user experience by allowing for better management of audio playback. --- .claude/CLAUDE.md | Bin 7594 -> 7641 bytes .../src/server/agent/tts-manager.ts | 31 +++++++++++++++++- .../voice-assistant/src/server/session.ts | 3 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 2d69fc42a16e5a51c62557381817f11a65618d82..24639969316ab96ef36ed6861532f3824f3f62c9 100644 GIT binary patch delta 55 zcmZ2webaiwDp`G9g}l_V)FOqV(maKf)G~$Q)FL2PN1+tRP$#z7Xxts diff --git a/packages/voice-assistant/src/server/agent/tts-manager.ts b/packages/voice-assistant/src/server/agent/tts-manager.ts index 6223a07af..431991988 100644 --- a/packages/voice-assistant/src/server/agent/tts-manager.ts +++ b/packages/voice-assistant/src/server/agent/tts-manager.ts @@ -25,20 +25,49 @@ export class TTSManager { */ public async generateAndWaitForPlayback( text: string, - emitMessage: (msg: SessionOutboundMessage) => void + emitMessage: (msg: SessionOutboundMessage) => void, + abortSignal?: AbortSignal ): Promise { + // Check if already aborted + if (abortSignal?.aborted) { + throw new Error("TTS playback aborted"); + } + // Generate TTS audio const { audio, format } = await synthesizeSpeech(text); // Create unique ID for this audio segment const audioId = uuidv4(); + // Store abort handler reference outside Promise constructor + let onAbort: (() => void) | undefined; + // Create promise that will be resolved when client confirms playback const playbackPromise = new Promise((resolve, reject) => { // Store handlers (no timeout - will resolve when client confirms or connection closes) this.pendingPlaybacks.set(audioId, { resolve, reject }); + + // Handle abort signal + if (abortSignal) { + onAbort = () => { + // Clean up pending playback + this.pendingPlaybacks.delete(audioId); + // Reject with abort error + reject(new Error("TTS playback aborted")); + }; + + // Listen for abort (once: true for auto-cleanup if abort fires) + abortSignal.addEventListener("abort", onAbort, { once: true }); + } }); + // Clean up abort listener when promise settles (in case abort never fires) + if (onAbort) { + playbackPromise.finally(() => { + abortSignal!.removeEventListener("abort", onAbort!); + }); + } + // Emit audio output message emitMessage({ type: "audio_output", diff --git a/packages/voice-assistant/src/server/session.ts b/packages/voice-assistant/src/server/session.ts index 3cdeb8390..4db3ec4b7 100644 --- a/packages/voice-assistant/src/server/session.ts +++ b/packages/voice-assistant/src/server/session.ts @@ -332,7 +332,8 @@ export class Session { if (enableTTS) { pendingTTS = this.ttsManager.generateAndWaitForPlayback( textBuffer, - (msg) => this.emit(msg) + (msg) => this.emit(msg), + this.abortController.signal ); }