macro_rules! mpv_cstr_to_str {
($cstr: expr) => {
std::ffi::CStr::from_ptr($cstr)
.to_str()
.map_err(Error::from)
};
}
mod errors;
pub mod events;
pub mod protocol;
#[cfg(feature = "render")]
pub mod render;
pub use self::errors::*;
use super::*;
use std::{
ffi::CString,
mem::MaybeUninit,
ops::Deref,
ptr::{self, NonNull},
};
fn mpv_err<T>(ret: T, err: ctype::c_int) -> Result<T> {
if err == 0 {
Ok(ret)
} else {
Err(Error::Raw(err))
}
}
pub unsafe trait GetData: Sized {
#[doc(hidden)]
fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<Self> {
let mut val = MaybeUninit::uninit();
let _ = fun(val.as_mut_ptr() as *mut _)?;
Ok(unsafe { val.assume_init() })
}
fn get_format() -> Format;
}
pub unsafe trait SetData: Sized {
#[doc(hidden)]
fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(
mut self,
mut fun: F,
) -> Result<T> {
fun(&mut self as *mut Self as _)
}
fn get_format() -> Format;
}
unsafe impl GetData for f64 {
fn get_format() -> Format {
Format::Double
}
}
unsafe impl SetData for f64 {
fn get_format() -> Format {
Format::Double
}
}
unsafe impl GetData for i64 {
fn get_format() -> Format {
Format::Int64
}
}
unsafe impl SetData for i64 {
fn get_format() -> Format {
Format::Int64
}
}
unsafe impl GetData for bool {
fn get_format() -> Format {
Format::Flag
}
}
unsafe impl SetData for bool {
fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
let mut cpy: i64 = if self { 1 } else { 0 };
fun(&mut cpy as *mut i64 as *mut _)
}
fn get_format() -> Format {
Format::Flag
}
}
unsafe impl GetData for String {
fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<String> {
let ptr = &mut ptr::null();
fun(ptr as *mut *const ctype::c_char as _)?;
let ret = unsafe { mpv_cstr_to_str!(*ptr) }?.to_owned();
unsafe { libmpv2_sys::mpv_free(*ptr as *mut _) };
Ok(ret)
}
fn get_format() -> Format {
Format::String
}
}
unsafe impl SetData for String {
fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
let string = CString::new(self)?;
fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _)
}
fn get_format() -> Format {
Format::String
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct MpvStr<'a>(&'a str);
impl<'a> Deref for MpvStr<'a> {
type Target = str;
fn deref(&self) -> &str {
self.0
}
}
impl<'a> Drop for MpvStr<'a> {
fn drop(&mut self) {
unsafe { libmpv2_sys::mpv_free(self.0.as_ptr() as *mut u8 as _) };
}
}
unsafe impl<'a> GetData for MpvStr<'a> {
fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(
mut fun: F,
) -> Result<MpvStr<'a>> {
let ptr = &mut ptr::null();
let _ = fun(ptr as *mut *const ctype::c_char as _)?;
Ok(MpvStr(unsafe { mpv_cstr_to_str!(*ptr) }?))
}
fn get_format() -> Format {
Format::String
}
}
unsafe impl<'a> SetData for &'a str {
fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
let string = CString::new(self)?;
fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _)
}
fn get_format() -> Format {
Format::String
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Format {
String,
Flag,
Int64,
Double,
Node,
}
impl Format {
fn as_mpv_format(&self) -> MpvFormat {
match *self {
Format::String => mpv_format::String,
Format::Flag => mpv_format::Flag,
Format::Int64 => mpv_format::Int64,
Format::Double => mpv_format::Double,
Format::Node => mpv_format::Node,
}
}
}
pub struct MpvInitializer {
ctx: *mut libmpv2_sys::mpv_handle,
}
impl MpvInitializer {
pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> {
let name = CString::new(name)?;
let format = T::get_format().as_mpv_format() as _;
data.call_as_c_void(|ptr| {
mpv_err((), unsafe {
libmpv2_sys::mpv_set_property(self.ctx, name.as_ptr(), format, ptr)
})
})
}
pub fn set_option<T: SetData>(&self, name: &str, data: T) -> Result<()> {
let name = CString::new(name)?;
let format = T::get_format().as_mpv_format() as _;
data.call_as_c_void(|ptr| {
mpv_err((), unsafe {
libmpv2_sys::mpv_set_option(self.ctx, name.as_ptr(), format, ptr)
})
})
}
pub fn load_config(&self, path: &str) -> Result<()> {
let file = CString::new(path)?;
mpv_err((), unsafe {
libmpv2_sys::mpv_load_config_file(self.ctx, file.as_ptr())
})
}
}
pub struct Mpv {
pub ctx: NonNull<libmpv2_sys::mpv_handle>,
wakeup_callback_cleanup: Option<Box<dyn FnOnce()>>,
}
unsafe impl Send for Mpv {}
unsafe impl Sync for Mpv {}
impl Drop for Mpv {
fn drop(&mut self) {
if let Some(wakeup_callback_cleanup) = self.wakeup_callback_cleanup.take() {
wakeup_callback_cleanup();
}
unsafe {
libmpv2_sys::mpv_destroy(self.ctx.as_ptr());
}
}
}
impl Mpv {
pub fn new() -> Result<Mpv> {
Mpv::with_initializer(|_| Ok(()))
}
pub fn with_initializer<F: FnOnce(MpvInitializer) -> Result<()>>(
initializer: F,
) -> Result<Mpv> {
let api_version = unsafe { libmpv2_sys::mpv_client_api_version() };
if crate::MPV_CLIENT_API_MAJOR != api_version >> 16 {
return Err(Error::VersionMismatch {
linked: crate::MPV_CLIENT_API_VERSION,
loaded: api_version,
});
}
let ctx = unsafe { libmpv2_sys::mpv_create() };
if ctx.is_null() {
return Err(Error::Null);
}
initializer(MpvInitializer { ctx })?;
mpv_err((), unsafe { libmpv2_sys::mpv_initialize(ctx) }).map_err(|err| {
unsafe { libmpv2_sys::mpv_terminate_destroy(ctx) };
err
})?;
let ctx = unsafe { NonNull::new_unchecked(ctx) };
Ok(Mpv {
ctx,
wakeup_callback_cleanup: None,
})
}
pub fn create_client(&self, name: Option<&str>) -> Result<Mpv> {
let mpv_handle = unsafe {
libmpv2_sys::mpv_create_client(
self.ctx.as_ptr(),
if let Some(name) = name {
CString::new(name)?.as_ptr()
} else {
ptr::null()
},
)
};
let ctx = unsafe { NonNull::new_unchecked(mpv_handle) };
Ok(Mpv {
ctx,
wakeup_callback_cleanup: None,
})
}
pub fn command(&self, name: &str, args: &[&str]) -> Result<()> {
let mut cstr_args: Vec<CString> = Vec::with_capacity(args.len() + 1);
cstr_args.push(CString::new(name)?);
for arg in args {
cstr_args.push(CString::new(*arg)?);
}
let mut ptrs: Vec<_> = cstr_args.iter().map(|cstr| cstr.as_ptr()).collect();
ptrs.push(std::ptr::null());
mpv_err((), unsafe {
libmpv2_sys::mpv_command(self.ctx.as_ptr(), ptrs.as_mut_ptr())
})
}
pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> {
let name = CString::new(name)?;
let format = T::get_format().as_mpv_format() as _;
data.call_as_c_void(|ptr| {
mpv_err((), unsafe {
libmpv2_sys::mpv_set_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr)
})
})
}
pub fn get_property<T: GetData>(&self, name: &str) -> Result<T> {
let name = CString::new(name)?;
let format = T::get_format().as_mpv_format() as _;
T::get_from_c_void(|ptr| {
mpv_err((), unsafe {
libmpv2_sys::mpv_get_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr)
})
})
}
pub fn get_time_ns(&self) -> i64 {
unsafe { libmpv2_sys::mpv_get_time_ns(self.ctx.as_ptr()) }
}
pub fn get_time_us(&self) -> i64 {
unsafe { libmpv2_sys::mpv_get_time_us(self.ctx.as_ptr()) }
}
}