Commit Graph
2 Commits
Author SHA1 Message Date
dtourolle 2f637d4775 feat(video): let mpv decode video at all, behind one shared flag
mpv has never decoded a video frame in this app: the backend sets `video: no`
unconditionally, because Linux video has always been the webview's job and
decoding it twice would burn a core for a picture nobody sees. The render path
built in the previous commit therefore had nothing to draw.

With native video on, mpv is configured for video *and* `vo=libmpv` — the render
API only works through that output, and the default would try to open a window
of its own. Set at construction, because mpv resolves its video output when it
initialises and flipping the property later does not re-open one.

The flag lives in `player::native_video`, read by all three things that must
agree: the backend (configured before anything plays), the surface (nothing to
draw otherwise), and `get_player_status` (which tells the frontend whether to
use a `<video>` element — two decoders on one stream would fight over the
audio). A function rather than three `env::var` checks, because a capability
answered in several places is a capability whose answers drift: four separate
bugs this cycle came from exactly that shape.

Also fixes an ordering bug the first run exposed. The surface was attached in
`setup` before the player backend was constructed, and the mpv handle is
registered *during* that construction — so it found nothing every time and
logged "no mpv handle". Attaching after the backend exists is the whole fix.

Confirmed on a real run: mpv accepts `vo=libmpv`, the GL context comes up on
Tauri's vbox, and `mpv_render_context_create` succeeds — which also proves the
libepoxy data-symbol handling is right, since a wrong `get_proc_address` would
have taken SIGSEGV on the first GL call rather than returning cleanly.

No frame has reached the screen yet. The webview is still opaque, so it will
paint over anything drawn beneath it until transparency is set up.

Security: quick-xml 0.38.4 carried RUSTSEC-2026-0194 (quadratic parse on
duplicate attribute names) and RUSTSEC-2026-0195 (unbounded namespace
allocation, memory-exhaustion DoS). `cargo deny` gates CI on advisories, so this
would have failed the next release. Fixed by plist 1.8 -> 1.10, which pulls
quick-xml 0.41. Licences, bans and sources still pass.

UT-216 pins the flag's parsing: absent, empty, `0`, `no` and anything
unrecognised all mean off. A half-set variable that half-enabled the renderer
would configure mpv for video with nothing drawing it — audio over a black
rectangle.

Also removes a wall-clock timer from the waitForRepository late-arrival test,
which failed once under load. The assertion is about ordering, so it now
publishes on a microtask and cannot race.
2026-08-22 13:45:04 +02:00
dtourolle a8c44145ff fix(player): letterbox the picture, and stop racing the session
Two bugs found by resizing the window during playback. Neither was introduced
by this branch; both are the kind that only surface when somebody actually
drags a window edge.

The picture cropped and sat at the top instead of letterboxing. The video's
flex wrapper had no `min-h-0`, and a flex item defaults to `min-height: auto` —
it refuses to shrink below its content's intrinsic size, and a <video> reports
the *media's* natural dimensions. So whenever the picture was larger than the
window the wrapper grew past the viewport, the overflow went off the bottom,
and what was visible was the top-left of an uncentred, uncropped image.
`object-contain` was doing its job the whole time, inside a box that was the
wrong size. This is also what put the picture at the bottom in fullscreen,
reported earlier and unexplained until now.

"Not connected to a server", shown as a *playback* error. The player page asks
for the repository on mount, but the session is restored asynchronously at
startup, so losing that race turned a perfectly good stream into a fatal error
screen. `getRepository()` throwing instantly is right for a click handler,
where the user is present; it is wrong for anything that runs on mount.
`waitForRepository()` resolves as soon as the session lands and still rejects
when there genuinely is not one, so a real logged-out state surfaces — just not
as a race.

Worth recording how this was found, because it was nearly misdiagnosed: the
symptom correlated with window resizes, but the log showed 230 Vite HMR updates
against a single app start — the frontend was being remounted under the test by
edits made while it ran, and a remount empties the in-memory auth store. The
race is real and worth fixing on its own merits, but "resize causes it" was an
artifact of how it was being observed, not a property of the bug.

UT-215 covers the waiting contract: resolves when already restored, resolves
when the session arrives late, still rejects when there is none, unsubscribes
once settled, and leaves no armed timer to reject an already-resolved promise.
2026-08-22 13:45:04 +02:00