Add music player protocol and implement most of it

This commit is contained in:
mars 2022-10-30 23:31:54 -06:00
parent 8c8d89b34d
commit 4a5f27a95d
7 changed files with 251 additions and 47 deletions

View File

@ -5,6 +5,7 @@ members = [
"apps/sandbox",
"crates/magpie-client",
"crates/magpie-types",
"crates/music-player-protocol",
"crates/script",
"crates/types",
"scripts/music-player",

View File

@ -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"

View File

@ -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<mpris::Metadata> 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);
}
}
}
}

View File

@ -26,8 +26,14 @@ impl MagpieClient {
}
pub fn send_json_message<T: Serialize>(&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));
}
}

View File

@ -0,0 +1,7 @@
[package]
name = "canary-music-player-protocol"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }

View File

@ -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<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AlbumInfo {
/// The title of the current album.
pub title: Option<String>,
/// The list of artists of the album.
pub artists: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TrackInfo {
/// The title of the current track.
pub title: Option<String>,
/// The list of artists on this track. May be empty.
pub artists: Vec<String>,
/// The optional track number on the disc the album the track appears on.
pub track_number: Option<i32>,
}
#[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 },
}

View File

@ -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"