1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 18:51:04 +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

@ -128,8 +128,6 @@ and will be replaced with values from the currently playing track:
| `{track}` | Track number | | `{track}` | Track number |
| `{disc}` | Disc number | | `{disc}` | Disc number |
| `{genre}` | Genre | | `{genre}` | Genre |
| `{duration}` | Duration in `mm:ss` |
| `{elapsed}` | Time elapsed in `mm:ss` |
## Styling ## Styling
@ -166,7 +164,10 @@ and will be replaced with values from the currently playing track:
| `.popup-music .controls .btn-pause` | Pause button inside popup box | | `.popup-music .controls .btn-pause` | Pause button inside popup box |
| `.popup-music .controls .btn-next` | Next button inside popup box | | `.popup-music .controls .btn-next` | Next button inside popup box |
| `.popup-music .volume` | Volume container inside popup box | | `.popup-music .volume` | Volume container inside popup box |
| `.popup-music .volume .slider` | Volume slider popup box | | `.popup-music .volume .slider` | Slider inside volume container |
| `.popup-music .volume .icon` | Volume icon label inside popup box | | `.popup-music .volume .icon` | Icon inside volume container |
| `.popup-music .progress` | Progress (seek) bar container |
| `.popup-music .progress .slider` | Slider inside progress container |
| `.popup-music .progress .label` | Duration label inside progress container |
For more information on styling, please see the [styling guide](styling-guide). For more information on styling, please see the [styling guide](styling-guide).

View file

@ -9,9 +9,17 @@ pub mod mpd;
#[cfg(feature = "music+mpris")] #[cfg(feature = "music+mpris")]
pub mod mpris; pub mod mpris;
pub const TICK_INTERVAL_MS: u64 = 200;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PlayerUpdate { 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), 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, Disconnect,
} }
@ -27,23 +35,27 @@ pub struct Track {
pub cover_path: Option<String>, pub cover_path: Option<String>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum PlayerState { pub enum PlayerState {
Playing, Playing,
Paused, Paused,
Stopped, Stopped,
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Status { pub struct Status {
pub state: PlayerState, pub state: PlayerState,
pub volume_percent: u8, pub volume_percent: Option<u8>,
pub duration: Option<Duration>,
pub elapsed: Option<Duration>,
pub playlist_position: u32, pub playlist_position: u32,
pub playlist_length: u32, pub playlist_length: u32,
} }
#[derive(Clone, Copy, Debug)]
pub struct ProgressTick {
pub duration: Option<Duration>,
pub elapsed: Option<Duration>,
}
pub trait MusicClient { pub trait MusicClient {
fn play(&self) -> Result<()>; fn play(&self) -> Result<()>;
fn pause(&self) -> Result<()>; fn pause(&self) -> Result<()>;
@ -51,6 +63,7 @@ pub trait MusicClient {
fn prev(&self) -> Result<()>; fn prev(&self) -> Result<()>;
fn set_volume_percent(&self, vol: u8) -> Result<()>; fn set_volume_percent(&self, vol: u8) -> Result<()>;
fn seek(&self, duration: Duration) -> Result<()>;
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate>; fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate>;
} }

View file

@ -1,9 +1,11 @@
use super::{MusicClient, Status, Track}; use super::{
use crate::await_sync; MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
use crate::clients::music::{PlayerState, PlayerUpdate}; };
use crate::{await_sync, send};
use color_eyre::Result; use color_eyre::Result;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mpd_client::client::{Connection, ConnectionEvent, Subsystem}; use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
use mpd_client::commands::SeekMode;
use mpd_client::protocol::MpdProtocolError; use mpd_client::protocol::MpdProtocolError;
use mpd_client::responses::{PlayState, Song}; use mpd_client::responses::{PlayState, Song};
use mpd_client::tag::Tag; use mpd_client::tag::Tag;
@ -16,7 +18,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::{TcpStream, UnixStream}; use tokio::net::{TcpStream, UnixStream};
use tokio::spawn; use tokio::spawn;
use tokio::sync::broadcast::{channel, error::SendError, Receiver, Sender}; use tokio::sync::broadcast;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
@ -29,8 +31,8 @@ lazy_static! {
pub struct MpdClient { pub struct MpdClient {
client: Client, client: Client,
music_dir: PathBuf, music_dir: PathBuf,
tx: Sender<PlayerUpdate>, tx: broadcast::Sender<PlayerUpdate>,
_rx: Receiver<PlayerUpdate>, _rx: broadcast::Receiver<PlayerUpdate>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -57,7 +59,7 @@ impl MpdClient {
let (client, mut state_changes) = let (client, mut state_changes) =
wait_for_connection(host, Duration::from_secs(5), None).await?; 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(); 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( async fn send_update(
client: &Client, client: &Client,
tx: &Sender<PlayerUpdate>, tx: &broadcast::Sender<PlayerUpdate>,
music_dir: &Path, music_dir: &Path,
) -> Result<(), SendError<PlayerUpdate>> { ) -> Result<(), broadcast::error::SendError<PlayerUpdate>> {
let current_song = client.command(commands::CurrentSong).await; let current_song = client.command(commands::CurrentSong).await;
let status = client.command(commands::Status).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 track = current_song.map(|s| Self::convert_song(&s.song, music_dir));
let status = Status::from(status); 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(()) 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 { fn is_connected(&self) -> bool {
!self.client.is_connection_closed() !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"); info!("Connection to MPD server lost");
self.tx.send(PlayerUpdate::Disconnect)?; self.tx.send(PlayerUpdate::Disconnect)?;
Ok(()) Ok(())
@ -182,7 +212,12 @@ impl MusicClient for MpdClient {
Ok(()) 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(); let rx = self.tx.subscribe();
await_sync(async { await_sync(async {
Self::send_update(&self.client, &self.tx, &self.music_dir) 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 { fn from(status: mpd_client::responses::Status) -> Self {
Self { Self {
state: PlayerState::from(status.state), state: PlayerState::from(status.state),
volume_percent: status.volume, volume_percent: Some(status.volume),
duration: status.duration,
elapsed: status.elapsed,
playlist_position: status.current_song.map_or(0, |(pos, _)| pos.0 as u32), playlist_position: status.current_song.map_or(0, |(pos, _)| pos.0 as u32),
playlist_length: status.playlist_length as u32, playlist_length: status.playlist_length as u32,
} }

View file

@ -1,15 +1,15 @@
use super::{MusicClient, PlayerUpdate, Status, Track}; use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
use crate::clients::music::PlayerState; use crate::clients::music::ProgressTick;
use crate::{arc_mut, lock, send}; use crate::{arc_mut, lock, send};
use color_eyre::Result; use color_eyre::Result;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder}; use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
use std::collections::HashSet; use std::collections::HashSet;
use std::string;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; 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 tokio::task::spawn_blocking;
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
@ -19,13 +19,13 @@ lazy_static! {
pub struct Client { pub struct Client {
current_player: Arc<Mutex<Option<String>>>, current_player: Arc<Mutex<Option<String>>>,
tx: Sender<PlayerUpdate>, tx: broadcast::Sender<PlayerUpdate>,
_rx: Receiver<PlayerUpdate>, _rx: broadcast::Receiver<PlayerUpdate>,
} }
impl Client { impl Client {
fn new() -> Self { fn new() -> Self {
let (tx, rx) = channel(32); let (tx, rx) = broadcast::channel(32);
let current_player = arc_mut!(None); 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 { Self {
current_player, current_player,
tx, tx,
@ -95,7 +109,7 @@ impl Client {
player_id: String, player_id: String,
players: Arc<Mutex<HashSet<String>>>, players: Arc<Mutex<HashSet<String>>>,
current_player: Arc<Mutex<Option<String>>>, current_player: Arc<Mutex<Option<String>>>,
tx: Sender<PlayerUpdate>, tx: broadcast::Sender<PlayerUpdate>,
) { ) {
spawn_blocking(move || { spawn_blocking(move || {
let player_finder = PlayerFinder::new()?; 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()); debug!("Sending update using '{}'", player.identity());
let metadata = player.get_metadata()?; let metadata = player.get_metadata()?;
@ -159,8 +173,6 @@ impl Client {
playlist_position: 1, playlist_position: 1,
playlist_length: track_list.map(|list| list.len() as u32).unwrap_or(u32::MAX), playlist_length: track_list.map(|list| list.len() as u32).unwrap_or(u32::MAX),
state: PlayerState::from(playback_status), state: PlayerState::from(playback_status),
elapsed: player.get_position().ok(),
duration: metadata.length(),
volume_percent, volume_percent,
}; };
@ -181,6 +193,26 @@ impl Client {
player_finder.find_by_name(player_name).ok() 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 { macro_rules! command {
@ -223,7 +255,23 @@ impl MusicClient for Client {
Ok(()) 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"); debug!("Creating new subscription");
let rx = self.tx.subscribe(); let rx = self.tx.subscribe();

View file

@ -1,17 +1,20 @@
mod config; mod config;
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track}; use crate::clients::music::{
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
};
use crate::gtk_helpers::add_class; use crate::gtk_helpers::add_class;
use crate::image::{new_icon_button, new_icon_label, ImageProvider}; use crate::image::{new_icon_button, new_icon_label, ImageProvider};
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup; use crate::popup::Popup;
use crate::{send_async, try_send}; use crate::{send_async, try_send};
use color_eyre::Result; use color_eyre::Result;
use glib::Continue; use glib::{Continue, PropertySet};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Label, Orientation, Scale}; use gtk::{Button, IconTheme, Label, Orientation, Scale};
use regex::Regex; use regex::Regex;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::spawn; use tokio::spawn;
@ -28,6 +31,7 @@ pub enum PlayerCommand {
Pause, Pause,
Next, Next,
Volume(u8), Volume(u8),
Seek(Duration),
} }
/// Formats a duration given in seconds /// Formats a duration given in seconds
@ -47,6 +51,12 @@ fn get_tokens(re: &Regex, format_string: &str) -> Vec<String> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
#[derive(Clone, Debug)]
pub enum ControllerEvent {
Update(Option<SongUpdate>),
UpdateProgress(ProgressTick),
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SongUpdate { pub struct SongUpdate {
song: Track, song: Track,
@ -67,7 +77,7 @@ async fn get_client(
} }
impl Module<Button> for MusicModule { impl Module<Button> for MusicModule {
type SendMessage = Option<SongUpdate>; type SendMessage = ControllerEvent;
type ReceiveMessage = PlayerCommand; type ReceiveMessage = PlayerCommand;
fn name() -> &'static str { fn name() -> &'static str {
@ -103,7 +113,7 @@ impl Module<Button> for MusicModule {
PlayerUpdate::Update(track, status) => match *track { PlayerUpdate::Update(track, status) => match *track {
Some(track) => { Some(track) => {
let display_string = let display_string =
replace_tokens(format.as_str(), &tokens, &track, &status); replace_tokens(format.as_str(), &tokens, &track);
let update = SongUpdate { let update = SongUpdate {
song: track, song: track,
@ -111,10 +121,24 @@ impl Module<Button> for MusicModule {
display_string, display_string,
}; };
send_async!(tx, ModuleUpdateEvent::Update(Some(update))); send_async!(
tx,
ModuleUpdateEvent::Update(ControllerEvent::Update(Some(
update
)))
);
} }
None => send_async!(tx, ModuleUpdateEvent::Update(None)), None => send_async!(
tx,
ModuleUpdateEvent::Update(ControllerEvent::Update(None))
),
}, },
PlayerUpdate::ProgressTick(progress_tick) => send_async!(
tx,
ModuleUpdateEvent::Update(ControllerEvent::UpdateProgress(
progress_tick
))
),
PlayerUpdate::Disconnect => break, PlayerUpdate::Disconnect => break,
} }
} }
@ -137,6 +161,7 @@ impl Module<Button> for MusicModule {
PlayerCommand::Pause => client.pause(), PlayerCommand::Pause => client.pause(),
PlayerCommand::Next => client.next(), PlayerCommand::Next => client.next(),
PlayerCommand::Volume(vol) => client.set_volume_percent(vol), PlayerCommand::Volume(vol) => client.set_volume_percent(vol),
PlayerCommand::Seek(duration) => client.seek(duration),
}; };
if let Err(err) = res { if let Err(err) = res {
@ -191,7 +216,9 @@ impl Module<Button> for MusicModule {
let button = button.clone(); let button = button.clone();
let tx = context.tx.clone(); let tx = context.tx.clone();
context.widget_rx.attach(None, move |mut event| { context.widget_rx.attach(None, move |event| {
let ControllerEvent::Update(mut event) = event else { return Continue(true) };
if let Some(event) = event.take() { if let Some(event) = event.take() {
label.set_label(&event.display_string); label.set_label(&event.display_string);
@ -241,7 +268,8 @@ impl Module<Button> for MusicModule {
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let icon_theme = info.icon_theme; let icon_theme = info.icon_theme;
let container = gtk::Box::new(Orientation::Horizontal, 10); let container = gtk::Box::new(Orientation::Vertical, 10);
let main_container = gtk::Box::new(Orientation::Horizontal, 10);
let album_image = gtk::Image::builder() let album_image = gtk::Image::builder()
.width_request(128) .width_request(128)
@ -299,9 +327,10 @@ impl Module<Button> for MusicModule {
volume_box.pack_start(&volume_slider, true, true, 0); volume_box.pack_start(&volume_slider, true, true, 0);
volume_box.pack_end(&volume_icon, false, false, 0); volume_box.pack_end(&volume_icon, false, false, 0);
container.add(&album_image); main_container.add(&album_image);
container.add(&info_box); main_container.add(&info_box);
container.add(&volume_box); main_container.add(&volume_box);
container.add(&main_container);
let tx_prev = tx.clone(); let tx_prev = tx.clone();
btn_prev.connect_clicked(move |_| { btn_prev.connect_clicked(move |_| {
@ -323,12 +352,49 @@ impl Module<Button> for MusicModule {
try_send!(tx_next, PlayerCommand::Next); try_send!(tx_next, PlayerCommand::Next);
}); });
let tx_vol = tx; let tx_vol = tx.clone();
volume_slider.connect_change_value(move |_, _, val| { volume_slider.connect_change_value(move |_, _, val| {
try_send!(tx_vol, PlayerCommand::Volume(val as u8)); try_send!(tx_vol, PlayerCommand::Volume(val as u8));
Inhibit(false) Inhibit(false)
}); });
let progress_box = gtk::Box::new(Orientation::Horizontal, 5);
add_class(&progress_box, "progress");
let progress_label = Label::new(None);
add_class(&progress_label, "label");
let progress = Scale::builder()
.orientation(Orientation::Horizontal)
.draw_value(false)
.hexpand(true)
.build();
add_class(&progress, "slider");
progress_box.add(&progress);
progress_box.add(&progress_label);
container.add(&progress_box);
let drag_lock = Arc::new(AtomicBool::new(false));
{
let drag_lock = drag_lock.clone();
progress.connect_button_press_event(move |_, _| {
drag_lock.set(true);
Inhibit(false)
});
}
{
let drag_lock = drag_lock.clone();
progress.connect_button_release_event(move |scale, _| {
let value = scale.value();
try_send!(tx, PlayerCommand::Seek(Duration::from_secs_f64(value)));
drag_lock.set(false);
Inhibit(false)
});
}
container.show_all(); container.show_all();
{ {
@ -336,25 +402,26 @@ impl Module<Button> for MusicModule {
let image_size = self.cover_image_size; let image_size = self.cover_image_size;
let mut prev_cover = None; let mut prev_cover = None;
rx.attach(None, move |update| { rx.attach(None, move |event| {
if let Some(update) = update { match event {
// only update art when album changes ControllerEvent::Update(Some(update)) => {
let new_cover = update.song.cover_path; // only update art when album changes
if prev_cover != new_cover { let new_cover = update.song.cover_path;
prev_cover = new_cover.clone(); if prev_cover != new_cover {
let res = if let Some(image) = new_cover.and_then(|cover_path| { prev_cover = new_cover.clone();
ImageProvider::parse(&cover_path, &icon_theme, image_size) let res = if let Some(image) = new_cover.and_then(|cover_path| {
}) { ImageProvider::parse(&cover_path, &icon_theme, image_size)
image.load_into_image(album_image.clone()) }) {
} else { image.load_into_image(album_image.clone())
album_image.set_from_pixbuf(None); } else {
Ok(()) album_image.set_from_pixbuf(None);
}; Ok(())
};
if let Err(err) = res { if let Err(err) = res {
error!("{err:?}"); error!("{err:?}");
}
} }
}
title_label title_label
.label .label
@ -366,38 +433,64 @@ impl Module<Button> for MusicModule {
.label .label
.set_text(&update.song.artist.unwrap_or_default()); .set_text(&update.song.artist.unwrap_or_default());
match update.status.state { match update.status.state {
PlayerState::Stopped => { PlayerState::Stopped => {
btn_pause.hide(); btn_pause.hide();
btn_play.show(); btn_play.show();
btn_play.set_sensitive(false); btn_play.set_sensitive(false);
} }
PlayerState::Playing => { PlayerState::Playing => {
btn_play.set_sensitive(false); btn_play.set_sensitive(false);
btn_play.hide(); btn_play.hide();
btn_pause.set_sensitive(true); btn_pause.set_sensitive(true);
btn_pause.show(); btn_pause.show();
} }
PlayerState::Paused => { PlayerState::Paused => {
btn_pause.set_sensitive(false); btn_pause.set_sensitive(false);
btn_pause.hide(); btn_pause.hide();
btn_play.set_sensitive(true); btn_play.set_sensitive(true);
btn_play.show(); btn_play.show();
}
}
let enable_prev = update.status.playlist_position > 0;
let enable_next =
update.status.playlist_position < update.status.playlist_length;
btn_prev.set_sensitive(enable_prev);
btn_next.set_sensitive(enable_next);
if let Some(volume) = update.status.volume_percent {
volume_slider.set_value(volume as f64);
volume_box.show();
} else {
volume_box.hide();
} }
} }
ControllerEvent::UpdateProgress(progress_tick)
if !drag_lock.load(Ordering::Relaxed) =>
{
if let (Some(elapsed), Some(duration)) =
(progress_tick.elapsed, progress_tick.duration)
{
progress_label.set_label(&format!(
"{}/{}",
format_time(elapsed),
format_time(duration)
));
let enable_prev = update.status.playlist_position > 0; progress.set_value(elapsed.as_secs_f64());
progress.set_range(0.0, duration.as_secs_f64());
let enable_next = progress_box.show_all();
update.status.playlist_position < update.status.playlist_length; } else {
progress_box.hide();
btn_prev.set_sensitive(enable_prev); }
btn_next.set_sensitive(enable_next); }
_ => {}
volume_slider.set_value(update.status.volume_percent as f64); };
}
Continue(true) Continue(true)
}); });
@ -409,15 +502,10 @@ impl Module<Button> for MusicModule {
/// Replaces each of the formatting tokens in the formatting string /// Replaces each of the formatting tokens in the formatting string
/// with actual data pulled from the music player /// with actual data pulled from the music player
fn replace_tokens( fn replace_tokens(format_string: &str, tokens: &Vec<String>, song: &Track) -> String {
format_string: &str,
tokens: &Vec<String>,
song: &Track,
status: &Status,
) -> String {
let mut compiled_string = format_string.to_string(); let mut compiled_string = format_string.to_string();
for token in tokens { for token in tokens {
let value = get_token_value(song, status, token); let value = get_token_value(song, token);
compiled_string = compiled_string.replace(format!("{{{token}}}").as_str(), value.as_str()); compiled_string = compiled_string.replace(format!("{{{token}}}").as_str(), value.as_str());
} }
compiled_string compiled_string
@ -425,7 +513,7 @@ fn replace_tokens(
/// Converts a string format token value /// Converts a string format token value
/// into its respective value. /// into its respective value.
fn get_token_value(song: &Track, status: &Status, token: &str) -> String { fn get_token_value(song: &Track, token: &str) -> String {
match token { match token {
"title" => song.title.clone(), "title" => song.title.clone(),
"album" => song.album.clone(), "album" => song.album.clone(),
@ -434,8 +522,6 @@ fn get_token_value(song: &Track, status: &Status, token: &str) -> String {
"disc" => song.disc.map(|x| x.to_string()), "disc" => song.disc.map(|x| x.to_string()),
"genre" => song.genre.clone(), "genre" => song.genre.clone(),
"track" => song.track.map(|x| x.to_string()), "track" => song.track.map(|x| x.to_string()),
"duration" => status.duration.map(format_time),
"elapsed" => status.elapsed.map(format_time),
_ => Some(token.to_string()), _ => Some(token.to_string()),
} }
.unwrap_or_default() .unwrap_or_default()