Add music player protocol and implement most of it
This commit is contained in:
parent
8c8d89b34d
commit
4a5f27a95d
|
@ -5,6 +5,7 @@ members = [
|
|||
"apps/sandbox",
|
||||
"crates/magpie-client",
|
||||
"crates/magpie-types",
|
||||
"crates/music-player-protocol",
|
||||
"crates/script",
|
||||
"crates/types",
|
||||
"scripts/music-player",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "canary-music-player-protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
|
@ -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 },
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue