Compare commits
No commits in common. "dbd209f904c71f859118fcfe284170a482295647" and "f8d139e36f6c3de4d2af35dd98fa28e2aacbc2ad" have entirely different histories.
dbd209f904
...
f8d139e36f
11 changed files with 319 additions and 666 deletions
|
@ -1,10 +1,14 @@
|
|||
mod imp {
|
||||
use crate::ui;
|
||||
use crate::{mpv, ui};
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::glib;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Application {}
|
||||
pub struct Application {
|
||||
// FIXME: move somewhere else
|
||||
mpv: Rc<mpv::Handle>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Application {
|
||||
|
@ -22,6 +26,64 @@ mod imp {
|
|||
None => {
|
||||
let window = ui::Window::new(self.obj().as_ref());
|
||||
window.present();
|
||||
|
||||
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();
|
||||
// TODO: observe properties
|
||||
|
||||
let mpv_weak = Rc::downgrade(&self.mpv);
|
||||
glib::spawn_future_local(async move {
|
||||
while let Some(mpv) = mpv_weak.upgrade() {
|
||||
match mpv.tick() {
|
||||
None => break,
|
||||
Some(listener) => {
|
||||
drop(mpv); // don't
|
||||
listener.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
let conn = zbus::connection::Builder::session()
|
||||
.expect("could not connect to the session bus")
|
||||
.internal_executor(false)
|
||||
.build()
|
||||
.await
|
||||
.expect("could not build connection to the session bus");
|
||||
|
||||
// run this in glib's main loop
|
||||
glib::spawn_future_local(glib::clone!(
|
||||
#[strong]
|
||||
conn,
|
||||
async move {
|
||||
loop {
|
||||
conn.executor().tick().await;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
crate::Mpris::setup(conn.object_server(), &window)
|
||||
.await
|
||||
.expect("could not serve mpris");
|
||||
crate::mpris::Player::setup(conn.object_server(), &window.playbin())
|
||||
.await
|
||||
.expect("could not serve mpris player");
|
||||
|
||||
drop(window); // don't keep this alive
|
||||
|
||||
// always set up handlers before requesting service name
|
||||
conn.request_name("org.mpris.MediaPlayer2.audrey")
|
||||
.await
|
||||
.expect("could not register name in session bus");
|
||||
});
|
||||
}
|
||||
Some(win) => win.present(),
|
||||
}
|
||||
|
@ -37,6 +99,7 @@ mod imp {
|
|||
impl Drop for Application {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping AudreyApplication");
|
||||
self.mpv.command(["quit"]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,6 @@ pub mod subsonic_vala;
|
|||
|
||||
mod playbin2;
|
||||
|
||||
mod signal;
|
||||
pub use signal::{Signal, SignalEmitter, SignalHandler};
|
||||
|
||||
use gettextrs::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{gio, glib};
|
||||
|
|
260
src/mpv.rs
260
src/mpv.rs
|
@ -1,13 +1,257 @@
|
|||
mod ffi;
|
||||
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
use std::ffi::{c_char, c_int, c_void, CStr, CString};
|
||||
|
||||
mod format;
|
||||
pub use format::SetProperty;
|
||||
#[link(name = "mpv")]
|
||||
extern "C" {}
|
||||
|
||||
mod handle;
|
||||
pub use handle::Handle;
|
||||
pub struct Error(c_int);
|
||||
|
||||
pub mod event;
|
||||
pub use event::Event;
|
||||
impl Error {
|
||||
fn from_return_code(rc: c_int) -> Result<(), Self> {
|
||||
if rc == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Self(rc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Display::fmt(
|
||||
unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) }
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_tuple("Error")
|
||||
.field(
|
||||
&unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) }
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
use event_listener::{Event, EventListener, IntoNotification};
|
||||
use std::cell::RefCell;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct Handle {
|
||||
inner: NonNull<ffi::mpv_handle>,
|
||||
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.
|
||||
//unsafe impl Send for Handle {}
|
||||
//unsafe impl Sync for Handle {}
|
||||
// ........but we'd rather keep this in a single thread to play better with gtk
|
||||
|
||||
impl Default for Handle {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn new() -> Self {
|
||||
let inner =
|
||||
NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle");
|
||||
|
||||
let wakeup = Box::pin(Event::new());
|
||||
|
||||
extern "C" fn wakeup_callback(d: *mut c_void) {
|
||||
let d = d as *const Event;
|
||||
let wakeup = unsafe { &*d };
|
||||
wakeup.notify(1_u32.relaxed()); // mpv has its own synchronization, trust it
|
||||
}
|
||||
unsafe {
|
||||
ffi::mpv_set_wakeup_callback(
|
||||
inner.as_ptr(),
|
||||
Some(wakeup_callback),
|
||||
std::ptr::from_ref::<Event>(&wakeup) as *mut c_void,
|
||||
);
|
||||
}
|
||||
|
||||
// set up verbose logging for now
|
||||
Error::from_return_code(unsafe {
|
||||
ffi::mpv_request_log_messages(inner.as_ptr(), c"v".as_ptr())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// TODO: maybe we need to set something before initialization, but idk
|
||||
// also wed need a builder, since "Note that you should avoid doing concurrent accesses on the uninitialized client 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(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_property(&self, name: &str, value: impl FormatValue) -> Result<(), Error> {
|
||||
// need to add zero terminator
|
||||
let name = CString::new(name).expect("null bytes in property name");
|
||||
value.into_data(|format, data| {
|
||||
Error::from_return_code(unsafe {
|
||||
ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn command<'a>(&self, args: impl IntoIterator<Item = &'a str>) -> Result<(), Error> {
|
||||
// add zero terminators to Everything
|
||||
let args: Vec<CString> = args
|
||||
.into_iter()
|
||||
.map(CString::new)
|
||||
.map(Result::unwrap)
|
||||
.collect();
|
||||
let mut args: Vec<*const c_char> = args
|
||||
.iter()
|
||||
.map(CString::as_c_str)
|
||||
.map(CStr::as_ptr)
|
||||
.collect();
|
||||
// must be null terminated
|
||||
args.push(std::ptr::null_mut());
|
||||
let args: *mut *const c_char = args.as_mut_ptr();
|
||||
|
||||
Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) })
|
||||
}
|
||||
|
||||
pub fn tick(&self) -> Option<EventListener> {
|
||||
// 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");
|
||||
|
||||
loop {
|
||||
let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) };
|
||||
|
||||
match event.event_id {
|
||||
ffi::mpv_event_id_MPV_EVENT_NONE => break,
|
||||
|
||||
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 => {
|
||||
return None; // good bye forever
|
||||
}
|
||||
|
||||
11 => { /* deprecated, ignore */ }
|
||||
|
||||
_ => todo!("event {}", event.event_id),
|
||||
}
|
||||
}
|
||||
|
||||
drop(borrowed); // make sure the borrow is held until here
|
||||
Some(listener)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handle {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping MpvHandle");
|
||||
|
||||
// let any executor ticking tasks know we're ded
|
||||
self.wakeup.notify(u32::MAX.relaxed());
|
||||
|
||||
// just in case
|
||||
unsafe {
|
||||
ffi::mpv_wait_async_requests(self.inner.as_ptr());
|
||||
}
|
||||
// drain event queue (we're &mut so we know we have exclusive access)
|
||||
self.tick();
|
||||
|
||||
unsafe {
|
||||
ffi::mpv_destroy(self.inner.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormatValue: Sized {
|
||||
const FORMAT: ffi::mpv_format;
|
||||
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T;
|
||||
fn from_data<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T;
|
||||
}
|
||||
|
||||
impl<'a> FormatValue for &'a str {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING;
|
||||
|
||||
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T {
|
||||
let str = CString::new(self).expect("null bytes in string");
|
||||
let str_ptr = str.as_ptr();
|
||||
f(Self::FORMAT, (&str_ptr) as *const _ as *mut c_void)
|
||||
}
|
||||
|
||||
fn from_data<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for bool {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG;
|
||||
|
||||
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T {
|
||||
let flag: c_int = if self { 1 } else { 0 };
|
||||
f(Self::FORMAT, (&flag) as *const c_int as *mut c_void)
|
||||
}
|
||||
|
||||
fn from_data<T>(
|
||||
_format: ffi::mpv_format,
|
||||
_data: *mut c_void,
|
||||
_f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatValue for f64 {
|
||||
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE;
|
||||
|
||||
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T {
|
||||
f(Self::FORMAT, (&self) as *const f64 as *mut c_void)
|
||||
}
|
||||
|
||||
fn from_data<T>(
|
||||
format: ffi::mpv_format,
|
||||
data: *mut c_void,
|
||||
f: impl FnOnce(Option<Self>) -> T,
|
||||
) -> T {
|
||||
match format {
|
||||
ffi::mpv_format_MPV_FORMAT_NONE => f(None),
|
||||
ffi::mpv_format_MPV_FORMAT_DOUBLE => f(Some(unsafe { *(data as *mut f64) })),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use super::ffi;
|
||||
use std::ffi::{c_int, CStr};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Error(c_int);
|
||||
|
||||
impl Error {
|
||||
pub(super) fn from_return_code(rc: c_int) -> Result<(), Self> {
|
||||
if rc == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Self(rc))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'static str {
|
||||
unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0)) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Display::fmt(&self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_tuple("Error").field(&self.as_str()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
|
@ -1,69 +0,0 @@
|
|||
use super::Error;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
Shutdown,
|
||||
LogMessage(LogMessageEvent),
|
||||
//GetPropertyReply(PropertyEvent), TODO
|
||||
//SetPropertyReply(PropertyEvent), TODO
|
||||
//CommandReply(CommandEvent), TODO
|
||||
StartFile(StartFileEvent),
|
||||
EndFile(EndFileEvent),
|
||||
FileLoaded,
|
||||
//ClientMessage(ClientMessageEvent), TODO
|
||||
VideoReconfig,
|
||||
AudioReconfig,
|
||||
//Seek,
|
||||
PlaybackRestart,
|
||||
PropertyChange(PropertyEvent),
|
||||
//QueueOverflow,
|
||||
//Hook,
|
||||
Unknown(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StartFileEvent {
|
||||
pub playlist_entry_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EndFileReason {
|
||||
Eof,
|
||||
Stop,
|
||||
Quit,
|
||||
Redirect,
|
||||
Unknown(u32),
|
||||
}
|
||||
|
||||
#[derive(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(Clone, Debug)]
|
||||
pub struct LogMessageEvent {
|
||||
pub prefix: String,
|
||||
pub level: String,
|
||||
pub text: String,
|
||||
//log_level: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PropertyEvent {
|
||||
pub reply_userdata: u64,
|
||||
pub name: String,
|
||||
pub value: PropertyEventValue,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PropertyEventValue {
|
||||
None,
|
||||
String(String),
|
||||
OsdString(String),
|
||||
Flag(bool),
|
||||
Int64(i64),
|
||||
Double(f64),
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use super::ffi;
|
||||
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;
|
||||
}
|
||||
|
||||
impl SetProperty for String {
|
||||
unsafe fn set_property(self, ctx: *mut ffi::mpv_handle, name: *const c_char) -> c_int {
|
||||
// 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(
|
||||
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 {
|
||||
// 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 {
|
||||
let value: c_int = if self { 1 } else { 0 };
|
||||
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(
|
||||
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(
|
||||
ctx,
|
||||
name,
|
||||
ffi::mpv_format_MPV_FORMAT_DOUBLE,
|
||||
std::ptr::from_ref::<f64>(&self) as *mut c_void,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
use super::{event, ffi, Error, Event as MpvEvent, SetProperty};
|
||||
use event::{
|
||||
EndFileEvent, EndFileReason, LogMessageEvent, PropertyEvent, PropertyEventValue, StartFileEvent,
|
||||
};
|
||||
use event_listener::{Event, EventListener, IntoNotification};
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct Handle {
|
||||
inner: NonNull<ffi::mpv_handle>,
|
||||
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.
|
||||
//unsafe impl Send for Handle {}
|
||||
//unsafe impl Sync for Handle {}
|
||||
// ........but we'd rather keep this in a single thread to play better with gtk
|
||||
|
||||
impl Default for Handle {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Handle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_tuple("Handle").field(&self.client_name()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn new() -> Self {
|
||||
let inner =
|
||||
NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle");
|
||||
|
||||
let wakeup = Box::pin(Event::new());
|
||||
|
||||
extern "C" fn wakeup_callback(d: *mut c_void) {
|
||||
let d = d as *const Event;
|
||||
let wakeup = unsafe { &*d };
|
||||
wakeup.notify(1_u32.relaxed()); // mpv has its own synchronization, trust it
|
||||
}
|
||||
unsafe {
|
||||
ffi::mpv_set_wakeup_callback(
|
||||
inner.as_ptr(),
|
||||
Some(wakeup_callback),
|
||||
std::ptr::from_ref::<Event>(&wakeup) as *mut c_void,
|
||||
);
|
||||
}
|
||||
|
||||
// set up info logging for now
|
||||
Error::from_return_code(unsafe {
|
||||
ffi::mpv_request_log_messages(inner.as_ptr(), c"info".as_ptr())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// TODO: maybe we need to set something before initialization, but idk
|
||||
// also wed need a builder, since "Note that you should avoid doing concurrent accesses on the uninitialized client 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(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_name(&self) -> &str {
|
||||
unsafe { CStr::from_ptr(ffi::mpv_client_name(self.inner.as_ptr())) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn client_id(&self) -> i64 {
|
||||
unsafe { ffi::mpv_client_id(self.inner.as_ptr()) }
|
||||
}
|
||||
|
||||
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()) })
|
||||
}
|
||||
|
||||
pub fn command<'a>(&self, args: impl IntoIterator<Item = &'a str>) -> Result<(), Error> {
|
||||
// add zero terminators to Everything
|
||||
let args: Vec<CString> = args
|
||||
.into_iter()
|
||||
.map(CString::new)
|
||||
.map(Result::unwrap)
|
||||
.collect();
|
||||
let mut args: Vec<*const c_char> = args
|
||||
.iter()
|
||||
.map(CString::as_c_str)
|
||||
.map(CStr::as_ptr)
|
||||
.collect();
|
||||
// must be null terminated
|
||||
args.push(std::ptr::null_mut());
|
||||
let args: *mut *const c_char = args.as_mut_ptr();
|
||||
|
||||
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> {
|
||||
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,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unobserve_property(&self, registered_reply_userdata: u64) -> Result<u32, Error> {
|
||||
let rc =
|
||||
unsafe { ffi::mpv_unobserve_property(self.inner.as_ptr(), registered_reply_userdata) };
|
||||
Error::from_return_code(rc).map(|()| rc as u32)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
pub fn wait_event(&self, timeout: f64) -> Option<MpvEvent> {
|
||||
// use refcell to ensure exclusive access
|
||||
let event = RefMut::map(self.wait_event_cell.borrow_mut(), |()| unsafe {
|
||||
&mut *ffi::mpv_wait_event(self.inner.as_ptr(), timeout)
|
||||
});
|
||||
|
||||
match event.event_id {
|
||||
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 => {
|
||||
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_string_lossy()
|
||||
.into(),
|
||||
level: unsafe { CStr::from_ptr(data.level) }
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
text: unsafe { CStr::from_ptr(data.text) }
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
}
|
||||
|
||||
ffi::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => {
|
||||
let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) };
|
||||
Some(MpvEvent::PropertyChange(PropertyEvent {
|
||||
reply_userdata: event.reply_userdata,
|
||||
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),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
11 => Some(MpvEvent::Unknown(event.event_id)),
|
||||
|
||||
_ => todo!("event {}", event.event_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handle {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping MpvHandle");
|
||||
|
||||
// let any executor ticking tasks know we're ded
|
||||
self.wakeup.notify(u32::MAX.relaxed());
|
||||
|
||||
// just in case
|
||||
unsafe {
|
||||
ffi::mpv_wait_async_requests(self.inner.as_ptr());
|
||||
}
|
||||
// drain event queue (we're &mut so we know we have exclusive access)
|
||||
while let Some(event) = self.wait_event(0.0) {
|
||||
println!("drained event on drop: {event:?}");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
ffi::mpv_destroy(self.inner.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
use super::ffi;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Node<'a> {
|
||||
pub(crate) ffi::mpv_node,
|
||||
_ref: PhantomData<&'a ()>,
|
||||
}
|
|
@ -1,43 +1 @@
|
|||
use crate::mpv;
|
||||
use event_listener::EventListener;
|
||||
|
||||
pub struct Playbin {
|
||||
mpv: mpv::Handle,
|
||||
}
|
||||
|
||||
impl Default for Playbin {
|
||||
fn default() -> Self {
|
||||
let mpv = mpv::Handle::new();
|
||||
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();
|
||||
|
||||
mpv.command(["loadfile", "https://www.youtube.com/watch?v=19y8YTbvri8"])
|
||||
.unwrap();
|
||||
|
||||
Self { mpv }
|
||||
}
|
||||
}
|
||||
|
||||
impl Playbin {
|
||||
pub fn tick(&self) -> EventListener {
|
||||
let listener = self.mpv.wakeup_listener();
|
||||
while let Some(event) = self.mpv.wait_event(0.0) {
|
||||
self.handle_event(event);
|
||||
}
|
||||
listener
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: mpv::Event) {
|
||||
println!("mpv event {:?}", event);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Playbin {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping Playbin2");
|
||||
self.mpv.command(["quit"]).unwrap();
|
||||
}
|
||||
}
|
||||
mod imp {}
|
||||
|
|
135
src/signal.rs
135
src/signal.rs
|
@ -1,135 +0,0 @@
|
|||
use gtk::{
|
||||
glib::Object,
|
||||
prelude::{IsA, ObjectExt},
|
||||
};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
type SignalHandlerBox<T> = Box<dyn FnMut(T) -> bool>;
|
||||
|
||||
pub struct SignalEmitter<T> {
|
||||
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>>);
|
||||
|
||||
impl SignalHandler {
|
||||
pub fn disconnect(self) -> bool {
|
||||
match self.0.upgrade() {
|
||||
None => false,
|
||||
Some(cell) => {
|
||||
cell.set(true);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Signal<'_, T> {
|
||||
fn connect_impl(&self, f: impl FnMut(T) -> bool + 'static) {
|
||||
self.just_connected.borrow_mut().push(Box::new(f));
|
||||
}
|
||||
|
||||
pub fn connect(&self, mut f: impl FnMut(T) -> bool + 'static) -> SignalHandler {
|
||||
let disconnect = Rc::new(Cell::new(false));
|
||||
let disconnect_weak = Rc::downgrade(&disconnect);
|
||||
|
||||
self.connect_impl(move |t| match disconnect.get() {
|
||||
false => false,
|
||||
true => {
|
||||
f(t);
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
SignalHandler(disconnect_weak)
|
||||
}
|
||||
|
||||
pub fn connect_rc<L: 'static>(
|
||||
&self,
|
||||
listener: &Rc<L>,
|
||||
mut f: impl FnMut(Rc<L>, T) -> bool + 'static,
|
||||
) -> SignalHandler {
|
||||
let listener = Rc::downgrade(listener);
|
||||
|
||||
self.connect(move |t| match listener.upgrade() {
|
||||
None => false,
|
||||
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> SignalEmitter<T> {
|
||||
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](f()) {
|
||||
i += 1;
|
||||
} else {
|
||||
skip += 1;
|
||||
}
|
||||
|
||||
if i + skip == handlers.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
handlers.swap(i, i + skip);
|
||||
}
|
||||
|
||||
handlers.truncate(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SignalEmitter<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
pub fn emit(&self, t: T) {
|
||||
self.emit_with(|| t.clone());
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ mod imp {
|
|||
use glib::subclass::InitializingObject;
|
||||
use gtk::{gdk, glib};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(gtk::CompositeTemplate, glib::Properties, Default)]
|
||||
#[template(resource = "/eu/callcc/audrey/window.ui")]
|
||||
|
@ -27,8 +26,6 @@ mod imp {
|
|||
|
||||
pub(super) setup: crate::ui::Setup,
|
||||
pub(super) api: RefCell<Option<crate::subsonic_vala::Client>>,
|
||||
|
||||
playbin2: Rc<crate::playbin2::Playbin>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -48,61 +45,7 @@ mod imp {
|
|||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for Window {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let playbin = Rc::downgrade(&self.playbin2);
|
||||
glib::spawn_future_local(glib::clone!(async move {
|
||||
loop {
|
||||
match playbin.upgrade() {
|
||||
None => break,
|
||||
Some(playbin) => {
|
||||
let listener = playbin.tick();
|
||||
drop(playbin);
|
||||
listener.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// set up mpris
|
||||
let window = self.obj().clone();
|
||||
glib::spawn_future_local(async move {
|
||||
let conn = zbus::connection::Builder::session()
|
||||
.expect("could not connect to the session bus")
|
||||
.internal_executor(false)
|
||||
.build()
|
||||
.await
|
||||
.expect("could not build connection to the session bus");
|
||||
|
||||
// run this in glib's main loop
|
||||
glib::spawn_future_local(glib::clone!(
|
||||
#[strong]
|
||||
conn,
|
||||
async move {
|
||||
loop {
|
||||
conn.executor().tick().await;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
crate::Mpris::setup(conn.object_server(), &window)
|
||||
.await
|
||||
.expect("could not serve mpris");
|
||||
crate::mpris::Player::setup(conn.object_server(), &window.playbin())
|
||||
.await
|
||||
.expect("could not serve mpris player");
|
||||
|
||||
drop(window); // don't keep this alive
|
||||
|
||||
// always set up handlers before requesting service name
|
||||
conn.request_name("org.mpris.MediaPlayer2.audrey")
|
||||
.await
|
||||
.expect("could not register name in session bus");
|
||||
});
|
||||
}
|
||||
}
|
||||
impl ObjectImpl for Window {}
|
||||
|
||||
impl WidgetImpl for Window {}
|
||||
|
||||
|
@ -173,12 +116,6 @@ mod imp {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping AudreyUiWindow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use adw::prelude::*;
|
||||
|
|
Loading…
Reference in a new issue