Compare commits

...

3 commits

Author SHA1 Message Date
20aaacd40e fix idle-active detection 2024-11-19 21:12:55 +01:00
378eb0761d mpris player metadata 2024-11-19 21:07:05 +01:00
eab11095c8 notify mpris on playback status change 2024-11-19 20:58:23 +01:00
4 changed files with 97 additions and 25 deletions

View file

@ -9,7 +9,7 @@ use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Value};
const MICROSECONDS: f64 = 1e6; // in a second const MICROSECONDS: f64 = 1e6; // in a second
#[derive(Default)] #[derive(Default)]
struct MetadataMap { pub struct MetadataMap {
// mpris // mpris
track_id: Option<OwnedObjectPath>, track_id: Option<OwnedObjectPath>,
length: Option<i64>, length: Option<i64>,
@ -37,7 +37,7 @@ struct MetadataMap {
} }
impl MetadataMap { impl MetadataMap {
fn from_playbin_song(song: Option<&Song>) -> Self { pub fn from_playbin_song(song: Option<&Song>) -> Self {
song.map(|song| MetadataMap { song.map(|song| MetadataMap {
// use a unique growing counter to identify tracks // use a unique growing counter to identify tracks
track_id: Some({ track_id: Some({
@ -115,7 +115,6 @@ impl MetadataMap {
} }
pub struct Player { pub struct Player {
metadata: MetadataMap,
window: SendWeakRef<Window>, window: SendWeakRef<Window>,
} }
@ -125,7 +124,6 @@ impl Player {
playbin: &Window, playbin: &Window,
) -> Result<InterfaceRef<Player>, zbus::Error> { ) -> Result<InterfaceRef<Player>, zbus::Error> {
let player = Self { let player = Self {
metadata: MetadataMap::from_playbin_song(None),
window: playbin.downgrade().into(), window: playbin.downgrade().into(),
}; };
@ -268,7 +266,8 @@ impl Player {
#[tracing::instrument(skip(self), parent = None, target = "audrey::mpris", level = Level::DEBUG, ret)] #[tracing::instrument(skip(self), parent = None, target = "audrey::mpris", level = Level::DEBUG, ret)]
fn set_position(&self, track_id: ObjectPath<'_>, position: i64) -> zbus::fdo::Result<()> { fn set_position(&self, track_id: ObjectPath<'_>, position: i64) -> zbus::fdo::Result<()> {
if Some(track_id) == self.metadata.track_id.as_ref().map(|x| x.as_ref()) { let metadata = self.metadata();
if Some(&track_id.into()) == metadata.get("track_id") {
self.window().set_time_pos(position as f64 / MICROSECONDS); self.window().set_time_pos(position as f64 / MICROSECONDS);
} }
Ok(()) Ok(())
@ -329,7 +328,8 @@ impl Player {
#[zbus(property)] #[zbus(property)]
fn metadata(&self) -> HashMap<&'static str, OwnedValue> { fn metadata(&self) -> HashMap<&'static str, OwnedValue> {
self.metadata.as_hash_map() // TODO: no need to MetadataMap wrapper anymore
MetadataMap::from_playbin_song(self.window().song().as_ref()).as_hash_map()
} }
#[zbus(property)] #[zbus(property)]

View file

@ -63,6 +63,7 @@ pub enum PropertyEventValue {
Int64(i64), Int64(i64),
Double(f64), Double(f64),
String(String), String(String),
Flag(bool),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -5,7 +5,7 @@ use super::event::{
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};
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::ffi::{c_char, c_void, CStr, CString}; use std::ffi::{c_char, c_int, c_void, CStr, CString};
use std::fmt; use std::fmt;
use std::pin::Pin; use std::pin::Pin;
use std::ptr::NonNull; use std::ptr::NonNull;
@ -177,6 +177,18 @@ impl Handle {
}) })
} }
pub fn observe_property_flag(&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_FLAG,
)
})
}
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) };
@ -299,6 +311,13 @@ impl Handle {
String::from_utf8_lossy(value.to_bytes()).into_owned(), String::from_utf8_lossy(value.to_bytes()).into_owned(),
)) ))
} }
ffi::mpv_format_MPV_FORMAT_FLAG => Some(PropertyEventValue::Flag(unsafe {
match *(data.data as *mut c_int) {
0 => false,
1 => true,
other => unreachable!("bad mpv flag value {other}"),
}
})),
_ => todo!(), _ => todo!(),
}, },
})) }))

View file

@ -99,7 +99,7 @@ mod imp {
mpv.observe_property_string(0, "path").unwrap(); mpv.observe_property_string(0, "path").unwrap();
mpv.observe_property_int64(3, "playlist-pos").unwrap(); mpv.observe_property_int64(3, "playlist-pos").unwrap();
mpv.observe_property(4, "idle-active").unwrap(); mpv.observe_property_flag(4, "idle-active").unwrap();
mpv.observe_property(6, "playlist-count").unwrap(); mpv.observe_property(6, "playlist-count").unwrap();
mpv.observe_property_double(7, "duration").unwrap(); mpv.observe_property_double(7, "duration").unwrap();
@ -407,6 +407,44 @@ mod imp {
self.setup.present(Some(self.obj().as_ref())); self.setup.present(Some(self.obj().as_ref()));
} }
fn mpris_player(&self) -> Option<InterfaceRef<mpris::Player>> {
let borrowed = self.mpris_player.borrow();
borrowed.as_ref().cloned()
}
fn mpris_player_playback_status_changed(&self) {
if let Some(iface_ref) = self.mpris_player() {
glib::spawn_future_local(async move {
let iface = iface_ref.get().await;
match iface
.playback_status_changed(iface_ref.signal_emitter())
.await
{
Ok(()) => {}
Err(err) => event!(
Level::ERROR,
"could not notify new playback status through mpris interface: {err}"
),
}
});
}
}
fn mpris_player_metadata_changed(&self) {
if let Some(iface_ref) = self.mpris_player() {
glib::spawn_future_local(async move {
let iface = iface_ref.get().await;
match iface.metadata_changed(iface_ref.signal_emitter()).await {
Ok(()) => {}
Err(err) => event!(
Level::ERROR,
"could not notify new metadata through mpris interface: {err}"
),
}
});
}
}
fn volume(&self) -> i64 { fn volume(&self) -> i64 {
self.mpv.get_property("volume").unwrap() self.mpv.get_property("volume").unwrap()
} }
@ -414,21 +452,18 @@ mod imp {
fn set_volume(&self, volume: i64) { fn set_volume(&self, volume: i64) {
self.mpv.set_property("volume", volume).unwrap(); self.mpv.set_property("volume", volume).unwrap();
let iface_borrowed = self.mpris_player.borrow(); if let Some(iface_ref) = self.mpris_player() {
let iface_ref = match iface_borrowed.as_ref() { glib::spawn_future_local(async move {
None => return, // zbus not set up yet let iface = iface_ref.get().await;
Some(iface_ref) => iface_ref.clone(), match iface.volume_changed(iface_ref.signal_emitter()).await {
}; Ok(()) => {}
glib::spawn_future_local(async move { Err(err) => event!(
let iface = iface_ref.get().await; Level::ERROR,
match iface.volume_changed(iface_ref.signal_emitter()).await { "could not notify new volume through mpris interface: {err}"
Ok(()) => {} ),
Err(err) => event!( }
Level::ERROR, });
"could not notify new volume through mpris interface: {err}" }
),
}
});
} }
fn mute(&self) -> bool { fn mute(&self) -> bool {
@ -445,6 +480,10 @@ mod imp {
fn set_pause(&self, pause: bool) { fn set_pause(&self, pause: bool) {
self.mpv.set_property("pause", pause).unwrap(); self.mpv.set_property("pause", pause).unwrap();
if !self.idle_active() {
self.mpris_player_playback_status_changed();
}
} }
fn time_pos(&self) -> f64 { fn time_pos(&self) -> f64 {
@ -544,8 +583,13 @@ mod imp {
4 => { 4 => {
assert_eq!(event.name, "idle-active"); assert_eq!(event.name, "idle-active");
let value = match event.value {
Some(PropertyEventValue::Flag(b)) => b,
_ => unreachable!(),
};
event!(Level::TRACE, "idle-active is now {value}");
self.obj().notify("idle-active"); self.obj().notify("idle-active");
if self.obj().idle_active() { if value {
self.on_idle_active(); self.on_idle_active();
} }
} }
@ -641,6 +685,9 @@ mod imp {
self.obj().set_playing_cover_art(None::<gdk::Texture>); self.obj().set_playing_cover_art(None::<gdk::Texture>);
self.obj().set_background(None::<gdk::Texture>); self.obj().set_background(None::<gdk::Texture>);
self.mpris_player_metadata_changed();
self.mpris_player_playback_status_changed();
} }
fn on_start_file(&self) { fn on_start_file(&self) {
@ -663,11 +710,16 @@ mod imp {
self.obj().notify("song"); self.obj().notify("song");
self.buffering_start(); self.buffering_start();
let duration = self.obj().song().unwrap().duration() as f64; let song = self.obj().song().unwrap();
let duration = song.duration() as f64;
self.duration.set(duration); self.duration.set(duration);
event!(target: "audrey::playback", Level::DEBUG, "duration is now {duration} (from subsonic)"); event!(target: "audrey::playback", Level::DEBUG, "duration is now {duration} (from subsonic)");
self.obj().notify("duration"); self.obj().notify("duration");
self.mpris_player_metadata_changed();
self.mpris_player_playback_status_changed();
let window = self.obj().clone(); let window = self.obj().clone();
let song_id = window.song().unwrap().id(); let song_id = window.song().unwrap().id();
if let Some(handle) = self if let Some(handle) = self