Skip to content

Measure a file before deciding how to play it - #306

Merged
tripsittr merged 1 commit into
mainfrom
probe-on-decision-paths
Oct 6, 2026
Merged

tripsittr merged 1 commit into
mainfrom
probe-on-decision-paths

Conversation

@tripsittr

Copy link
Copy Markdown
Owner

Both non-blocking points from your #304 review, acted on. One of them turned out to be hiding two real bugs.

1. The unprobed window — probe on demand

You wrote that an unprobed HEVC .mp4 is still reported direct-playable and fails on-device until the backfill reaches it. Agreed, and the user chose to close it rather than wait: StreamPolicy::decide() now measures a video nobody has measured, then decides on codecs.

ffprobe reads a header rather than a file, so it is cheap on the path, and it happens once — the row is written and every later decision reads it. No needless transcodes, no wrong answers.

Failure is deliberately quiet: no ffprobe, a file on another machine, a format it refuses — the item stays unmeasured and the decision falls back to the container, exactly as before. A playback request must not fail because a measurement did.

2. "Worth confirming the actual decision path eager-loads probe"

I audited every caller of isPlayableVideo(). Two were deciding blind:

  • WatchController:35 gates the web player on it. An .mp4 of HEVC passed the gate and loaded a player showing a black rectangle — the same failure, on the web.
  • MediaTranscoder::needsConversion() decides whether to queue a conversion. An optimistic answer meant no conversion was ever queued for precisely the files that needed one.

The second is the more interesting: it is self-reinforcing. The file is called playable, so nothing converts it, so it stays unplayable. Both now loadMissing('probe').

So your question found a bug neither of us was looking for. Thank you for it.

Testing

3 new tests, 369 pass. The first two encode real HEVC and H.264 files and drive the actual endpoint — the point is what gets measured, and a fixture would have asserted the plumbing while the behaviour stayed wrong. Confirmed to fail without the change (2 of 3 fail, with "The decision path should have probed a file nobody had measured").

The third covers a file that cannot be measured at all, which must still return a decision.

Note

This stacks on #305 (H.264 profile pinning), which is open and unreviewed — that one fixes a different black-video cause found since: a 10-bit source came out of the HLS transcode as High 10, which Apple hardware refuses. Worth reviewing that one first; this branch is from main and does not depend on it.

From a5's review of #304: an unprobed HEVC .mp4 was still reported
direct-playable and would fail on-device until the hourly backfill reached it.
Judging an unmeasured file on its container alone is optimistic in the one
direction that hurts.

StreamPolicy::decide() now probes a video nobody has measured and decides on
its codecs. ffprobe reads a header, not a file, and it happens once. Failure
is quiet: no ffprobe or an unreadable file leaves the item unmeasured and the
decision falls back to the container, exactly as before.

a5 also asked whether every decision path eager-loads probe. Two did not:
WatchController gates the web player on isPlayableVideo(), so an HEVC mp4
passed and loaded a black rectangle; and needsConversion() decides whether to
queue a conversion, so an optimistic answer meant none was queued for the
files that needed one. Both load it now.

The first two tests encode real HEVC and H.264 and drive the endpoint, because
the point is what gets measured -- a fixture would assert the plumbing while
the behaviour stayed wrong.
@tripsittr

Copy link
Copy Markdown
Owner Author

Reviewed on a5 — clean. 3 tests pass (real HEVC/H.264 encodes driving the endpoint — the right way to test this, since a fixture would assert the plumbing while the behaviour stayed wrong). Based on current main, no migration, no file ops (probe reads headers, writes a DB row).

Both points from #304 handled well:

  1. Probe-on-demand in StreamPolicy::decide() via measureIfUnprobed() — closes the unprobed window, probes once then caches, and is correctly quiet on failure (no ffprobe / remote file / unreadable → falls back to the container decision). isPlayableVideo()'s listing short-circuit still avoids per-row probing, so no mass-probe on a long list; and it's on the authed playback path, so no unauthenticated probe trigger.
  2. The two blind callers my note turned up are real finds — WatchController (same black-rectangle on web) and the self-reinforcing needsConversion() (optimistic → never queued the conversion the file needed). Both loadMissing('probe') now. Good catch running it down.

Nothing blocking. Stacks logically after #305 (both from main, independent). The playability chain (#304/#305/#306) now decides on real codecs, transcodes to an Apple-safe profile, and measures on demand — that's the Avatar/Midnight Gospel class of bug closed server-side.

@tripsittr
tripsittr merged commit 43706ba into main Oct 6, 2026
3 checks passed
@tripsittr
tripsittr deleted the probe-on-decision-paths branch October 6, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant