# 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.