From 4a5f27a95df88ad72161209605d710ca0ff1c662 Mon Sep 17 00:00:00 2001 From: mars Date: Sun, 30 Oct 2022 23:31:54 -0600 Subject: [PATCH] Add music player protocol and implement most of it --- Cargo.toml | 1 + apps/music-player/Cargo.toml | 2 +- apps/music-player/src/main.rs | 174 ++++++++++++++++++------ crates/magpie-client/src/lib.rs | 10 +- crates/music-player-protocol/Cargo.toml | 7 + crates/music-player-protocol/src/lib.rs | 103 ++++++++++++++ scripts/music-player/Cargo.toml | 1 + 7 files changed, 251 insertions(+), 47 deletions(-) create mode 100644 crates/music-player-protocol/Cargo.toml create mode 100644 crates/music-player-protocol/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e821b37..c49fcc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "apps/sandbox", "crates/magpie-client", "crates/magpie-types", + "crates/music-player-protocol", "crates/script", "crates/types", "scripts/music-player", diff --git a/apps/music-player/Cargo.toml b/apps/music-player/Cargo.toml index fe9c724..d53b716 100644 --- a/apps/music-player/Cargo.toml +++ b/apps/music-player/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +canary-music-player-protocol = { path = "../../crates/music-player-protocol" } magpie-client = { path = "../../crates/magpie-client" } mpris = "2.0.0-rc3" -ouroboros = "^0.15" diff --git a/apps/music-player/src/main.rs b/apps/music-player/src/main.rs index c979e5c..28925b1 100644 --- a/apps/music-player/src/main.rs +++ b/apps/music-player/src/main.rs @@ -1,49 +1,60 @@ +use canary_music_player_protocol::*; use magpie_client::{magpie_types, MagpieClient}; use magpie_types::{CreatePanel, MagpieServerMsg}; -use mpris::{PlayerFinder, ProgressTracker}; -use ouroboros::self_referencing; +use mpris::PlayerFinder; -#[self_referencing] -pub struct Player { - player: mpris::Player, - - #[borrows(player)] - #[covariant] - tracker: ProgressTracker<'this>, +pub struct MetadataTracker { + pub album: AlbumInfo, + pub track: TrackInfo, } -impl Player { - pub fn from_player(player: mpris::Player) -> Self { - println!("Connected to {}", player.identity()); - PlayerBuilder { - player, - tracker_builder: |player| player.track_progress(100).unwrap(), - } - .build() +impl From for MetadataTracker { + fn from(metadata: mpris::Metadata) -> Self { + let album = AlbumInfo { + title: metadata.album_name().map(ToString::to_string), + artists: metadata + .album_artists() + .unwrap_or(Vec::new()) + .iter() + .map(ToString::to_string) + .collect(), + }; + + let track = TrackInfo { + title: metadata.title().map(ToString::to_string), + artists: metadata + .artists() + .unwrap_or(Vec::new()) + .iter() + .map(ToString::to_string) + .collect(), + track_number: metadata.track_number(), + }; + + Self { album, track } + } +} + +impl MetadataTracker { + pub fn new(magpie: &mut MagpieClient, metadata: mpris::Metadata) -> Self { + let new: Self = metadata.into(); + magpie.send_json_message(0, &InMsg::AlbumChanged(new.album.clone())); + magpie.send_json_message(0, &InMsg::TrackChanged(new.track.clone())); + new } - pub fn tick(&mut self) -> bool { - self.with_tracker_mut(|tracker| { - let tick = tracker.tick(); + pub fn update(&mut self, messenger: &mut MagpieClient, metadata: mpris::Metadata) { + let new: Self = metadata.into(); - if tick.progress_changed { - let p = tick.progress; - println!( - "({:?}/{:?}) {:?} - {:?} - {:?}", - p.position(), - p.length(), - p.metadata().artists(), - p.metadata().album_name(), - p.metadata().title(), - ); - } + if self.album != new.album { + messenger.send_json_message(0, &InMsg::AlbumChanged(new.album.clone())); + } - if tick.track_list_changed { - println!("{:#?}", tick.progress.metadata()); - } + if self.track != new.track { + messenger.send_json_message(0, &InMsg::TrackChanged(new.track.clone())); + } - tick.player_quit - }) + *self = new; } } @@ -62,21 +73,96 @@ fn main() { let msg = MagpieServerMsg::CreatePanel(msg); magpie.messenger.send(&msg).unwrap(); - loop { - println!("Connecting to MRPIS..."); + let mut first_loop = true; + let mut connected = false; - let mut player = match player_finder.find_active() { - Ok(player) => Player::from_player(player), + loop { + if !first_loop { + let wait = std::time::Duration::from_secs(1); + std::thread::sleep(wait); + } + + first_loop = false; + + if connected { + println!("Disconnected from MPRIS"); + let msg = InMsg::Disconnected; + magpie.send_json_message(0, &msg); + connected = false; + } + + println!("Connecting to MPRIS..."); + + let player = match player_finder.find_active() { + Ok(player) => player, Err(err) => { eprintln!("Couldn't find player: {:?}", err); - let wait = std::time::Duration::from_secs(1); - std::thread::sleep(wait); continue; } }; - while !player.tick() {} + println!( + "Connected to \"{}\" ({})", + player.identity(), + player.bus_name() + ); + connected = true; + magpie.send_json_message(0, &InMsg::Connected); - println!("Disconnected from MPRIS"); + let metadata = player.get_metadata().unwrap(); + let mut metadata_tracker = MetadataTracker::new(&mut magpie, metadata); + + let mut events = match player.events() { + Ok(events) => events, + Err(err) => { + eprintln!("Player events D-Bus error: {:?}", err); + continue; + } + }; + + loop { + let event = match events.next() { + None => break, + Some(Ok(e)) => e, + Some(Err(err)) => { + eprintln!("D-Bus error while reading player events: {:?}", err); + continue; + } + }; + + use mpris::Event::*; + let in_msg = match event { + Playing => Some(InMsg::PlaybackStatusChanged(PlaybackStatus::Playing)), + Paused => Some(InMsg::PlaybackStatusChanged(PlaybackStatus::Paused)), + Stopped => Some(InMsg::PlaybackStatusChanged(PlaybackStatus::Stopped)), + LoopingChanged(status) => { + use mpris::LoopStatus::*; + let status = match status { + None => LoopStatus::None, + Track => LoopStatus::Track, + Playlist => LoopStatus::Playlist, + }; + + Some(InMsg::LoopingChanged(status)) + } + ShuffleToggled(shuffle) => Some(InMsg::ShuffleChanged { shuffle }), + VolumeChanged(volume) => Some(InMsg::VolumeChanged { + volume: volume as f32, + }), + PlayerShutDown => None, + TrackChanged(metadata) => { + metadata_tracker.update(&mut magpie, metadata); + None + } + _ => { + eprintln!("Unhandled MPRIS message: {:?}", event); + None + } + }; + + if let Some(msg) = in_msg { + magpie.send_json_message(0, &msg); + } + } } } diff --git a/crates/magpie-client/src/lib.rs b/crates/magpie-client/src/lib.rs index 6a8bd1e..7729c9c 100644 --- a/crates/magpie-client/src/lib.rs +++ b/crates/magpie-client/src/lib.rs @@ -26,8 +26,14 @@ impl MagpieClient { } pub fn send_json_message(&mut self, id: PanelId, msg: &T) { - let msg = serde_json::to_vec(msg).unwrap(); - let msg = SendMessage { id, msg }; + let msg = serde_json::to_string(msg).unwrap(); + eprintln!("Sending message: {}", msg); + + let msg = SendMessage { + id, + msg: msg.into_bytes(), + }; + let _ = self.messenger.send(&MagpieServerMsg::SendMessage(msg)); } } diff --git a/crates/music-player-protocol/Cargo.toml b/crates/music-player-protocol/Cargo.toml new file mode 100644 index 0000000..83c8930 --- /dev/null +++ b/crates/music-player-protocol/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "canary-music-player-protocol" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1", features = ["derive"] } diff --git a/crates/music-player-protocol/src/lib.rs b/crates/music-player-protocol/src/lib.rs new file mode 100644 index 0000000..c88e356 --- /dev/null +++ b/crates/music-player-protocol/src/lib.rs @@ -0,0 +1,103 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PlaybackStatus { + /// A track is currently playing. + Playing, + + /// A track is currently paused. + Paused, + + /// No track is currently playing. + Stopped, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum LoopStatus { + /// The playback will stop when there are no more tracks to play. + None, + + /// The current track will start again from the beginning once it has finished playing. + Track, + + /// The playback loops through a list of tracks. + Playlist, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProgressChanged { + /// Current position into the track in seconds. + pub position: f32, + + /// Length of the current track in seconds. + pub length: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct AlbumInfo { + /// The title of the current album. + pub title: Option, + + /// The list of artists of the album. + pub artists: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct TrackInfo { + /// The title of the current track. + pub title: Option, + + /// The list of artists on this track. May be empty. + pub artists: Vec, + + /// The optional track number on the disc the album the track appears on. + pub track_number: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum InMsg { + Connected, + Disconnected, + PlaybackStatusChanged(PlaybackStatus), + VolumeChanged { volume: f32 }, + ShuffleChanged { shuffle: bool }, + LoopingChanged(LoopStatus), + ProgressChanged(ProgressChanged), + AlbumChanged(AlbumInfo), + TrackChanged(TrackInfo), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum OutMsg { + /// Brings the media player's user interface to the front using any + /// appropriate mechanism available. + Raise, + + /// Pauses playback. + Pause, + + /// Resumes playback. + Play, + + /// Toggles playback between paused and resumed. + PlayPause, + + /// Stops playback. + Stop, + + /// Skips to the next track in the tracklist. Stops playback if there is + /// none. + Next, + + /// Skips to the previous track in the tracklist. Stops playback if there + /// is no previous track and endless playback and track repeat are off. + Previous, + + /// Sets the volume. Values are clamped to 0.0 to 1.0. + SetVolume { volume: f32 }, + + /// Set the current track position in seconds. + SetPosition { position: f32 }, +} diff --git a/scripts/music-player/Cargo.toml b/scripts/music-player/Cargo.toml index dc84be5..f57dd5f 100644 --- a/scripts/music-player/Cargo.toml +++ b/scripts/music-player/Cargo.toml @@ -8,4 +8,5 @@ crate-type = ["cdylib"] [dependencies] canary_script = { path = "../../crates/script" } +canary-music-player-protocol = { path = "../../crates/music-player-protocol" } wee_alloc = "^0.4"