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.
This commit is contained in:
Mohamed Boudra
2025-10-20 16:19:34 +02:00
parent c5b24d3dfc
commit 97f2ff2cac
3 changed files with 32 additions and 2 deletions

Binary file not shown.

View File

@@ -25,20 +25,49 @@ export class TTSManager {
*/
public async generateAndWaitForPlayback(
text: string,
emitMessage: (msg: SessionOutboundMessage) => void
emitMessage: (msg: SessionOutboundMessage) => void,
abortSignal?: AbortSignal
): Promise<void> {
// 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<void>((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",

View File

@@ -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
);
}