Rust becomes the single owner of which stream to play — direct play or
transcode, at what ceiling, over what transport — and hands every player
backend a self-describing StreamSelection instead of a bare URL. mpv,
ExoPlayer and the HTML5/hls.js path all consume one decision rather than
three places re-deriving it.
The motivating leak is concrete. VideoPlayer.svelte determines transport with
`currentStreamUrl.includes(".m3u8")`, in two places, for a URL Rust
constructed and therefore already knows the shape of. That is the boundary
rule in miniature: not item-type taxonomy, but the same error of
reconstructing a domain fact in the presentation layer because the wire shape
did not carry it. A tagged Transport enum deletes it.
The design line, which ExoPlayer forces: Rust decides *what stream*, the
player decides *how to deliver it*. ExoPlayer has genuine adaptive track
selection; this spec must not reimplement or fight it. Rust only adapts where
the player cannot (mpv) and the server actually offers a ladder.
Six phases, and phase 1 stands alone as pure ownership movement with no
behaviour change. Phase 4 (direct-play negotiation) is what removes the
transcode and unblocks the Linux native-video work. Phase 5 (adaptation) is
gated on counting EXT-X-STREAM-INF entries in a real playlist — the
acceptance criteria require that count be recorded before it is either
started or dropped.
Takes DR-121 from read-through-media-cache.md, which specced Rust-owned
quality reporting but never built it; that spec keeps its capture half.
Three corrections to the Linux native-video spike, each of which reverses
something recorded earlier in the same session:
- The crash is unexplained. It was first blamed on hwdec=auto-safe's Vulkan
failures, on a misreading of the logs — those are two per run at start-up,
not per-frame, and every clean run has the same two. A 300s soak on
auto-safe survived. So did 240s of automated fullscreen toggling (~120
transitions) and 240s of continuous resizing (~2000 reallocations). Three
hypotheses, none reproduced. Recorded rather than dismissed: an
intermittent fault nobody can reproduce is worse to inherit than a
deterministic one.
- G5 drops to amber. It looked and felt smooth, but the only SIGSEGV observed
came from the only session in which fullscreen was exercised, and the spike
has no lifecycle handling at all — it never frees the render context. An
implementation must bind that to the GL context's lifetime regardless of
what caused this crash, because Android already paid for that lesson as
DR-184.
- Finding 3's premise is in doubt. "The webview path already has real ABR via
hls.js" was never checked against the URLs this app builds:
get_video_stream_url requests a single rendition, the frontend has no
level-handling code at all, and a quality switch is implemented by
re-opening the stream. If the playlist is single-variant there is no
adaptation to lose. The decisive test needs a live server and is recorded
as unrun.
Also records hardware decode working through the render API (nvdec-copy
engaged), and that hwdec=vaapi silently fell back to software on this box.