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 event_listener::{Event, 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 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 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) }) }) } fn drain_event_queue(&self) { loop { let borrowed = self.wait_event_cell.borrow_mut(); 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}"); }, 11 => { /* deprecated, ignore */ } _ => todo!("event {}", event.event_id), } drop(borrowed); // make sure the borrow is held until here } } pub fn tick(&self) -> impl std::future::Future { // take listener before we drain the event queue, so we don't miss any notifications let listener = self.wakeup.listen(); self.drain_event_queue(); listener } } impl Drop for Handle { fn drop(&mut self) { 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!(), } } }