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; pub struct Handle { inner: NonNull, _wakeup_send: Pin>>, // the wakeup callback controls this one wakeup_recv: async_channel::Receiver<()>, 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::create() } } impl Handle { pub fn create() -> Self { let inner = NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"); let (wakeup_send, wakeup_recv) = async_channel::bounded(1); let wakeup_send = Box::pin(wakeup_send); extern "C" fn wakeup_callback(d: *mut c_void) { let d = d as *const async_channel::Sender<()>; let wakeup_send = unsafe { &*d }; match wakeup_send.try_send(()) { Ok(()) => {} Err(async_channel::TrySendError::Full(())) => { // assume the main thread has already been notified } Err(async_channel::TrySendError::Closed(())) => { panic!("wakeup channgel was closed before mpv handle was finalized"); } } } unsafe { ffi::mpv_set_wakeup_callback( inner.as_ptr(), Some(wakeup_callback), std::ptr::from_ref::>(&wakeup_send) as *mut c_void, ); } Self { inner: inner, _wakeup_send: wakeup_send, wakeup_recv: wakeup_recv, observed_property_callbacks: vec![], } } pub fn initialize(&self) -> Result<(), Error> { Error::from_return_code(unsafe { ffi::mpv_initialize(self.inner.as_ptr()) }) } 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 wait(&self) { match self.wakeup_recv.recv().await { Ok(()) => {} Err(async_channel::RecvError) => unreachable!(), } } pub fn tick(&mut self) { 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), } } } } 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!(), } } }