split up mpv into modules
This commit is contained in:
parent
070f25f862
commit
8699f7bf4f
4 changed files with 274 additions and 269 deletions
275
src/mpv.rs
275
src/mpv.rs
|
@ -1,273 +1,10 @@
|
|||
mod ffi;
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void, CStr, CString};
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
#[link(name = "mpv")]
|
||||
extern "C" {}
|
||||
mod format;
|
||||
pub use format::FormatValue;
|
||||
|
||||
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<ffi::mpv_handle>,
|
||||
wakeup: Pin<Box<Event>>, // 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::<Event>(&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<Item = &'a str>) -> Result<(), Error> {
|
||||
// add zero terminators to Everything
|
||||
let args: Vec<CString> = 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<EventListener> {
|
||||
// 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<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T;
|
||||
fn from_data<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T;
|
||||
}
|
||||
|
||||
impl<'a> FormatValue for &'a str {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for bool {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for f64 {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
mod handle;
|
||||
pub use handle::Handle;
|
||||
|
|
36
src/mpv/error.rs
Normal file
36
src/mpv/error.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use super::ffi;
|
||||
use std::ffi::{c_int, CStr};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Error(c_int);
|
||||
|
||||
impl Error {
|
||||
pub(super) fn from_return_code(rc: c_int) -> Result<(), Self> {
|
||||
if rc == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Self(rc))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'static str {
|
||||
unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0)) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Display::fmt(&self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_tuple("Error").field(&self.as_str()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
67
src/mpv/format.rs
Normal file
67
src/mpv/format.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use super::ffi;
|
||||
use std::ffi::{c_int, c_void, CString};
|
||||
|
||||
pub trait FormatValue: Sized {
|
||||
const FORMAT: ffi::mpv_format;
|
||||
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T;
|
||||
fn from_data<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T;
|
||||
}
|
||||
|
||||
impl<'a> FormatValue for &'a str {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for bool {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for f64 {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE;
|
||||
|
||||
fn into_data<T>(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<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> 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!(),
|
||||
}
|
||||
}
|
||||
}
|
165
src/mpv/handle.rs
Normal file
165
src/mpv/handle.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use super::{ffi, Error, FormatValue};
|
||||
use event_listener::{Event, EventListener, IntoNotification};
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct Handle {
|
||||
inner: NonNull<ffi::mpv_handle>,
|
||||
wakeup: Pin<Box<Event>>, // 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::<Event>(&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<Item = &'a str>) -> Result<(), Error> {
|
||||
// add zero terminators to Everything
|
||||
let args: Vec<CString> = 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<EventListener> {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue