mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix(voice-call): settle cleared tts queue
This commit is contained in:
@@ -106,6 +106,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/OpenRouter: add an OpenRouter TTS provider using the OpenAI-compatible `/audio/speech` endpoint and `OPENROUTER_API_KEY`. Fixes #71268.
|
||||
- macOS Talk Mode: retry failed local ElevenLabs stream playback through gateway `talk.speak` before falling back to the system voice, so configured ElevenLabs voices still play when streaming playback fails. Fixes #65662.
|
||||
- Plugins/Voice Call: reap stale pre-answer calls by default, honor configured TTS timeouts for Twilio media-stream playback, and fail empty telephony audio instead of completing as silence. Fixes #42071; supersedes #60957. Thanks @Ryce and @sliekens.
|
||||
- Plugins/Voice Call: resolve queued-but-not-yet-playing Twilio TTS entries when barge-in or stream teardown clears the playback queue, so callers awaiting `queueTts()` do not hang. Thanks @kevinWangSheng.
|
||||
- Plugins/Voice Call: terminate expired restored call sessions with the provider and restart restored max-duration timers with only the remaining duration, preventing stale outbound retry loops after Gateway restarts. Fixes #48739. Thanks @mira-solari.
|
||||
- Plugins/Voice Call: start provider STT after Telnyx outbound conversation greetings and pass configured Telnyx voice IDs through to the speak action. Fixes #56091. Thanks @Roshan.
|
||||
- Skills: honor legacy `metadata.clawdbot` requirements and installer hints when `metadata.openclaw` is absent, so older skills no longer appear ready when required binaries are missing. Fixes #71323. Thanks @chen-zhang-cs-code.
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("MediaStreamHandler TTS queue", () => {
|
||||
started.push("active");
|
||||
await waitForAbort(signal);
|
||||
});
|
||||
void handler.queueTts("stream-1", async () => {
|
||||
const queued = handler.queueTts("stream-1", async () => {
|
||||
queuedRan = true;
|
||||
});
|
||||
|
||||
@@ -112,10 +112,37 @@ describe("MediaStreamHandler TTS queue", () => {
|
||||
|
||||
handler.clearTtsQueue("stream-1");
|
||||
await active;
|
||||
await withTimeout(queued);
|
||||
await flush();
|
||||
|
||||
expect(queuedRan).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves pending queued playback during stream teardown", async () => {
|
||||
const handler = new MediaStreamHandler({
|
||||
transcriptionProvider: createStubSttProvider(),
|
||||
providerConfig: {},
|
||||
});
|
||||
|
||||
let queuedRan = false;
|
||||
const active = handler.queueTts("stream-1", async (signal) => {
|
||||
await waitForAbort(signal);
|
||||
});
|
||||
const queued = handler.queueTts("stream-1", async () => {
|
||||
queuedRan = true;
|
||||
});
|
||||
|
||||
await flush();
|
||||
(
|
||||
handler as unknown as {
|
||||
clearTtsState(streamSid: string): void;
|
||||
}
|
||||
).clearTtsState("stream-1");
|
||||
|
||||
await withTimeout(active);
|
||||
await withTimeout(queued);
|
||||
expect(queuedRan).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MediaStreamHandler security hardening", () => {
|
||||
|
||||
@@ -561,7 +561,7 @@ export class MediaStreamHandler {
|
||||
*/
|
||||
clearTtsQueue(streamSid: string, _reason = "unspecified"): void {
|
||||
const queue = this.getTtsQueue(streamSid);
|
||||
queue.length = 0;
|
||||
this.resolveQueuedTtsEntries(queue);
|
||||
this.ttsActiveControllers.get(streamSid)?.abort();
|
||||
this.clearAudio(streamSid);
|
||||
}
|
||||
@@ -634,13 +634,21 @@ export class MediaStreamHandler {
|
||||
private clearTtsState(streamSid: string): void {
|
||||
const queue = this.ttsQueues.get(streamSid);
|
||||
if (queue) {
|
||||
queue.length = 0;
|
||||
this.resolveQueuedTtsEntries(queue);
|
||||
}
|
||||
this.ttsActiveControllers.get(streamSid)?.abort();
|
||||
this.ttsActiveControllers.delete(streamSid);
|
||||
this.ttsPlaying.delete(streamSid);
|
||||
this.ttsQueues.delete(streamSid);
|
||||
}
|
||||
|
||||
private resolveQueuedTtsEntries(queue: TtsQueueEntry[]): void {
|
||||
const pending = queue.splice(0);
|
||||
for (const entry of pending) {
|
||||
entry.controller.abort();
|
||||
entry.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user