Files
jellytau/docs/specs/libmpv2-migration.md
dtourolle 32043a2152 docs: fold shipped specs into the architecture docs and delete them
A spec was a promise; sixteen of them had become descriptions of code that
already shipped, sitting beside four that describe work still outstanding, with
nothing in the file telling the two apart. Half the statuses were also wrong —
audio-equalizer read "Accepted" with the EQ live on both platforms, the native
video spec said the flag stays off after the default was flipped on.

The shipped designs move into docs/architecture, which is the maintained
description of the build, and the spec files go. Git history keeps the
originals; what a future change still needs is carried across:

- 01-rust-backend: favourites rewritten (the old section named a file that no
  longer exists and called shipped buttons "planned"), domain vocabulary owned
  by Rust (SearchScope, exclusions, the bitrate ladder), background workers
- 02-svelte-frontend: app shell and chrome, library mosaic, series/episode
  navigation, downloaded browse, safe-area insets, native-video store, logging
- 03-data-flow: locally-indexed search
- 05-platform-backends: audio settings on ExoPlayer, the equalizer's band
  vocabulary, native video compositing, the background-audio handoff
- 06-downloads-and-offline: one storage model, offline catalog visibility
- 09-security: path confinement and input binding

docs/specs/README.md now says what the directory is for and where each shipped
design went. Deferred work the specs recorded is kept beside the code it
concerns rather than lost: season-bounded autoplay, the two dead search
commands, why indexing is a full crawl.

requirements.md had fourteen stale statuses — Android audio parity still read
"Linux only", DR-150 still said the native-video default was off, DR-190 was
Proposed after DR-196 implemented it, and five tooling requirements were
Proposed after landing. Three unbuilt specs suggested requirement ids that have
since been allocated to other work; each now carries a warning.
2026-08-21 18:15:58 +02:00

172 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec: Migrate to libmpv2 and declare the project licence
**Status:** Partially implemented — the `LICENSE` file has landed (part 2). The
`libmpv``libmpv2` swap (part 1) is **not** done: `src-tauri/Cargo.toml` still
pins the abandoned crate to a git branch.
**Requirements:** UR-003 → IR-003 (revises the MPV integration); no new user-facing behaviour
**UX spec:** n/a
**Supersedes / revises:** dependency and licensing housekeeping identified in [playback-backend-unification.md](playback-backend-unification.md)
## Summary
Two related pieces of housekeeping that block or complicate later work:
1. Replace the abandoned `libmpv` crate (pinned to a git branch) with the
maintained `libmpv2`.
2. Add a `LICENSE` file. The project has none, which leaves its legal status
undefined while it links GPL-licensed libmpv.
Neither changes user-visible behaviour. Both are prerequisites for
[windows-native-audio-backend.md](windows-native-audio-backend.md).
## Motivation
### The dependency is dead
```toml
# src-tauri/Cargo.toml
libmpv = { git = "https://github.com/ParadoxSpiral/libmpv-rs.git", branch = "master" }
```
- crates.io `libmpv` 2.0.1 was published **2020-09-29**.
- The upstream repo's last commit was **2023-01-08**; nothing since was released.
- We pin a git *branch*, so builds are not reproducible — the same lockfile-less
checkout can resolve differently over time, and CI has no protection if the
branch moves or the repo disappears.
`libmpv2` (kohsine/libmpv2-rs) is a maintained fork of exactly this crate:
6.0.0 released **2026-05-12**, ~23.5k recent downloads against the original's
~1.1k, releases roughly quarterly since 2024.
### The project has no licence
There is no `LICENSE`/`COPYING` file and `src-tauri/Cargo.toml` has no `license`
field. The project is open source and will never be commercial, so this is purely
an omission — but it matters because we link libmpv, and "no licence" defaults to
*all rights reserved*, which is incompatible with distributing a GPL-derived
work.
## Design
### Part 1 — licence
**Use GPLv3.** This is forced, not chosen:
- mpv's default build is **GPLv2-or-later**, so the combined work must be
GPL-compatible.
- Apache-2.0 is **GPLv2-incompatible** (patent-termination and indemnification
clauses) but GPLv3-compatible.
- A scan of the dependency tree found Apache-2.0-**only** crates with no
alternative arm — most importantly **`tao`** (Tauri's own windowing crate),
plus `sync_wrapper`, `gethostname`, and `ring` (Apache-2.0 AND ISC).
`tao` is unavoidable in a Tauri app, so GPLv2 is unavailable. Exercising mpv's
"or later" option puts the combination at **GPLv3**.
Actions:
- Add `LICENSE` containing the GPLv3 text.
- Add `license = "GPL-3.0-or-later"` to `src-tauri/Cargo.toml` and `license` to
`package.json`.
- Note in the README that the binary links libmpv (GPLv2+) and FFmpeg.
Because the project is open source, we use mpv's **default GPL build** — no
`-Dgpl=false`, no LGPL FFmpeg build, and none of the LGPL §6 relinking analysis
that a proprietary app would need. We keep VAAPI/VDPAU/X11 and every GPL FFmpeg
filter.
🔴 Never build FFmpeg with `--enable-nonfree` — that produces a binary that is
**unredistributable under any licence**, open source or not.
### Part 2 — libmpv → libmpv2
```toml
# Linux (and later Windows, per the Windows audio spec)
libmpv2 = "=6.0.0"
```
Pin exactly: `libmpv2` has broken its API in **every** major release.
Breaking changes to expect, from the changelog:
| Version | Change | Impact here |
|---|---|---|
| 4.0.0 | Removed command helper methods — call `mpv.command(...)` directly | Low; we already use `command`/`set_property` |
| 5.0.0 | Removed `mpv_node` support entirely (properties return strings; parse JSON yourself); `EventContext` folded into `Mpv`; `ProtocolContext``Protocol` | **Medium**`start_event_loop` uses `create_event_context()`; check whether that call still exists |
| 6.0.0 | `RenderContext::new()``Mpv::create_render_context()`; `'static` bound on `OpenGLInitParams`; render context now borrows `Mpv` (fixes a use-after-free) | **None** — we do not use the render API |
The last row matters: we run mpv audio-only (`video = no`), so the entire render
surface is irrelevant to us. Consider disabling the default `render` feature to
reduce build surface.
The main porting work is the event loop in `mpv_backend.rs``wait_event`,
`disable_deprecated_events`, and the `FileLoaded` / `PlaybackRestart` /
`PropertyChange` / `EndFile` handling, given 5.0.0 folded `EventContext` into
`Mpv`.
Everything else — `set_property` calls, the `af` filter graph, the 250ms position
thread, the seek-suppression window — should port unchanged.
## Layer assignment
No logic moves. This is a dependency swap plus a licence file; the
`PlayerBackend` trait boundary is untouched.
| Logic / responsibility | Layer | Why it belongs there |
|------------------------|-------|----------------------|
| mpv event → `PlayerStatusEvent` mapping | Rust (unchanged) | Already correct; only the binding API beneath it changes. |
## Out of scope
- Any behaviour change. If playback behaves differently after this, that is a bug.
- Windows support — separate spec, but this must land first.
- Adopting the render API. We are audio-only on mpv.
- Re-licensing decisions beyond adding the file the project already implies.
## Acceptance criteria
- [ ] `LICENSE` (GPLv3) present; `license` field set in `Cargo.toml` and `package.json`.
- [ ] A full dependency-licence audit has been run (`cargo install cargo-license && cargo license`) and confirms no GPLv3-incompatible dependency. *(The scan behind this spec resolved 441 of 575 crates from the local registry cache; the remaining 134 are unverified.)*
- [ ] `libmpv` git dependency removed; `libmpv2` pinned to an exact version.
- [ ] Linux audio playback works identically: play/pause/seek/volume, queue advance, gapless, EQ, normalization, sleep timer.
- [ ] Position updates still arrive at 250ms; the 150ms post-seek suppression still prevents the jump-to-zero glitch.
- [ ] `EndFile` still emits `PlaybackEnded` only for EOF (not STOP/QUIT/ERROR) — autoplay depends on this.
- [ ] Builder image updated if the libmpv dev package requirement changed; **no toolchain install added to any CI step**.
- [ ] `bun run check`, `bun run test`, `bun run check:boundary` pass.
- [ ] `cargo fmt` clean, `cargo clippy` clean, `bun run test:rust` passes.
## Testing
The existing `mpv_backend_test.rs` plus the `build_af_filter`,
`eq_filter_entries`, and `normalize_filter_entry` tests are the regression net —
they must pass unchanged, since none of them touch the binding API.
The event loop has no unit tests and is where the risk concentrates. Verify
manually on Linux:
1. Play → pause → play; confirm position does not flash to 0:00 (the known
playing-event regression).
2. Seek mid-track; confirm no jump-to-zero within 150ms.
3. Let a track end naturally; confirm autoplay advances (exercises `EndFile` EOF).
4. Press stop; confirm autoplay does **not** advance.
5. Sleep-timer expiry; confirm it stops without triggering autoplay.
Cases 35 are the ones most likely to break silently, and each corresponds to a
bug already fixed once in this codebase.
## TRACES
- `MpvBackend` construction / event loop → existing `// TRACES: UR-003 | IR-003`, unchanged
- No new requirement IDs; this is a dependency migration.
## Notes for the implementer
- Do this **before** the Windows audio backend.
- Read the 4.0/5.0/6.0 changelogs before writing code — the crate has broken API
in every major release, most recently two months before this spec.
- The crates.io `repository` field for `libmpv2` points at `kohsine/libmpv-rs`,
but the repo was renamed to **`libmpv2-rs`**; the old raw URLs 404.
- `libmpv2-sys` ships pregenerated bindings and vendored headers, so no libclang
is needed at build time — relevant to keeping the builder image thin.
- A parallel Claude session may be active — `git diff` before "repairing"
unexpected changes.