Skip to main content

mpv_client_cross/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5mod error;
6mod format;
7pub mod node;
8
9pub use error::{Error, Result};
10pub use format::Format;
11pub use node::Node;
12
13use std::ffi::{CStr, CString, c_char, c_void};
14use std::fmt;
15use std::mem::MaybeUninit;
16use std::ops::{Deref, DerefMut};
17use std::ptr::slice_from_raw_parts_mut;
18
19pub use ffi::mpv_handle;
20use ffi::*;
21
22use crate::node::from_mpv_node;
23
24/// Representation of a borrowed client context used by the client API.
25/// Every client has its own private handle.
26pub struct Handle {
27    inner: [mpv_handle],
28}
29
30/// A type representing an owned client context.
31pub struct Client(*mut mpv_handle);
32
33/// An enum representing the available events that can be received by
34/// `Handle::wait_event`.
35pub enum Event {
36    /// Nothing happened. Happens on timeouts or sporadic wakeups.
37    None,
38    /// Happens when the player quits. The player enters a state where it tries
39    /// to disconnect all clients.
40    Shutdown,
41    /// See `Handle::request_log_messages`.
42    /// See also `LogMessage`.
43    LogMessage(LogMessage),
44    /// Reply to a `Handle::get_property_async` request.
45    /// See also `Property`.
46    GetPropertyReply(Result<()>, u64, Property),
47    /// Reply to a `Handle::set_property_async` request.
48    /// (Unlike `GetPropertyReply`, `Property` is not used.)
49    SetPropertyReply(Result<()>, u64),
50    /// Reply to a `Handle::command_async` or mpv_command_node_async() request.
51    /// See also `Command`.
52    CommandReply(Result<()>, u64), // TODO mpv_event_command and mpv_node
53    /// Notification before playback start of a file (before the file is loaded).
54    /// See also `StartFile`.
55    StartFile(StartFile),
56    /// Notification after playback end (after the file was unloaded).
57    /// See also `EndFile`.
58    EndFile(EndFile),
59    /// Notification when the file has been loaded (headers were read etc.), and
60    /// decoding starts.
61    FileLoaded,
62    /// Triggered by the script-message input command. The command uses the
63    /// first argument of the command as client name (see `Handle::client_name`) to
64    /// dispatch the message, and passes along all arguments starting from the
65    /// second argument as strings.
66    /// See also `ClientMessage`.
67    ClientMessage(ClientMessage),
68    /// Happens after video changed in some way. This can happen on resolution
69    /// changes, pixel format changes, or video filter changes. The event is
70    /// sent after the video filters and the VO are reconfigured. Applications
71    /// embedding a mpv window should listen to this event in order to resize
72    /// the window if needed.
73    /// Note that this event can happen sporadically, and you should check
74    /// yourself whether the video parameters really changed before doing
75    /// something expensive.
76    VideoReconfig,
77    /// Similar to `VideoReconfig`. This is relatively uninteresting,
78    /// because there is no such thing as audio output embedding.
79    AudioReconfig,
80    /// Happens when a seek was initiated. Playback stops. Usually it will
81    /// resume with `PlaybackRestart` as soon as the seek is finished.
82    Seek,
83    /// There was a discontinuity of some sort (like a seek), and playback
84    /// was reinitialized. Usually happens on start of playback and after
85    /// seeking. The main purpose is allowing the client to detect when a seek
86    /// request is finished.
87    PlaybackRestart,
88    /// Event sent due to `mpv_observe_property()`.
89    /// See also `Property`.
90    PropertyChange(u64, Property),
91    /// Happens if the internal per-mpv_handle ringbuffer overflows, and at
92    /// least 1 event had to be dropped. This can happen if the client doesn't
93    /// read the event queue quickly enough with `Handle::wait_event`, or if the
94    /// client makes a very large number of asynchronous calls at once.
95    ///
96    /// Event delivery will continue normally once this event was returned
97    /// (this forces the client to empty the queue completely).
98    QueueOverflow,
99    /// Triggered if a hook handler was registered with `Handle::hook_add`, and the
100    /// hook is invoked. If you receive this, you must handle it, and continue
101    /// the hook with `Handle::hook_continue`.
102    /// See also `Hook`.
103    Hook(u64, Hook),
104}
105
106/// Data associated with `Event::GetPropertyReply` and `Event::PropertyChange`.
107pub struct Property(*const mpv_event_property);
108
109/// Data associated with `Event::LogMessage`.
110pub struct LogMessage(*const mpv_event_log_message);
111
112/// Data associated with `Event::StartFile`.
113pub struct StartFile(*const mpv_event_start_file);
114
115/// Data associated with `Event::EndFile`.
116pub struct EndFile(*const mpv_event_end_file);
117
118/// Data associated with `Event::ClientMessage`.
119pub struct ClientMessage(*const mpv_event_client_message);
120
121/// Data associated with `Event::Hook`.
122pub struct Hook(*const mpv_event_hook);
123
124macro_rules! result {
125    ($f:expr) => {
126        match $f {
127            mpv_error_MPV_ERROR_SUCCESS => Ok(()),
128            e => Err(Error::new(e)),
129        }
130    };
131}
132
133macro_rules! result_with_code {
134    ($f:expr) => {
135        if $f >= mpv_error_MPV_ERROR_SUCCESS {
136            Ok($f)
137        } else {
138            Err(Error::new($f))
139        }
140    };
141}
142
143#[macro_export]
144macro_rules! osd {
145    ($client:expr, $duration:expr, $($arg:tt)*) => {
146        $client.command(&["show-text", &format!($($arg)*), &$duration.as_millis().to_string()])
147    }
148}
149
150#[macro_export]
151macro_rules! osd_async {
152    ($client:expr, $reply:expr, $duration:expr, $($arg:tt)*) => {
153        $client.command_async($reply, &["show-text", &format!($($arg)*), &$duration.as_millis().to_string()])
154    }
155}
156
157impl Handle {
158    /// Wrap a raw mpv_handle
159    ///
160    /// This function will wrap the provided `ptr` with a `Handle` wrapper, which
161    /// allows inspection and interoperation of non-owned `mpv_handle`.
162    ///
163    /// # Safety
164    ///
165    /// * `ptr` must be non null.
166    ///
167    /// * The memory referenced by the returned `Handle` must not be freed for
168    ///   the duration of lifetime `'a`.
169    #[inline]
170    pub fn from_ptr<'a>(ptr: *mut mpv_handle) -> &'a mut Self {
171        unsafe { &mut *(slice_from_raw_parts_mut(ptr, 1) as *mut Self) }
172    }
173
174    /// # Safety
175    ///
176    /// `Handle` must have been created from a valid, non-null `mpv_handle` pointer.
177    #[inline]
178    pub unsafe fn as_ptr(&self) -> *const mpv_handle {
179        self.inner.as_ptr()
180    }
181
182    /// # Safety
183    ///
184    /// `Handle` must have been created from a valid, non-null `mpv_handle` pointer,
185    /// and there must be no other active mutable references to the underlying handle.
186    #[inline]
187    pub unsafe fn as_mut_ptr(&mut self) -> *mut mpv_handle {
188        self.inner.as_mut_ptr()
189    }
190
191    pub fn create_client(&mut self, name: impl AsRef<str>) -> Result<Client> {
192        let name = CString::new(name.as_ref())?;
193        let handle = unsafe { mpv_create_client(self.as_mut_ptr(), name.as_ptr()) };
194        if handle.is_null() {
195            Err(Error::new(mpv_error_MPV_ERROR_NOMEM))
196        } else {
197            Ok(Client(handle))
198        }
199    }
200
201    pub fn create_weak_client(&mut self, name: impl AsRef<str>) -> Result<Client> {
202        let name = CString::new(name.as_ref())?;
203        let handle = unsafe { mpv_create_weak_client(self.as_mut_ptr(), name.as_ptr()) };
204        if handle.is_null() {
205            Err(Error::new(mpv_error_MPV_ERROR_NOMEM))
206        } else {
207            Ok(Client(handle))
208        }
209    }
210
211    pub fn initialize(&mut self) -> Result<()> {
212        unsafe { result!(mpv_initialize(self.as_mut_ptr())) }
213    }
214
215    /// Wait for the next event, or until the timeout expires, or if another thread
216    /// makes a call to `mpv_wakeup()`. Passing 0 as timeout will never wait, and
217    /// is suitable for polling.
218    ///
219    /// The internal event queue has a limited size (per client handle). If you
220    /// don't empty the event queue quickly enough with `Handle::wait_event`, it will
221    /// overflow and silently discard further events. If this happens, making
222    /// asynchronous requests will fail as well (with MPV_ERROR_EVENT_QUEUE_FULL).
223    ///
224    /// Only one thread is allowed to call this on the same `Handle` at a time.
225    /// The API won't complain if more than one thread calls this, but it will cause
226    /// race conditions in the client when accessing the shared mpv_event struct.
227    /// Note that most other API functions are not restricted by this, and no API
228    /// function internally calls `mpv_wait_event()`. Additionally, concurrent calls
229    /// to different handles are always safe.
230    ///
231    /// As long as the timeout is 0, this is safe to be called from mpv render API
232    /// threads.
233    pub fn wait_event(&mut self, timeout: f64) -> Event {
234        unsafe { Event::from_ptr(mpv_wait_event(self.as_mut_ptr(), timeout)) }
235    }
236
237    /// Return the name of this client handle. Every client has its own unique
238    /// name, which is mostly used for user interface purposes.
239    pub fn name<'a>(&mut self) -> &'a str {
240        unsafe {
241            CStr::from_ptr(mpv_client_name(self.as_mut_ptr()))
242                .to_str()
243                .unwrap_or("unknown")
244        }
245    }
246
247    /// Return the ID of this client handle. Every client has its own unique ID. This
248    /// ID is never reused by the core, even if the mpv_handle at hand gets destroyed
249    /// and new handles get allocated.
250    ///
251    /// IDs are never 0 or negative.
252    ///
253    /// Some mpv APIs (not necessarily all) accept a name in the form "@<id>" in
254    /// addition of the proper mpv_client_name(), where "<id>" is the ID in decimal
255    /// form (e.g. "@123"). For example, the "script-message-to" command takes the
256    /// client name as first argument, but also accepts the client ID formatted in
257    /// this manner.
258    #[inline]
259    pub fn id(&mut self) -> i64 {
260        unsafe { mpv_client_id(self.as_mut_ptr()) }
261    }
262
263    /// Send a command to the player. Commands are the same as those used in
264    /// input.conf, except that this function takes parameters in a pre-split
265    /// form.
266    pub fn command<I, S>(&mut self, args: I) -> Result<()>
267    where
268        I: IntoIterator<Item = S>,
269        S: AsRef<str>,
270    {
271        let args: Vec<CString> = args.into_iter().map(|s| CString::new(s.as_ref()).unwrap()).collect();
272        let mut raw_args: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
273        raw_args.push(std::ptr::null()); // Adding null at the end
274        unsafe { result!(mpv_command(self.as_mut_ptr(), raw_args.as_mut_ptr())) }
275    }
276
277    pub fn command_ret<I, S>(&mut self, args: I) -> Result<Node>
278    where
279        I: IntoIterator<Item = S>,
280        S: AsRef<str>,
281    {
282        let args: Vec<CString> = args.into_iter().map(|s| CString::new(s.as_ref()).unwrap()).collect();
283        let mut raw_args: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
284        raw_args.push(std::ptr::null()); // Adding null at the end
285
286        let mut res = MaybeUninit::<mpv_node>::zeroed();
287        let ret = unsafe { mpv_command_ret(self.as_mut_ptr(), raw_args.as_mut_ptr(), res.as_mut_ptr()) };
288
289        result!(ret)?;
290        unsafe { Ok(from_mpv_node(res.assume_init_mut())) }
291    }
292
293    /// Same as `Handle::command`, but run the command asynchronously.
294    ///
295    /// Commands are executed asynchronously. You will receive a
296    /// `CommandReply` event. This event will also have an
297    /// error code set if running the command failed. For commands that
298    /// return data, the data is put into mpv_event_command.result.
299    ///
300    /// The only case when you do not receive an event is when the function call
301    /// itself fails. This happens only if parsing the command itself (or otherwise
302    /// validating it) fails, i.e. the return code of the API call is not 0 or
303    /// positive.
304    ///
305    /// Safe to be called from mpv render API threads.
306    pub fn command_async<I, S>(&mut self, reply: u64, args: I) -> Result<()>
307    where
308        I: IntoIterator<Item = S>,
309        S: AsRef<str>,
310    {
311        let args: Vec<CString> = args.into_iter().map(|s| CString::new(s.as_ref()).unwrap()).collect();
312        let mut raw_args: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
313        raw_args.push(std::ptr::null()); // Adding null at the end
314        unsafe { result!(mpv_command_async(self.as_mut_ptr(), reply, raw_args.as_mut_ptr())) }
315    }
316
317    pub fn set_property<T: Format>(&mut self, name: impl AsRef<str>, data: T) -> Result<()> {
318        let name = CString::new(name.as_ref())?;
319        let handle = unsafe { self.as_mut_ptr() };
320        data.to_mpv(|data| unsafe { result!(mpv_set_property(handle, name.as_ptr(), T::MPV_FORMAT, data)) })
321    }
322
323    /// Read the value of the given property.
324    ///
325    /// If the format doesn't match with the internal format of the property, access
326    /// usually will fail with `MPV_ERROR_PROPERTY_FORMAT`. In some cases, the data
327    /// is automatically converted and access succeeds. For example, i64 is always
328    /// converted to f64, and access using String usually invokes a string formatter.
329    pub fn get_property<T: Format>(&mut self, name: impl AsRef<str>) -> Result<T> {
330        let name = CString::new(name.as_ref())?;
331        let handle = unsafe { self.as_mut_ptr() };
332        T::from_mpv(|data| unsafe { result!(mpv_get_property(handle, name.as_ptr(), T::MPV_FORMAT, data)) })
333    }
334
335    pub fn observe_property<T: Format>(&mut self, reply: u64, name: impl AsRef<str>) -> Result<()> {
336        let name = CString::new(name.as_ref())?;
337        unsafe {
338            result!(mpv_observe_property(
339                self.as_mut_ptr(),
340                reply,
341                name.as_ptr(),
342                T::MPV_FORMAT
343            ))
344        }
345    }
346
347    /// Undo `Handle::observe_property`. This will remove all observed properties for
348    /// which the given number was passed as reply to `Handle::observe_property`.
349    ///
350    /// Safe to be called from mpv render API threads.
351    pub fn unobserve_property(&mut self, registered_reply: u64) -> Result<i32> {
352        unsafe { result_with_code!(mpv_unobserve_property(self.as_mut_ptr(), registered_reply)) }
353    }
354
355    pub fn hook_add(&mut self, reply: u64, name: &str, priority: i32) -> Result<()> {
356        let name = CString::new(name)?;
357        unsafe { result!(mpv_hook_add(self.as_mut_ptr(), reply, name.as_ptr(), priority)) }
358    }
359
360    pub fn hook_continue(&mut self, id: u64) -> Result<()> {
361        unsafe { result!(mpv_hook_continue(self.as_mut_ptr(), id)) }
362    }
363}
364
365impl Client {
366    pub fn new() -> Result<Self> {
367        let handle = unsafe { mpv_create() };
368        if handle.is_null() {
369            Err(Error::new(mpv_error_MPV_ERROR_NOMEM))
370        } else {
371            Ok(Self(handle))
372        }
373    }
374
375    pub fn initialize(self) -> Result<Self> {
376        unsafe { result!(mpv_initialize(self.0)).map(|()| self) }
377    }
378}
379
380impl Drop for Client {
381    fn drop(&mut self) {
382        unsafe { mpv_destroy(self.0) }
383    }
384}
385
386impl Deref for Client {
387    type Target = Handle;
388
389    #[inline]
390    fn deref(&self) -> &Self::Target {
391        Handle::from_ptr(self.0)
392    }
393}
394
395impl DerefMut for Client {
396    #[inline]
397    fn deref_mut(&mut self) -> &mut Self::Target {
398        Handle::from_ptr(self.0)
399    }
400}
401
402unsafe impl Send for Client {}
403
404impl Event {
405    unsafe fn from_ptr(event: *const mpv_event) -> Event {
406        unsafe {
407            match (*event).event_id {
408                mpv_event_id_MPV_EVENT_SHUTDOWN => Event::Shutdown,
409                mpv_event_id_MPV_EVENT_LOG_MESSAGE => Event::LogMessage(LogMessage::from_ptr((*event).data)),
410                mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY => Event::GetPropertyReply(
411                    result!((*event).error),
412                    (*event).reply_userdata,
413                    Property::from_ptr((*event).data),
414                ),
415                mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY => {
416                    Event::SetPropertyReply(result!((*event).error), (*event).reply_userdata)
417                }
418                mpv_event_id_MPV_EVENT_COMMAND_REPLY => {
419                    Event::CommandReply(result!((*event).error), (*event).reply_userdata)
420                }
421                mpv_event_id_MPV_EVENT_START_FILE => Event::StartFile(StartFile::from_ptr((*event).data)),
422                mpv_event_id_MPV_EVENT_END_FILE => Event::EndFile(EndFile::from_ptr((*event).data)),
423                mpv_event_id_MPV_EVENT_FILE_LOADED => Event::FileLoaded,
424                mpv_event_id_MPV_EVENT_CLIENT_MESSAGE => Event::ClientMessage(ClientMessage::from_ptr((*event).data)),
425                mpv_event_id_MPV_EVENT_VIDEO_RECONFIG => Event::VideoReconfig,
426                mpv_event_id_MPV_EVENT_AUDIO_RECONFIG => Event::AudioReconfig,
427                mpv_event_id_MPV_EVENT_SEEK => Event::Seek,
428                mpv_event_id_MPV_EVENT_PLAYBACK_RESTART => Event::PlaybackRestart,
429                mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => {
430                    Event::PropertyChange((*event).reply_userdata, Property::from_ptr((*event).data))
431                }
432                mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW => Event::QueueOverflow,
433                mpv_event_id_MPV_EVENT_HOOK => Event::Hook((*event).reply_userdata, Hook::from_ptr((*event).data)),
434                _ => Event::None,
435            }
436        }
437    }
438}
439
440impl fmt::Display for Event {
441    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
442        let event = match *self {
443            Self::Shutdown => mpv_event_id_MPV_EVENT_SHUTDOWN,
444            Self::LogMessage(..) => mpv_event_id_MPV_EVENT_LOG_MESSAGE,
445            Self::GetPropertyReply(..) => mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY,
446            Self::SetPropertyReply(..) => mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY,
447            Self::CommandReply(..) => mpv_event_id_MPV_EVENT_COMMAND_REPLY,
448            Self::StartFile(..) => mpv_event_id_MPV_EVENT_START_FILE,
449            Self::EndFile(..) => mpv_event_id_MPV_EVENT_END_FILE,
450            Self::FileLoaded => mpv_event_id_MPV_EVENT_FILE_LOADED,
451            Self::ClientMessage(..) => mpv_event_id_MPV_EVENT_CLIENT_MESSAGE,
452            Self::VideoReconfig => mpv_event_id_MPV_EVENT_VIDEO_RECONFIG,
453            Self::AudioReconfig => mpv_event_id_MPV_EVENT_AUDIO_RECONFIG,
454            Self::Seek => mpv_event_id_MPV_EVENT_SEEK,
455            Self::PlaybackRestart => mpv_event_id_MPV_EVENT_PLAYBACK_RESTART,
456            Self::PropertyChange(..) => mpv_event_id_MPV_EVENT_PROPERTY_CHANGE,
457            Self::QueueOverflow => mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW,
458            Self::Hook(..) => mpv_event_id_MPV_EVENT_HOOK,
459            _ => mpv_event_id_MPV_EVENT_NONE,
460        };
461
462        f.write_str(unsafe {
463            CStr::from_ptr(mpv_event_name(event))
464                .to_str()
465                .unwrap_or("unknown event")
466        })
467    }
468}
469
470impl Property {
471    /// Wrap a raw mpv_event_property
472    /// The pointer must not be null
473    fn from_ptr(ptr: *const c_void) -> Self {
474        assert!(!ptr.is_null());
475        Self(ptr as *const mpv_event_property)
476    }
477
478    /// Name of the property.
479    pub fn name(&self) -> &str {
480        unsafe { CStr::from_ptr((*self.0).name) }.to_str().unwrap_or("unknown")
481    }
482
483    pub fn data<T: Format>(&self) -> Option<T> {
484        unsafe {
485            if (*self.0).format == T::MPV_FORMAT {
486                T::from_ptr((*self.0).data).ok()
487            } else {
488                None
489            }
490        }
491    }
492}
493
494impl fmt::Display for Property {
495    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496        f.write_str(self.name())
497    }
498}
499
500impl LogMessage {
501    /// Wrap a raw mpv_event_log_message
502    /// The pointer must not be null
503    fn from_ptr(ptr: *const c_void) -> Self {
504        assert!(!ptr.is_null());
505        Self(ptr as *const mpv_event_log_message)
506    }
507}
508
509impl fmt::Display for LogMessage {
510    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511        f.write_str("log message")
512    }
513}
514
515impl StartFile {
516    /// Wrap a raw mpv_event_start_file
517    /// The pointer must not be null
518    fn from_ptr(ptr: *const c_void) -> Self {
519        assert!(!ptr.is_null());
520        Self(ptr as *const mpv_event_start_file)
521    }
522
523    /// Playlist entry ID of the file being loaded now.
524    pub fn playlist_entry_id(&self) -> i64 {
525        unsafe { (*self.0).playlist_entry_id }
526    }
527}
528
529impl fmt::Display for StartFile {
530    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
531        f.write_str("start file")
532    }
533}
534
535impl EndFile {
536    /// Wrap a raw mpv_event_end_file
537    /// The pointer must not be null
538    fn from_ptr(ptr: *const c_void) -> Self {
539        assert!(!ptr.is_null());
540        Self(ptr as *const mpv_event_end_file)
541    }
542}
543
544impl fmt::Display for EndFile {
545    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
546        f.write_str("end file")
547    }
548}
549
550impl ClientMessage {
551    /// Wrap a raw mpv_event_client_message.
552    /// The pointer must not be null
553    fn from_ptr(ptr: *const c_void) -> Self {
554        assert!(!ptr.is_null());
555        Self(ptr as *const mpv_event_client_message)
556    }
557
558    pub fn args<'a>(&self) -> Vec<&'a str> {
559        unsafe {
560            let args = std::slice::from_raw_parts((*self.0).args, (*self.0).num_args as usize);
561            args.iter().map(|arg| CStr::from_ptr(*arg).to_str().unwrap()).collect()
562        }
563    }
564}
565
566impl fmt::Display for ClientMessage {
567    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
568        f.write_str("client-message")
569    }
570}
571
572impl Hook {
573    /// Wrap a raw mpv_event_hook.
574    /// The pointer must not be null
575    fn from_ptr(ptr: *const c_void) -> Self {
576        assert!(!ptr.is_null());
577        Self(ptr as *const mpv_event_hook)
578    }
579
580    /// The hook name as passed to `Handle::hook_add`.
581    pub fn name(&self) -> &str {
582        unsafe { CStr::from_ptr((*self.0).name).to_str().unwrap_or("unknown") }
583    }
584
585    /// Internal ID that must be passed to `Handle::hook_continue`.
586    pub fn id(&self) -> u64 {
587        unsafe { (*self.0).id }
588    }
589}
590
591impl fmt::Display for Hook {
592    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
593        f.write_str(self.name())
594    }
595}