mod ffi; use std::ffi::{c_int, c_void, CStr, CString}; #[link(name = "mpv")] extern "C" {} pub struct Error(c_int); impl Error { fn from_return_code(rc: c_int) -> Result<(), Self> { if rc == 0 { Ok(()) } else { Err(Self(rc)) } } } use std::fmt; impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Display::fmt( unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) } .to_str() .unwrap(), f, ) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_tuple("Error") .field( &unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) } .to_str() .unwrap(), ) .finish() } } impl std::error::Error for Error {} use std::pin::Pin; use std::ptr::NonNull; use event_listener::{Event, IntoNotification}; use std::cell::RefCell; pub struct Handle { inner: NonNull, wakeup: Pin>, // the wakeup callback holds a pointer to this tick_cell: RefCell<()>, observed_property_callbacks: Vec>>>, } // The client API is generally fully thread-safe, unless otherwise noted. //unsafe impl Send for Handle {} //unsafe impl Sync for Handle {} // ........but we'd rather keep this in a single thread to play better with gtk impl Default for Handle { fn default() -> Self { Self::new() } } impl Handle { pub fn new() -> Self { let inner = NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"); let wakeup = Box::pin(Event::new()); extern "C" fn wakeup_callback(d: *mut c_void) { let d = d as *const Event; let wakeup = unsafe { &*d }; wakeup.notify(1_u32.relaxed()); // mpv has its own synchronization, trust it } unsafe { ffi::mpv_set_wakeup_callback( inner.as_ptr(), Some(wakeup_callback), std::ptr::from_ref::(&wakeup) as *mut c_void, ); } // TODO: maybe we need to set something before initialization, but idk // also wed need a builder, since "Note that you should avoid doing concurrent accesses on the uninitialized client handle." Error::from_return_code(unsafe { ffi::mpv_initialize(inner.as_ptr()) }).expect("could not initialize mpv handle"); Self { inner: inner, wakeup: wakeup, tick_cell: RefCell::new(()), observed_property_callbacks: vec![], } } pub fn set_property<'a>(&self, name: &str, value: impl FormatValue) -> Result<(), Error> { // need to add zero terminator let name = CString::new(name).expect("null bytes in property name"); value.into_data(|format, data| { Error::from_return_code(unsafe { ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data) }) }) } // TODO: some way to deregister these maybe?? pub fn observe_property( &mut self, name: &str, mut cb: impl FnMut(Option) + 'static, ) -> Result<(), Error> where T: FormatValue, { let name = CString::new(name).unwrap(); let cb = Box::new(move |format, data| T::from_data(format, data, |x| cb(x))); let cb = Box::::from(cb); let cb = Box::pin(cb); let userdata = std::ptr::from_ref::>(&cb) as usize as u64; Error::from_return_code(unsafe { ffi::mpv_observe_property(self.inner.as_ptr(), userdata, name.as_ptr(), T::FORMAT) })?; self.observed_property_callbacks.push(cb); Ok(()) } pub async fn tick(&self) { // mpv_wait_event is not reentrant!! use a refcell to ensure that self.tick_cell.borrow_mut(); // take listener before we drain the event loop, so we don't miss any notifications let listener = self.wakeup.listen(); loop { let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) }; match event.event_id { ffi::mpv_event_id_MPV_EVENT_NONE => break, ffi::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => { let cb = unsafe { &mut *(event.reply_userdata as *mut Box) }; let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) }; cb(data.format, data.data); } 11 => { /* deprecated, ignore */ } _ => todo!("event {}", event.event_id), } } listener.await; } } impl Drop for Handle { fn drop(&mut self) { // destroy? terminate_destroy?? todo!() } } pub trait FormatValue: Sized { const FORMAT: ffi::mpv_format; fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T; fn from_data( format: ffi::mpv_format, data: *mut c_void, f: impl FnOnce(Option) -> T, ) -> T; } impl<'a> FormatValue for &'a str { const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING; fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { let str = CString::new(self).expect("null bytes in string"); let str_ptr = str.as_ptr(); f(Self::FORMAT, (&str_ptr) as *const _ as *mut c_void) } fn from_data( _format: ffi::mpv_format, _data: *mut c_void, _f: impl FnOnce(Option) -> T, ) -> T { todo!() } } impl FormatValue for bool { const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG; fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { let flag: c_int = if self { 1 } else { 0 }; f(Self::FORMAT, (&flag) as *const c_int as *mut c_void) } fn from_data( _format: ffi::mpv_format, _data: *mut c_void, _f: impl FnOnce(Option) -> T, ) -> T { todo!() } } impl FormatValue for f64 { const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE; fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { f(Self::FORMAT, (&self) as *const f64 as *mut c_void) } fn from_data( format: ffi::mpv_format, data: *mut c_void, f: impl FnOnce(Option) -> T, ) -> T { match format { ffi::mpv_format_MPV_FORMAT_NONE => f(None), ffi::mpv_format_MPV_FORMAT_DOUBLE => f(Some(unsafe { *(data as *mut f64) })), _ => panic!(), } } }