From 40d358ab52cca5b11cf3b62b56a573e9d6301baa Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 23 Aug 2026 10:39:37 +0200 Subject: [PATCH] docs: a verification plan for the native player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not a generic smoke test. Every case exists because something specific went wrong, and most were found on hardware after the suites were already green. The sequences are load-bearing. Two defects this cycle only appeared in a particular order of actions — play, enable background audio, background, foreground, exit — and testing the same features in any other order found neither. So the plan asks for that order explicitly rather than listing "background audio" as a feature to try. It also states plainly that a green conformance run is not sufficient evidence to ship, because both regressions introduced during this work passed conformance and were caught by a person using the app. Includes a symptom-to-cause table, because none of these presented as their cause: a dead play/pause button was an unobserved property, a black screen was a float that could not become a Duration, and a scrub bar with no scale was a duration of zero being believed. "Known open" lists what is deliberately unfixed so each gets a decision rather than a surprise — device-local resume, the unconfirmed handoff swap, and the broken side-by-side debug install whose own error message advises an uninstall that would destroy the real app's data. --- docs-site/SUMMARY.md | 1 + docs/native-player-verification.md | 191 +++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 docs/native-player-verification.md diff --git a/docs-site/SUMMARY.md b/docs-site/SUMMARY.md index 7398021e..bdaebfa1 100644 --- a/docs-site/SUMMARY.md +++ b/docs-site/SUMMARY.md @@ -48,6 +48,7 @@ - [Build & Release](build/build-release.md) - [Release Checklist](release-checklist.md) +- [Native Player Verification](native-player-verification.md) - [Desktop Packaging](build/build-desktop-packages.md) - [Windows Build](build/build-windows.md) - [Defect Windows](defect-windows.md) diff --git a/docs/native-player-verification.md b/docs/native-player-verification.md new file mode 100644 index 00000000..4f0e9cf0 --- /dev/null +++ b/docs/native-player-verification.md @@ -0,0 +1,191 @@ +# Native player — verification plan + +What to check before the `MediaPlayer` contract and Linux native video reach +`master`. + +This is not a generic smoke test. Every case below exists because something +specific went wrong, and most of them were found on hardware **after** the +automated suites were green. Treat the sequences as load-bearing: several +defects only appeared in a particular order of actions, and testing the same +features in a different order missed them entirely. + +Companion to [release-checklist.md](release-checklist.md), which covers the +release mechanics. This covers whether the player is fit to release at all. + +## What is risky about this change + +- `PlayerController` now talks to a `MediaPlayer` contract instead of + `PlayerBackend`. Every engine reaches it through an adapter that did not exist + before (DR-245). +- mpv decodes video on Linux for the first time, composited under the webview + (DR-231). +- Seek strategy is driven by an ability each engine declares rather than by a + truth table (DR-246). +- Two regressions were introduced during this work and caught only on a device: + a wrong capability for ExoPlayer (DR-246 follow-up) and a `Duration` panic + (DR-252). Both were invisible to the test suites. + +The suites verify engines that behave. **The manual passes exist to catch +engines that do not.** + +## 1. Automated gates + +Cheap, fast, and non-negotiable. Run from the worktree. + +```bash +bun run check # 0 errors, 0 warnings +bun run test # frontend +bun run test:rust # Rust +bun run format:check +bun run lint # 0 errors; warnings at or below the CI ratchet +bun run check:boundary +bun run traces:validate +bun run traces:coverage # at or above MIN_THRESHOLD +cd src-tauri && cargo fmt --check && cargo clippy --all-targets -- -D warnings +cargo clippy --all-targets --features conformance -- -D warnings +``` + +The eslint warning count is a **ratchet**: equal to the CI limit is a pass, one +over fails the build. Going one over is how a piece of dead state was found +during this work — do not raise the limit to get past it. + +## 2. Engine conformance + +```bash +bun run test:player # mpv + legacy, desktop +bun run test:player:android # ExoPlayer, on a connected device +``` + +Expected, and each deviation is meaningful rather than noise: + +| Engine | Result | If it differs | +|---|---|---| +| `MpvPlayer` | 9/9 | A real regression. Stop. | +| `LegacyPlayer` | 8/9 | The one failure is `transport_settings_round_trip`: the old trait has no mute or rate. Any *other* failure is a regression. | +| ExoPlayer (device) | 7/7 | Two cases are absent because the Kotlin player exposes no mute or rate. | + +A green conformance run is **not** sufficient evidence to ship. Both regressions +introduced during this work passed conformance. + +## 3. Desktop (Linux) + +Run with native video on, since that is what is new: + +```bash +JELLYTAU_NATIVE_VIDEO=1 bun run tauri dev +``` + +- [ ] **Direct play** — a file the server does not transcode. Picture and sound. +- [ ] **Transcoded play** — something the server must re-encode (4K, HEVC, or an + audio codec the renderer cannot take). +- [ ] **Resume** — an item watched previously *on this install*. The prompt + appears and playback starts at the offered position, not at zero. + *(Resume is device-local — see "Known open".)* +- [ ] **Scrub** on a direct-play item; position lands and playback continues. +- [ ] **Scrub on a transcoded item.** Separate case on purpose: it takes a + different path, and it silently did nothing for months (DR-238). +- [ ] **Pause and resume** — the button follows the player. It stopped doing so + when a property was handled but never observed (DR-239). +- [ ] **Fullscreen** — the window really fills the display. Measure it if + unsure: the log prints `rendering WxH`, and a height short of the panel + means the document went fullscreen and the window did not (DR-240). +- [ ] **Exit the player** — audio stops. Listen; do not assume. +- [ ] **Audio-only playback** still works: mini player, queue, next/previous. +- [ ] Nothing in the log matches `PANIC` or `ERROR`. + +## 4. Android + +The tablet needs the *side-by-side* build. **Do not uninstall the release app** +to make an install succeed — see "Known open" for why the normal command is +currently wrong. + +```bash +bun run android:build --device +./scripts/sync-android-sources.sh +cd src-tauri/gen/android && ANDROID_HOME="$HOME/Android/Sdk" ./gradlew \ + :app:assembleUniversalDebug -x :app:rustBuildUniversalDebug \ + -x :app:rustBuildArm64Debug -x :app:rustBuildArmDebug \ + -x :app:rustBuildX86Debug -x :app:rustBuildX86_64Debug +adb install -r app/build/outputs/apk/universal/debug/app-universal-debug.apk +``` + +Confirm the package is `com.dtourolle.jellytau.debug` before installing: + +```bash +aapt2 dump packagename +``` + +If it says `com.dtourolle.jellytau`, the suffix was lost — **stop**, re-sync and +re-assemble. Installing it would try to replace the real app. + +Then, with `adb logcat` capturing: + +- [ ] Play a video. Picture, sound, and controls. +- [ ] **Scrub.** The bar has a scale — a duration of `0.0` means the seek bar has + nothing to scrub against (DR-251). +- [ ] Transcoded seek lands rather than restarting the stream. ExoPlayer seeks a + transcode in place; declaring otherwise re-opened it (DR-246). +- [ ] PiP. +- [ ] Lockscreen: controls respond and position tracks. +- [ ] **The handoff sequence, in this exact order:** + 1. play a video + 2. enable background audio + 3. background the app — audio continues + 4. foreground the app — **video returns** + 5. exit the player — **everything stops** + + Steps 4 and 5 are where two separate defects lived (DR-250, DR-252). Doing + the same actions in another order finds neither. +- [ ] `grep -c 'PANIC at' ` returns 0. + +## 5. Regression checks with a named cause + +Each of these presented as something other than its cause, which is why they are +listed separately from the feature passes above. + +| Symptom to look for | Was actually | Ref | +|---|---|---| +| Skip on a transcoded item does nothing, or jumps to zero | Seek strategy keyed on the container, not the engine | DR-238, DR-246 | +| Play/pause button does not follow the player | A property handled but never observed, so the event never arrived | DR-239 | +| Fullscreen leaves a strip of desktop | The document went fullscreen, the window did not | DR-240 | +| Resume plays from the beginning | A seek issued before the engine had a file was discarded | DR-241 | +| Scrub bar has no scale | Duration reported as `0.0` and believed | DR-251 | +| Black screen, no controls, after a background-audio round trip | A junk duration converted to a `Duration` panicked the backend | DR-252 | +| Audio still playing after leaving the player | The stop was aimed at whichever renderer bookkeeping believed was active | DR-250 | + +## Known open — decide, do not discover + +None of these are fixed. Each needs an explicit ship / do-not-ship call rather +than being met with surprise during testing. + +- **Resume is device-local.** Progress is read from the local database and + nothing consults the server's `UserData`. A fresh install, a second device or + a reinstall offers no resume even though the server knows the position. Not a + regression — it has always been so. +- **The background-audio handoff is an unconfirmed state swap.** + `exit_background_audio` marks the video element the player again the moment it + is called, while the element has not reloaded. DR-250 makes the visible + symptom impossible; the race is intact and can still misdirect a lockscreen + command or a position read. See + [media-player-controller.md](specs/media-player-controller.md). +- 🔴 **The side-by-side debug install is broken.** `bun run android:dev` + produces an APK with the *release* application id, because the Tauri build + regenerates `gen/build.gradle.kts` after the sync drops the `.debug` suffix in. + It then fails on signatures, and its own error message advises uninstalling — + which would destroy the real app's data. **Fix this before anyone else builds + for Android.** +- **`PlayerBackend` still exists** behind `LegacyPlayer`, and the frontend still + carries some playback state. DR-248 and DR-249 are not started. + +## Ship criteria + +Ship when: + +1. Every automated gate in §1 passes. +2. Conformance matches §2 exactly, deviations included. +3. §3 and §4 are complete, on real hardware, by a person. +4. §5 shows no symptom returning. +5. Every item in "Known open" has a recorded decision. + +Do not ship on green suites alone. Both regressions introduced during this work +passed every suite and were caught by a person using the app.