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.
172 lines
7.9 KiB
Markdown
172 lines
7.9 KiB
Markdown
# 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 3–5 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.
|