this bad girl can hold so many architectural changes
This commit is contained in:
parent
40db5b1b18
commit
bbd635c7c0
6 changed files with 203 additions and 69 deletions
|
@ -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::*;
|
||||||
|
|
|
@ -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
52
src/mpv/event.rs
Normal 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,
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue