diff --git a/src/mpris.vala b/src/mpris.vala index 14b5a6f..c3f2a96 100644 --- a/src/mpris.vala +++ b/src/mpris.vala @@ -1,3 +1,4 @@ +/* [DBus (name = "org.mpris.MediaPlayer2.Player")] class Audrey.MprisPlayer : Object { internal signal void on_next (); @@ -22,9 +23,9 @@ class Audrey.MprisPlayer : Object { public signal void seeked (int64 position); public string playback_status { owned get; internal set; default = "Stopped"; } - public string loop_status { owned get; /*set;*/ default = "None"; } - public double rate { get; /*set*/ default = 1.0; } - public bool shuffle { get; /*set*/ default = false; } + public string loop_status { owned get; set; default = "None"; } + public double rate { get; set default = 1.0; } + public bool shuffle { get; set default = false; } public HashTable metadata { owned get; private set; default = new HashTable (null, null); } public double volume { get; set; default = 1.0; } [CCode (notify = false)] @@ -219,3 +220,4 @@ class Audrey.MprisPlayer : Object { debug ("destroying mpris player"); } } +*/ diff --git a/src/mpris/player.rs b/src/mpris/player.rs index 0b2f94d..81009d3 100644 --- a/src/mpris/player.rs +++ b/src/mpris/player.rs @@ -2,27 +2,37 @@ use glib::SendWeakRef; use gtk::glib; use std::collections::HashMap; use zbus::object_server::SignalEmitter; -use zbus::zvariant::{ObjectPath, OwnedObjectPath, Type, Value}; +use zbus::zvariant::{ObjectPath, OwnedObjectPath, Value}; -#[derive(Clone, Value, Type)] -#[zvariant(signature = "a{sv}")] +const MICROSECONDS: f64 = 1e6; // in a second + +#[derive(Default)] struct MetadataMap { - pub trackid: OwnedObjectPath, + pub track_id: Option, // rest: TODO } impl MetadataMap { - fn from_playbin(_playbin: &crate::Playbin) -> Self { - // TODO - Self { - trackid: "/org/mpris/MediaPlayer2/TrackList/NoTrack" - .try_into() - .unwrap(), - } + fn from_playbin_song(song: Option<&crate::playbin::Song>) -> Self { + song.map(|song| MetadataMap { + // FIXME: see if there's a better way to do this lol + track_id: Some({ + let song_object_intptr = + glib::translate::ToGlibPtr::<*const _>::to_glib_none(song).0 as usize; + format!("/eu/callcc/audrey/TrackId/{song_object_intptr:x}") + .try_into() + .unwrap() + }), + }) + .unwrap_or_else(Default::default) } fn as_hash_map(&self) -> HashMap<&'static str, Value> { - HashMap::from([("mpris:trackid", Value::new(self.trackid.as_ref()))]) + let mut map = HashMap::new(); + if let Some(track_id) = &self.track_id { + map.insert("mpris:trackid", Value::new(track_id.as_ref())); + } + map } } @@ -41,7 +51,7 @@ impl Player { let player = Self { playbin: playbin.downgrade().into(), - metadata: MetadataMap::from_playbin(playbin), + metadata: MetadataMap::from_playbin_song(None), volume: (playbin.volume() as f64) / 100.0, }; @@ -55,10 +65,20 @@ impl Player { "new-track", false, glib::closure_local!( - //#[strong] - //player, - move |_playbin: &crate::Playbin| { - // TODO + #[strong] + player_ref, + move |_playbin: &crate::Playbin, song: &crate::playbin::Song| { + let metadata = MetadataMap::from_playbin_song(Some(song)); + + let player_ref = player_ref.clone(); + glib::spawn_future_local(async move { + let mut player = player_ref.get_mut().await; + player.metadata = metadata; + player + .metadata_changed(player_ref.signal_emitter()) + .await + .unwrap(); + }); } ), ); @@ -91,39 +111,122 @@ impl Player { #[zbus::interface(name = "org.mpris.MediaPlayer2.Player")] impl Player { fn next(&self) { - todo!() + // If CanGoNext is false, attempting to call this method should have no effect. + if !self.can_go_next() { + return; + } + + let playbin = self.playbin.upgrade().unwrap(); + if playbin.play_queue_position() + 1 > playbin.play_queue_length() { + // If there is no next track (and endless playback and track repeat are both off), stop playback. + self.stop(); + } else { + playbin.go_to_next_track(); + } } fn previous(&self) { - todo!() + // If CanGoPrevious is false, attempting to call this method should have no effect. + if !self.can_go_previous() { + return; + } + + let playbin = self.playbin.upgrade().unwrap(); + if playbin.play_queue_position() == 0 { + // If there is no previous track (and endless playback and track repeat are both off), stop playback. + self.stop(); + } else { + playbin.go_to_prev_track(); + } } fn pause(&self) { - todo!() + // If CanPause is false, attempting to call this method should have no effect. + if !self.can_pause() { + return; + } + self.playbin.upgrade().unwrap().pause(); } fn play_pause(&self) { - todo!() + // don't think this is exactly according to spec but it looks more reasonable to me + if self.playbin.upgrade().unwrap().state() == crate::playbin::State::Paused { + self.play(); + } else { + self.pause(); + } } fn stop(&self) { - todo!() + // TODO: Calling Play after this should cause playback to start again from the beginning of the track. + // (not the play queue!!) + self.playbin.upgrade().unwrap().stop(); } fn play(&self) { - todo!() + // If CanPlay is false, attempting to call this method should have no effect. + if !self.can_play() { + return; + } + + let playbin = self.playbin.upgrade().unwrap(); + // If there is no track to play, this has no effect. + if playbin.play_queue_length() == 0 { + return; + } + + playbin.play(); } - fn seek(&self, _offset: i64) { - todo!() + fn seek(&self, offset: i64) { + // If the CanSeek property is false, this has no effect. + if !self.can_seek() { + return; + } + + let playbin = self.playbin.upgrade().unwrap(); + // Seeks forward in the current track by the specified number of microseconds. + let mut new_position = (playbin.position() * MICROSECONDS) as i64 + offset; + + // A negative value seeks back. If this would mean seeking back further than the start of the track, the position is set to 0. + if new_position < 0 { + new_position = 0; + } + + // If the value passed in would mean seeking beyond the end of the track, acts like a call to Next. + if new_position >= (playbin.duration() * MICROSECONDS) as i64 { + return self.next(); + } + + playbin.seek(new_position as f64 / MICROSECONDS); } - fn set_position(&self, _track_id: ObjectPath<'_>, _position: i64) { - todo!() + fn set_position(&self, track_id: ObjectPath<'_>, position: i64) { + let playbin = self.playbin.upgrade().unwrap(); + + // If the Position argument is less than 0, do nothing. + if position < 0 { + return; + } + // If the Position argument is greater than the track length, do nothing. + if position > (playbin.duration() * MICROSECONDS) as i64 { + return; + } + // If the CanSeek property is false, this has no effect. + if !self.can_seek() { + return; + } + // check if it's stale + if self.metadata.track_id.as_deref() != Some(&track_id) { + // TODO: warn of stale seek + return; + } + + playbin.seek(position as f64 / MICROSECONDS); } - fn open_uri(&self, _s: &str) { - todo!() + fn open_uri(&self, _s: &str) -> zbus::fdo::Result<()> { + Err(zbus::fdo::Error::NotSupported("OpenUri".into())) } #[zbus(signal)] @@ -154,8 +257,8 @@ impl Player { } #[zbus(property)] - fn set_playback_rate(&self, _playback_rate: f64) { - todo!() + fn set_playback_rate(&self, _playback_rate: f64) -> zbus::Result<()> { + Err(zbus::Error::Unsupported) } #[zbus(property)] @@ -187,7 +290,7 @@ impl Player { #[zbus(property(emits_changed_signal = "false"))] fn position(&self) -> i64 { - (self.playbin.upgrade().unwrap().position() * 1e9) as i64 + (self.playbin.upgrade().unwrap().position() * MICROSECONDS) as i64 } #[zbus(property)] @@ -202,27 +305,27 @@ impl Player { #[zbus(property)] fn can_go_next(&self) -> bool { - false // TODO + self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped } #[zbus(property)] fn can_go_previous(&self) -> bool { - false // TODO + self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped } #[zbus(property)] fn can_play(&self) -> bool { - false // TODO + self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped } #[zbus(property)] fn can_pause(&self) -> bool { - false // TODO + self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped } #[zbus(property)] fn can_seek(&self) -> bool { - false // TODO + self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped } #[zbus(property(emits_changed_signal = "const"))] diff --git a/src/playbin.rs b/src/playbin.rs index ceb0f94..f9bfdb4 100644 --- a/src/playbin.rs +++ b/src/playbin.rs @@ -35,6 +35,7 @@ pub mod ffi { pub fn audrey_playbin_get_play_queue_position( self_: *mut AudreyPlaybin, ) -> std::ffi::c_uint; + pub fn audrey_playbin_get_play_queue_length(self_: *mut AudreyPlaybin) -> std::ffi::c_uint; pub fn audrey_playbin_move_track( self_: *mut AudreyPlaybin, from: std::ffi::c_uint, @@ -111,6 +112,10 @@ impl Playbin { unsafe { ffi::audrey_playbin_get_play_queue_position(self.to_glib_none().0) } } + pub fn play_queue_length(&self) -> u32 { + unsafe { ffi::audrey_playbin_get_play_queue_length(self.to_glib_none().0) } + } + pub fn move_track(&self, from: u32, to: u32) { unsafe { ffi::audrey_playbin_move_track(self.to_glib_none().0, from, to) } } diff --git a/src/playbin.vala b/src/playbin.vala index a247efe..d15fd67 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -98,9 +98,11 @@ public class Audrey.Playbin : GLib.Object { public int play_queue_position { get; private set; default = -1; } // signalled when a new track is current - public signal void new_track (); + public signal void new_track (PlaybinSong song); // signalled when the last track is over public signal void stopped (); + // signalled when the position changes discontinuously + public signal void seeked (double position); // these are mostly synced with mpv public double position { get; private set; default = 0.0; } @@ -211,9 +213,10 @@ public class Audrey.Playbin : GLib.Object { // estimate duration from api data // while mpv doesn't know it - this.duration = ((PlaybinSong) this._play_queue.get_item (this.play_queue_position)).duration; + var song = (PlaybinSong) this._play_queue.get_item (this.play_queue_position); + this.duration = song.duration; - this.new_track (); + this.new_track (song); break; case Mpv.EventId.END_FILE: @@ -262,6 +265,7 @@ public class Audrey.Playbin : GLib.Object { warning (@"could not seek to $position: $rc"); } else { this.position = position; + this.seeked (position); } } diff --git a/src/ui/window.vala b/src/ui/window.vala index dfac7ca..e9408c5 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -29,8 +29,6 @@ public class Audrey.Ui.Window : Adw.ApplicationWindow { Object (application: app); } - private MprisPlayer mpris_player; - private void now_playing (PlaybinSong song) { this.song = song; // api.scrobble.begin (this.song.id); TODO