refactor: improve TTS abort handling and add debug logging
This commit is contained in:
@@ -26,16 +26,25 @@ export class TTSManager {
|
|||||||
public async generateAndWaitForPlayback(
|
public async generateAndWaitForPlayback(
|
||||||
text: string,
|
text: string,
|
||||||
emitMessage: (msg: SessionOutboundMessage) => void,
|
emitMessage: (msg: SessionOutboundMessage) => void,
|
||||||
abortSignal?: AbortSignal
|
abortSignal: AbortSignal
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Check if already aborted
|
if (abortSignal.aborted) {
|
||||||
if (abortSignal?.aborted) {
|
console.log(
|
||||||
throw new Error("TTS playback aborted");
|
`[TTS-Manager ${this.sessionId}] Aborted before generating audio`
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate TTS audio
|
// Generate TTS audio
|
||||||
const { audio, format } = await synthesizeSpeech(text);
|
const { audio, format } = await synthesizeSpeech(text);
|
||||||
|
|
||||||
|
if (abortSignal.aborted) {
|
||||||
|
console.log(
|
||||||
|
`[TTS-Manager ${this.sessionId}] Aborted after generating audio`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create unique ID for this audio segment
|
// Create unique ID for this audio segment
|
||||||
const audioId = uuidv4();
|
const audioId = uuidv4();
|
||||||
|
|
||||||
@@ -48,17 +57,18 @@ export class TTSManager {
|
|||||||
this.pendingPlaybacks.set(audioId, { resolve, reject });
|
this.pendingPlaybacks.set(audioId, { resolve, reject });
|
||||||
|
|
||||||
// Handle abort signal
|
// Handle abort signal
|
||||||
if (abortSignal) {
|
onAbort = () => {
|
||||||
onAbort = () => {
|
console.log(
|
||||||
// Clean up pending playback
|
`[TTS-Manager ${this.sessionId}] Aborted while waiting for playback`
|
||||||
this.pendingPlaybacks.delete(audioId);
|
);
|
||||||
// Reject with abort error
|
// Clean up pending playback
|
||||||
reject(new Error("TTS playback aborted"));
|
this.pendingPlaybacks.delete(audioId);
|
||||||
};
|
// Reject with abort error
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
// Listen for abort (once: true for auto-cleanup if abort fires)
|
// Listen for abort (once: true for auto-cleanup if abort fires)
|
||||||
abortSignal.addEventListener("abort", onAbort, { once: true });
|
abortSignal.addEventListener("abort", onAbort, { once: true });
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up abort listener when promise settles (in case abort never fires)
|
// Clean up abort listener when promise settles (in case abort never fires)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export class Session {
|
|||||||
`[Session ${this.clientId}] Waiting for aborted stream to finish cleanup`
|
`[Session ${this.clientId}] Waiting for aborted stream to finish cleanup`
|
||||||
);
|
);
|
||||||
await this.currentStreamPromise;
|
await this.currentStreamPromise;
|
||||||
|
console.log(`[Session ${this.clientId}] Aborted stream finished cleanup`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit user message activity log
|
// Emit user message activity log
|
||||||
|
|||||||
@@ -45,17 +45,22 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
this.conversationIdToWs.set(session.getConversationId(), ws);
|
this.conversationIdToWs.set(session.getConversationId(), ws);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[WS] Client connected: ${clientId} with conversation ${session.getConversationId()} (total: ${this.sessions.size})`
|
`[WS] Client connected: ${clientId} with conversation ${session.getConversationId()} (total: ${
|
||||||
|
this.sessions.size
|
||||||
|
})`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send welcome message
|
// Send welcome message
|
||||||
this.sendToClient(ws, wrapSessionMessage({
|
this.sendToClient(
|
||||||
type: "status",
|
ws,
|
||||||
payload: {
|
wrapSessionMessage({
|
||||||
status: "connected",
|
type: "status",
|
||||||
message: "WebSocket connection established",
|
payload: {
|
||||||
},
|
status: "connected",
|
||||||
}));
|
message: "WebSocket connection established",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Set up message handler
|
// Set up message handler
|
||||||
ws.on("message", (data) => {
|
ws.on("message", (data) => {
|
||||||
@@ -68,7 +73,9 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[WS] Client disconnected: ${clientId} (total: ${this.sessions.size - 1})`
|
`[WS] Client disconnected: ${clientId} (total: ${
|
||||||
|
this.sessions.size - 1
|
||||||
|
})`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up session
|
// Clean up session
|
||||||
@@ -110,7 +117,7 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
// Validate with Zod
|
// Validate with Zod
|
||||||
const message = WSInboundMessageSchema.parse(parsed);
|
const message = WSInboundMessageSchema.parse(parsed);
|
||||||
|
|
||||||
console.log(`[WS] Received message type: ${message.type}`);
|
console.log(`[WS] Received message type: ${message.type}`, message);
|
||||||
|
|
||||||
// Handle WebSocket-level messages
|
// Handle WebSocket-level messages
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
@@ -138,13 +145,16 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("[WS] Failed to parse/handle message:", error);
|
console.error("[WS] Failed to parse/handle message:", error);
|
||||||
// Send error to client
|
// Send error to client
|
||||||
this.sendToClient(ws, wrapSessionMessage({
|
this.sendToClient(
|
||||||
type: "status",
|
ws,
|
||||||
payload: {
|
wrapSessionMessage({
|
||||||
status: "error",
|
type: "status",
|
||||||
message: `Invalid message: ${error.message}`,
|
payload: {
|
||||||
},
|
status: "error",
|
||||||
}));
|
message: `Invalid message: ${error.message}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user