mod ffi; #[link(name = "mpv")] extern "C" {} pub struct Error(std::ffi::c_int); impl Error { fn from_return_code(rc: std::ffi::c_int) -> Result<(), Self> { if rc == 0 { Ok(()) } else { Err(Self(rc)) } } } impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_tuple("Error") .field(unsafe { &std::ffi::CStr::from_ptr(ffi::mpv_error_string(self.0 as std::ffi::c_int)) }) .finish() } } use std::borrow::Cow; use std::ptr::NonNull; pub struct Handle(NonNull); impl Handle { pub fn create() -> Self { Self(NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle")) } pub fn initialize(&self) -> Result<(), Error> { Error::from_return_code(unsafe { ffi::mpv_initialize(self.0.as_ptr()) }) } pub fn wait_event(&mut self, timeout: f64) -> Option> { let event = unsafe { &*ffi::mpv_wait_event(self.0.as_ptr(), timeout) }; match event.event_id { 0 => None, 1 => Some(Event::Shutdown), 2 => todo!("Event::LogMessage"), 3 => Some(Event::GetPropertyReply( event.reply_userdata, Error::from_return_code(event.error).map(|()| { let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) }; EventProperty { name: unsafe { std::ffi::CStr::from_ptr(data.name) } .to_str() .unwrap(), value: unsafe { FormatData::new(data.format, data.data) }, } }), )), 4 => todo!("Event::SetPropertyReply"), 5 => todo!("Event::CommandReply"), 6 => todo!("Event::StartFile"), 7 => todo!("Event::EndFile"), 8 => todo!("Event::FileLoaded"), 16 => todo!("Event::ClientMessage"), 17 => todo!("Event::VideoReconfig"), 18 => todo!("Event::AudioReconfig"), 20 => todo!("Event::Seek"), 21 => todo!("Event::PlaybackRestart"), 22 => todo!("Event::PropertyChange"), 24 => todo!("Event::QueueOverflow"), 25 => todo!("Event::Hook"), _ => Some(Event::Ignore(event.event_id)), } } } pub enum FormatData<'a> { None, String(Cow<'a, str>), OsdString(&'a str), Flag(bool), Int64(i64), Double(f64), //Node(Node<'a>), } impl<'a> FormatData<'a> { unsafe fn new(format: ffi::mpv_format, data: *mut std::ffi::c_void) -> Self { match format { 0 => Self::None, 1 => { let data = data as *mut *mut std::ffi::c_char; Self::String(String::from_utf8_lossy( std::ffi::CStr::from_ptr(*data).to_bytes(), )) } 2 => { let data = data as *mut *mut std::ffi::c_char; Self::OsdString( std::ffi::CStr::from_ptr(*data) .to_str() .expect("OSD string wasn't UTF-8"), ) } 3 => { let data = data as *mut std::ffi::c_int; Self::Flag(match *data { 0 => false, 1 => true, _ => panic!("invalid mpv flag value"), }) } 4 => { let data = data as *mut i64; Self::Int64(*data) } 5 => { let data = data as *mut f64; Self::Double(*data) } 6 => todo!(), 7 => todo!(), 8 => todo!(), 9 => todo!(), _ => panic!("invalid mpv format"), } } } pub struct EventProperty<'a> { pub name: &'a str, pub value: FormatData<'a>, } pub struct EventLogMessage<'a> { pub prefix: &'a str, pub level: &'a str, pub text: &'a str, pub log_level: i32, } pub enum EndFileReason { Eof, Stop, Quit, Error(Error), Redirect, } pub struct EventStartFile { pub playlist_entry_id: i64, } pub struct EventEndFile { pub reason: EndFileReason, pub playlist_entry_id: i64, pub playlist_insert_id: i64, pub playlist_insert_num_entries: i32, } pub struct EventClientMessage<'a> { pub num_args: i32, pub args: &'a [&'a str], } pub struct EventHook<'a> { pub name: &'a str, pub id: u64, } pub enum Event<'a> { Shutdown, LogMessage(EventLogMessage<'a>), GetPropertyReply(u64, Result, Error>), SetPropertyReply(u64, Result<(), Error>), //CommandReply(u64, Result, Error>), StartFile(EventStartFile), EndFile(EventEndFile), FileLoaded, ClientMessage(EventClientMessage<'a>), VideoReconfig, AudioReconfig, Seek, PlaybackRestart, PropertyChange(u64, EventProperty<'a>), QueueOverflow, Hook(u64, EventHook<'a>), // "Keep in mind that later ABI compatible releases might add new event // types. These should be ignored by the API user." Ignore(u32), } impl Drop for Handle { fn drop(&mut self) { // destroy? terminate_destroy?? todo!() } }