mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
test(voice): clarify playback and timeline regression journeys
This commit is contained in:
@@ -125,6 +125,10 @@ function isAudioOutput(message: SessionOutboundMessage): boolean {
|
||||
return message.type === "audio_output";
|
||||
}
|
||||
|
||||
async function waitForAudioOutput(host: FakeVoiceHost): Promise<void> {
|
||||
await vi.waitFor(() => expect(host.emitted.filter(isAudioOutput)).toHaveLength(1));
|
||||
}
|
||||
|
||||
async function settle(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
@@ -140,13 +144,14 @@ describe("VoiceSession streaming transcription", () => {
|
||||
};
|
||||
const { voiceSession, detector, host, speak } = createVoiceSession(tts);
|
||||
const interrupted: string[] = [];
|
||||
host.interruptAgentIfRunning = async (agentId) => {
|
||||
async function recordInterruption(agentId: string) {
|
||||
interrupted.push(agentId);
|
||||
};
|
||||
}
|
||||
host.interruptAgentIfRunning = recordInterruption;
|
||||
await voiceSession.handleSetVoiceMode(true, VOICE_AGENT_ID);
|
||||
const playback = speak({ text: "A spoken response." });
|
||||
try {
|
||||
await vi.waitFor(() => expect(host.emitted.filter(isAudioOutput)).toHaveLength(1));
|
||||
await waitForAudioOutput(host);
|
||||
detector.emit("speech_started");
|
||||
await settle();
|
||||
expect(host.emitted).toContainEqual({
|
||||
@@ -171,18 +176,19 @@ describe("VoiceSession streaming transcription", () => {
|
||||
const external = new AbortController();
|
||||
let abortRequested = false;
|
||||
const emit = host.emit;
|
||||
host.emit = (message) => {
|
||||
function acknowledgeLaterAudio(message: SessionOutboundMessage) {
|
||||
emit(message);
|
||||
if (message.type === "audio_output" && abortRequested)
|
||||
voiceSession.handleAudioPlayed(message.payload.id);
|
||||
};
|
||||
}
|
||||
host.emit = acknowledgeLaterAudio;
|
||||
await voiceSession.handleSetVoiceMode(true, VOICE_AGENT_ID);
|
||||
const playback = speak({
|
||||
text: "First sentence. Second sentence. Third sentence.",
|
||||
signal: external.signal,
|
||||
});
|
||||
try {
|
||||
await vi.waitFor(() => expect(host.emitted.filter(isAudioOutput)).toHaveLength(1));
|
||||
await waitForAudioOutput(host);
|
||||
abortRequested = true;
|
||||
await voiceSession.handleAbort();
|
||||
await playback;
|
||||
|
||||
@@ -46,6 +46,18 @@ function createTts(numThreads?: number, beforeGenerate?: () => Promise<void>) {
|
||||
return { tts, requests, nativeConfig, released };
|
||||
}
|
||||
|
||||
async function yieldNativeQueue(): Promise<void> {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
}
|
||||
|
||||
function holdNativeSynthesis() {
|
||||
const pending: Array<() => void> = [];
|
||||
function wait() {
|
||||
return new Promise<void>((resolve) => pending.push(resolve));
|
||||
}
|
||||
return { pending, wait };
|
||||
}
|
||||
|
||||
describe("SherpaOnnxTTS", () => {
|
||||
it("requests Electron-compatible copied samples and returns PCM audio", async () => {
|
||||
const { tts, requests } = createTts();
|
||||
@@ -58,21 +70,18 @@ describe("SherpaOnnxTTS", () => {
|
||||
});
|
||||
|
||||
it("keeps the event loop available and serializes native synthesis", async () => {
|
||||
const pending: Array<() => void> = [];
|
||||
const { tts, requests } = createTts(
|
||||
undefined,
|
||||
() => new Promise<void>((resolve) => pending.push(resolve)),
|
||||
);
|
||||
const { pending, wait } = holdNativeSynthesis();
|
||||
const { tts, requests } = createTts(undefined, wait);
|
||||
const first = tts.synthesizeSpeech("first");
|
||||
const second = tts.synthesizeSpeech("second");
|
||||
try {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
await yieldNativeQueue();
|
||||
expect(requests).toEqual([]);
|
||||
expect(pending).toHaveLength(1);
|
||||
pending.shift()!();
|
||||
const audio = await first;
|
||||
audio.stream.destroy();
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
await yieldNativeQueue();
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(pending).toHaveLength(1);
|
||||
pending.shift()!();
|
||||
@@ -103,21 +112,18 @@ describe("SherpaOnnxTTS", () => {
|
||||
});
|
||||
|
||||
it("waits for active native synthesis before freeing and drops queued synthesis", async () => {
|
||||
let finish!: () => void;
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
finish = resolve;
|
||||
});
|
||||
const { tts, requests, released } = createTts(undefined, () => gate);
|
||||
const { pending, wait } = holdNativeSynthesis();
|
||||
const { tts, requests, released } = createTts(undefined, wait);
|
||||
const first = tts.synthesizeSpeech("first");
|
||||
const second = tts.synthesizeSpeech("second");
|
||||
const rejected = expect(second).rejects.toThrow("TTS provider closed");
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
await yieldNativeQueue();
|
||||
tts.free();
|
||||
expect(released).toEqual([]);
|
||||
finish();
|
||||
pending.shift()!();
|
||||
(await first).stream.destroy();
|
||||
await rejected;
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
await yieldNativeQueue();
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(released).toEqual([true]);
|
||||
await expect(tts.synthesizeSpeech("third")).rejects.toThrow("TTS provider closed");
|
||||
|
||||
Reference in New Issue
Block a user