many changes
This commit is contained in:
parent
dbd209f904
commit
225292d08a
6 changed files with 290 additions and 47 deletions
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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::<c_int>(&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::<i64>(&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::<f64>(&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<Self, Error>;
|
||||
}
|
||||
|
||||
impl GetProperty for bool {
|
||||
unsafe fn get_property(ctx: *mut ffi::mpv_handle, name: *const c_char) -> Result<Self, Error> {
|
||||
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::<c_int>(&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<Self, Error> {
|
||||
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::<i64>(&mut value) as *mut c_void,
|
||||
))?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T: GetProperty>(&self, name: &str) -> Result<T, Error> {
|
||||
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<Item = &'a str>) -> 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,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
175
src/playbin2.rs
175
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<E> {
|
||||
mpv: mpv::Handle,
|
||||
entries: RefCell<Vec<E>>,
|
||||
|
||||
paused_changed: SignalEmitter<()>,
|
||||
current_entry_changed: SignalEmitter<()>,
|
||||
|
||||
entry_inserted: SignalEmitter<u32>,
|
||||
stopped: SignalEmitter<()>,
|
||||
entry_removed: SignalEmitter<u32>,
|
||||
}
|
||||
|
||||
impl<E> Default for Playbin<E> {
|
||||
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<E> Playbin<E>
|
||||
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<f64> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn current_entry(&self) -> Option<u32> {
|
||||
self.mpv
|
||||
.get_property::<i64>("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<E> Drop for Playbin<E> {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping Playbin2");
|
||||
self.mpv.command(["quit"]).unwrap();
|
||||
|
|
|
@ -28,7 +28,7 @@ mod imp {
|
|||
pub(super) setup: crate::ui::Setup,
|
||||
pub(super) api: RefCell<Option<crate::subsonic_vala::Client>>,
|
||||
|
||||
playbin2: Rc<crate::playbin2::Playbin>,
|
||||
playbin2: Rc<crate::playbin2::Playbin<url::Url>>,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
Loading…
Reference in a new issue