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

View file

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

View file

@ -1,5 +1,6 @@
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 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> {
let rc =
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) }
.to_string_lossy()
.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]
fn on_play_pause_clicked(&self, _button: &gtk::Button) {
if self.window().idle_active() {

View file

@ -15,9 +15,9 @@ mod imp {
pub(super) enum State {
Idle,
FileLoading,
FileLoaded, // internal?
FileLoaded, // internal
Active,
FileEnded, // internal?
FileEnded, // internal
Seeking,
}
@ -61,8 +61,8 @@ mod imp {
_mute: (),
#[property(type = bool, get = Self::pause, set = Self::set_pause)]
_pause: (),
#[property(type = i64, get = Self::playlist_pos)]
_playlist_pos: (),
#[property(get)]
playlist_pos: Cell<i64>,
#[property(type = f64, get = Self::time_pos)]
_time_pos: (),
#[property(type = f64, get = Self::duration)]
@ -97,7 +97,7 @@ mod imp {
mpv.set_property("vid", false).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(6, "playlist-count").unwrap();
mpv.observe_property(7, "duration").unwrap();
@ -105,7 +105,7 @@ mod imp {
// "Useful to drain property changes before a new file is loaded."
mpv.add_hook(0, "on_before_start_file", 0).unwrap();
// "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 {
state: Cell::new(State::Idle),
@ -126,7 +126,7 @@ mod imp {
_volume: (),
_mute: (),
_pause: (),
_playlist_pos: (),
playlist_pos: Cell::new(-1),
_time_pos: (),
_duration: (),
_idle_active: (),
@ -170,6 +170,66 @@ mod imp {
fn constructed(&self) {
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);
settings.bind("mute", self.obj().as_ref(), "mute").build();
settings
@ -205,40 +265,69 @@ mod imp {
let mpv_event_loop_handle = glib::spawn_future_local(async move {
loop {
let window = window.upgrade().unwrap();
let listener = window.imp().mpv.wakeup_listener();
// only send property change notifications after the event queue is drained
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) {
use crate::mpv::Event;
let listener = loop {
let listener = window.imp().mpv.wakeup_listener();
match event {
Event::PropertyChange(event) => window.imp().on_property_change(event),
Event::Hook(event) => window.imp().on_hook(event),
Event::LogMessage(event) => window.imp().on_log_message(event),
while let Some(event) = window.imp().mpv.wait_event(0.0) {
use crate::mpv::Event;
Event::StartFile(_) => window.imp().on_start_file(),
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),
match event {
Event::PropertyChange(event) => {
window.imp().on_property_change(event)
}
Event::Hook(event) => window.imp().on_hook(event),
Event::LogMessage(event) => window.imp().on_log_message(event),
Event::Unknown(_) => {
// either deprecated or future, ignore
Event::StartFile(_) => window.imp().on_start_file(),
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
drop(freeze_notify);
event!(
Level::TRACE,
"after event loop, state is {:?}",
window.imp().state.get()
);
drop(window);
listener.await;
}
@ -379,10 +468,6 @@ mod imp {
self.mpv.set_property("pause", pause).unwrap();
}
fn playlist_pos(&self) -> i64 {
self.mpv.get_property("playlist-pos").unwrap()
}
fn time_pos(&self) -> f64 {
if let Some(queued_seek) = self.queued_seek.get() {
// counterfeit time-pos while the seek is ongoing
@ -467,9 +552,17 @@ mod imp {
}
fn on_property_change(&self, event: crate::mpv::event::PropertyEvent) {
use crate::mpv::event::PropertyEventValue;
match event.reply_userdata {
3 => {
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");
}
@ -650,6 +743,7 @@ mod imp {
}
self.state.set(State::Seeking);
self.obj().notify("time-pos");
self.buffering_start();
}