diff --git a/src/main.rs b/src/main.rs index 7c64e3b..b8e44df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ pub mod subsonic_vala; mod playbin2; mod signal; -pub use signal::{Signal, SignalHandler}; +pub use signal::{Signal, SignalEmitter, SignalHandler}; use gettextrs::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory}; use gtk::prelude::*; diff --git a/src/mpv.rs b/src/mpv.rs index 381be2f..a287984 100644 --- a/src/mpv.rs +++ b/src/mpv.rs @@ -8,3 +8,6 @@ pub use format::SetProperty; mod handle; pub use handle::Handle; + +mod event; +pub use event::{EndFileEvent, EndFileReason, Event, LogMessageEvent, StartFileEvent}; diff --git a/src/mpv/event.rs b/src/mpv/event.rs new file mode 100644 index 0000000..e8fa1f4 --- /dev/null +++ b/src/mpv/event.rs @@ -0,0 +1,52 @@ +use super::Error; + +#[derive(Copy, Clone, Debug)] +pub enum Event<'a> { + Shutdown, + LogMessage(LogMessageEvent<'a>), + //GetPropertyReply(PropertyEvent), TODO + //SetPropertyReply(PropertyEvent), TODO + //CommandReply(CommandEvent), TODO + StartFile(StartFileEvent), + EndFile(EndFileEvent), + FileLoaded, + //ClientMessage(ClientMessageEvent), TODO + VideoReconfig, + AudioReconfig, + //Seek, + PlaybackRestart, + //PropertyChange, + //QueueOverflow, + //Hook, + Unknown(u32), +} + +#[derive(Copy, Clone, Debug)] +pub struct StartFileEvent { + pub playlist_entry_id: i64, +} + +#[derive(Copy, Clone, Debug)] +pub enum EndFileReason { + Eof, + Stop, + Quit, + Redirect, + Unknown(u32), +} + +#[derive(Copy, Clone, Debug)] +pub struct EndFileEvent { + pub reason: Result, + pub playlist_entry_id: i64, + pub playlist_insert_id: i64, + pub playlist_insert_num_entries: i32, +} + +#[derive(Copy, Clone, Debug)] +pub struct LogMessageEvent<'a> { + pub prefix: &'a str, + pub level: &'a str, + pub text: &'a str, + //log_level: i32, +} diff --git a/src/mpv/handle.rs b/src/mpv/handle.rs index d28fbfb..d733a13 100644 --- a/src/mpv/handle.rs +++ b/src/mpv/handle.rs @@ -1,6 +1,8 @@ -use super::{ffi, Error, SetProperty}; +use super::{ + ffi, EndFileEvent, EndFileReason, Error, Event as MpvEvent, LogMessageEvent, SetProperty, + StartFileEvent, +}; 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; @@ -9,7 +11,6 @@ use std::ptr::NonNull; pub struct Handle { inner: NonNull, wakeup: Pin>, // the wakeup callback holds a pointer to this - wait_event_cell: RefCell<()>, } // The client API is generally fully thread-safe, unless otherwise noted. @@ -49,9 +50,9 @@ impl Handle { ); } - // set up verbose logging for now + // set up info logging for now Error::from_return_code(unsafe { - ffi::mpv_request_log_messages(inner.as_ptr(), c"v".as_ptr()) + ffi::mpv_request_log_messages(inner.as_ptr(), c"info".as_ptr()) }) .unwrap(); @@ -60,11 +61,7 @@ impl 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(()), - } + Self { inner, wakeup } } pub fn client_name(&self) -> &str { @@ -102,41 +99,67 @@ impl Handle { Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) } - pub fn tick(&self) -> Option { - // 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"); + // should take listener before we drain the event queue, so we don't miss any notifications + pub fn wakeup_listener(&self) -> EventListener { + self.wakeup.listen() + } - loop { - let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) }; + pub fn wait_event(&mut self, timeout: f64) -> Option> { + let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), timeout) }; - match event.event_id { - ffi::mpv_event_id_MPV_EVENT_NONE => break, + match event.event_id { + ffi::mpv_event_id_MPV_EVENT_NONE => None, - 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 => Some(MpvEvent::Shutdown), - ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => { - return None; // good bye forever - } - - 11 => { /* deprecated, ignore */ } - - _ => todo!("event {}", event.event_id), + ffi::mpv_event_id_MPV_EVENT_LOG_MESSAGE => { + let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) }; + // TODO: actual logging? + Some(MpvEvent::LogMessage(LogMessageEvent { + prefix: unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(), + level: unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(), + text: unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(), + })) } - } - drop(borrowed); // make sure the borrow is held until here - Some(listener) + ffi::mpv_event_id_MPV_EVENT_START_FILE => { + let data = unsafe { &*(event.data as *mut ffi::mpv_event_start_file) }; + Some(MpvEvent::StartFile(StartFileEvent { + playlist_entry_id: data.playlist_entry_id, + })) + } + + ffi::mpv_event_id_MPV_EVENT_AUDIO_RECONFIG => Some(MpvEvent::AudioReconfig), + + ffi::mpv_event_id_MPV_EVENT_FILE_LOADED => Some(MpvEvent::FileLoaded), + + ffi::mpv_event_id_MPV_EVENT_PLAYBACK_RESTART => Some(MpvEvent::PlaybackRestart), + + ffi::mpv_event_id_MPV_EVENT_END_FILE => { + let data = unsafe { &*(event.data as *mut ffi::mpv_event_end_file) }; + Some(MpvEvent::EndFile(EndFileEvent { + reason: Error::from_return_code(data.error).map(|()| match data.reason { + ffi::mpv_end_file_reason_MPV_END_FILE_REASON_EOF => EndFileReason::Eof, + ffi::mpv_end_file_reason_MPV_END_FILE_REASON_STOP => EndFileReason::Stop, + ffi::mpv_end_file_reason_MPV_END_FILE_REASON_QUIT => EndFileReason::Quit, + ffi::mpv_end_file_reason_MPV_END_FILE_REASON_ERROR => { + unreachable!("end file reason is error, but error field is zero") + } + ffi::mpv_end_file_reason_MPV_END_FILE_REASON_REDIRECT => { + EndFileReason::Redirect + } + unknown => EndFileReason::Unknown(unknown), + }), + playlist_entry_id: data.playlist_entry_id, + playlist_insert_id: data.playlist_insert_id, + playlist_insert_num_entries: data.playlist_insert_num_entries, + })) + } + + 11 => Some(MpvEvent::Unknown(event.event_id)), + + _ => todo!("event {}", event.event_id), + } } } @@ -152,7 +175,9 @@ impl Drop for Handle { ffi::mpv_wait_async_requests(self.inner.as_ptr()); } // drain event queue (we're &mut so we know we have exclusive access) - self.tick(); + while let Some(event) = self.wait_event(0.0) { + println!("drained event on drop: {event:?}"); + } unsafe { ffi::mpv_destroy(self.inner.as_ptr()); diff --git a/src/signal.rs b/src/signal.rs index 7691e1c..3443339 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,12 +1,31 @@ +use gtk::{ + glib::Object, + prelude::{IsA, ObjectExt}, +}; use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak}; -#[derive(Default)] -pub struct Signal { - handlers: RefCell bool>>>, - just_connected: RefCell bool>>>, +type SignalHandlerBox = Box bool>; + +pub struct SignalEmitter { + handlers: RefCell>>, + just_connected: RefCell>>, } +impl Default for SignalEmitter { + fn default() -> Self { + Self { + handlers: RefCell::new(vec![]), + just_connected: RefCell::new(vec![]), + } + } +} + +pub struct Signal<'a, T> { + just_connected: &'a RefCell>>, +} + +#[derive(Clone)] pub struct SignalHandler(Weak>); impl SignalHandler { @@ -21,7 +40,7 @@ impl SignalHandler { } } -impl Signal { +impl Signal<'_, T> { fn connect_impl(&self, f: impl FnMut(T) -> bool + 'static) { self.just_connected.borrow_mut().push(Box::new(f)); } @@ -41,7 +60,7 @@ impl Signal { SignalHandler(disconnect_weak) } - pub fn connect_listener( + pub fn connect_rc( &self, listener: &Rc, mut f: impl FnMut(Rc, T) -> bool + 'static, @@ -53,23 +72,43 @@ impl Signal { Some(listener) => f(listener, t), }) } + + pub fn connect_object>( + &self, + listener: &L, + mut f: impl FnMut(L, T) -> bool + 'static, + ) -> SignalHandler { + let listener = listener.downgrade(); + + self.connect(move |t| match listener.upgrade() { + None => false, + Some(listener) => f(listener, t), + }) + } } -impl Signal -where - T: Clone, -{ - pub fn emit(&self, t: T) { +impl SignalEmitter { + pub fn signal(&self) -> Signal<'_, T> { + Signal { + just_connected: &self.just_connected, + } + } + + pub fn emit_with(&self, mut f: impl FnMut() -> T) { let mut handlers = self .handlers .try_borrow_mut() .expect("tried to re-emit signal during emission"); handlers.append(self.just_connected.borrow_mut().as_mut()); + if handlers.is_empty() { + return; + } + let mut i = 0; let mut skip = 0; loop { - if handlers[i + skip](t.clone()) { + if handlers[i + skip](f()) { i += 1; } else { skip += 1; @@ -85,3 +124,12 @@ where handlers.truncate(i); } } + +impl SignalEmitter +where + T: Clone, +{ + pub fn emit(&self, t: T) { + self.emit_with(|| t.clone()); + } +} diff --git a/src/ui/window.rs b/src/ui/window.rs index b91b223..a50680a 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -28,7 +28,7 @@ mod imp { pub(super) setup: crate::ui::Setup, pub(super) api: RefCell>, - pub(super) mpv: Rc, + pub(super) mpv: Rc>, } #[glib::object_subclass] @@ -53,27 +53,33 @@ mod imp { self.parent_constructed(); // set up mpv - self.mpv - .set_property("audio-client-name", "audrey") - .unwrap(); - self.mpv - .set_property("user-agent", crate::USER_AGENT) - .unwrap(); - self.mpv.set_property("video", false).unwrap(); - self.mpv.set_property("prefetch-playlist", true).unwrap(); - self.mpv.set_property("gapless-audio", true).unwrap(); + let mpv = self.mpv.borrow(); + 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.command(["loadfile", "https://www.youtube.com/watch?v=19y8YTbvri8"]) + .unwrap(); + let mpv_weak = Rc::downgrade(&self.mpv); glib::spawn_future_local(async move { - while let Some(mpv) = mpv_weak.upgrade() { - match mpv.tick() { + loop { + let mpv = match mpv_weak.upgrade() { + Some(mpv) => mpv, None => break, - Some(listener) => { - drop(mpv); // don't - listener.await; + }; + { + let mut mpv_ref = mpv.borrow_mut(); + let listener = mpv_ref.wakeup_listener(); + while let Some(event) = mpv_ref.wait_event(0.0) { + println!("mpv event {:?}", event); } + listener } + .await } }); @@ -188,7 +194,7 @@ mod imp { impl Drop for Window { fn drop(&mut self) { println!("dropping AudreyUiWindow"); - self.mpv.command(["quit"]).unwrap(); + self.mpv.borrow().command(["quit"]).unwrap(); } } }