use libmpv_sys::mpv_event;
use crate::{mpv::mpv_err, *};
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw as ctype;
use std::ptr::NonNull;
use std::slice;
use std::sync::atomic::Ordering;
pub use libmpv_sys::mpv_event_id as EventId;
pub mod mpv_event_id {
pub use libmpv_sys::mpv_event_id_MPV_EVENT_AUDIO_RECONFIG as AudioReconfig;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_CLIENT_MESSAGE as ClientMessage;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_COMMAND_REPLY as CommandReply;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_END_FILE as EndFile;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_FILE_LOADED as FileLoaded;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY as GetPropertyReply;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_HOOK as Hook;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_LOG_MESSAGE as LogMessage;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_NONE as None;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_PLAYBACK_RESTART as PlaybackRestart;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE as PropertyChange;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW as QueueOverflow;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_SEEK as Seek;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY as SetPropertyReply;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_SHUTDOWN as Shutdown;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_START_FILE as StartFile;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_TICK as Tick;
pub use libmpv_sys::mpv_event_id_MPV_EVENT_VIDEO_RECONFIG as VideoReconfig;
}
impl Mpv {
pub fn create_event_context(&self) -> EventContext {
if self
.events_guard
.compare_and_swap(false, true, Ordering::AcqRel)
{
panic!("Event context already creates")
} else {
EventContext {
ctx: self.ctx,
_does_not_outlive: PhantomData::<&Self>,
}
}
}
}
#[derive(Debug)]
pub enum PropertyData<'a> {
Str(&'a str),
OsdStr(&'a str),
Flag(bool),
Int64(i64),
Double(ctype::c_double),
Node(&'a MpvNode),
}
impl<'a> PropertyData<'a> {
unsafe fn from_raw(format: MpvFormat, ptr: *mut ctype::c_void) -> Result<PropertyData<'a>> {
assert!(!ptr.is_null());
match format {
mpv_format::Flag => Ok(PropertyData::Flag(*(ptr as *mut bool))),
mpv_format::String => {
let char_ptr = *(ptr as *mut *mut ctype::c_char);
Ok(PropertyData::Str(mpv_cstr_to_str!(char_ptr)?))
}
mpv_format::OsdString => {
let char_ptr = *(ptr as *mut *mut ctype::c_char);
Ok(PropertyData::OsdStr(mpv_cstr_to_str!(char_ptr)?))
}
mpv_format::Double => Ok(PropertyData::Double(*(ptr as *mut f64))),
mpv_format::Int64 => Ok(PropertyData::Int64(*(ptr as *mut i64))),
mpv_format::Node => Ok(PropertyData::Node(&*(ptr as *mut MpvNode))),
mpv_format::None => unreachable!(),
_ => unimplemented!(),
}
}
}
#[derive(Debug)]
pub enum Event<'a> {
Shutdown,
LogMessage {
prefix: &'a str,
level: &'a str,
text: &'a str,
log_level: LogLevel,
},
GetPropertyReply {
name: &'a str,
result: PropertyData<'a>,
reply_userdata: u64,
},
SetPropertyReply(u64),
CommandReply(u64),
StartFile,
EndFile(EndFileReason),
FileLoaded,
ClientMessage(Vec<&'a str>),
VideoReconfig,
AudioReconfig,
Seek,
PlaybackRestart,
PropertyChange {
name: &'a str,
change: PropertyData<'a>,
reply_userdata: u64,
},
QueueOverflow,
Deprecated(mpv_event),
}
pub struct EventContext<'parent> {
ctx: NonNull<libmpv_sys::mpv_handle>,
_does_not_outlive: PhantomData<&'parent Mpv>,
}
unsafe impl<'parent> Send for EventContext<'parent> {}
impl<'parent> EventContext<'parent> {
pub fn enable_event(&self, ev: events::EventId) -> Result<()> {
mpv_err((), unsafe {
libmpv_sys::mpv_request_event(self.ctx.as_ptr(), ev, 1)
})
}
pub fn enable_all_events(&self) -> Result<()> {
for i in (2..9)
.chain(14..15)
.chain(16..19)
.chain(20..23)
.chain(23..26)
{
self.enable_event(i)?;
}
Ok(())
}
pub fn disable_event(&self, ev: events::EventId) -> Result<()> {
mpv_err((), unsafe {
libmpv_sys::mpv_request_event(self.ctx.as_ptr(), ev, 0)
})
}
pub fn disable_deprecated_events(&self) -> Result<()> {
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_TRACKS_CHANGED)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_TRACK_SWITCHED)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_IDLE)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_PAUSE)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_UNPAUSE)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_SCRIPT_INPUT_DISPATCH)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_METADATA_UPDATE)?;
self.disable_event(libmpv_sys::mpv_event_id_MPV_EVENT_CHAPTER_CHANGE)?;
Ok(())
}
pub fn disable_all_events(&self) -> Result<()> {
for i in 2..26 {
self.disable_event(i as _)?;
}
Ok(())
}
pub fn observe_property(&self, name: &str, format: Format, id: u64) -> Result<()> {
let name = CString::new(name)?;
mpv_err((), unsafe {
libmpv_sys::mpv_observe_property(
self.ctx.as_ptr(),
id,
name.as_ptr(),
format.as_mpv_format() as _,
)
})
}
pub fn unobserve_property(&self, id: u64) -> Result<()> {
mpv_err((), unsafe {
libmpv_sys::mpv_unobserve_property(self.ctx.as_ptr(), id)
})
}
pub fn wait_event(&mut self, timeout: f64) -> Option<Result<Event>> {
let event = unsafe { *libmpv_sys::mpv_wait_event(self.ctx.as_ptr(), timeout) };
if event.event_id != mpv_event_id::None {
if let Err(e) = mpv_err((), event.error) {
return Some(Err(e));
}
}
match event.event_id {
mpv_event_id::None => None,
mpv_event_id::Shutdown => Some(Ok(Event::Shutdown)),
mpv_event_id::LogMessage => {
let log_message =
unsafe { *(event.data as *mut libmpv_sys::mpv_event_log_message) };
let prefix = unsafe { mpv_cstr_to_str!(log_message.prefix) };
Some(prefix.and_then(|prefix| {
Ok(Event::LogMessage {
prefix,
level: unsafe { mpv_cstr_to_str!(log_message.level)? },
text: unsafe { mpv_cstr_to_str!(log_message.text)? },
log_level: log_message.log_level,
})
}))
}
mpv_event_id::GetPropertyReply => {
let property = unsafe { *(event.data as *mut libmpv_sys::mpv_event_property) };
let name = unsafe { mpv_cstr_to_str!(property.name) };
Some(name.and_then(|name| {
let result = unsafe { PropertyData::from_raw(property.format, property.data) }?;
Ok(Event::GetPropertyReply {
name,
result,
reply_userdata: event.reply_userdata,
})
}))
}
mpv_event_id::SetPropertyReply => Some(mpv_err(
Event::SetPropertyReply(event.reply_userdata),
event.error,
)),
mpv_event_id::CommandReply => Some(mpv_err(
Event::CommandReply(event.reply_userdata),
event.error,
)),
mpv_event_id::StartFile => Some(Ok(Event::StartFile)),
mpv_event_id::EndFile => {
let end_file = unsafe { *(event.data as *mut libmpv_sys::mpv_event_end_file) };
if let Err(e) = mpv_err((), end_file.error) {
Some(Err(e))
} else if end_file.reason.is_positive() {
Some(Ok(Event::EndFile(end_file.reason as _)))
} else {
None
}
}
mpv_event_id::FileLoaded => Some(Ok(Event::FileLoaded)),
mpv_event_id::ClientMessage => {
let client_message =
unsafe { *(event.data as *mut libmpv_sys::mpv_event_client_message) };
let messages = unsafe {
slice::from_raw_parts_mut(client_message.args, client_message.num_args as _)
};
Some(Ok(Event::ClientMessage(
messages
.iter()
.map(|msg| unsafe { mpv_cstr_to_str!(*msg) })
.collect::<Result<Vec<_>>>()
.unwrap(),
)))
}
mpv_event_id::VideoReconfig => Some(Ok(Event::VideoReconfig)),
mpv_event_id::AudioReconfig => Some(Ok(Event::AudioReconfig)),
mpv_event_id::Seek => Some(Ok(Event::Seek)),
mpv_event_id::PlaybackRestart => Some(Ok(Event::PlaybackRestart)),
mpv_event_id::PropertyChange => {
let property = unsafe { *(event.data as *mut libmpv_sys::mpv_event_property) };
if property.format == mpv_format::None {
None
} else {
let name = unsafe { mpv_cstr_to_str!(property.name) };
Some(name.and_then(|name| {
let change =
unsafe { PropertyData::from_raw(property.format, property.data) }?;
Ok(Event::PropertyChange {
name,
change,
reply_userdata: event.reply_userdata,
})
}))
}
}
mpv_event_id::QueueOverflow => Some(Ok(Event::QueueOverflow)),
_ => Some(Ok(Event::Deprecated(event))),
}
}
}