canary-rs/apps/music-player/src/main.rs

200 lines
6.1 KiB
Rust

// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::{os::unix::net::UnixStream, path::Path};
use canary_magpie::protocol::{ClientMessenger, CreatePanel, MagpieServerMsg, MAGPIE_SOCK};
use canary_music_player::*;
pub type MagpieClient<'a> = ClientMessenger<&'a UnixStream>;
pub mod mpris;
use mpris::*;
#[derive(Debug)]
pub struct Metadata {
pub album: AlbumInfo,
pub track: TrackInfo,
}
impl<'a> From<MetadataMap<'a>> 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("xesam: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_panel_json(0, &InMsg::AlbumChanged(new.album.clone()));
magpie.send_panel_json(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_panel_json(0, &InMsg::AlbumChanged(new.album.clone()));
}
if self.track != new.track {
messenger.send_panel_json(0, &InMsg::TrackChanged(new.track.clone()));
}
*self = new;
}
}
async fn player_main(
player: &PlayerProxy<'_>,
magpie: &mut MagpieClient<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
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 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_panel_json(0, &InMsg::PlaybackStatusChanged(status));
}
}
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<String> = std::env::args().collect();
let module_path = args
.get(1)
.expect("Please pass a path to a Canary script!")
.to_owned();
let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set");
let sock_dir = Path::new(&sock_dir);
let sock_path = sock_dir.join(MAGPIE_SOCK);
let socket = UnixStream::connect(sock_path).unwrap();
let mut magpie = MagpieClient::new(&socket);
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.send(&msg).unwrap();
smol::block_on(async {
let dbus = zbus::Connection::session().await.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_panel_json(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_panel_json(0, &InMsg::Connected);
match player_main(&player, &mut magpie).await {
Ok(()) => {}
Err(err) => {
eprintln!("D-Bus error while connected to player: {:?}", err);
}
}
}
});
}