libmpv/mpv/
events.rs

1// Copyright (C) 2016  ParadoxSpiral
2//
3// This file is part of libmpv-rs.
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; either
8// version 2.1 of the License, or (at your option) any later version.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
19use libmpv_sys::mpv_event;
20
21use crate::{mpv::mpv_err, *};
22
23use std::ffi::CString;
24use std::marker::PhantomData;
25use std::os::raw as ctype;
26use std::ptr::NonNull;
27use std::slice;
28use std::sync::atomic::Ordering;
29
30/// An `Event`'s ID.
31pub use libmpv_sys::mpv_event_id as EventId;
32pub mod mpv_event_id {
33    pub use libmpv_sys::mpv_event_id_MPV_EVENT_AUDIO_RECONFIG as AudioReconfig;
34    pub use libmpv_sys::mpv_event_id_MPV_EVENT_CLIENT_MESSAGE as ClientMessage;
35    pub use libmpv_sys::mpv_event_id_MPV_EVENT_COMMAND_REPLY as CommandReply;
36    pub use libmpv_sys::mpv_event_id_MPV_EVENT_END_FILE as EndFile;
37    pub use libmpv_sys::mpv_event_id_MPV_EVENT_FILE_LOADED as FileLoaded;
38    pub use libmpv_sys::mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY as GetPropertyReply;
39    pub use libmpv_sys::mpv_event_id_MPV_EVENT_HOOK as Hook;
40    pub use libmpv_sys::mpv_event_id_MPV_EVENT_LOG_MESSAGE as LogMessage;
41    pub use libmpv_sys::mpv_event_id_MPV_EVENT_NONE as None;
42    pub use libmpv_sys::mpv_event_id_MPV_EVENT_PLAYBACK_RESTART as PlaybackRestart;
43    pub use libmpv_sys::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE as PropertyChange;
44    pub use libmpv_sys::mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW as QueueOverflow;
45    pub use libmpv_sys::mpv_event_id_MPV_EVENT_SEEK as Seek;
46    pub use libmpv_sys::mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY as SetPropertyReply;
47    pub use libmpv_sys::mpv_event_id_MPV_EVENT_SHUTDOWN as Shutdown;
48    pub use libmpv_sys::mpv_event_id_MPV_EVENT_START_FILE as StartFile;
49    pub use libmpv_sys::mpv_event_id_MPV_EVENT_TICK as Tick;
50    pub use libmpv_sys::mpv_event_id_MPV_EVENT_VIDEO_RECONFIG as VideoReconfig;
51}
52
53impl Mpv {
54    /// Create a context that can be used to wait for events and control which events are listened
55    /// for.
56    ///
57    /// # Panics
58    /// Panics if a context already exists
59    pub fn create_event_context(&self) -> EventContext {
60        match self
61            .events_guard
62            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
63        {
64            Ok(_) => EventContext {
65                ctx: self.ctx,
66                _does_not_outlive: PhantomData::<&Self>,
67            },
68            Err(_) => panic!("Event context already exists"),
69        }
70    }
71}
72
73#[derive(Debug)]
74/// Data that is returned by both `GetPropertyReply` and `PropertyChange` events.
75pub enum PropertyData<'a> {
76    Str(&'a str),
77    OsdStr(&'a str),
78    Flag(bool),
79    Int64(i64),
80    Double(ctype::c_double),
81    Node(&'a MpvNode),
82}
83
84impl<'a> PropertyData<'a> {
85    // SAFETY: meant to extract the data from an event property. See `mpv_event_property` in
86    // `client.h`
87    unsafe fn from_raw(format: MpvFormat, ptr: *mut ctype::c_void) -> Result<PropertyData<'a>> {
88        assert!(!ptr.is_null());
89        match format {
90            mpv_format::Flag => Ok(PropertyData::Flag(*(ptr as *mut bool))),
91            mpv_format::String => {
92                let char_ptr = *(ptr as *mut *mut ctype::c_char);
93                Ok(PropertyData::Str(mpv_cstr_to_str!(char_ptr)?))
94            }
95            mpv_format::OsdString => {
96                let char_ptr = *(ptr as *mut *mut ctype::c_char);
97                Ok(PropertyData::OsdStr(mpv_cstr_to_str!(char_ptr)?))
98            }
99            mpv_format::Double => Ok(PropertyData::Double(*(ptr as *mut f64))),
100            mpv_format::Int64 => Ok(PropertyData::Int64(*(ptr as *mut i64))),
101            mpv_format::Node => Ok(PropertyData::Node(&*(ptr as *mut MpvNode))),
102            mpv_format::None => unreachable!(),
103            _ => unimplemented!(),
104        }
105    }
106}
107
108#[derive(Debug)]
109pub enum Event<'a> {
110    /// Received when the player is shutting down
111    Shutdown,
112    /// *Has not been tested*, received when explicitly asked to MPV
113    LogMessage {
114        prefix: &'a str,
115        level: &'a str,
116        text: &'a str,
117        log_level: LogLevel,
118    },
119    /// Received when using get_property_async
120    GetPropertyReply {
121        name: &'a str,
122        result: PropertyData<'a>,
123        reply_userdata: u64,
124    },
125    /// Received when using set_property_async
126    SetPropertyReply(u64),
127    /// Received when using command_async
128    CommandReply(u64),
129    /// Event received when a new file is playing
130    StartFile,
131    /// Event received when the file being played currently has stopped, for an error or not
132    EndFile(EndFileReason),
133    /// Event received when a file has been *loaded*, but has not been started
134    FileLoaded,
135    ClientMessage(Vec<&'a str>),
136    VideoReconfig,
137    AudioReconfig,
138    /// The player changed current position
139    Seek,
140    PlaybackRestart,
141    /// Received when used with observe_property
142    PropertyChange {
143        name: &'a str,
144        change: PropertyData<'a>,
145        reply_userdata: u64,
146    },
147    /// Received when the Event Queue is full
148    QueueOverflow,
149    /// A deprecated event
150    Deprecated(mpv_event),
151}
152
153/// Context to listen to events.
154pub struct EventContext<'parent> {
155    ctx: NonNull<libmpv_sys::mpv_handle>,
156    _does_not_outlive: PhantomData<&'parent Mpv>,
157}
158
159unsafe impl<'parent> Send for EventContext<'parent> {}
160
161impl<'parent> EventContext<'parent> {
162    /// Enable an event.
163    pub fn enable_event(&self, ev: events::EventId) -> Result<()> {
164        mpv_err((), unsafe {
165            libmpv_sys::mpv_request_event(self.ctx.as_ptr(), ev, 1)
166        })
167    }
168
169    /// Enable all, except deprecated, events.
170    pub fn enable_all_events(&self) -> Result<()> {
171        for i in (2..9)
172            .chain(14..15)
173            .chain(16..19)
174            .chain(20..23)
175            .chain(23..26)
176        {
177            self.enable_event(i)?;
178        }
179        Ok(())
180    }
181
182    /// Disable an event.
183    pub fn disable_event(&self, ev: events::EventId) -> Result<()> {
184        mpv_err((), unsafe {
185            libmpv_sys::mpv_request_event(self.ctx.as_ptr(), ev, 0)
186        })
187    }
188
189    /// Diable all deprecated events.
190    pub fn disable_deprecated_events(&self) -> Result<()> {
191        self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_IDLE)?;
192        Ok(())
193    }
194
195    /// Diable all events.
196    pub fn disable_all_events(&self) -> Result<()> {
197        for i in 2..26 {
198            self.disable_event(i as _)?;
199        }
200        Ok(())
201    }
202
203    /// Observe `name` property for changes. `id` can be used to unobserve this (or many) properties
204    /// again.
205    pub fn observe_property(&self, name: &str, format: Format, id: u64) -> Result<()> {
206        let name = CString::new(name)?;
207        mpv_err((), unsafe {
208            libmpv_sys::mpv_observe_property(
209                self.ctx.as_ptr(),
210                id,
211                name.as_ptr(),
212                format.as_mpv_format() as _,
213            )
214        })
215    }
216
217    /// Unobserve any property associated with `id`.
218    pub fn unobserve_property(&self, id: u64) -> Result<()> {
219        mpv_err((), unsafe {
220            libmpv_sys::mpv_unobserve_property(self.ctx.as_ptr(), id)
221        })
222    }
223
224    /// Wait for `timeout` seconds for an `Event`. Passing `0` as `timeout` will poll.
225    /// For more information, as always, see the mpv-sys docs of `mpv_wait_event`.
226    ///
227    /// This function is intended to be called repeatedly in a wait-event loop.
228    ///
229    /// Returns `Some(Err(...))` if there was invalid utf-8, or if either an
230    /// `MPV_EVENT_GET_PROPERTY_REPLY`, `MPV_EVENT_SET_PROPERTY_REPLY`, `MPV_EVENT_COMMAND_REPLY`,
231    /// or `MPV_EVENT_PROPERTY_CHANGE` event failed, or if `MPV_EVENT_END_FILE` reported an error.
232    pub fn wait_event(&mut self, timeout: f64) -> Option<Result<Event>> {
233        let event = unsafe { *libmpv_sys::mpv_wait_event(self.ctx.as_ptr(), timeout) };
234        if event.event_id != mpv_event_id::None {
235            if let Err(e) = mpv_err((), event.error) {
236                return Some(Err(e));
237            }
238        }
239
240        match event.event_id {
241            mpv_event_id::None => None,
242            mpv_event_id::Shutdown => Some(Ok(Event::Shutdown)),
243            mpv_event_id::LogMessage => {
244                let log_message =
245                    unsafe { *(event.data as *mut libmpv_sys::mpv_event_log_message) };
246
247                let prefix = unsafe { mpv_cstr_to_str!(log_message.prefix) };
248                Some(prefix.and_then(|prefix| {
249                    Ok(Event::LogMessage {
250                        prefix,
251                        level: unsafe { mpv_cstr_to_str!(log_message.level)? },
252                        text: unsafe { mpv_cstr_to_str!(log_message.text)? },
253                        log_level: log_message.log_level,
254                    })
255                }))
256            }
257            mpv_event_id::GetPropertyReply => {
258                let property = unsafe { *(event.data as *mut libmpv_sys::mpv_event_property) };
259
260                let name = unsafe { mpv_cstr_to_str!(property.name) };
261                Some(name.and_then(|name| {
262                    // SAFETY: safe because we are passing format + data from an mpv_event_property
263                    let result = unsafe { PropertyData::from_raw(property.format, property.data) }?;
264
265                    Ok(Event::GetPropertyReply {
266                        name,
267                        result,
268                        reply_userdata: event.reply_userdata,
269                    })
270                }))
271            }
272            mpv_event_id::SetPropertyReply => Some(mpv_err(
273                Event::SetPropertyReply(event.reply_userdata),
274                event.error,
275            )),
276            mpv_event_id::CommandReply => Some(mpv_err(
277                Event::CommandReply(event.reply_userdata),
278                event.error,
279            )),
280            mpv_event_id::StartFile => Some(Ok(Event::StartFile)),
281            mpv_event_id::EndFile => {
282                let end_file = unsafe { *(event.data as *mut libmpv_sys::mpv_event_end_file) };
283
284                if let Err(e) = mpv_err((), end_file.error) {
285                    Some(Err(e))
286                } else {
287                    Some(Ok(Event::EndFile(end_file.reason as _)))
288                }
289            }
290            mpv_event_id::FileLoaded => Some(Ok(Event::FileLoaded)),
291            mpv_event_id::ClientMessage => {
292                let client_message =
293                    unsafe { *(event.data as *mut libmpv_sys::mpv_event_client_message) };
294                let messages = unsafe {
295                    slice::from_raw_parts_mut(client_message.args, client_message.num_args as _)
296                };
297                Some(Ok(Event::ClientMessage(
298                    messages
299                        .iter()
300                        .map(|msg| unsafe { mpv_cstr_to_str!(*msg) })
301                        .collect::<Result<Vec<_>>>()
302                        .unwrap(),
303                )))
304            }
305            mpv_event_id::VideoReconfig => Some(Ok(Event::VideoReconfig)),
306            mpv_event_id::AudioReconfig => Some(Ok(Event::AudioReconfig)),
307            mpv_event_id::Seek => Some(Ok(Event::Seek)),
308            mpv_event_id::PlaybackRestart => Some(Ok(Event::PlaybackRestart)),
309            mpv_event_id::PropertyChange => {
310                let property = unsafe { *(event.data as *mut libmpv_sys::mpv_event_property) };
311
312                // This happens if the property is not available. For example,
313                // if you reached EndFile while observing a property.
314                if property.format == mpv_format::None {
315                    None
316                } else {
317                    let name = unsafe { mpv_cstr_to_str!(property.name) };
318                    Some(name.and_then(|name| {
319                        // SAFETY: safe because we are passing format + data from an mpv_event_property
320                        let change =
321                            unsafe { PropertyData::from_raw(property.format, property.data) }?;
322
323                        Ok(Event::PropertyChange {
324                            name,
325                            change,
326                            reply_userdata: event.reply_userdata,
327                        })
328                    }))
329                }
330            }
331            mpv_event_id::QueueOverflow => Some(Ok(Event::QueueOverflow)),
332            _ => Some(Ok(Event::Deprecated(event))),
333        }
334    }
335}