From 225292d08a6141b12489f0fc116fd2fbfb0c0a1f Mon Sep 17 00:00:00 2001 From: Erica Z Date: Sun, 3 Nov 2024 14:59:54 +0100 Subject: [PATCH] many changes --- src/mpv.rs | 2 +- src/mpv/event.rs | 15 ++-- src/mpv/format.rs | 90 ++++++++++++++++++++---- src/mpv/handle.rs | 46 ++++++++---- src/playbin2.rs | 175 +++++++++++++++++++++++++++++++++++++++++++--- src/ui/window.rs | 9 ++- 6 files changed, 290 insertions(+), 47 deletions(-) diff --git a/src/mpv.rs b/src/mpv.rs index c84f1d5..0bd7dab 100644 --- a/src/mpv.rs +++ b/src/mpv.rs @@ -4,7 +4,7 @@ mod error; pub use error::Error; mod format; -pub use format::SetProperty; +pub use format::{GetProperty, SetProperty}; mod handle; pub use handle::Handle; diff --git a/src/mpv/event.rs b/src/mpv/event.rs index 6c22463..de3e768 100644 --- a/src/mpv/event.rs +++ b/src/mpv/event.rs @@ -17,7 +17,7 @@ pub enum Event { PlaybackRestart, PropertyChange(PropertyEvent), //QueueOverflow, - //Hook, + Hook(HookEvent), Unknown(u32), } @@ -55,15 +55,12 @@ pub struct LogMessageEvent { pub struct PropertyEvent { pub reply_userdata: u64, pub name: String, - pub value: PropertyEventValue, + //pub value: PropertyEventValue, } #[derive(Clone, Debug)] -pub enum PropertyEventValue { - None, - String(String), - OsdString(String), - Flag(bool), - Int64(i64), - Double(f64), +pub struct HookEvent { + pub reply_userdata: u64, + pub name: String, + pub id: u64, } diff --git a/src/mpv/format.rs b/src/mpv/format.rs index 5fef129..57c4bd1 100644 --- a/src/mpv/format.rs +++ b/src/mpv/format.rs @@ -1,63 +1,123 @@ -use super::ffi; +use super::{ffi, Error}; use std::ffi::{c_char, c_int, c_void, CString}; pub trait SetProperty { /// # Safety /// see mpv_set_property - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int; + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error>; } impl SetProperty for String { - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int { + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error> { // should add zero terminator directly let value = CString::new(self).expect("null bytes in string property value"); let value_ptr: *const c_char = value.as_ptr(); - ffi::mpv_set_property( + Error::from_return_code(ffi::mpv_set_property( ctx, name, ffi::mpv_format_MPV_FORMAT_STRING, std::ptr::from_ref::<*const c_char>(&value_ptr) as *mut c_void, - ) + )) } } impl<'a> SetProperty for &'a str { - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int { + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error> { // need to add zero terminator String::set_property(self.into(), ctx, name) } } impl SetProperty for bool { - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int { + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error> { let value: c_int = if self { 1 } else { 0 }; - ffi::mpv_set_property( + Error::from_return_code(ffi::mpv_set_property( ctx, name, ffi::mpv_format_MPV_FORMAT_FLAG, std::ptr::from_ref::(&value) as *mut c_void, - ) + )) } } impl SetProperty for i64 { - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int { - ffi::mpv_set_property( + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error> { + Error::from_return_code(ffi::mpv_set_property( ctx, name, ffi::mpv_format_MPV_FORMAT_INT64, std::ptr::from_ref::(&self) as *mut c_void, - ) + )) } } impl SetProperty for f64 { - unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int { - ffi::mpv_set_property( + unsafe fn set_property( + self, + ctx: *mut ffi::mpv_handle, + name: *const c_char, + ) -> Result<(), Error> { + Error::from_return_code(ffi::mpv_set_property( ctx, name, ffi::mpv_format_MPV_FORMAT_DOUBLE, std::ptr::from_ref::(&self) as *mut c_void, - ) + )) + } +} + +pub trait GetProperty: Sized { + /// # Safety + /// see mpv_get_property + unsafe fn get_property(ctx: *mut ffi::mpv_handle, name: *const c_char) -> Result; +} + +impl GetProperty for bool { + unsafe fn get_property(ctx: *mut ffi::mpv_handle, name: *const c_char) -> Result { + let mut value: c_int = -1; + Error::from_return_code(ffi::mpv_get_property( + ctx, + name, + ffi::mpv_format_MPV_FORMAT_FLAG, + std::ptr::from_mut::(&mut value) as *mut c_void, + ))?; + match value { + 0 => Ok(false), + 1 => Ok(true), + _ => unreachable!(), + } + } +} + +impl GetProperty for i64 { + unsafe fn get_property(ctx: *mut ffi::mpv_handle, name: *const c_char) -> Result { + let mut value: i64 = -1; + Error::from_return_code(ffi::mpv_get_property( + ctx, + name, + ffi::mpv_format_MPV_FORMAT_INT64, + std::ptr::from_mut::(&mut value) as *mut c_void, + ))?; + Ok(value) } } diff --git a/src/mpv/handle.rs b/src/mpv/handle.rs index 8f32851..92e9d42 100644 --- a/src/mpv/handle.rs +++ b/src/mpv/handle.rs @@ -1,6 +1,6 @@ -use super::{event, ffi, Error, Event as MpvEvent, SetProperty}; +use super::{event, ffi, Error, Event as MpvEvent, GetProperty, SetProperty}; use event::{ - EndFileEvent, EndFileReason, LogMessageEvent, PropertyEvent, PropertyEventValue, StartFileEvent, + EndFileEvent, EndFileReason, HookEvent, LogMessageEvent, PropertyEvent, StartFileEvent, }; use event_listener::{Event, EventListener, IntoNotification}; use std::cell::{RefCell, RefMut}; @@ -83,7 +83,12 @@ impl Handle { pub fn set_property(&self, name: &str, value: impl SetProperty) -> Result<(), Error> { // need to add zero terminator let name = CString::new(name).expect("null bytes in property name"); - Error::from_return_code(unsafe { value.set_property(self.inner.as_ptr(), name.as_ptr()) }) + unsafe { value.set_property(self.inner.as_ptr(), name.as_ptr()) } + } + + pub fn get_property(&self, name: &str) -> Result { + let name = CString::new(name).expect("null bytes in property name"); + unsafe { T::get_property(self.inner.as_ptr(), name.as_ptr()) } } pub fn command<'a>(&self, args: impl IntoIterator) -> Result<(), Error> { @@ -105,14 +110,14 @@ impl Handle { Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) } - pub fn observe_property_i64(&self, reply_userdata: u64, name: &str) -> Result<(), Error> { + pub fn observe_property(&self, reply_userdata: u64, name: &str) -> Result<(), Error> { let name = CString::new(name).expect("null bytes in property name"); Error::from_return_code(unsafe { ffi::mpv_observe_property( self.inner.as_ptr(), reply_userdata, name.as_ptr(), - ffi::mpv_format_MPV_FORMAT_INT64, + ffi::mpv_format_MPV_FORMAT_NONE, ) }) } @@ -123,6 +128,19 @@ impl Handle { Error::from_return_code(rc).map(|()| rc as u32) } + pub fn add_hook(&self, reply_userdata: u64, name: &str, priority: i32) -> Result<(), Error> { + let name = CString::new(name).expect("null bytes in property name"); + Error::from_return_code(unsafe { + ffi::mpv_hook_add(self.inner.as_ptr(), reply_userdata, name.as_ptr(), priority) + }) + } + + /// # Safety + /// > It is explicitly undefined behavior to call this more than once for each MPV_EVENT_HOOK, to pass an incorrect ID, or to call this on a mpv_handle different from the one that registered the handler and received the event. + pub unsafe fn continue_hook_unchecked(&self, id: u64) -> Result<(), Error> { + Error::from_return_code(unsafe { ffi::mpv_hook_continue(self.inner.as_ptr(), id) }) + } + // 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() @@ -196,13 +214,17 @@ impl Handle { name: unsafe { CStr::from_ptr(data.name) } .to_string_lossy() .into(), - value: match data.format { - ffi::mpv_format_MPV_FORMAT_NONE => PropertyEventValue::None, - ffi::mpv_format_MPV_FORMAT_INT64 => { - PropertyEventValue::Int64(*unsafe { &*(data.data as *mut i64) }) - } - _ => todo!("{:?}", data.format), - }, + })) + } + + ffi::mpv_event_id_MPV_EVENT_HOOK => { + let data = unsafe { &*(event.data as *mut ffi::mpv_event_hook) }; + Some(MpvEvent::Hook(HookEvent { + reply_userdata: event.reply_userdata, + name: unsafe { CStr::from_ptr(data.name) } + .to_string_lossy() + .into(), + id: data.id, })) } diff --git a/src/playbin2.rs b/src/playbin2.rs index 40f310f..e149d96 100644 --- a/src/playbin2.rs +++ b/src/playbin2.rs @@ -1,11 +1,33 @@ use crate::mpv; +use crate::signal::SignalEmitter; use event_listener::EventListener; +use std::cell::{Ref, RefCell}; +use url::Url; -pub struct Playbin { - mpv: mpv::Handle, +pub trait PlaybinEntry { + fn url(&self) -> &Url; } -impl Default for Playbin { +impl PlaybinEntry for Url { + fn url(&self) -> &Url { + self + } +} + +// E: generic entry type +pub struct Playbin { + mpv: mpv::Handle, + entries: RefCell>, + + paused_changed: SignalEmitter<()>, + current_entry_changed: SignalEmitter<()>, + + entry_inserted: SignalEmitter, + stopped: SignalEmitter<()>, + entry_removed: SignalEmitter, +} + +impl Default for Playbin { fn default() -> Self { let mpv = mpv::Handle::new(); mpv.set_property("audio-client-name", "audrey").unwrap(); @@ -14,14 +36,118 @@ impl Default for Playbin { mpv.set_property("prefetch-playlist", true).unwrap(); mpv.set_property("gapless-audio", true).unwrap(); - mpv.command(["loadfile", "https://www.youtube.com/watch?v=19y8YTbvri8"]) - .unwrap(); + mpv.observe_property(0, "pause").unwrap(); + mpv.observe_property(1, "playlist-pos").unwrap(); - Self { mpv } + // "Useful to drain property changes before a new file is loaded." + mpv.add_hook(0, "on_before_start_file", 0).unwrap(); + + Self { + mpv, + entries: RefCell::new(vec![]), + + paused_changed: Default::default(), + current_entry_changed: Default::default(), + + entry_inserted: Default::default(), + stopped: Default::default(), + entry_removed: Default::default(), + } } } -impl Playbin { +impl Playbin +where + E: PlaybinEntry, +{ + pub fn paused(&self) -> bool { + self.mpv.get_property("pause").unwrap() + } + + pub fn set_paused(&self, paused: bool) { + self.mpv.set_property("pause", paused).unwrap(); + } + + pub fn position(&self) -> Option { + todo!() + } + + pub fn current_entry(&self) -> Option { + self.mpv + .get_property::("playlist-pos") + .unwrap() + .try_into() + .ok() + } + + pub fn seek(&self, _position: f64) { + todo!() + } + + pub fn next_entry(&self) { + self.mpv.command(["playlist-next"]).unwrap(); + } + + pub fn prev_entry(&self) { + self.mpv.command(["playlist-prev"]).unwrap(); + } + + pub fn play_entry(&self, index: u32) { + self.mpv + .command(["playlist-play-index", &index.to_string()]) + .unwrap(); + } + + pub fn entries(&self) -> Ref<'_, [E]> { + Ref::map(self.entries.borrow(), Vec::as_ref) + } + + pub fn push_entry(&self, entry: E) { + let mut entries = self.entries.borrow_mut(); + self.mpv + .command(["loadfile", entry.url().as_str(), "append-play"]) + .unwrap(); + let index = entries.len(); + entries.push(entry); + + drop(entries); + self.entry_inserted.emit(index as u32); + } + + pub fn insert_entry(&self, index: u32, entry: E) { + let mut entries = self.entries.borrow_mut(); + self.mpv + .command(["loadfile", entry.url().as_str(), "insert-at-play"]) + .unwrap(); + entries.insert(index as usize, entry); + + drop(entries); + self.entry_inserted.emit(index); + } + + // stop playback and clear playlist + pub fn stop(&self) { + let mut entries = self.entries.borrow_mut(); + self.mpv.command(["stop"]).unwrap(); + entries.clear(); + + drop(entries); + self.stopped.emit(()); + } + + pub fn remove_entry(&self, index: u32) { + let mut entries = self.entries.borrow_mut(); + self.mpv.command(["remove", &index.to_string()]).unwrap(); + entries.remove(index as usize); + + drop(entries); + self.entry_removed.emit(index); + } + + pub fn move_entry(&self, _from: u32, _to: u32) { + todo!() + } + pub fn tick(&self) -> EventListener { let listener = self.mpv.wakeup_listener(); while let Some(event) = self.mpv.wait_event(0.0) { @@ -31,11 +157,42 @@ impl Playbin { } fn handle_event(&self, event: mpv::Event) { - println!("mpv event {:?}", event); + match event { + mpv::Event::PropertyChange(event) => match event.reply_userdata { + 0 => { + assert_eq!(&event.name, "pause"); + self.paused_changed.emit(()); + println!("new paused! {:?}", self.paused()); + } + + 1 => { + assert_eq!(&event.name, "playlist-pos"); + self.current_entry_changed.emit(()); + println!("new current_entry! {:?}", self.current_entry()); + } + + _ => unreachable!(), + }, + + mpv::Event::Hook(event) => match event.reply_userdata { + 0 => { + assert_eq!(&event.name, "on_before_start_file"); + // just use this as a barrier + println!("on_before_start_file triggered"); + unsafe { + self.mpv.continue_hook_unchecked(event.id).unwrap(); + } + } + + _ => unreachable!(), + }, + + _ => println!("mpv event {:?}", event), + } } } -impl Drop for Playbin { +impl Drop for Playbin { fn drop(&mut self) { println!("dropping Playbin2"); self.mpv.command(["quit"]).unwrap(); diff --git a/src/ui/window.rs b/src/ui/window.rs index b27bf4a..6d5aaff 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>, - playbin2: Rc, + playbin2: Rc>, } #[glib::object_subclass] @@ -52,6 +52,13 @@ mod imp { fn constructed(&self) { self.parent_constructed(); + self.playbin2.tick(); + self.playbin2.push_entry( + "https://www.youtube.com/watch?v=19y8YTbvri8" + .try_into() + .unwrap(), + ); + let playbin = Rc::downgrade(&self.playbin2); glib::spawn_future_local(glib::clone!(async move { loop {