Compare commits

..

3 commits

Author SHA1 Message Date
5dc1ed221b sigh. alright, cache playlist-pos locally 2024-11-13 20:08:48 +01:00
cd43f50fdb fun 2024-11-13 19:55:23 +01:00
2e0a0ef40f fun with actions 2024-11-13 19:40:04 +01:00
5 changed files with 158 additions and 61 deletions

View file

@ -130,8 +130,7 @@ template $AudreyUiPlaybar: Adw.Bin {
Button { Button {
icon-name: "media-seek-backward"; icon-name: "media-seek-backward";
valign: center; valign: center;
sensitive: bind template.idle-active inverted; action-name: "app.seek-backward";
clicked => $seek_backward() swapped;
} }
Button { Button {
@ -144,8 +143,7 @@ template $AudreyUiPlaybar: Adw.Bin {
Button { Button {
icon-name: "media-seek-forward"; icon-name: "media-seek-forward";
valign: center; valign: center;
sensitive: bind template.idle-active inverted; action-name: "app.seek-forward";
clicked => $seek_forward() swapped;
} }
Button { Button {

View file

@ -55,7 +55,12 @@ pub struct LogMessageEvent {
pub struct PropertyEvent { pub struct PropertyEvent {
pub reply_userdata: u64, pub reply_userdata: u64,
pub name: String, pub name: String,
//pub value: PropertyEventValue, pub value: Option<PropertyEventValue>,
}
#[derive(Clone, Debug)]
pub enum PropertyEventValue {
Int64(i64),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -1,5 +1,6 @@
use super::event::{ use super::event::{
EndFileEvent, EndFileReason, HookEvent, LogMessageEvent, PropertyEvent, StartFileEvent, EndFileEvent, EndFileReason, HookEvent, LogMessageEvent, PropertyEvent, PropertyEventValue,
StartFileEvent,
}; };
use super::{ffi, Error, Event as MpvEvent, GetProperty, SetProperty}; use super::{ffi, Error, Event as MpvEvent, GetProperty, SetProperty};
use event_listener::{Event, EventListener, IntoNotification}; use event_listener::{Event, EventListener, IntoNotification};
@ -140,6 +141,18 @@ impl Handle {
}) })
} }
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> { pub fn unobserve_property(&self, registered_reply_userdata: u64) -> Result<u32, Error> {
let rc = let rc =
unsafe { ffi::mpv_unobserve_property(self.inner.as_ptr(), registered_reply_userdata) }; unsafe { ffi::mpv_unobserve_property(self.inner.as_ptr(), registered_reply_userdata) };
@ -244,6 +257,15 @@ impl Handle {
name: unsafe { CStr::from_ptr(data.name) } name: unsafe { CStr::from_ptr(data.name) }
.to_string_lossy() .to_string_lossy()
.into(), .into(),
value: match data.format {
ffi::mpv_format_MPV_FORMAT_NONE => None,
ffi::mpv_format_MPV_FORMAT_INT64 => {
Some(PropertyEventValue::Int64(unsafe {
*(data.data as *mut i64)
}))
}
_ => todo!(),
},
})) }))
} }

View file

@ -111,28 +111,6 @@ mod imp {
} }
} }
#[template_callback]
fn seek_backward(&self) {
// 10 seconds
let mut new_position = self.window().time_pos() - 10.0;
if new_position < 0.0 {
new_position = 0.0;
}
self.window().seek(new_position);
}
#[template_callback]
fn seek_forward(&self) {
// 10 seconds
let new_position = self.window().time_pos() + 10.0;
if new_position > self.window().duration() {
// just seek to the next track
self.on_skip_forward_clicked();
} else {
self.window().seek(new_position);
}
}
#[template_callback] #[template_callback]
fn on_play_pause_clicked(&self, _button: &gtk::Button) { fn on_play_pause_clicked(&self, _button: &gtk::Button) {
if self.window().idle_active() { if self.window().idle_active() {

View file

@ -15,9 +15,9 @@ mod imp {
pub(super) enum State { pub(super) enum State {
Idle, Idle,
FileLoading, FileLoading,
FileLoaded, // internal? FileLoaded, // internal
Active, Active,
FileEnded, // internal? FileEnded, // internal
Seeking, Seeking,
} }
@ -61,8 +61,8 @@ mod imp {
_mute: (), _mute: (),
#[property(type = bool, get = Self::pause, set = Self::set_pause)] #[property(type = bool, get = Self::pause, set = Self::set_pause)]
_pause: (), _pause: (),
#[property(type = i64, get = Self::playlist_pos)] #[property(get)]
_playlist_pos: (), playlist_pos: Cell<i64>,
#[property(type = f64, get = Self::time_pos)] #[property(type = f64, get = Self::time_pos)]
_time_pos: (), _time_pos: (),
#[property(type = f64, get = Self::duration)] #[property(type = f64, get = Self::duration)]
@ -97,7 +97,7 @@ mod imp {
mpv.set_property("vid", false).unwrap(); mpv.set_property("vid", false).unwrap();
mpv.set_property("prefetch-playlist", true).unwrap(); mpv.set_property("prefetch-playlist", true).unwrap();
mpv.observe_property(3, "playlist-pos").unwrap(); mpv.observe_property_i64(3, "playlist-pos").unwrap();
mpv.observe_property(4, "idle-active").unwrap(); mpv.observe_property(4, "idle-active").unwrap();
mpv.observe_property(6, "playlist-count").unwrap(); mpv.observe_property(6, "playlist-count").unwrap();
mpv.observe_property(7, "duration").unwrap(); mpv.observe_property(7, "duration").unwrap();
@ -105,7 +105,7 @@ mod imp {
// "Useful to drain property changes before a new file is loaded." // "Useful to drain property changes before a new file is loaded."
mpv.add_hook(0, "on_before_start_file", 0).unwrap(); mpv.add_hook(0, "on_before_start_file", 0).unwrap();
// "Useful to drain property changes after a file has finished." // "Useful to drain property changes after a file has finished."
mpv.add_hook(0, "o_after_start_file", 0).unwrap(); mpv.add_hook(0, "on_after_end_file", 0).unwrap();
Self { Self {
state: Cell::new(State::Idle), state: Cell::new(State::Idle),
@ -126,7 +126,7 @@ mod imp {
_volume: (), _volume: (),
_mute: (), _mute: (),
_pause: (), _pause: (),
_playlist_pos: (), playlist_pos: Cell::new(-1),
_time_pos: (), _time_pos: (),
_duration: (), _duration: (),
_idle_active: (), _idle_active: (),
@ -170,6 +170,66 @@ mod imp {
fn constructed(&self) { fn constructed(&self) {
self.parent_constructed(); self.parent_constructed();
use gio::ActionEntry;
let action_seek_backward = ActionEntry::builder("seek-backward")
.activate(glib::clone!(
#[weak(rename_to = window)]
self.obj(),
move |_, _, _| {
let new_position = window.time_pos() - 10.0;
if new_position < 0.0 {
window.seek(0.0);
} else {
window.seek(new_position);
}
},
))
.build();
let action_seek_forward = ActionEntry::builder("seek-forward")
.activate(glib::clone!(
#[weak(rename_to = window)]
self.obj(),
move |_, _, _| {
let new_position = window.time_pos() + 10.0;
if new_position > window.duration() {
// just seek to the next track
if window.playlist_pos() + 1 < window.playlist_count() {
window.playlist_next();
} else {
window.playlist_play_index(None);
}
} else {
window.seek(new_position);
}
},
))
.build();
let actions = gio::SimpleActionGroup::new();
actions.add_action_entries([action_seek_backward, action_seek_forward]);
self.obj().insert_action_group("app", Some(&actions));
let playlist_not_empty = gtk::ClosureExpression::with_callback(
[gtk::ObjectExpression::new(self.obj().as_ref())
.chain_property::<super::Window>("playlist-count")],
|values| {
let playlist_count: i64 = values[1].get().unwrap();
playlist_count > 0
},
);
playlist_not_empty.bind(
&actions.lookup_action("seek-backward").unwrap(),
"enabled",
None::<&glib::Object>,
);
playlist_not_empty.bind(
&actions.lookup_action("seek-forward").unwrap(),
"enabled",
None::<&glib::Object>,
);
let settings = gio::Settings::new(crate::APP_ID); let settings = gio::Settings::new(crate::APP_ID);
settings.bind("mute", self.obj().as_ref(), "mute").build(); settings.bind("mute", self.obj().as_ref(), "mute").build();
settings settings
@ -205,40 +265,69 @@ mod imp {
let mpv_event_loop_handle = glib::spawn_future_local(async move { let mpv_event_loop_handle = glib::spawn_future_local(async move {
loop { loop {
let window = window.upgrade().unwrap(); let window = window.upgrade().unwrap();
let listener = window.imp().mpv.wakeup_listener();
// only send property change notifications after the event queue is drained // only send property change notifications after the event queue is drained
let freeze_notify = window.freeze_notify(); let freeze_notify = window.freeze_notify();
event!(
Level::TRACE,
"before event loop, state is {:?}",
window.imp().state.get()
);
while let Some(event) = window.imp().mpv.wait_event(0.0) { let listener = loop {
use crate::mpv::Event; let listener = window.imp().mpv.wakeup_listener();
match event { while let Some(event) = window.imp().mpv.wait_event(0.0) {
Event::PropertyChange(event) => window.imp().on_property_change(event), use crate::mpv::Event;
Event::Hook(event) => window.imp().on_hook(event),
Event::LogMessage(event) => window.imp().on_log_message(event),
Event::StartFile(_) => window.imp().on_start_file(), match event {
Event::FileLoaded => window.imp().on_file_loaded(), Event::PropertyChange(event) => {
Event::PlaybackRestart => window.imp().on_playback_restart(), window.imp().on_property_change(event)
Event::Seek => window.imp().on_seek(), }
Event::EndFile(event) => window.imp().on_end_file(event), Event::Hook(event) => window.imp().on_hook(event),
Event::LogMessage(event) => window.imp().on_log_message(event),
Event::Unknown(_) => { Event::StartFile(_) => window.imp().on_start_file(),
// either deprecated or future, ignore Event::FileLoaded => window.imp().on_file_loaded(),
Event::PlaybackRestart => window.imp().on_playback_restart(),
Event::Seek => window.imp().on_seek(),
Event::EndFile(event) => window.imp().on_end_file(event),
Event::Unknown(_) => {
// either deprecated or future, ignore
}
Event::AudioReconfig => {
// "This is relatively uninteresting, because there is no such thing as audio output embedding."
// ^ ignore
}
_ => event!(Level::DEBUG, "unhandled {event:?}"),
} }
Event::AudioReconfig => {
// "This is relatively uninteresting, because there is no such thing as audio output embedding."
// ^ ignore
}
_ => event!(Level::DEBUG, "unhandled {event:?}"),
} }
}
match window.imp().state.get() {
State::FileLoaded | State::FileEnded => {
// really annoying intermediary states we're not really interested
// in seeing outside of this loop
// just block until it's done lol
use event_listener::Listener;
event!(Level::INFO, "blocking");
listener.wait();
}
_ => break listener,
}
};
// send property change notifications now // send property change notifications now
drop(freeze_notify); drop(freeze_notify);
event!(
Level::TRACE,
"after event loop, state is {:?}",
window.imp().state.get()
);
drop(window); drop(window);
listener.await; listener.await;
} }
@ -379,10 +468,6 @@ mod imp {
self.mpv.set_property("pause", pause).unwrap(); self.mpv.set_property("pause", pause).unwrap();
} }
fn playlist_pos(&self) -> i64 {
self.mpv.get_property("playlist-pos").unwrap()
}
fn time_pos(&self) -> f64 { fn time_pos(&self) -> f64 {
if let Some(queued_seek) = self.queued_seek.get() { if let Some(queued_seek) = self.queued_seek.get() {
// counterfeit time-pos while the seek is ongoing // counterfeit time-pos while the seek is ongoing
@ -467,9 +552,17 @@ mod imp {
} }
fn on_property_change(&self, event: crate::mpv::event::PropertyEvent) { fn on_property_change(&self, event: crate::mpv::event::PropertyEvent) {
use crate::mpv::event::PropertyEventValue;
match event.reply_userdata { match event.reply_userdata {
3 => { 3 => {
assert_eq!(event.name, "playlist-pos"); assert_eq!(event.name, "playlist-pos");
let value = match event.value {
Some(PropertyEventValue::Int64(i)) => i,
_ => unreachable!(),
};
self.playlist_pos.set(value);
event!(Level::TRACE, "playlist-pos is now {value}");
self.obj().notify("playlist-pos"); self.obj().notify("playlist-pos");
} }
@ -650,6 +743,7 @@ mod imp {
} }
self.state.set(State::Seeking); self.state.set(State::Seeking);
self.obj().notify("time-pos");
self.buffering_start(); self.buffering_start();
} }