Commit Graph
5 Commits
Author SHA1 Message Date
dtourolle 14b6a8609d fix(player): three defects native video exposed, and the logs to see them
Each of these was invisible while Linux video played in the webview, and each
became reachable the moment mpv started rendering.

DR-238 — a transcoded seek re-negotiates the stream on every renderer, not
just the webview. `determine_video_seek_strategy` treated `is_hls` as a proxy
for "seekable in place", which held only because hls.js was always the HLS
renderer: it seeks within the VOD playlist it is handed and lets the server
catch up. mpv's HLS demuxer cannot make Jellyfin transcode from a new offset,
so with native video on, every transcoded seek became a backend seek that
silently did nothing. One cell of the truth table changes; all four webview
cells are byte-identical.

DR-239 — properties the mpv event loop handles are now observed. libmpv
delivers PropertyChange only for properties registered with
observe_property, so the `pause` arm was unreachable code that read as
implemented: StateChanged was never emitted and the play/pause control never
moved. UT-218 asserts the two lists agree, so the class cannot recur.

DR-240 — fullscreen moves whatever owns the pixels. requestFullscreen()
fullscreens the *document*, which sufficed while the <video> element lived
inside it and WebKit scaled it. A native surface is drawn behind the webview
at window size, so a document-only fullscreen expanded the page and left the
picture at its old size — on WebKitGTK, a maximised window with decorations
still holding a strip of the screen. Measured on a 3440x1440 panel: 1361 tall
before, 1440 after.

DR-241 — a seek issued before mpv has a file to seek in is honoured rather
than dropped. loadfile returns as soon as the command is queued, so
`time-pos` does not resolve yet and setting it fails. The two callers that
always hit that window are resume and a transcoded seek, both of which
re-open the stream and then ask for a position; the failed seek was discarded
and playback began at zero.

Also adds the instrumentation that made the diagnosis possible rather than
speculative: an entry log on player_stop, a render-size log that re-fires on
change instead of latching once, and decoded-vs-display video geometry on file
load. The last of those retired a wrong theory — a picture that does not fill
an ultrawide turned out to be a 16:9 source with its letterbox baked in, not a
rendering fault.
2026-08-22 21:17:29 +02:00
dtourolle 8500da1a42 chore(rust): clear the clippy backlog and finish the poison-tolerant lock sweep
`cargo clippy --all-targets` went from 51 warnings (23 in the lib) to zero.
Most were mechanical — needless borrows, `assert_eq!` against a bool literal,
`vec!` where an array does, `or_insert_with(Vec::new)`, a loop index used only
to index — and were applied with `clippy --fix`, then reviewed line by line.
That review caught one auto-fix that was *not* semantically neutral: dropping
the redundant `use hostname;` left its `#[cfg(target_os = "linux")]` orphaned
directly above `SERVICE_NAME`, which would have silently cfg'd the constant out
of every non-Linux build. Removed the stray attribute with the import.

Where a lint asked for a risky change rather than a better one, it is suppressed
with a comment saying why:

- `too_many_arguments` on five `#[tauri::command]` handlers and
  `ThumbnailCache::save_thumbnail` — most of the arity is `State<'_, _>`
  injection, and a parameter struct would change the IPC contract and the
  generated TypeScript for no readability gain.
- `large_enum_variant` on `PlayerStatusEvent` and `AutoplayDecision` — both are
  serde + specta wire types emitted a handful of times a second, never bulk
  allocated; boxing would have to stay invisible to the generated bindings while
  every match arm gained a deref.
- `await_holding_lock` on the `hybrid`/`offline` test modules — the guard is a
  test-only serialisation lock for the process-global `INCLUDE_CATALOG_BROWSE`
  flag, and the await it spans *is* the critical section. Each `#[tokio::test]`
  gets its own single-threaded runtime, so this is not the production deadlock
  class the lint targets; restructuring would reintroduce the flag race.

Real fixes elsewhere: `JellyfinItem::to_media_item` takes `self` by value, so it
is now `into_media_item`; the five-tuple episode row in the download commands
has a named `EpisodeRow` alias; the mpv `PropertyChange` arm matches
`name: "pause"` instead of guarding on it.

Also converted the last 27 raw `.lock().unwrap()` call sites to `lock_safe()`,
completing the `MutexSafe`/`RwLockSafe` convention. All of them turned out to be
in test modules — production code was already clean — so this is consistency
rather than a fix. The two raw locks in `utils/lock.rs` stay raw on purpose:
those tests deliberately poison a mutex to prove the helpers recover from it.

Pure refactoring: all 698 tests still pass.
2026-08-16 23:05:13 +02:00
dtourolle 3fbf6afdbc Background-audio handoff for video + repository/player refactor
Hand video playback off to a native audio-only stream when the app is
backgrounded or locked, with no on-device video decode (UR-040). Adds
player_enter/exit_background_audio commands, an audio-only stream URL
for video items across the repository layer, and the frontend handoff
state machine wired into VideoPlayer. Includes accompanying
repository/offline/player refactoring and regenerates the traceability
matrix.
2026-07-22 21:52:07 +02:00
dtourolle e3797f32ca many changes
🏗️ Build and Test JellyTau / Build APK and Run Tests (push) Has been cancelled
Traceability Validation / Check Requirement Traces (push) Failing after 1m18s
2026-02-14 00:09:47 +01:00
dtourolle cfddc1edea First working POC 2026-01-26 22:21:54 +01:00