mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:00:45 +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.
|
- 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.
|
- 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: 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: 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.
|
- 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.
|
- 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");
|
started.push("active");
|
||||||
await waitForAbort(signal);
|
await waitForAbort(signal);
|
||||||
});
|
});
|
||||||
void handler.queueTts("stream-1", async () => {
|
const queued = handler.queueTts("stream-1", async () => {
|
||||||
queuedRan = true;
|
queuedRan = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,10 +112,37 @@ describe("MediaStreamHandler TTS queue", () => {
|
|||||||
|
|
||||||
handler.clearTtsQueue("stream-1");
|
handler.clearTtsQueue("stream-1");
|
||||||
await active;
|
await active;
|
||||||
|
await withTimeout(queued);
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
expect(queuedRan).toBe(false);
|
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", () => {
|
describe("MediaStreamHandler security hardening", () => {
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ export class MediaStreamHandler {
|
|||||||
*/
|
*/
|
||||||
clearTtsQueue(streamSid: string, _reason = "unspecified"): void {
|
clearTtsQueue(streamSid: string, _reason = "unspecified"): void {
|
||||||
const queue = this.getTtsQueue(streamSid);
|
const queue = this.getTtsQueue(streamSid);
|
||||||
queue.length = 0;
|
this.resolveQueuedTtsEntries(queue);
|
||||||
this.ttsActiveControllers.get(streamSid)?.abort();
|
this.ttsActiveControllers.get(streamSid)?.abort();
|
||||||
this.clearAudio(streamSid);
|
this.clearAudio(streamSid);
|
||||||
}
|
}
|
||||||
@@ -634,13 +634,21 @@ export class MediaStreamHandler {
|
|||||||
private clearTtsState(streamSid: string): void {
|
private clearTtsState(streamSid: string): void {
|
||||||
const queue = this.ttsQueues.get(streamSid);
|
const queue = this.ttsQueues.get(streamSid);
|
||||||
if (queue) {
|
if (queue) {
|
||||||
queue.length = 0;
|
this.resolveQueuedTtsEntries(queue);
|
||||||
}
|
}
|
||||||
this.ttsActiveControllers.get(streamSid)?.abort();
|
this.ttsActiveControllers.get(streamSid)?.abort();
|
||||||
this.ttsActiveControllers.delete(streamSid);
|
this.ttsActiveControllers.delete(streamSid);
|
||||||
this.ttsPlaying.delete(streamSid);
|
this.ttsPlaying.delete(streamSid);
|
||||||
this.ttsQueues.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