Skip to main content

mpv_engine/
lib.rs

1//! # mpv-engine
2//!
3//! Toolkit-agnostic core for embedding libmpv: handle lifecycle, commands,
4//! properties, typed playback events, and render seams. Extracted from two
5//! independent embedders — a GTK4 app (GLArea direct-to-FBO) and an iced
6//! widget (side-context + readback/GPU export) — after their engine layers
7//! converged on the same shape.
8//!
9//! What belongs here is decided by one rule: **a quirk lives in this crate
10//! if its consumer-facing interface survives the upstream fix**; anything
11//! whose shape would change with a toolkit's API belongs in that toolkit's
12//! adapter crate. See README for the seam map and roadmap.
13//!
14//! The playback surface covers what a player UI consumes: transport
15//! (load/pause/seek/stop), the mixer set (volume/mute/speed), typed
16//! lifecycle events ([`PlaybackEvent`] — including seek-completion for
17//! scrubber snap and end reasons for playlist logic), and push-based
18//! property observation ([`Engine::observe`]) for state that polling
19//! can't track well (duration becoming known, external pause flips, buffering,
20//! video dimensions). Everything else goes through the property/command
21//! escape hatches — which speak crate-owned types ([`PropertyValue`],
22//! the sealed [`PropertyGet`]), so the underlying binding's traits never
23//! enter this crate's public API.
24//!
25//! Two render backends share one attach slot: OpenGL
26//! ([`Engine::attach_gl_render`] / [`Engine::render_gl`]) and software
27//! ([`Engine::attach_sw_render`] / [`Engine::render_sw`], RGBA into a
28//! caller buffer, no GL anywhere). GL attach takes [`GlRenderOptions`]
29//! to fix the shell's render-loop discipline: whether `render_gl` blocks
30//! until the frame's target time (right for a toolkit paint handler,
31//! wrong on a compositor thread), and mpv's advanced control (which
32//! obligates [`Engine::render_update`] after every update callback). Event delivery is pull-based
33//! ([`Engine::pump_events`]) with an optional push signal
34//! ([`Engine::set_wakeup_callback`]) for shells that don't want a
35//! polling timer.
36//!
37//! Quirks this crate owns so consumers don't have to rediscover them:
38//! - `LC_NUMERIC=C` forced before `mpv_create` (toolkits setlocale behind
39//!   your back; mpv's number parsing breaks under comma-decimal locales).
40//! - Render context freed strictly before the mpv handle — structural
41//!   since rsmpv 0.2 (the context co-owns the core) — and, for OpenGL,
42//!   only with the GL context current: an obligation
43//!   [`Engine::attach_gl_render`] carries as its `unsafe` contract (see
44//!   [`Engine::detach_render`]).
45//! - Commands pass pre-tokenized argument arrays (`mpv_command`), so paths
46//!   with spaces/quotes need no escaping — pinned by a regression test.
47//! - Errored end-of-file surfaces as a typed
48//!   [`PlaybackEvent::Failed`] carrying the raw `mpv_error` code (for
49//!   integrators mapping to their own error copy) and a diagnostic
50//!   message (mpv's `mpv_error_string` text plus the numeric code).
51//! - The update callback's threading contract (mpv render thread, plus one
52//!   synchronous call at registration) is documented at the seam.
53//!
54//! No toolkit dependencies, no git dependencies, no main-loop opinions:
55//! events are *pumped*, and the update callback is `Send + Sync` —
56//! bridging to a main loop is the shell's job, because that part is
57//! toolkit-shaped.
58
59#![warn(missing_docs)]
60
61mod engine;
62mod error;
63mod render;
64
65pub use engine::{
66    EndReason, Engine, EngineBuilder, ObserveId, PlaybackEvent, PropertyFormat, PropertyGet,
67    PropertyValue,
68};
69pub use error::{Error, Result};
70pub use render::{GlRenderOptions, ProcAddressFn};