hauuu so complicated
This commit is contained in:
parent
8a6a056b36
commit
4e2c992cc9
2 changed files with 215 additions and 160 deletions
|
@ -1,11 +1,13 @@
|
|||
mod imp {
|
||||
use crate::ui;
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use crate::{mpv, ui};
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::glib;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Application;
|
||||
pub struct Application {
|
||||
mpv: RefCell<mpv::Handle>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Application {
|
||||
|
@ -24,6 +26,30 @@ mod imp {
|
|||
let window = ui::Window::new(self.obj().as_ref());
|
||||
window.present();
|
||||
|
||||
let mut mpv = self.mpv.borrow_mut();
|
||||
mpv.initialize().expect("could not initialize mpv");
|
||||
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();
|
||||
// TODO: observe properties
|
||||
mpv.observe_property("time-pos", |time_pos: Option<f64>| {
|
||||
println!("new time pos: {time_pos:?}")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
glib::spawn_future_local(glib::clone!(
|
||||
#[weak(rename_to = app)]
|
||||
self,
|
||||
async move {
|
||||
loop {
|
||||
app.mpv.borrow().wait().await;
|
||||
app.mpv.borrow_mut().tick();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
let conn = zbus::connection::Builder::session()
|
||||
.expect("could not connect to the session bus")
|
||||
|
@ -66,6 +92,8 @@ mod imp {
|
|||
impl GtkApplicationImpl for Application {}
|
||||
|
||||
impl AdwApplicationImpl for Application {}
|
||||
|
||||
impl Application {}
|
||||
}
|
||||
|
||||
use gtk::{gio, glib};
|
||||
|
|
327
src/mpv.rs
327
src/mpv.rs
|
@ -1,12 +1,14 @@
|
|||
mod ffi;
|
||||
|
||||
use std::ffi::{c_int, c_void, CStr, CString};
|
||||
|
||||
#[link(name = "mpv")]
|
||||
extern "C" {}
|
||||
|
||||
pub struct Error(std::ffi::c_int);
|
||||
pub struct Error(c_int);
|
||||
|
||||
impl Error {
|
||||
fn from_return_code(rc: std::ffi::c_int) -> Result<(), Self> {
|
||||
fn from_return_code(rc: c_int) -> Result<(), Self> {
|
||||
if rc == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -18,177 +20,137 @@ impl Error {
|
|||
impl std::fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.debug_tuple("Error")
|
||||
.field(unsafe {
|
||||
&std::ffi::CStr::from_ptr(ffi::mpv_error_string(self.0 as std::ffi::c_int))
|
||||
})
|
||||
.field(unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) })
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct Handle(NonNull<ffi::mpv_handle>);
|
||||
pub struct Handle {
|
||||
inner: NonNull<ffi::mpv_handle>,
|
||||
_wakeup_send: Pin<Box<async_channel::Sender<()>>>, // the wakeup callback controls this one
|
||||
wakeup_recv: async_channel::Receiver<()>,
|
||||
|
||||
observed_property_callbacks:
|
||||
Vec<Pin<Box<Box<dyn FnMut(ffi::mpv_format, *mut c_void) + 'static>>>>,
|
||||
}
|
||||
|
||||
// The client API is generally fully thread-safe, unless otherwise noted.
|
||||
unsafe impl Send for Handle {}
|
||||
unsafe impl Sync for Handle {}
|
||||
|
||||
impl Default for Handle {
|
||||
fn default() -> Self {
|
||||
Self::create()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn create() -> Self {
|
||||
Self(NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"))
|
||||
let inner =
|
||||
NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle");
|
||||
|
||||
let (wakeup_send, wakeup_recv) = async_channel::bounded(1);
|
||||
let wakeup_send = Box::pin(wakeup_send);
|
||||
|
||||
extern "C" fn wakeup_callback(d: *mut c_void) {
|
||||
let d = d as *const async_channel::Sender<()>;
|
||||
let wakeup_send = unsafe { &*d };
|
||||
match wakeup_send.try_send(()) {
|
||||
Ok(()) => {}
|
||||
Err(async_channel::TrySendError::Full(())) => {
|
||||
// assume the main thread has already been notified
|
||||
}
|
||||
Err(async_channel::TrySendError::Closed(())) => {
|
||||
panic!("wakeup channgel was closed before mpv handle was finalized");
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
ffi::mpv_set_wakeup_callback(
|
||||
inner.as_ptr(),
|
||||
Some(wakeup_callback),
|
||||
std::ptr::from_ref::<async_channel::Sender<()>>(&wakeup_send) as *mut c_void,
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
inner: inner,
|
||||
_wakeup_send: wakeup_send,
|
||||
wakeup_recv: wakeup_recv,
|
||||
|
||||
observed_property_callbacks: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&self) -> Result<(), Error> {
|
||||
Error::from_return_code(unsafe { ffi::mpv_initialize(self.0.as_ptr()) })
|
||||
Error::from_return_code(unsafe { ffi::mpv_initialize(self.inner.as_ptr()) })
|
||||
}
|
||||
|
||||
pub fn wait_event(&mut self, timeout: f64) -> Option<Event<'_>> {
|
||||
let event = unsafe { &*ffi::mpv_wait_event(self.0.as_ptr(), timeout) };
|
||||
match event.event_id {
|
||||
0 => None,
|
||||
1 => Some(Event::Shutdown),
|
||||
2 => todo!("Event::LogMessage"),
|
||||
3 => Some(Event::GetPropertyReply(
|
||||
event.reply_userdata,
|
||||
Error::from_return_code(event.error).map(|()| {
|
||||
let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) };
|
||||
EventProperty {
|
||||
name: unsafe { std::ffi::CStr::from_ptr(data.name) }
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
value: unsafe { FormatData::new(data.format, data.data) },
|
||||
}
|
||||
}),
|
||||
)),
|
||||
4 => todo!("Event::SetPropertyReply"),
|
||||
5 => todo!("Event::CommandReply"),
|
||||
6 => todo!("Event::StartFile"),
|
||||
7 => todo!("Event::EndFile"),
|
||||
8 => todo!("Event::FileLoaded"),
|
||||
16 => todo!("Event::ClientMessage"),
|
||||
17 => todo!("Event::VideoReconfig"),
|
||||
18 => todo!("Event::AudioReconfig"),
|
||||
20 => todo!("Event::Seek"),
|
||||
21 => todo!("Event::PlaybackRestart"),
|
||||
22 => todo!("Event::PropertyChange"),
|
||||
24 => todo!("Event::QueueOverflow"),
|
||||
25 => todo!("Event::Hook"),
|
||||
_ => Some(Event::Ignore(event.event_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FormatData<'a> {
|
||||
None,
|
||||
String(Cow<'a, str>),
|
||||
OsdString(&'a str),
|
||||
Flag(bool),
|
||||
Int64(i64),
|
||||
Double(f64),
|
||||
//Node(Node<'a>),
|
||||
}
|
||||
|
||||
impl<'a> FormatData<'a> {
|
||||
unsafe fn new(format: ffi::mpv_format, data: *mut std::ffi::c_void) -> Self {
|
||||
match format {
|
||||
0 => Self::None,
|
||||
1 => {
|
||||
let data = data as *mut *mut std::ffi::c_char;
|
||||
Self::String(String::from_utf8_lossy(
|
||||
std::ffi::CStr::from_ptr(*data).to_bytes(),
|
||||
))
|
||||
}
|
||||
2 => {
|
||||
let data = data as *mut *mut std::ffi::c_char;
|
||||
Self::OsdString(
|
||||
std::ffi::CStr::from_ptr(*data)
|
||||
.to_str()
|
||||
.expect("OSD string wasn't UTF-8"),
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
let data = data as *mut std::ffi::c_int;
|
||||
Self::Flag(match *data {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("invalid mpv flag value"),
|
||||
pub fn set_property<'a>(&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)
|
||||
})
|
||||
})
|
||||
}
|
||||
4 => {
|
||||
let data = data as *mut i64;
|
||||
Self::Int64(*data)
|
||||
|
||||
// TODO: some way to deregister these maybe??
|
||||
pub fn observe_property<T>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
mut cb: impl FnMut(Option<T>) + 'static,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: FormatValue,
|
||||
{
|
||||
let name = CString::new(name).unwrap();
|
||||
let cb = Box::new(move |format, data| T::from_data(format, data, |x| cb(x)));
|
||||
let cb = Box::<dyn FnMut(ffi::mpv_format, *mut c_void)>::from(cb);
|
||||
let cb = Box::pin(cb);
|
||||
let userdata =
|
||||
std::ptr::from_ref::<Box<dyn FnMut(ffi::mpv_format, *mut c_void)>>(&cb) as usize as u64;
|
||||
|
||||
Error::from_return_code(unsafe {
|
||||
ffi::mpv_observe_property(self.inner.as_ptr(), userdata, name.as_ptr(), T::FORMAT)
|
||||
})?;
|
||||
|
||||
self.observed_property_callbacks.push(cb);
|
||||
Ok(())
|
||||
}
|
||||
5 => {
|
||||
let data = data as *mut f64;
|
||||
Self::Double(*data)
|
||||
}
|
||||
6 => todo!(),
|
||||
7 => todo!(),
|
||||
8 => todo!(),
|
||||
9 => todo!(),
|
||||
_ => panic!("invalid mpv format"),
|
||||
|
||||
pub async fn wait(&self) {
|
||||
match self.wakeup_recv.recv().await {
|
||||
Ok(()) => {}
|
||||
Err(async_channel::RecvError) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventProperty<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: FormatData<'a>,
|
||||
}
|
||||
pub fn tick(&mut self) {
|
||||
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,
|
||||
|
||||
pub struct EventLogMessage<'a> {
|
||||
pub prefix: &'a str,
|
||||
pub level: &'a str,
|
||||
pub text: &'a str,
|
||||
pub log_level: i32,
|
||||
}
|
||||
ffi::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => {
|
||||
let cb = unsafe {
|
||||
&mut *(event.reply_userdata
|
||||
as *mut Box<dyn FnMut(ffi::mpv_format, *mut c_void)>)
|
||||
};
|
||||
let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) };
|
||||
cb(data.format, data.data);
|
||||
}
|
||||
|
||||
pub enum EndFileReason {
|
||||
Eof,
|
||||
Stop,
|
||||
Quit,
|
||||
Error(Error),
|
||||
Redirect,
|
||||
}
|
||||
11 => { /* deprecated, ignore */ }
|
||||
|
||||
pub struct EventStartFile {
|
||||
pub playlist_entry_id: i64,
|
||||
}
|
||||
|
||||
pub struct EventEndFile {
|
||||
pub reason: EndFileReason,
|
||||
pub playlist_entry_id: i64,
|
||||
pub playlist_insert_id: i64,
|
||||
pub playlist_insert_num_entries: i32,
|
||||
}
|
||||
|
||||
pub struct EventClientMessage<'a> {
|
||||
pub num_args: i32,
|
||||
pub args: &'a [&'a str],
|
||||
}
|
||||
|
||||
pub struct EventHook<'a> {
|
||||
pub name: &'a str,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
pub enum Event<'a> {
|
||||
Shutdown,
|
||||
LogMessage(EventLogMessage<'a>),
|
||||
GetPropertyReply(u64, Result<EventProperty<'a>, Error>),
|
||||
SetPropertyReply(u64, Result<(), Error>),
|
||||
//CommandReply(u64, Result<Node<'a>, Error>),
|
||||
StartFile(EventStartFile),
|
||||
EndFile(EventEndFile),
|
||||
FileLoaded,
|
||||
ClientMessage(EventClientMessage<'a>),
|
||||
VideoReconfig,
|
||||
AudioReconfig,
|
||||
Seek,
|
||||
PlaybackRestart,
|
||||
PropertyChange(u64, EventProperty<'a>),
|
||||
QueueOverflow,
|
||||
Hook(u64, EventHook<'a>),
|
||||
// "Keep in mind that later ABI compatible releases might add new event
|
||||
// types. These should be ignored by the API user."
|
||||
Ignore(u32),
|
||||
_ => todo!("event {}", event.event_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handle {
|
||||
|
@ -197,3 +159,68 @@ impl Drop for Handle {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue