mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-03 03:31:03 +02:00
parent
bd90167f4e
commit
12053f111a
5 changed files with 284 additions and 103 deletions
|
@ -9,9 +9,17 @@ pub mod mpd;
|
|||
#[cfg(feature = "music+mpris")]
|
||||
pub mod mpris;
|
||||
|
||||
pub const TICK_INTERVAL_MS: u64 = 200;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PlayerUpdate {
|
||||
/// Triggered when the track or player state notably changes,
|
||||
/// such as a new track playing, the player being paused, or a volume change.
|
||||
Update(Box<Option<Track>>, Status),
|
||||
/// Triggered at regular intervals while a track is playing.
|
||||
/// Used to keep track of the progress through the current track.
|
||||
ProgressTick(ProgressTick),
|
||||
/// Triggered when the client disconnects from the player.
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
|
@ -27,23 +35,27 @@ pub struct Track {
|
|||
pub cover_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PlayerState {
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Status {
|
||||
pub state: PlayerState,
|
||||
pub volume_percent: u8,
|
||||
pub duration: Option<Duration>,
|
||||
pub elapsed: Option<Duration>,
|
||||
pub volume_percent: Option<u8>,
|
||||
pub playlist_position: u32,
|
||||
pub playlist_length: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ProgressTick {
|
||||
pub duration: Option<Duration>,
|
||||
pub elapsed: Option<Duration>,
|
||||
}
|
||||
|
||||
pub trait MusicClient {
|
||||
fn play(&self) -> Result<()>;
|
||||
fn pause(&self) -> Result<()>;
|
||||
|
@ -51,6 +63,7 @@ pub trait MusicClient {
|
|||
fn prev(&self) -> Result<()>;
|
||||
|
||||
fn set_volume_percent(&self, vol: u8) -> Result<()>;
|
||||
fn seek(&self, duration: Duration) -> Result<()>;
|
||||
|
||||
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate>;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use super::{MusicClient, Status, Track};
|
||||
use crate::await_sync;
|
||||
use crate::clients::music::{PlayerState, PlayerUpdate};
|
||||
use super::{
|
||||
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
|
||||
};
|
||||
use crate::{await_sync, send};
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
|
||||
use mpd_client::commands::SeekMode;
|
||||
use mpd_client::protocol::MpdProtocolError;
|
||||
use mpd_client::responses::{PlayState, Song};
|
||||
use mpd_client::tag::Tag;
|
||||
|
@ -16,7 +18,7 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use tokio::net::{TcpStream, UnixStream};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::broadcast::{channel, error::SendError, Receiver, Sender};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, info};
|
||||
|
@ -29,8 +31,8 @@ lazy_static! {
|
|||
pub struct MpdClient {
|
||||
client: Client,
|
||||
music_dir: PathBuf,
|
||||
tx: Sender<PlayerUpdate>,
|
||||
_rx: Receiver<PlayerUpdate>,
|
||||
tx: broadcast::Sender<PlayerUpdate>,
|
||||
_rx: broadcast::Receiver<PlayerUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -57,7 +59,7 @@ impl MpdClient {
|
|||
let (client, mut state_changes) =
|
||||
wait_for_connection(host, Duration::from_secs(5), None).await?;
|
||||
|
||||
let (tx, rx) = channel(16);
|
||||
let (tx, rx) = broadcast::channel(16);
|
||||
|
||||
{
|
||||
let music_dir = music_dir.clone();
|
||||
|
@ -78,7 +80,19 @@ impl MpdClient {
|
|||
}
|
||||
}
|
||||
|
||||
Ok::<(), SendError<(Option<Track>, Status)>>(())
|
||||
Ok::<(), broadcast::error::SendError<(Option<Track>, Status)>>(())
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let client = client.clone();
|
||||
let tx = tx.clone();
|
||||
|
||||
spawn(async move {
|
||||
loop {
|
||||
Self::send_tick_update(&client, &tx).await;
|
||||
sleep(Duration::from_millis(TICK_INTERVAL_MS)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -92,9 +106,9 @@ impl MpdClient {
|
|||
|
||||
async fn send_update(
|
||||
client: &Client,
|
||||
tx: &Sender<PlayerUpdate>,
|
||||
tx: &broadcast::Sender<PlayerUpdate>,
|
||||
music_dir: &Path,
|
||||
) -> Result<(), SendError<PlayerUpdate>> {
|
||||
) -> Result<(), broadcast::error::SendError<PlayerUpdate>> {
|
||||
let current_song = client.command(commands::CurrentSong).await;
|
||||
let status = client.command(commands::Status).await;
|
||||
|
||||
|
@ -102,17 +116,33 @@ impl MpdClient {
|
|||
let track = current_song.map(|s| Self::convert_song(&s.song, music_dir));
|
||||
let status = Status::from(status);
|
||||
|
||||
tx.send(PlayerUpdate::Update(Box::new(track), status))?;
|
||||
let update = PlayerUpdate::Update(Box::new(track), status);
|
||||
send!(tx, update);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_tick_update(client: &Client, tx: &broadcast::Sender<PlayerUpdate>) {
|
||||
let status = client.command(commands::Status).await;
|
||||
|
||||
if let Ok(status) = status {
|
||||
if status.state == PlayState::Playing {
|
||||
let update = PlayerUpdate::ProgressTick(ProgressTick {
|
||||
duration: status.duration,
|
||||
elapsed: status.elapsed,
|
||||
});
|
||||
|
||||
send!(tx, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
!self.client.is_connection_closed()
|
||||
}
|
||||
|
||||
fn send_disconnect_update(&self) -> Result<(), SendError<PlayerUpdate>> {
|
||||
fn send_disconnect_update(&self) -> Result<(), broadcast::error::SendError<PlayerUpdate>> {
|
||||
info!("Connection to MPD server lost");
|
||||
self.tx.send(PlayerUpdate::Disconnect)?;
|
||||
Ok(())
|
||||
|
@ -182,7 +212,12 @@ impl MusicClient for MpdClient {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_change(&self) -> Receiver<PlayerUpdate> {
|
||||
fn seek(&self, duration: Duration) -> Result<()> {
|
||||
async_command!(self.client, commands::Seek(SeekMode::Absolute(duration)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate> {
|
||||
let rx = self.tx.subscribe();
|
||||
await_sync(async {
|
||||
Self::send_update(&self.client, &self.tx, &self.music_dir)
|
||||
|
@ -291,9 +326,7 @@ impl From<mpd_client::responses::Status> for Status {
|
|||
fn from(status: mpd_client::responses::Status) -> Self {
|
||||
Self {
|
||||
state: PlayerState::from(status.state),
|
||||
volume_percent: status.volume,
|
||||
duration: status.duration,
|
||||
elapsed: status.elapsed,
|
||||
volume_percent: Some(status.volume),
|
||||
playlist_position: status.current_song.map_or(0, |(pos, _)| pos.0 as u32),
|
||||
playlist_length: status.playlist_length as u32,
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use super::{MusicClient, PlayerUpdate, Status, Track};
|
||||
use crate::clients::music::PlayerState;
|
||||
use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
|
||||
use crate::clients::music::ProgressTick;
|
||||
use crate::{arc_mut, lock, send};
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||
use std::collections::HashSet;
|
||||
use std::string;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
use std::{cmp, string};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::task::spawn_blocking;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
|
@ -19,13 +19,13 @@ lazy_static! {
|
|||
|
||||
pub struct Client {
|
||||
current_player: Arc<Mutex<Option<String>>>,
|
||||
tx: Sender<PlayerUpdate>,
|
||||
_rx: Receiver<PlayerUpdate>,
|
||||
tx: broadcast::Sender<PlayerUpdate>,
|
||||
_rx: broadcast::Receiver<PlayerUpdate>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn new() -> Self {
|
||||
let (tx, rx) = channel(32);
|
||||
let (tx, rx) = broadcast::channel(32);
|
||||
|
||||
let current_player = arc_mut!(None);
|
||||
|
||||
|
@ -84,6 +84,20 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
{
|
||||
let current_player = current_player.clone();
|
||||
let tx = tx.clone();
|
||||
|
||||
spawn_blocking(move || {
|
||||
let player_finder = PlayerFinder::new().expect("to get new player finder");
|
||||
|
||||
loop {
|
||||
Self::send_tick_update(&player_finder, ¤t_player, &tx);
|
||||
sleep(Duration::from_millis(TICK_INTERVAL_MS));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
current_player,
|
||||
tx,
|
||||
|
@ -95,7 +109,7 @@ impl Client {
|
|||
player_id: String,
|
||||
players: Arc<Mutex<HashSet<String>>>,
|
||||
current_player: Arc<Mutex<Option<String>>>,
|
||||
tx: Sender<PlayerUpdate>,
|
||||
tx: broadcast::Sender<PlayerUpdate>,
|
||||
) {
|
||||
spawn_blocking(move || {
|
||||
let player_finder = PlayerFinder::new()?;
|
||||
|
@ -138,7 +152,7 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
fn send_update(player: &Player, tx: &Sender<PlayerUpdate>) -> Result<()> {
|
||||
fn send_update(player: &Player, tx: &broadcast::Sender<PlayerUpdate>) -> Result<()> {
|
||||
debug!("Sending update using '{}'", player.identity());
|
||||
|
||||
let metadata = player.get_metadata()?;
|
||||
|
@ -159,8 +173,6 @@ impl Client {
|
|||
playlist_position: 1,
|
||||
playlist_length: track_list.map(|list| list.len() as u32).unwrap_or(u32::MAX),
|
||||
state: PlayerState::from(playback_status),
|
||||
elapsed: player.get_position().ok(),
|
||||
duration: metadata.length(),
|
||||
volume_percent,
|
||||
};
|
||||
|
||||
|
@ -181,6 +193,26 @@ impl Client {
|
|||
player_finder.find_by_name(player_name).ok()
|
||||
})
|
||||
}
|
||||
|
||||
fn send_tick_update(
|
||||
player_finder: &PlayerFinder,
|
||||
current_player: &Mutex<Option<String>>,
|
||||
tx: &broadcast::Sender<PlayerUpdate>,
|
||||
) {
|
||||
if let Some(player) = lock!(current_player)
|
||||
.as_ref()
|
||||
.and_then(|name| player_finder.find_by_name(name).ok())
|
||||
{
|
||||
if let Ok(metadata) = player.get_metadata() {
|
||||
let update = PlayerUpdate::ProgressTick(ProgressTick {
|
||||
elapsed: player.get_position().ok(),
|
||||
duration: metadata.length(),
|
||||
});
|
||||
|
||||
send!(tx, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! command {
|
||||
|
@ -223,7 +255,23 @@ impl MusicClient for Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_change(&self) -> Receiver<PlayerUpdate> {
|
||||
fn seek(&self, duration: Duration) -> Result<()> {
|
||||
if let Some(player) = Self::get_player(self) {
|
||||
let pos = player.get_position().unwrap_or_default();
|
||||
|
||||
let duration = duration.as_micros() as i64;
|
||||
let position = pos.as_micros() as i64;
|
||||
|
||||
let seek = cmp::max(duration, 0) - position;
|
||||
|
||||
player.seek(seek)?;
|
||||
} else {
|
||||
error!("Could not find player");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate> {
|
||||
debug!("Creating new subscription");
|
||||
let rx = self.tx.subscribe();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue