mpv_client_dyn/
lib.rs

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