Compare commits

..

No commits in common. "5dc1ed221b74e8067ba88f8b591d35c5f65e715c" and "3940392f2d4f22b87f4d440016a6773b416a3199" have entirely different histories.

5 changed files with 60 additions and 157 deletions

View file

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

View file

@ -55,12 +55,7 @@ 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: Option<PropertyEventValue>, //pub value: PropertyEventValue,
}
#[derive(Clone, Debug)]
pub enum PropertyEventValue {
Int64(i64),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -1,6 +1,5 @@
use super::event::{ use super::event::{
EndFileEvent, EndFileReason, HookEvent, LogMessageEvent, PropertyEvent, PropertyEventValue, EndFileEvent, EndFileReason, HookEvent, LogMessageEvent, PropertyEvent, StartFileEvent,
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};
@ -141,18 +140,6 @@ 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) };
@ -257,15 +244,6 @@ 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,6 +111,28 @@ 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(get)] #[property(type = i64, get = Self::playlist_pos)]
playlist_pos: Cell<i64>, _playlist_pos: (),
#[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_i64(3, "playlist-pos").unwrap(); mpv.observe_property(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, "on_after_end_file", 0).unwrap(); mpv.add_hook(0, "o_after_start_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: Cell::new(-1), _playlist_pos: (),
_time_pos: (), _time_pos: (),
_duration: (), _duration: (),
_idle_active: (), _idle_active: (),
@ -170,66 +170,6 @@ 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
@ -265,69 +205,40 @@ 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()
);
let listener = loop { while let Some(event) = window.imp().mpv.wait_event(0.0) {
let listener = window.imp().mpv.wakeup_listener(); use crate::mpv::Event;
while let Some(event) = window.imp().mpv.wait_event(0.0) { match event {
use crate::mpv::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),
match event { Event::StartFile(_) => window.imp().on_start_file(),
Event::PropertyChange(event) => { Event::FileLoaded => window.imp().on_file_loaded(),
window.imp().on_property_change(event) Event::PlaybackRestart => window.imp().on_playback_restart(),
} Event::Seek => window.imp().on_seek(),
Event::Hook(event) => window.imp().on_hook(event), Event::EndFile(event) => window.imp().on_end_file(event),
Event::LogMessage(event) => window.imp().on_log_message(event),
Event::StartFile(_) => window.imp().on_start_file(), Event::Unknown(_) => {
Event::FileLoaded => window.imp().on_file_loaded(), // either deprecated or future, ignore
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:?}"),
}
}
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, Event::AudioReconfig => {
// "This is relatively uninteresting, because there is no such thing as audio output embedding."
// ^ ignore
}
_ => event!(Level::DEBUG, "unhandled {event:?}"),
} }
}; }
// 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;
} }
@ -468,6 +379,10 @@ 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
@ -552,17 +467,9 @@ 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");
} }
@ -743,7 +650,6 @@ mod imp {
} }
self.state.set(State::Seeking); self.state.set(State::Seeking);
self.obj().notify("time-pos");
self.buffering_start(); self.buffering_start();
} }