Files
jellytau/docs/specs/libmpv2-migration.md
T
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

7.9 KiB
Raw Permalink Blame History

Spec: Migrate to libmpv2 and declare the project licence

Status: Partially implemented — the LICENSE file has landed (part 2). The libmpvlibmpv2 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:

  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.

Motivation

The dependency is dead

# 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

# 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; ProtocolContextProtocol Mediumstart_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.rswait_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.