fix(visualizer): bars recover after far-forward seeks into undecoded audio
ensureDecodeAround let a running decode pass close ANY forward gap in place — at 4x pacing, skipping 30 min ahead meant ~7.5 min for the frontier to arrive: bars held their last frame indefinitely. Now a gap beyond 15s restarts the pass at the seek target (network coverage there in ~1.6s, verified); smaller gaps close in place (cheaper than a reconnect + range request). Seek-key holds are debounced (400ms) so rapid re-seeks don't reconnect-spam the stream's server, and pending seek-decode cancels on pause/stop so no ffmpeg restarts while paused. Also fixes a pre-existing config-write race that intermittently failed visualizer-toggle.test.ts: updateConfig re-resolved the config path and re-read the patch state when the deferred write-chain drained, so a queued save could land in a directory XDG_CONFIG_HOME had since been pointed at (or carry state mutated after queueing). Path and patch snapshot are now captured eagerly at call time.
This commit is contained in:
@@ -97,6 +97,55 @@ function tmpWav(): string {
|
||||
}
|
||||
|
||||
const hasFfmpeg = !!Bun.which("ffmpeg");
|
||||
|
||||
test.skipIf(!hasFfmpeg)(
|
||||
"far-forward seek into undecoded territory restarts decode AT the target (bars recover in seconds, not minutes)",
|
||||
async () => {
|
||||
const wav = tmpWav();
|
||||
writeSineWav(wav, 60);
|
||||
const cache = new EpisodePcmCache({ url: wav });
|
||||
try {
|
||||
cache.startDecode(0);
|
||||
await waitForCoverage(cache, 1);
|
||||
|
||||
// Skipping 45s ahead while the pass still crawls at 4x must restart
|
||||
// the segment at the target — waiting for the frontier to chew
|
||||
// through the skipped region is minutes of frozen bars.
|
||||
cache.ensureDecodeAround(45);
|
||||
expect(cache.decoding).toBe(true);
|
||||
expect(cache.activeDecodeBaseSec).toBe(45);
|
||||
await waitForCoverage(cache, 45.1);
|
||||
} finally {
|
||||
cache.stop();
|
||||
await Bun.$`rm -f ${wav}`.quiet();
|
||||
}
|
||||
},
|
||||
{ timeout: 20000 },
|
||||
);
|
||||
|
||||
test.skipIf(!hasFfmpeg)(
|
||||
"small forward gap closes in place — no needless reconnect",
|
||||
async () => {
|
||||
const wav = tmpWav();
|
||||
writeSineWav(wav, 60);
|
||||
const cache = new EpisodePcmCache({ url: wav });
|
||||
try {
|
||||
cache.startDecode(0);
|
||||
await waitForCoverage(cache, 2);
|
||||
|
||||
// ~5s past the running frontier: at 4x pacing this closes in ~1.5s,
|
||||
// cheaper than a reconnect — the pass must NOT restart.
|
||||
const target = cache.coverageEndSec + 5;
|
||||
cache.ensureDecodeAround(target);
|
||||
expect(cache.activeDecodeBaseSec).toBe(0);
|
||||
await waitForCoverage(cache, target);
|
||||
} finally {
|
||||
cache.stop();
|
||||
await Bun.$`rm -f ${wav}`.quiet();
|
||||
}
|
||||
},
|
||||
{ timeout: 20000 },
|
||||
);
|
||||
const FIVE_SEC_BASE = 5 * SAMPLE_RATE; // decode offset for position-mapping tests
|
||||
|
||||
test.skipIf(!hasFfmpeg)(
|
||||
|
||||
Reference in New Issue
Block a user