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.
7.9 KiB
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
Summary
Two related pieces of housekeeping that block or complicate later work:
- Replace the abandoned
libmpvcrate (pinned to a git branch) with the maintainedlibmpv2. - Add a
LICENSEfile. 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.
Motivation
The dependency is dead
# src-tauri/Cargo.toml
libmpv = { git = "https://github.com/ParadoxSpiral/libmpv-rs.git", branch = "master" }
- crates.io
libmpv2.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), plussync_wrapper,gethostname, andring(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
LICENSEcontaining the GPLv3 text. - Add
license = "GPL-3.0-or-later"tosrc-tauri/Cargo.tomlandlicensetopackage.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
# 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;licensefield set inCargo.tomlandpackage.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.) libmpvgit dependency removed;libmpv2pinned 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.
EndFilestill emitsPlaybackEndedonly 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:boundarypass.cargo fmtclean,cargo clippyclean,bun run test:rustpasses.
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:
- Play → pause → play; confirm position does not flash to 0:00 (the known playing-event regression).
- Seek mid-track; confirm no jump-to-zero within 150ms.
- Let a track end naturally; confirm autoplay advances (exercises
EndFileEOF). - Press stop; confirm autoplay does not advance.
- 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
MpvBackendconstruction / 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
repositoryfield forlibmpv2points atkohsine/libmpv-rs, but the repo was renamed tolibmpv2-rs; the old raw URLs 404. libmpv2-sysships 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 diffbefore "repairing" unexpected changes.