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.
170 lines
7.8 KiB
Markdown
170 lines
7.8 KiB
Markdown
# 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](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.
|