Files
oh-my-pi/packages/coding-agent/test/tts/streaming-player.test.ts
T
can1357 eaa3002b9d fix(tts): cap replay retention so long utterances stay bounded in memory
Streamed PCM kept for the nonzero-exit replay is now dropped once the
utterance exceeds 60s (~5.8 MB at 24 kHz mono f32): the recovered failure
is a short clip that fits the pipe buffer before a broken backend dies,
while replaying a long already-played utterance would duplicate audio and
unbounded retention would defeat streaming for long input.
2026-07-23 22:15:24 +02:00

73 lines
2.5 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import type { PlayerCommand } from "@oh-my-pi/pi-coding-agent/tts/player";
import { StreamingAudioPlayer } from "@oh-my-pi/pi-coding-agent/tts/streaming-player";
/** A one-segment ~0.5s clip at 24 kHz, the shape `omp say "hi"` produces. */
const clip = (): Float32Array => new Float32Array(24_000 / 2).fill(0.1);
describe("StreamingAudioPlayer nonzero-exit fallback", () => {
it("replays the buffered clip via file playback when the streaming backend exits nonzero", async () => {
// `cat` drains stdin so the pipe write succeeds, then the shell exits 1 —
// exactly the bundled-ffmpeg-without-outdev failure that used to silently
// drop `omp say`'s single short clip.
const played: string[] = [];
const player = new StreamingAudioPlayer({
commandsFor: (): PlayerCommand[] => [{ cmd: "sh", args: ["-c", "cat >/dev/null; exit 1"] }],
playAudio: async wavPath => {
played.push(wavPath);
},
});
player.start(24_000);
player.write(clip());
await player.end();
expect(played.length).toBe(1);
});
it("does not replay when the streaming backend exits cleanly", async () => {
const played: string[] = [];
const player = new StreamingAudioPlayer({
commandsFor: (): PlayerCommand[] => [{ cmd: "sh", args: ["-c", "cat >/dev/null"] }],
playAudio: async wavPath => {
played.push(wavPath);
},
});
player.start(24_000);
player.write(clip());
await player.end();
expect(played.length).toBe(0);
});
it("plays every chunk via the file fallback when no streaming backend exists", async () => {
const played: string[] = [];
const player = new StreamingAudioPlayer({
commandsFor: (): PlayerCommand[] => [],
playAudio: async wavPath => {
played.push(wavPath);
},
});
player.start(24_000);
player.write(clip());
player.write(clip());
await player.end();
expect(played.length).toBe(2);
});
it("drops the replay buffer once the utterance exceeds the retention cap", async () => {
// Long input must not accumulate unbounded PCM; past the cap the
// nonzero-exit replay is forfeited rather than duplicating audio the
// backend already played.
const played: string[] = [];
const player = new StreamingAudioPlayer({
commandsFor: (): PlayerCommand[] => [{ cmd: "sh", args: ["-c", "cat >/dev/null; exit 1"] }],
playAudio: async wavPath => {
played.push(wavPath);
},
replayRetentionSeconds: 0.25,
});
player.start(24_000);
player.write(clip());
await player.end();
expect(played.length).toBe(0);
});
});