Files
jellytau/docs/specs/libmpv2-migration.md
T
dtourolle b11188e9dd docs(player): backend unification findings + correct false parity claims
Investigation into unifying the playback backends (Linux/MPV, Android/ExoPlayer,
Windows/webview) onto one engine with hardware acceleration. Conclusion: video
cannot be unified onto a native engine; audio can.

The blocker is not mpv-specific. WebKitGTK, WebView2 and Android WebView each
draw into their own compositor surface, so a native video surface sits either
entirely above or entirely below the webview and cannot interleave with HTML.
GStreamer and libVLC fail identically. mpv would additionally regress streaming:
it has no adaptive bitrate, while the current hls.js path does.

Six specs added:
- playback-backend-unification: the analysis and decision, with evidence
- android-audio-settings-parity: set_audio_settings on ExoPlayerBackend
- android-native-video-spike: timeboxed test of SurfaceView compositing
- windows-native-audio-backend: replace the webview <audio> shim with libmpv
- libmpv2-migration: dead libmpv git pin -> libmpv2, plus a LICENSE file
- playback-docs-corrections: the requirement-status fixes applied here

Corrections to requirements.md, all verified against source:
- UR-031/DR-034 claimed crossfade was "Done (Linux only)". It is implemented
  nowhere (mpv_backend.rs has a bare TODO) and is architecturally blocked on
  mpv, whose single-stream audio chain cannot feed acrossfade's two inputs.
- Parity matrix listed crossfade as a Linux/Android gap; it is neither.
- The matrix omitted the equalizer, which has the same Linux-only shape.
- The suggested ConcatenatingMediaSource is deprecated in current Media3.

nativeAdapter.ts cited tauri#10152 as an upstream blocker for native Android
video. That issue is a stale feature request, dead since 2024-07-01; the
capability shipped in tauri 27d01834 (2024-09-02), and the related
black-screen bug was fixed in wry 0.39.4 (we ship 0.55.x). What is genuinely
unproven is SurfaceView-behind-WebView compositing, which the spike now tracks.
2026-07-28 23:03:17 +02:00

7.8 KiB
Raw Blame History

Spec: Migrate to libmpv2 and declare the project licence

Status: Proposed 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.