1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-03 03:31:03 +02:00

feat(music): progress/seek bar in popup

Resolves #128.
This commit is contained in:
Jake Stanger 2023-06-29 18:17:13 +01:00
parent bd90167f4e
commit 12053f111a
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
5 changed files with 284 additions and 103 deletions

View file

@ -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>;
}

View file

@ -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,
}

View file

@ -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, &current_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();