diff --git a/docs/specs/linux-native-video-spike.md b/docs/specs/linux-native-video-spike.md index 9ce7bc1d..c7be86f2 100644 --- a/docs/specs/linux-native-video-spike.md +++ b/docs/specs/linux-native-video-spike.md @@ -1,6 +1,6 @@ # Spec: Linux native video — bounded compositing spike -**Status:** **Run 2026-08-21 — G1-G6 green except the Tauri-tree half of G1.** +**Status:** **Run 2026-08-21 — compositing works; G5 carries an open crash.** The compositing claim it set out to test is falsified on Linux. See "Result". This file stays open until the implementation spec exists; ABR is unresolved. **Requirements:** none allocated. This spike produces a decision record, not @@ -186,7 +186,7 @@ mpv's render API with an update callback, frame-gated repaints and | G2 webview paints transparently over it | ✅ green | `with_transparent(true)` alone. No window-level transparency was used or needed. | | G3 mpv renders into our FBO | ✅ green | `vo=libmpv` + `mpv_render_context_create` with `MPV_RENDER_PARAM_OPENGL_FBO` into the FBO GTK binds. | | G4 HTML over video | ✅ green | Opaque panel and a translucent control bar both drew over moving video. | -| G5 resize / drag / fullscreen | ✅ green | No flicker, no gap, no misalignment. Fullscreen juddered until frame pacing was done properly — see trap 3; it is smooth with `report_swap` in place. | +| G5 resize / drag / fullscreen | 🟡 **green on appearance, suspect underneath** | No flicker, gap or misalignment, and smooth once frame pacing was correct (trap 3). But the only crash observed came from the only session where fullscreen was exercised — see "What is still open". | | G6 X11 **and** Wayland | ✅ green | Identical on both; `GDK_BACKEND` flipped between runs. | **Finding 2 of [playback-backend-unification.md](playback-backend-unification.md) @@ -244,9 +244,12 @@ which is the least efficient hardware path. An implementation should evaluate zero-copy VA-API on the iGPU (after confirming the driver is installed) before accepting `auto`. -`hwdec=auto-safe`, the default this spike started with, probes Vulkan video -decode, which this GPU does not support. It failed per-frame and logged -`no frame!` on every frame. Do not ship `auto-safe` here without checking that. +`hwdec=auto-safe` probes Vulkan video decode, which this GPU does not support. +It logs two `Failed setup for format vulkan` / `no frame!` pairs at start-up and +then settles on `nvdec-copy` — the same place `auto` lands. A first reading of +these logs mistook the start-up pair for a per-frame flood; **it is not**. Every +run, clean or crashed, contains exactly two. `auto-safe` is not implicated in +anything. ### What is still open @@ -255,15 +258,72 @@ decode, which this GPU does not support. It failed per-frame and logged Tauri's existing webview into an overlay. Low risk — the same widgets, one extra reparent — but unproven, and it is the only place Tauri-specific behaviour could still bite. -- **ABR — unchanged and still the blocker.** Nothing here addresses finding 3. - What has changed is that the direct-play/transcode split is now worth designing - rather than moot. -- **One unexplained SIGSEGV.** A ~180s run crashed in a *decoder* thread - (libavcodec -> `av_log` -> libmpv's log handler -> libc), not in the GL or - compositing path, while `hwdec=auto-safe` was failing its Vulkan probe on every - frame. It did **not** reproduce across five subsequent runs (2x45s, 3x20s) on - `no`, `auto` and `vaapi`. Cause unconfirmed; recorded rather than dismissed. - Anyone implementing this should run a multi-hour soak before trusting it. +- 🔴 **ABR — finding 3's premise is in doubt.** Finding 3 says mpv would regress + streaming quality because "the webview path already has real ABR via hls.js". + Three pieces of evidence in this repo suggest that is **not true of the URLs we + actually build**: + + 1. `get_video_stream_url` (`repository/online.rs`) requests a *single* + rendition — one `VideoBitrate`, one `MaxStreamingBitrate`, one `MaxHeight`. + Jellyfin transcodes to what it is asked for; it does not build a ladder. + 2. The frontend contains **no level-handling code at all** — no `hls.levels`, + no `LEVEL_SWITCH`, no `currentLevel`. The `abrEwma*` options in + `VideoPlayer.svelte` are default tuning with nothing to act on. hls.js is + serving as an HLS *demuxer* (WebKitGTK cannot play HLS natively), not as an + adaptation engine. + 3. That function's own comment describes a quality switch as **rebuilding the + URL** — "every path that re-opens a stream (quality switch, transcoded seek, + audio-track switch)". Manual selection by stream re-open is what you build + when there is no adaptation, and mpv can do the same thing. + + **The decisive test has not been run** and needs a live server plus an API key: + count `#EXT-X-STREAM-INF` lines in a real `master.m3u8`. One line means there + is no ABR to lose and this blocker disappears. More than one means finding 3 + stands and the work below applies. + + If ABR does turn out to be real, it belongs in **Rust**, not in mpv, and there + are three designs in increasing cost: pick the variant at open; re-open at a + new bitrate on sustained throughput drops (this is the quality-switch path the + app already has, so it is nearly free); or run a local proxy serving mpv a + synthesized single-variant playlist while swapping renditions underneath. The + middle option is almost certainly sufficient. + + Either way the **direct-play path still does not exist** — every video play + currently goes through the HLS transcode endpoint. Building it is the real + project; the compositing work proven above is the smaller half. +- 🔴 **One unexplained SIGSEGV.** A ~180s + run died in a *decoder* thread (libavcodec -> `av_log` -> libmpv's log handler + -> libc). No Tauri, wry, WebKitGTK, GTK or GL frame appears anywhere in the + stack, so the fault is on the mpv/ffmpeg side of the process rather than in the + compositing seam. + + Three hypotheses were tested and **none reproduced it**: + + | Hypothesis | Test | Result | + |---|---|---| + | `hwdec=auto-safe`'s Vulkan failures | 300s soak on `auto-safe` | Survived. Also based on a misreading — the failures are 2 per run at start-up, not per-frame. Dead. | + | Fullscreen transitions recreating the GL context under mpv's render context | 240s soak, ~120 automated transitions | Survived, no core dumped. | + | Continuous resize thrashing the GL framebuffer | 240s soak, ~2000 resizes | Survived, no core dumped. | + + **The crash is therefore unexplained.** It was observed exactly once, in the + only session a human interacted with, and did not recur in ~13 minutes of + targeted stress across the three most plausible causes. It is recorded here + rather than dismissed precisely because nothing explains it: an intermittent + fault that nobody can reproduce is worse to inherit than a deterministic one, + not better. + + The underlying concern stands regardless of which test eventually reproduces + it. A SIGSEGV in an unrelated thread is characteristic of memory corruption, + and this spike never calls `mpv_render_context_free` and never tears down on + `unrealize` — it has no defence against the GL context being recreated beneath + the render context. That is DR-184 on Android restated: a surface outliving its + player. An implementation must bind the two lifetimes together whether or not + this particular crash is ever explained. + + **Therefore G5 is recorded green on appearance only**, and this crash is the + single largest piece of unfinished business in the spike. Do not read the green + gates above as "safe to build on" until it is explained or a long soak clears + it. - Long-run stability, seeking, track switching, HDR, and multi-window were not exercised at all. diff --git a/docs/specs/playback-backend-unification.md b/docs/specs/playback-backend-unification.md index 130179cd..8aafb5e3 100644 --- a/docs/specs/playback-backend-unification.md +++ b/docs/specs/playback-backend-unification.md @@ -111,6 +111,17 @@ The webview path already has real ABR via hls.js. Moving video to mpv would be a **downgrade** on every platform — no graceful degradation on weak networks, and quality changes requiring teardown and reload. +> **Premise in doubt (2026-08-21).** "The webview path already has real ABR" +> was not verified against the URLs this app actually builds. +> `get_video_stream_url` requests a *single* rendition (one `VideoBitrate`, one +> `MaxHeight`), the frontend has **no** level-handling code (`hls.levels`, +> `LEVEL_SWITCH`, `currentLevel` appear nowhere), and this repo implements a +> quality switch by *re-opening the stream* — all of which point to a +> single-variant playlist, i.e. no ABR to lose. The decisive test is counting +> `#EXT-X-STREAM-INF` lines in a real `master.m3u8`; it needs a live server and +> has not been run. See +> [linux-native-video-spike.md](linux-native-video-spike.md). + ### 4. Crossfade is architecturally blocked on mpv mpv's audio chain is single-stream. FFmpeg's `acrossfade` is an `N→A` filter