diff --git a/README.md b/README.md index e9ba35be..73e0edb7 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,7 @@ List of supported commands: | `Shuffle` | toggle the shuffle mode | `C-s` | | `VolumeUp` | increase playback volume by 5% | `+` | | `VolumeDown` | decrease playback volume by 5% | `-` | +| `Mute` | toggle playback volume between 0% and previous level | `_` | | `SeekForward` | seek forward by 5s | `>` | | `SeekBackward` | seek backward by 5s | `<` | | `Quit` | quit the application | `C-c`, `q` | diff --git a/spotify_player/src/client/mod.rs b/spotify_player/src/client/mod.rs index 2c77b413..52832c97 100644 --- a/spotify_player/src/client/mod.rs +++ b/spotify_player/src/client/mod.rs @@ -166,8 +166,25 @@ impl Client { self.spotify.volume(volume, device_id).await?; playback.volume = Some(volume as u32); + // upon updating volume, also reset the player's mute state + state.player.write().mute_state = None; state.player.write().buffered_playback = Some(playback); } + PlayerRequest::ToggleMute => { + let mute_state = state.player.read().mute_state; + let new_mute_state = match mute_state { + None => { + self.spotify.volume(0, device_id).await?; + Some(playback.volume.unwrap_or_default()) + } + Some(volume) => { + self.spotify.volume(volume as u8, device_id).await?; + None + } + }; + + state.player.write().mute_state = new_mute_state; + } PlayerRequest::StartPlayback(p) => { self.start_playback(p, device_id).await?; // for some reasons, when starting a new playback, the integrated `spotify_player` @@ -1254,7 +1271,10 @@ impl Client { device_name: p.device.name.clone(), device_id: p.device.id.clone(), is_playing: p.is_playing, - volume: p.device.volume_percent, + volume: match player.mute_state { + Some(volume) => Some(volume), + None => p.device.volume_percent, + }, repeat_state: p.repeat_state, shuffle_state: p.shuffle_state, }); diff --git a/spotify_player/src/command.rs b/spotify_player/src/command.rs index a93418cd..07378975 100644 --- a/spotify_player/src/command.rs +++ b/spotify_player/src/command.rs @@ -14,6 +14,7 @@ pub enum Command { Shuffle, VolumeUp, VolumeDown, + Mute, SeekForward, SeekBackward, @@ -149,6 +150,7 @@ impl Command { Self::Shuffle => "toggle the shuffle mode", Self::VolumeUp => "increase playback volume by 5%", Self::VolumeDown => "decrease playback volume by 5%", + Self::Mute => "toggle playback volume between 0% and previous level", Self::SeekForward => "seek forward by 5s", Self::SeekBackward => "seek backward by 5s", Self::Quit => "quit the application", diff --git a/spotify_player/src/config/keymap.rs b/spotify_player/src/config/keymap.rs index 6e54371d..92cd076c 100644 --- a/spotify_player/src/config/keymap.rs +++ b/spotify_player/src/config/keymap.rs @@ -55,6 +55,10 @@ impl Default for KeymapConfig { key_sequence: "-".into(), command: Command::VolumeDown, }, + Keymap { + key_sequence: "_".into(), + command: Command::Mute, + }, Keymap { key_sequence: ">".into(), command: Command::SeekForward, diff --git a/spotify_player/src/event/mod.rs b/spotify_player/src/event/mod.rs index caea5e20..37c3681f 100644 --- a/spotify_player/src/event/mod.rs +++ b/spotify_player/src/event/mod.rs @@ -23,6 +23,7 @@ pub enum PlayerRequest { Repeat, Shuffle, Volume(u8), + ToggleMute, TransferPlayback(String, bool), StartPlayback(Playback), } @@ -236,6 +237,9 @@ fn handle_global_command( } } } + Command::Mute => { + client_pub.send(ClientRequest::Player(PlayerRequest::ToggleMute))?; + } Command::SeekForward => { if let Some(progress) = state.player.read().playback_progress() { client_pub.send(ClientRequest::Player(PlayerRequest::SeekTrack( diff --git a/spotify_player/src/state/player.rs b/spotify_player/src/state/player.rs index 66f3ea09..0a180f22 100644 --- a/spotify_player/src/state/player.rs +++ b/spotify_player/src/state/player.rs @@ -12,6 +12,8 @@ pub struct PlayerState { pub buffered_playback: Option, pub queue: Option, + + pub mute_state: Option, } impl PlayerState { diff --git a/spotify_player/src/ui/playback.rs b/spotify_player/src/ui/playback.rs index b80f85d2..67173267 100644 --- a/spotify_player/src/ui/playback.rs +++ b/spotify_player/src/ui/playback.rs @@ -91,7 +91,15 @@ pub fn render_playback_window( }; if let Some(ref playback) = player.buffered_playback { - render_playback_text(frame, state, ui, metadata_rect, track, playback); + render_playback_text( + frame, + state, + ui, + metadata_rect, + track, + playback, + player.mute_state, + ); } let progress = std::cmp::min( @@ -133,6 +141,7 @@ fn render_playback_text( rect: Rect, track: &rspotify_model::FullTrack, playback: &SimplifiedPlayback, + mute_state: Option, ) { // Construct a "styled" text (`playback_text`) from playback's data // based on a user-configurable format string (app_config.playback_format) @@ -144,6 +153,12 @@ fn render_playback_text( // this regex is to handle a format argument or a newline let re = regex::Regex::new(r"\{.*?\}|\n").unwrap(); + // build the volume string (vol% when unmuted, old_vol% (muted) if currently muted) + let volume = match mute_state { + Some(volume) => format!("{volume}% (muted)"), + None => format!("{}%", playback.volume.unwrap_or_default()), + }; + let mut ptr = 0; for m in re.find_iter(format_str) { let s = m.start(); @@ -180,10 +195,10 @@ fn render_playback_text( "{album}" => (track.album.name.to_owned(), ui.theme.playback_album()), "{metadata}" => ( format!( - "repeat: {} | shuffle: {} | volume: {}% | device: {}", + "repeat: {} | shuffle: {} | volume: {} | device: {}", <&'static str>::from(playback.repeat_state), playback.shuffle_state, - playback.volume.unwrap_or_default(), + volume, playback.device_name, ), ui.theme.playback_metadata(),