mod ffi; use std::ffi::{c_char, 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 event_listener::{Event, EventListener, IntoNotification}; use std::cell::RefCell; use std::pin::Pin; use std::ptr::NonNull; pub struct Handle { inner: NonNull, wakeup: Pin>, // the wakeup callback holds a pointer to this wait_event_cell: RefCell<()>, } // 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 fmt::Debug for Handle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_tuple("Handle").field(&self.client_name()).finish() } } 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, ); } // set up verbose logging for now Error::from_return_code(unsafe { ffi::mpv_request_log_messages(inner.as_ptr(), c"v".as_ptr()) }) .unwrap(); // 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, wakeup, wait_event_cell: RefCell::new(()), } } pub fn client_name(&self) -> &str { unsafe { CStr::from_ptr(ffi::mpv_client_name(self.inner.as_ptr())) } .to_str() .unwrap() } pub fn client_id(&self) -> i64 { unsafe { ffi::mpv_client_id(self.inner.as_ptr()) } } pub fn set_property(&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) }) }) } pub fn command<'a>(&self, args: impl IntoIterator) -> Result<(), Error> { // add zero terminators to Everything let args: Vec = args .into_iter() .map(CString::new) .map(Result::unwrap) .collect(); let mut args: Vec<*const c_char> = args .iter() .map(CString::as_c_str) .map(CStr::as_ptr) .collect(); // must be null terminated args.push(std::ptr::null_mut()); let args: *mut *const c_char = args.as_mut_ptr(); Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) } pub fn tick(&self) -> Option { // take listener before we drain the event queue, so we don't miss any notifications let listener = self.wakeup.listen(); let borrowed = self .wait_event_cell .try_borrow_mut() .expect("Mpv::tick is not reentrant"); 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_LOG_MESSAGE => { let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) }; // TODO: actual logging? let prefix = unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(); let level = unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(); let text = unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(); print!("[{prefix}] {level}: {text}"); } ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => { return None; // good bye forever } 11 => { /* deprecated, ignore */ } _ => todo!("event {}", event.event_id), } } drop(borrowed); // make sure the borrow is held until here Some(listener) } } impl Drop for Handle { fn drop(&mut self) { println!("dropping MpvHandle"); // let any executor ticking tasks know we're ded self.wakeup.notify(u32::MAX.relaxed()); // just in case unsafe { ffi::mpv_wait_async_requests(self.inner.as_ptr()); } // drain event queue (we're &mut so we know we have exclusive access) self.tick(); unsafe { ffi::mpv_destroy(self.inner.as_ptr()); } } } 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!(), } } }