From b3b7f0f65295f2100eb3c9880e870349c43701a6 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 9 May 2026 15:14:33 +0200 Subject: Major rewrite: play command, queue display, and shuffle modes - Play command: no args queues to end (play only if queue empty + player playing), -i inserts after current and skips, -n inserts after current, removed -l flag - Queue display: detect terminal size, use pager only if queue exceeds terminal rows - Shuffle modes: always remember original queue, restore on 'shuffle none', album shuffle shuffles within each album's range separately --- src/cli.rs | 11 +++----- src/daemon.rs | 14 ++++++---- src/queue.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/cli.rs b/src/cli.rs index 0da3078..bd81983 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,15 +21,12 @@ pub enum Commands { Restart, /// Play a track, album, or artist Play { - /// Play immediately (add after current and skip to it) - #[arg(short = 'i', long = "now", group = "position")] + /// Add after current track and skip to it + #[arg(short = 'i', long = "now")] now: bool, /// Add to queue after current track (don't skip) - #[arg(short = 'n', long = "next", group = "position")] + #[arg(short = 'n', long = "next")] next: bool, - /// Add to end of queue (default) - #[arg(short = 'l', long = "last", group = "position")] - last: bool, #[arg(value_name = "QUERY")] query: Option, }, @@ -126,7 +123,7 @@ impl Cli { }; crate::daemon::send_command(format!("play {} {}", pos, q))?; } else { - crate::daemon::send_command("play".to_string())?; + crate::daemon::send_command("play last".to_string())?; } } Commands::Pause => { diff --git a/src/daemon.rs b/src/daemon.rs index e88669a..f4a72b7 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -399,9 +399,9 @@ fn process_command( format!("Queued next: {}\n", path_str) } _ => { - let was_playing = audio.is_playing(); + let was_empty = q.is_empty(); q.add_track(path.clone()); - if !was_playing { + if was_empty && audio.is_playing() { let track = Track::from_path(path.clone()); audio.play(path); notification::notify_track(&track); @@ -452,7 +452,8 @@ fn process_command( for file in &files { q.add_track(file.clone()); } - if !audio.is_playing() + if was_empty + && audio.is_playing() && let Some(track) = q.current() { audio.play(track.path.clone()); @@ -519,8 +520,11 @@ fn process_command( } } } - // Use pager if output is long (more than 24 lines) - if output.lines().count() > 24 { + // Use pager if queue is longer than terminal height + let terminal_rows = terminal_size::terminal_size() + .map(|(w, _)| w.0 as usize) + .unwrap_or(24); + if output.lines().count() > terminal_rows { use std::process::{Command, Stdio}; let pager = Command::new("less").arg("-R").stdin(Stdio::piped()).spawn(); if let Ok(mut child) = pager { diff --git a/src/queue.rs b/src/queue.rs index 9e8e1af..109b748 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -124,6 +124,7 @@ pub struct Queue { position: usize, shuffle: ShuffleMode, shuffled_indices: Vec, + original_tracks: Vec, } impl Default for Queue { @@ -139,6 +140,7 @@ impl Queue { position: 0, shuffle: ShuffleMode::None, shuffled_indices: Vec::new(), + original_tracks: Vec::new(), } } @@ -162,8 +164,38 @@ impl Queue { } pub fn set_shuffle(&mut self, mode: ShuffleMode) { - self.shuffle = mode; - self.update_shuffle_indices(); + match mode { + ShuffleMode::None => { + self.shuffle = ShuffleMode::None; + self.restore_from_original(); + } + ShuffleMode::All => { + self.original_tracks = self.tracks.clone(); + self.shuffle = ShuffleMode::All; + self.update_shuffle_indices(); + } + ShuffleMode::Album => { + self.original_tracks = self.tracks.clone(); + self.shuffle = ShuffleMode::Album; + self.update_shuffle_indices_album(); + } + } + } + + fn restore_from_original(&mut self) { + if self.original_tracks.is_empty() { + self.shuffled_indices.clear(); + return; + } + + let current_pos = self.position; + let played: Vec = self.tracks[..current_pos].to_vec(); + let unplayed_original: Vec = self.original_tracks[current_pos..].to_vec(); + + self.tracks = played; + self.tracks.extend(unplayed_original); + self.position = current_pos; + self.shuffled_indices.clear(); } fn update_shuffle_indices(&mut self) { @@ -180,6 +212,55 @@ impl Queue { } } + fn update_shuffle_indices_album(&mut self) { + let len = self.tracks.len(); + self.shuffled_indices = (0..len).collect(); + + let mut rng = rand::rng(); + let pos = self.position.min(len.saturating_sub(1)); + + if pos == 0 && len > 1 { + self.shuffle_album_range(0..len, &mut rng); + } else if pos + 1 < len { + self.shuffle_album_range(pos + 1..len, &mut rng); + } + } + + fn shuffle_album_range(&mut self, range: std::ops::Range, rng: &mut impl rand::Rng) { + if range.start >= range.end { + return; + } + + let mut album_ranges: Vec<(usize, usize)> = Vec::new(); + let mut current_album: Option<&str> = None; + let mut range_start: Option = None; + + for i in range.clone() { + let album = self.tracks.get(i).and_then(|t| t.album.as_deref()); + match (current_album, range_start) { + (None, None) => { + current_album = album; + range_start = Some(i); + } + (Some(_), Some(start)) if album != current_album => { + album_ranges.push((start, i)); + current_album = album; + range_start = Some(i); + } + _ => {} + } + } + if let Some(start) = range_start { + album_ranges.push((start, range.end)); + } + + for (start, end) in album_ranges { + if start + 1 < end { + self.shuffled_indices[start..end].shuffle(rng); + } + } + } + pub fn current(&self) -> Option<&Track> { if self.tracks.is_empty() { None -- cgit v1.2.3