From 6a5aef96b08c4e842033a4c34c6261d2bbcb7651 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 5 May 2026 22:59:43 +0200 Subject: Add flexible play command with position flags - Add -i/--now, -n/--next, -l/--last flags to play command: - 'play MUSIC' (default) adds to end of queue and plays - 'play -i MUSIC' inserts after current and skips to it - 'play -n MUSIC' inserts after current, doesn't skip - 'play -l MUSIC' adds to end of queue (default behavior) - Add insert_after_current and track_at methods to Queue in daemon - Update play command handler to handle position arguments - Fix queue display to correctly show playing track (compare track_idx instead of display_idx with current_position) --- TODO.md | 2 ++ src/cli.rs | 22 +++++++++++-- src/daemon.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/TODO.md b/TODO.md index 347a529..efcc440 100644 --- a/TODO.md +++ b/TODO.md @@ -34,6 +34,8 @@ - [x] **Metadata extraction and display** - Extract ID3/metadata tags from audio files and display as "INDEX. ARTIST - TITLE", falls back to filename. Uses symphonia for metadata extraction. +- [x] **Flexible play command** - Added `-i/--now`, `-n/--next`, `-l/--last` flags to play command: `play MUSIC` (default - add to end and play), `play -i MUSIC` (insert after current and skip to it), `play -n MUSIC` (insert after current, don't skip), `play -l MUSIC` (add to end of queue). Also fixed queue display to correctly show playing track. + ## Potential Improvements - [ ] **Add error handling for audio playback** - Consider more robust error handling in the audio thread. diff --git a/src/cli.rs b/src/cli.rs index 13ef67c..1d83262 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,6 +21,15 @@ 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")] + now: bool, + /// Add to queue after current track (don't skip) + #[arg(short = 'n', long = "next", group = "position")] + next: bool, + /// Add to end of queue (default) + #[arg(short = 'l', long = "last", group = "position")] + last: bool, #[arg(value_name = "QUERY")] query: Option, }, @@ -102,9 +111,18 @@ impl Cli { Commands::Restart => { crate::daemon::restart_daemon()?; } - Commands::Play { query } => { + Commands::Play { + now, next, query, .. + } => { if let Some(q) = query { - crate::daemon::send_command(format!("play {}", q))?; + let pos = if *now { + "now" + } else if *next { + "next" + } else { + "last" + }; + crate::daemon::send_command(format!("play {} {}", pos, q))?; } else { crate::daemon::send_command("play".to_string())?; } diff --git a/src/daemon.rs b/src/daemon.rs index 4122c37..b0da698 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -149,6 +149,16 @@ impl Queue { self.update_shuffle_indices(); } + fn insert_after_current(&mut self, path: PathBuf) { + let insert_idx = self.position + 1; + self.tracks.insert(insert_idx, Track::from_path(path)); + self.update_shuffle_indices(); + } + + fn track_at(&self, idx: usize) -> Option<&Track> { + self.tracks.get(idx) + } + fn set_shuffle(&mut self, mode: ShuffleMode) { self.shuffle = mode.clone(); self.update_shuffle_indices(); @@ -545,30 +555,89 @@ fn process_command( } } _ if cmd.starts_with("play ") => { - let path_str = &cmd[5..].trim(); - let path = std::path::PathBuf::from(path_str); + let rest = &cmd[5..].trim(); + let (position, path_str): (&str, &str) = if let Some((pos, p)) = rest.split_once(' ') { + if pos == "now" || pos == "next" || pos == "last" { + (pos, p) + } else { + ("last", rest) + } + } else { + ("last", rest) + }; + let path_str = path_str.to_string(); + + if path_str.is_empty() { + return "Usage: play [now|next|last] \n".to_string(); + } + + let path = std::path::PathBuf::from(path_str.clone()); if path.is_file() { - // Add to queue and play if let Ok(mut q) = queue.lock() { - q.add_track(path.clone()); + match position { + "now" => { + q.insert_after_current(path.clone()); + q.position += 1; + if let Some(track) = q.track_at(q.position) { + audio.play(track.path.clone()); + } + format!("Playing now: {}\n", path_str) + } + "next" => { + q.insert_after_current(path.clone()); + format!("Queued next: {}\n", path_str) + } + _ => { + q.add_track(path.clone()); + audio.play(path); + format!("Playing: {}\n", path_str) + } + } + } else { + "Error accessing queue\n".to_string() } - audio.play(path); - format!("Playing: {}\n", path_str) } else if path.is_dir() { - // Find all audio files in directory match find_audio_in_dir(&path) { Some(files) if !files.is_empty() => { if let Ok(mut q) = queue.lock() { - for file in &files { - q.add_track(file.clone()); - } - // If shuffle is on, shuffled_indices is already set - // Play the first track from the shuffled list - if let Some(track) = q.current() { - audio.play(track.path.clone()); + match position { + "now" => { + for file in &files { + q.insert_after_current(file.clone()); + } + q.position += 1; + if let Some(track) = q.track_at(q.position) { + audio.play(track.path.clone()); + } + format!( + "Playing now: {} tracks from {}\n", + files.len(), + path_str + ) + } + "next" => { + for file in &files { + q.insert_after_current(file.clone()); + } + format!( + "Queued next: {} tracks from {}\n", + files.len(), + path_str + ) + } + _ => { + for file in &files { + q.add_track(file.clone()); + } + if let Some(track) = q.current() { + audio.play(track.path.clone()); + } + format!("Queued {} tracks from: {}\n", files.len(), path_str) + } } + } else { + "Error accessing queue\n".to_string() } - format!("Queued {} tracks from: {}\n", files.len(), path_str) } _ => format!("Error: No audio files found in '{}'\n", path_str), } @@ -621,7 +690,7 @@ fn process_command( for (display_idx, &track_idx) in indices.iter().enumerate() { if let Some(track) = q.tracks.get(track_idx) { let track_str = track.display_string(display_idx); - if display_idx == current_position { + if track_idx == current_position { output.push_str(&format!("> {} (playing)\n", track_str)); } else { output.push_str(&format!(" {}\n", track_str)); -- cgit v1.2.3