// Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: AGPL-3.0-or-later use canary_magpie::client::MagpieClient; use canary_magpie::protocol::{CreatePanel, MagpieServerMsg}; use canary_music_player::*; pub mod mpris; use mpris::*; #[derive(Debug)] pub struct Metadata { pub album: AlbumInfo, pub track: TrackInfo, } impl<'a> From> for Metadata { fn from(map: MetadataMap<'a>) -> Self { let album = AlbumInfo { title: map .get("xesam:album") .and_then(|v| TryFrom::try_from(v).ok()), artists: map .get("xesam:albumArtist") .cloned() .and_then(|v| TryFrom::try_from(v).ok()) .unwrap_or(Vec::new()), }; let track = TrackInfo { title: map .get("xesam:title") .and_then(|v| TryFrom::try_from(v).ok()), artists: map .get("xesam:artist") .cloned() .and_then(|v| TryFrom::try_from(v).ok()) .unwrap_or(Vec::new()), track_number: map .get("xesam:trackNumber") .and_then(|v| TryFrom::try_from(v).ok()), length: map .get("mpris:length") .and_then(|v| i64::try_from(v).ok()) .map(|us| us as f32 / 1_000_000.0), // 1,000,000 microseconds in a second }; Self { album, track } } } impl Metadata { pub fn new(magpie: &mut MagpieClient, metadata: MetadataMap) -> 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 update(&mut self, messenger: &mut MagpieClient, metadata: MetadataMap) { let new: Self = metadata.into(); if self.album != new.album { messenger.send_json_message(0, &InMsg::AlbumChanged(new.album.clone())); } if self.track != new.track { messenger.send_json_message(0, &InMsg::TrackChanged(new.track.clone())); } *self = new; } } async fn player_main( player: &PlayerProxy<'_>, magpie: &mut MagpieClient, ) -> Result<(), Box> { use futures_util::StreamExt; let mut playback_status = player.receive_playback_status_changed().await.fuse(); let mut metadata_tracker = player.receive_metadata_changed().await.fuse(); let mut position_tracker = player.receive_position_changed().await.fuse(); let mut metadata = Metadata::new(magpie, player.metadata().await?); loop { futures_util::select! { // TODO also update volume, shuffle status, and loop status status = playback_status.next() => { let status = match status { Some(v) => v, None => break, }; let status = status.get().await?; let status = match status.as_str() { "Playing" => Some(PlaybackStatus::Playing), "Paused" => Some(PlaybackStatus::Paused), "Stopped" => Some(PlaybackStatus::Stopped), _ => None, }; if let Some(status) = status { magpie.send_json_message(0, &InMsg::PlaybackStatusChanged(status)); } } position = position_tracker.next() => { let position = match position { Some(v) => v, None => break, }; let position = position.get().await?; let position = position as f32 / 1_000_000.0; // Microseconds to seconds let progress = ProgressChanged { position }; magpie.send_json_message(0, &InMsg::ProgressChanged(progress)); } new_metadata = metadata_tracker.next() => { let new_metadata = match new_metadata { Some(v) => v, None => break, }; let new_metadata = new_metadata.get().await?; metadata.update(magpie, new_metadata); } }; } Ok(()) } fn main() { let args: Vec = std::env::args().collect(); let module_path = args .get(1) .expect("Please pass a path to a Canary script!") .to_owned(); let mut magpie = MagpieClient::new().unwrap(); smol::block_on(async { let dbus = zbus::Connection::session().await.unwrap(); let protocol = "tebibyte-media.desktop.music-player-controller".to_string(); let script = std::path::PathBuf::from(&module_path); let msg = CreatePanel { id: 0, protocol, script, }; let msg = MagpieServerMsg::CreatePanel(msg); magpie.messenger.send(&msg).unwrap(); let mut first_loop = true; let mut connected = false; 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 find_player(&dbus).await { Ok(Some(player)) => player, Ok(None) => { eprintln!("Couldn't find player"); continue; } Err(err) => { eprintln!("D-Bus error while finding player: {:?}", err); return; } }; println!( "Connected to \"{}\" ({})", player.path().as_str(), player.destination().as_str() ); connected = true; magpie.send_json_message(0, &InMsg::Connected); match player_main(&player, &mut magpie).await { Ok(()) => {} Err(err) => { eprintln!("D-Bus error while connected to player: {:?}", err); } } } }); }