From 4e2c992cc9d06975da9db8486d419c6203a48c04 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Sat, 2 Nov 2024 15:43:43 +0100 Subject: [PATCH] hauuu so complicated --- src/application.rs | 36 ++++- src/mpv.rs | 339 ++++++++++++++++++++++++--------------------- 2 files changed, 215 insertions(+), 160 deletions(-) diff --git a/src/application.rs b/src/application.rs index 3166521..e251e37 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,11 +1,13 @@ mod imp { - use crate::ui; - use adw::prelude::*; - use adw::subclass::prelude::*; + use crate::{mpv, ui}; + use adw::{prelude::*, subclass::prelude::*}; use gtk::glib; + use std::cell::RefCell; #[derive(Default)] - pub struct Application; + pub struct Application { + mpv: RefCell, + } #[glib::object_subclass] impl ObjectSubclass for Application { @@ -24,6 +26,30 @@ mod imp { let window = ui::Window::new(self.obj().as_ref()); window.present(); + let mut mpv = self.mpv.borrow_mut(); + mpv.initialize().expect("could not initialize mpv"); + mpv.set_property("audio-client-name", "audrey").unwrap(); + mpv.set_property("user-agent", crate::USER_AGENT).unwrap(); + mpv.set_property("video", false).unwrap(); + mpv.set_property("prefetch-playlist", true).unwrap(); + mpv.set_property("gapless-audio", true).unwrap(); + // TODO: observe properties + mpv.observe_property("time-pos", |time_pos: Option| { + println!("new time pos: {time_pos:?}") + }) + .unwrap(); + + glib::spawn_future_local(glib::clone!( + #[weak(rename_to = app)] + self, + async move { + loop { + app.mpv.borrow().wait().await; + app.mpv.borrow_mut().tick(); + } + } + )); + glib::spawn_future_local(async move { let conn = zbus::connection::Builder::session() .expect("could not connect to the session bus") @@ -66,6 +92,8 @@ mod imp { impl GtkApplicationImpl for Application {} impl AdwApplicationImpl for Application {} + + impl Application {} } use gtk::{gio, glib}; diff --git a/src/mpv.rs b/src/mpv.rs index 15e83d0..bd8ff30 100644 --- a/src/mpv.rs +++ b/src/mpv.rs @@ -1,12 +1,14 @@ mod ffi; +use std::ffi::{c_int, c_void, CStr, CString}; + #[link(name = "mpv")] extern "C" {} -pub struct Error(std::ffi::c_int); +pub struct Error(c_int); impl Error { - fn from_return_code(rc: std::ffi::c_int) -> Result<(), Self> { + fn from_return_code(rc: c_int) -> Result<(), Self> { if rc == 0 { Ok(()) } else { @@ -18,182 +20,207 @@ impl Error { 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)) - }) + .field(unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) }) .finish() } } -use std::borrow::Cow; +use std::pin::Pin; use std::ptr::NonNull; -pub struct Handle(NonNull); +pub struct Handle { + inner: NonNull, + _wakeup_send: Pin>>, // the wakeup callback controls this one + wakeup_recv: async_channel::Receiver<()>, + + observed_property_callbacks: + Vec>>>, +} + +// The client API is generally fully thread-safe, unless otherwise noted. +unsafe impl Send for Handle {} +unsafe impl Sync for Handle {} + +impl Default for Handle { + fn default() -> Self { + Self::create() + } +} impl Handle { pub fn create() -> Self { - Self(NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle")) + let inner = + NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"); + + let (wakeup_send, wakeup_recv) = async_channel::bounded(1); + let wakeup_send = Box::pin(wakeup_send); + + extern "C" fn wakeup_callback(d: *mut c_void) { + let d = d as *const async_channel::Sender<()>; + let wakeup_send = unsafe { &*d }; + match wakeup_send.try_send(()) { + Ok(()) => {} + Err(async_channel::TrySendError::Full(())) => { + // assume the main thread has already been notified + } + Err(async_channel::TrySendError::Closed(())) => { + panic!("wakeup channgel was closed before mpv handle was finalized"); + } + } + } + unsafe { + ffi::mpv_set_wakeup_callback( + inner.as_ptr(), + Some(wakeup_callback), + std::ptr::from_ref::>(&wakeup_send) as *mut c_void, + ); + } + + Self { + inner: inner, + _wakeup_send: wakeup_send, + wakeup_recv: wakeup_recv, + + observed_property_callbacks: vec![], + } } pub fn initialize(&self) -> Result<(), Error> { - Error::from_return_code(unsafe { ffi::mpv_initialize(self.0.as_ptr()) }) + Error::from_return_code(unsafe { ffi::mpv_initialize(self.inner.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(|()| { + pub fn set_property<'a>(&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) + }) + }) + } + + // TODO: some way to deregister these maybe?? + pub fn observe_property( + &mut self, + name: &str, + mut cb: impl FnMut(Option) + 'static, + ) -> Result<(), Error> + where + T: FormatValue, + { + let name = CString::new(name).unwrap(); + let cb = Box::new(move |format, data| T::from_data(format, data, |x| cb(x))); + let cb = Box::::from(cb); + let cb = Box::pin(cb); + let userdata = + std::ptr::from_ref::>(&cb) as usize as u64; + + Error::from_return_code(unsafe { + ffi::mpv_observe_property(self.inner.as_ptr(), userdata, name.as_ptr(), T::FORMAT) + })?; + + self.observed_property_callbacks.push(cb); + Ok(()) + } + + pub async fn wait(&self) { + match self.wakeup_recv.recv().await { + Ok(()) => {} + Err(async_channel::RecvError) => unreachable!(), + } + } + + pub fn tick(&mut self) { + 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_PROPERTY_CHANGE => { + let cb = unsafe { + &mut *(event.reply_userdata + as *mut Box) + }; 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)), + cb(data.format, data.data); + } + + 11 => { /* deprecated, ignore */ } + + _ => todo!("event {}", 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!() } } + +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!(), + } + } +}