ADR 0001: Playback engine (libmpv)
Why NOVA plays media through libmpv.
As writtenPublished unedited from the NOVA repository. Links to other repository files open on GitHub.
- Status: Accepted (M0, 2026-10-15)
- Deciders: NOVA engineering
Context
NOVA must play a broad range of local media reliably on Windows first, without writing a decoder. The handoff specifies a hybrid architecture: a backend-neutral playback API with libmpv as the initial backend.
Decision
crates/nova-playback-apidefines the contract:PlaybackCommand,PlaybackSnapshot,PlaybackError,Capabilities, thePlaybackBackendtrait and a pure state reducer (PlaybackState). It depends only onserdeandthiserror.crates/nova-playback-mpvimplements the contract with libmpv.- libmpv is loaded at runtime (
libloading) with hand-written bindings for ~17 functions ofclient.h(API 2.x), rather than linked via thelibmpv2crate. - The phase is derived from observed engine facts (file-loaded, playback-restart, seek, pause, eof, end-file). The UI never infers state; commands return only an acknowledgement and results arrive as snapshots.
- Every
Opencreates a session id. libmpvplaylist_entry_ids are mapped to sessions under a lock held acrossloadfile, so stale events from a superseded file are discarded by the reducer.
Why runtime loading instead of libmpv2
- The available Windows builds are MinGW; linking from MSVC needs a generated import library. Runtime loading needs nothing at build time.
- A missing/incompatible DLL becomes a typed
EngineUnavailableerror shown in the UI instead of a process that fails to start. - The engine stays a separate, replaceable file, which matters for the licensing options in ADR 0003.
- The surface is small (verified against the vendored
client.h,MPV_CLIENT_API_VERSION 2.5); the API major version is checked at load.
Trade-off: we own the unsafe FFI. It is confined to ffi.rs/node.rs and
covered by the integration suite against the real library.
Engine isolation settings
config=no, load-scripts=no, ytdl=no, osc=no, input-default-bindings=no,
input-vo-keyboard=no, terminal=no: a user’s mpv configuration, scripts or
network helpers can never change NOVA’s behaviour. Only local file paths are
accepted (MediaSource), and commands are passed as argument arrays
(mpv_command), never parsed command strings.
Consequences
- Future backends must pass the same contract tests
(
crates/nova-playback-mpv/tests/engine.rsis the seed of that suite). - mpv’s
keep-open=yesis used: at end of media the last frame stays and the phase isEnded;PlayatEndedrestarts from 0.