fix(r5): address the fifth /code-review high round — 6 findings

- run_ffmpeg stops the process from ONE exit path (`except BaseException`), so
  an exception out of on_progress (a DB write) can no longer orphan a live
  ffmpeg after a 10s join on its pipes; _stop is now a no-op on a dead process
- both run_ffmpeg callbacks are throttled to ~1s (they were a DB round-trip per
  output line: ~10^5 queries on a long re-encode), mirroring the download hook
- volume arithmetic extracted to lib/playerVolume with unit tests; a corrupt
  level now falls back to the LAST GOOD one instead of full blast, and the
  sanitising happens where the stored value enters
- RSS is back to two except clauses (the shared tail already sits after the try)
- new tests: callback-exception cleanup, the two throttles; the fake Popen grew
  poll() and its wait count is now asserted

Mutation-checked: each new group goes red when its fix is reverted.
This commit is contained in:
2026-07-24 01:50:50 +02:00
parent fc82c8b47a
commit c4942eb590
8 changed files with 268 additions and 33 deletions
+30
View File
@@ -0,0 +1,30 @@
// The YouTube player's volume arithmetic, kept out of PlayerModal so it can be unit-tested. Every
// rule here was established by a bug: a wheel spin that lost notches to the iframe's lagging
// getVolume, a partial stored object that became NaN and persisted as `null`, and a "corrupt →
// default" fallback that jumped an already-quiet session to full blast.
/** Level used when nothing trustworthy is available. */
export const DEFAULT_VOLUME = 100;
const clamp = (n: number) => Math.max(0, Math.min(100, Math.round(n)));
/**
* Coerce anything that claims to be a volume into a usable 0–100 level.
*
* Only a real, finite `number` is accepted: the stored blob is JSON, so a level is a number or it
* is corrupt — `null`, `undefined`, `"80"` and `NaN` all mean "we don't know", and `Number(null)`
* being `0` (silently muting the player) is exactly the coercion to avoid. `fallback` is the last
* level known to work; it is itself validated, so a corrupt value can never travel through it.
*/
export function normalizeVolume(raw: unknown, fallback: number = DEFAULT_VOLUME): number {
if (typeof raw === "number" && Number.isFinite(raw)) return clamp(raw);
if (typeof fallback === "number" && Number.isFinite(fallback)) return clamp(fallback);
return DEFAULT_VOLUME;
}
/** One volume step (wheel notch / arrow key) from `current`, falling back to `fallback` when the
* player answered something unusable. */
export function stepVolume(current: unknown, delta: number, fallback: number): number {
const base = normalizeVolume(current, fallback);
return clamp(base + delta);
}