this bad girl can hold so many architectural changes

This commit is contained in:
Erica Z 2024-11-03 12:51:04 +01:00
parent 40db5b1b18
commit bbd635c7c0
6 changed files with 203 additions and 69 deletions

View file

@ -20,7 +20,7 @@ pub mod subsonic_vala;
mod playbin2; mod playbin2;
mod signal; mod signal;
pub use signal::{Signal, SignalHandler}; pub use signal::{Signal, SignalEmitter, SignalHandler};
use gettextrs::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory}; use gettextrs::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory};
use gtk::prelude::*; use gtk::prelude::*;

View file

@ -8,3 +8,6 @@ pub use format::SetProperty;
mod handle; mod handle;
pub use handle::Handle; pub use handle::Handle;
mod event;
pub use event::{EndFileEvent, EndFileReason, Event, LogMessageEvent, StartFileEvent};

52
src/mpv/event.rs Normal file
View file

@ -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<EndFileReason, Error>,
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,
}

View file

@ -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 event_listener::{Event, EventListener, IntoNotification};
use std::cell::RefCell;
use std::ffi::{c_char, c_void, CStr, CString}; use std::ffi::{c_char, c_void, CStr, CString};
use std::fmt; use std::fmt;
use std::pin::Pin; use std::pin::Pin;
@ -9,7 +11,6 @@ use std::ptr::NonNull;
pub struct Handle { pub struct Handle {
inner: NonNull<ffi::mpv_handle>, inner: NonNull<ffi::mpv_handle>,
wakeup: Pin<Box<Event>>, // the wakeup callback holds a pointer to this 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. // 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 { 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(); .unwrap();
@ -60,11 +61,7 @@ impl Handle {
Error::from_return_code(unsafe { ffi::mpv_initialize(inner.as_ptr()) }) Error::from_return_code(unsafe { ffi::mpv_initialize(inner.as_ptr()) })
.expect("could not initialize mpv handle"); .expect("could not initialize mpv handle");
Self { Self { inner, wakeup }
inner,
wakeup,
wait_event_cell: RefCell::new(()),
}
} }
pub fn client_name(&self) -> &str { pub fn client_name(&self) -> &str {
@ -102,42 +99,68 @@ impl Handle {
Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) })
} }
pub fn tick(&self) -> Option<EventListener> { // should take listener before we drain the event queue, so we don't miss any notifications
// take listener before we drain the event queue, so we don't miss any notifications pub fn wakeup_listener(&self) -> EventListener {
let listener = self.wakeup.listen(); self.wakeup.listen()
let borrowed = self }
.wait_event_cell
.try_borrow_mut()
.expect("Mpv::tick is not reentrant");
loop { pub fn wait_event(&mut self, timeout: f64) -> Option<MpvEvent<'_>> {
let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) }; let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), timeout) };
match event.event_id { match event.event_id {
ffi::mpv_event_id_MPV_EVENT_NONE => break, ffi::mpv_event_id_MPV_EVENT_NONE => None,
ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => Some(MpvEvent::Shutdown),
ffi::mpv_event_id_MPV_EVENT_LOG_MESSAGE => { ffi::mpv_event_id_MPV_EVENT_LOG_MESSAGE => {
let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) }; let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) };
// TODO: actual logging? // TODO: actual logging?
let prefix = unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(); Some(MpvEvent::LogMessage(LogMessageEvent {
let level = unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(); prefix: unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(),
let text = unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(); level: unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(),
print!("[{prefix}] {level}: {text}"); text: unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(),
}))
} }
ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => { ffi::mpv_event_id_MPV_EVENT_START_FILE => {
return None; // good bye forever let data = unsafe { &*(event.data as *mut ffi::mpv_event_start_file) };
Some(MpvEvent::StartFile(StartFileEvent {
playlist_entry_id: data.playlist_entry_id,
}))
} }
11 => { /* deprecated, ignore */ } 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), _ => todo!("event {}", event.event_id),
} }
} }
drop(borrowed); // make sure the borrow is held until here
Some(listener)
}
} }
impl Drop for Handle { impl Drop for Handle {
@ -152,7 +175,9 @@ impl Drop for Handle {
ffi::mpv_wait_async_requests(self.inner.as_ptr()); ffi::mpv_wait_async_requests(self.inner.as_ptr());
} }
// drain event queue (we're &mut so we know we have exclusive access) // 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 { unsafe {
ffi::mpv_destroy(self.inner.as_ptr()); ffi::mpv_destroy(self.inner.as_ptr());

View file

@ -1,12 +1,31 @@
use gtk::{
glib::Object,
prelude::{IsA, ObjectExt},
};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
#[derive(Default)] type SignalHandlerBox<T> = Box<dyn FnMut(T) -> bool>;
pub struct Signal<T> {
handlers: RefCell<Vec<Box<dyn FnMut(T) -> bool>>>, pub struct SignalEmitter<T> {
just_connected: RefCell<Vec<Box<dyn FnMut(T) -> bool>>>, handlers: RefCell<Vec<SignalHandlerBox<T>>>,
just_connected: RefCell<Vec<SignalHandlerBox<T>>>,
} }
impl<T> Default for SignalEmitter<T> {
fn default() -> Self {
Self {
handlers: RefCell::new(vec![]),
just_connected: RefCell::new(vec![]),
}
}
}
pub struct Signal<'a, T> {
just_connected: &'a RefCell<Vec<SignalHandlerBox<T>>>,
}
#[derive(Clone)]
pub struct SignalHandler(Weak<Cell<bool>>); pub struct SignalHandler(Weak<Cell<bool>>);
impl SignalHandler { impl SignalHandler {
@ -21,7 +40,7 @@ impl SignalHandler {
} }
} }
impl<T> Signal<T> { impl<T> Signal<'_, T> {
fn connect_impl(&self, f: impl FnMut(T) -> bool + 'static) { fn connect_impl(&self, f: impl FnMut(T) -> bool + 'static) {
self.just_connected.borrow_mut().push(Box::new(f)); self.just_connected.borrow_mut().push(Box::new(f));
} }
@ -41,7 +60,7 @@ impl<T> Signal<T> {
SignalHandler(disconnect_weak) SignalHandler(disconnect_weak)
} }
pub fn connect_listener<L: 'static>( pub fn connect_rc<L: 'static>(
&self, &self,
listener: &Rc<L>, listener: &Rc<L>,
mut f: impl FnMut(Rc<L>, T) -> bool + 'static, mut f: impl FnMut(Rc<L>, T) -> bool + 'static,
@ -53,23 +72,43 @@ impl<T> Signal<T> {
Some(listener) => f(listener, t), Some(listener) => f(listener, t),
}) })
} }
pub fn connect_object<L: IsA<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<T> Signal<T> impl<T> SignalEmitter<T> {
where pub fn signal(&self) -> Signal<'_, T> {
T: Clone, Signal {
{ just_connected: &self.just_connected,
pub fn emit(&self, t: T) { }
}
pub fn emit_with(&self, mut f: impl FnMut() -> T) {
let mut handlers = self let mut handlers = self
.handlers .handlers
.try_borrow_mut() .try_borrow_mut()
.expect("tried to re-emit signal during emission"); .expect("tried to re-emit signal during emission");
handlers.append(self.just_connected.borrow_mut().as_mut()); handlers.append(self.just_connected.borrow_mut().as_mut());
if handlers.is_empty() {
return;
}
let mut i = 0; let mut i = 0;
let mut skip = 0; let mut skip = 0;
loop { loop {
if handlers[i + skip](t.clone()) { if handlers[i + skip](f()) {
i += 1; i += 1;
} else { } else {
skip += 1; skip += 1;
@ -85,3 +124,12 @@ where
handlers.truncate(i); handlers.truncate(i);
} }
} }
impl<T> SignalEmitter<T>
where
T: Clone,
{
pub fn emit(&self, t: T) {
self.emit_with(|| t.clone());
}
}

View file

@ -28,7 +28,7 @@ mod imp {
pub(super) setup: crate::ui::Setup, pub(super) setup: crate::ui::Setup,
pub(super) api: RefCell<Option<crate::subsonic_vala::Client>>, pub(super) api: RefCell<Option<crate::subsonic_vala::Client>>,
pub(super) mpv: Rc<mpv::Handle>, pub(super) mpv: Rc<RefCell<mpv::Handle>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -53,27 +53,33 @@ mod imp {
self.parent_constructed(); self.parent_constructed();
// set up mpv // set up mpv
self.mpv let mpv = self.mpv.borrow();
.set_property("audio-client-name", "audrey") mpv.set_property("audio-client-name", "audrey").unwrap();
.unwrap(); mpv.set_property("user-agent", crate::USER_AGENT).unwrap();
self.mpv mpv.set_property("video", false).unwrap();
.set_property("user-agent", crate::USER_AGENT) mpv.set_property("prefetch-playlist", true).unwrap();
.unwrap(); mpv.set_property("gapless-audio", true).unwrap();
self.mpv.set_property("video", false).unwrap();
self.mpv.set_property("prefetch-playlist", true).unwrap();
self.mpv.set_property("gapless-audio", true).unwrap();
// TODO: observe properties // TODO: observe properties
mpv.command(["loadfile", "https://www.youtube.com/watch?v=19y8YTbvri8"])
.unwrap();
let mpv_weak = Rc::downgrade(&self.mpv); let mpv_weak = Rc::downgrade(&self.mpv);
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
while let Some(mpv) = mpv_weak.upgrade() { loop {
match mpv.tick() { let mpv = match mpv_weak.upgrade() {
Some(mpv) => mpv,
None => break, 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 { impl Drop for Window {
fn drop(&mut self) { fn drop(&mut self) {
println!("dropping AudreyUiWindow"); println!("dropping AudreyUiWindow");
self.mpv.command(["quit"]).unwrap(); self.mpv.borrow().command(["quit"]).unwrap();
} }
} }
} }