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 {
|
mod imp {
|
||||||
use crate::ui;
|
use crate::{mpv, ui};
|
||||||
use adw::prelude::*;
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
use adw::subclass::prelude::*;
|
|
||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Application;
|
pub struct Application {
|
||||||
|
mpv: RefCell<mpv::Handle>,
|
||||||
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for Application {
|
impl ObjectSubclass for Application {
|
||||||
|
@ -24,6 +26,30 @@ mod imp {
|
||||||
let window = ui::Window::new(self.obj().as_ref());
|
let window = ui::Window::new(self.obj().as_ref());
|
||||||
window.present();
|
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 {
|
glib::spawn_future_local(async move {
|
||||||
let conn = zbus::connection::Builder::session()
|
let conn = zbus::connection::Builder::session()
|
||||||
.expect("could not connect to the session bus")
|
.expect("could not connect to the session bus")
|
||||||
|
@ -66,6 +92,8 @@ mod imp {
|
||||||
impl GtkApplicationImpl for Application {}
|
impl GtkApplicationImpl for Application {}
|
||||||
|
|
||||||
impl AdwApplicationImpl for Application {}
|
impl AdwApplicationImpl for Application {}
|
||||||
|
|
||||||
|
impl Application {}
|
||||||
}
|
}
|
||||||
|
|
||||||
use gtk::{gio, glib};
|
use gtk::{gio, glib};
|
||||||
|
|
327
src/mpv.rs
327
src/mpv.rs
|
@ -1,12 +1,14 @@
|
||||||
mod ffi;
|
mod ffi;
|
||||||
|
|
||||||
|
use std::ffi::{c_int, c_void, CStr, CString};
|
||||||
|
|
||||||
#[link(name = "mpv")]
|
#[link(name = "mpv")]
|
||||||
extern "C" {}
|
extern "C" {}
|
||||||
|
|
||||||
pub struct Error(std::ffi::c_int);
|
pub struct Error(c_int);
|
||||||
|
|
||||||
impl Error {
|
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 {
|
if rc == 0 {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -18,177 +20,137 @@ impl Error {
|
||||||
impl std::fmt::Debug for Error {
|
impl std::fmt::Debug for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
f.debug_tuple("Error")
|
f.debug_tuple("Error")
|
||||||
.field(unsafe {
|
.field(unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) })
|
||||||
&std::ffi::CStr::from_ptr(ffi::mpv_error_string(self.0 as std::ffi::c_int))
|
|
||||||
})
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::pin::Pin;
|
||||||
use std::ptr::NonNull;
|
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 {
|
impl Handle {
|
||||||
pub fn create() -> Self {
|
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> {
|
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<'_>> {
|
pub fn set_property<'a>(&self, name: &str, value: impl FormatValue) -> Result<(), Error> {
|
||||||
let event = unsafe { &*ffi::mpv_wait_event(self.0.as_ptr(), timeout) };
|
// need to add zero terminator
|
||||||
match event.event_id {
|
let name = CString::new(name).expect("null bytes in property name");
|
||||||
0 => None,
|
value.into_data(|format, data| {
|
||||||
1 => Some(Event::Shutdown),
|
Error::from_return_code(unsafe {
|
||||||
2 => todo!("Event::LogMessage"),
|
ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data)
|
||||||
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"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
4 => {
|
|
||||||
let data = data as *mut i64;
|
// TODO: some way to deregister these maybe??
|
||||||
Self::Int64(*data)
|
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;
|
pub async fn wait(&self) {
|
||||||
Self::Double(*data)
|
match self.wakeup_recv.recv().await {
|
||||||
}
|
Ok(()) => {}
|
||||||
6 => todo!(),
|
Err(async_channel::RecvError) => unreachable!(),
|
||||||
7 => todo!(),
|
|
||||||
8 => todo!(),
|
|
||||||
9 => todo!(),
|
|
||||||
_ => panic!("invalid mpv format"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventProperty<'a> {
|
pub fn tick(&mut self) {
|
||||||
pub name: &'a str,
|
loop {
|
||||||
pub value: FormatData<'a>,
|
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> {
|
ffi::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => {
|
||||||
pub prefix: &'a str,
|
let cb = unsafe {
|
||||||
pub level: &'a str,
|
&mut *(event.reply_userdata
|
||||||
pub text: &'a str,
|
as *mut Box<dyn FnMut(ffi::mpv_format, *mut c_void)>)
|
||||||
pub log_level: i32,
|
};
|
||||||
}
|
let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) };
|
||||||
|
cb(data.format, data.data);
|
||||||
|
}
|
||||||
|
|
||||||
pub enum EndFileReason {
|
11 => { /* deprecated, ignore */ }
|
||||||
Eof,
|
|
||||||
Stop,
|
|
||||||
Quit,
|
|
||||||
Error(Error),
|
|
||||||
Redirect,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventStartFile {
|
_ => todo!("event {}", event.event_id),
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Handle {
|
impl Drop for Handle {
|
||||||
|
@ -197,3 +159,68 @@ impl Drop for Handle {
|
||||||
todo!()
|
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