diff options
Diffstat (limited to '')
| -rw-r--r-- | TODO.md | 2 | ||||
| -rw-r--r-- | src/cli.rs | 22 | ||||
| -rw-r--r-- | src/daemon.rs | 101 |
3 files changed, 107 insertions, 18 deletions
| @@ -34,6 +34,8 @@ | |||
| 34 | 34 | ||
| 35 | - [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. | 35 | - [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. |
| 36 | 36 | ||
| 37 | - [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. | ||
| 38 | |||
| 37 | ## Potential Improvements | 39 | ## Potential Improvements |
| 38 | 40 | ||
| 39 | - [ ] **Add error handling for audio playback** - Consider more robust error handling in the audio thread. | 41 | - [ ] **Add error handling for audio playback** - Consider more robust error handling in the audio thread. |
| @@ -21,6 +21,15 @@ pub enum Commands { | |||
| 21 | Restart, | 21 | Restart, |
| 22 | /// Play a track, album, or artist | 22 | /// Play a track, album, or artist |
| 23 | Play { | 23 | Play { |
| 24 | /// Play immediately (add after current and skip to it) | ||
| 25 | #[arg(short = 'i', long = "now", group = "position")] | ||
| 26 | now: bool, | ||
| 27 | /// Add to queue after current track (don't skip) | ||
| 28 | #[arg(short = 'n', long = "next", group = "position")] | ||
| 29 | next: bool, | ||
| 30 | /// Add to end of queue (default) | ||
| 31 | #[arg(short = 'l', long = "last", group = "position")] | ||
| 32 | last: bool, | ||
| 24 | #[arg(value_name = "QUERY")] | 33 | #[arg(value_name = "QUERY")] |
| 25 | query: Option<String>, | 34 | query: Option<String>, |
| 26 | }, | 35 | }, |
| @@ -102,9 +111,18 @@ impl Cli { | |||
| 102 | Commands::Restart => { | 111 | Commands::Restart => { |
| 103 | crate::daemon::restart_daemon()?; | 112 | crate::daemon::restart_daemon()?; |
| 104 | } | 113 | } |
| 105 | Commands::Play { query } => { | 114 | Commands::Play { |
| 115 | now, next, query, .. | ||
| 116 | } => { | ||
| 106 | if let Some(q) = query { | 117 | if let Some(q) = query { |
| 107 | crate::daemon::send_command(format!("play {}", q))?; | 118 | let pos = if *now { |
| 119 | "now" | ||
| 120 | } else if *next { | ||
| 121 | "next" | ||
| 122 | } else { | ||
| 123 | "last" | ||
| 124 | }; | ||
| 125 | crate::daemon::send_command(format!("play {} {}", pos, q))?; | ||
| 108 | } else { | 126 | } else { |
| 109 | crate::daemon::send_command("play".to_string())?; | 127 | crate::daemon::send_command("play".to_string())?; |
| 110 | } | 128 | } |
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 { | |||
| 149 | self.update_shuffle_indices(); | 149 | self.update_shuffle_indices(); |
| 150 | } | 150 | } |
| 151 | 151 | ||
| 152 | fn insert_after_current(&mut self, path: PathBuf) { | ||
| 153 | let insert_idx = self.position + 1; | ||
| 154 | self.tracks.insert(insert_idx, Track::from_path(path)); | ||
| 155 | self.update_shuffle_indices(); | ||
| 156 | } | ||
| 157 | |||
| 158 | fn track_at(&self, idx: usize) -> Option<&Track> { | ||
| 159 | self.tracks.get(idx) | ||
| 160 | } | ||
| 161 | |||
| 152 | fn set_shuffle(&mut self, mode: ShuffleMode) { | 162 | fn set_shuffle(&mut self, mode: ShuffleMode) { |
| 153 | self.shuffle = mode.clone(); | 163 | self.shuffle = mode.clone(); |
| 154 | self.update_shuffle_indices(); | 164 | self.update_shuffle_indices(); |
| @@ -545,30 +555,89 @@ fn process_command( | |||
| 545 | } | 555 | } |
| 546 | } | 556 | } |
| 547 | _ if cmd.starts_with("play ") => { | 557 | _ if cmd.starts_with("play ") => { |
| 548 | let path_str = &cmd[5..].trim(); | 558 | let rest = &cmd[5..].trim(); |
| 549 | let path = std::path::PathBuf::from(path_str); | 559 | let (position, path_str): (&str, &str) = if let Some((pos, p)) = rest.split_once(' ') { |
| 560 | if pos == "now" || pos == "next" || pos == "last" { | ||
| 561 | (pos, p) | ||
| 562 | } else { | ||
| 563 | ("last", rest) | ||
| 564 | } | ||
| 565 | } else { | ||
| 566 | ("last", rest) | ||
| 567 | }; | ||
| 568 | let path_str = path_str.to_string(); | ||
| 569 | |||
| 570 | if path_str.is_empty() { | ||
| 571 | return "Usage: play [now|next|last] <path>\n".to_string(); | ||
| 572 | } | ||
| 573 | |||
| 574 | let path = std::path::PathBuf::from(path_str.clone()); | ||
| 550 | if path.is_file() { | 575 | if path.is_file() { |
| 551 | // Add to queue and play | ||
| 552 | if let Ok(mut q) = queue.lock() { | 576 | if let Ok(mut q) = queue.lock() { |
| 553 | q.add_track(path.clone()); | 577 | match position { |
| 578 | "now" => { | ||
| 579 | q.insert_after_current(path.clone()); | ||
| 580 | q.position += 1; | ||
| 581 | if let Some(track) = q.track_at(q.position) { | ||
| 582 | audio.play(track.path.clone()); | ||
| 583 | } | ||
| 584 | format!("Playing now: {}\n", path_str) | ||
| 585 | } | ||
| 586 | "next" => { | ||
| 587 | q.insert_after_current(path.clone()); | ||
| 588 | format!("Queued next: {}\n", path_str) | ||
| 589 | } | ||
| 590 | _ => { | ||
| 591 | q.add_track(path.clone()); | ||
| 592 | audio.play(path); | ||
| 593 | format!("Playing: {}\n", path_str) | ||
| 594 | } | ||
| 595 | } | ||
| 596 | } else { | ||
| 597 | "Error accessing queue\n".to_string() | ||
| 554 | } | 598 | } |
| 555 | audio.play(path); | ||
| 556 | format!("Playing: {}\n", path_str) | ||
| 557 | } else if path.is_dir() { | 599 | } else if path.is_dir() { |
| 558 | // Find all audio files in directory | ||
| 559 | match find_audio_in_dir(&path) { | 600 | match find_audio_in_dir(&path) { |
| 560 | Some(files) if !files.is_empty() => { | 601 | Some(files) if !files.is_empty() => { |
| 561 | if let Ok(mut q) = queue.lock() { | 602 | if let Ok(mut q) = queue.lock() { |
| 562 | for file in &files { | 603 | match position { |
| 563 | q.add_track(file.clone()); | 604 | "now" => { |
| 564 | } | 605 | for file in &files { |
| 565 | // If shuffle is on, shuffled_indices is already set | 606 | q.insert_after_current(file.clone()); |
| 566 | // Play the first track from the shuffled list | 607 | } |
| 567 | if let Some(track) = q.current() { | 608 | q.position += 1; |
| 568 | audio.play(track.path.clone()); | 609 | if let Some(track) = q.track_at(q.position) { |
| 610 | audio.play(track.path.clone()); | ||
| 611 | } | ||
| 612 | format!( | ||
| 613 | "Playing now: {} tracks from {}\n", | ||
| 614 | files.len(), | ||
| 615 | path_str | ||
| 616 | ) | ||
| 617 | } | ||
| 618 | "next" => { | ||
| 619 | for file in &files { | ||
| 620 | q.insert_after_current(file.clone()); | ||
| 621 | } | ||
| 622 | format!( | ||
| 623 | "Queued next: {} tracks from {}\n", | ||
| 624 | files.len(), | ||
| 625 | path_str | ||
| 626 | ) | ||
| 627 | } | ||
| 628 | _ => { | ||
| 629 | for file in &files { | ||
| 630 | q.add_track(file.clone()); | ||
| 631 | } | ||
| 632 | if let Some(track) = q.current() { | ||
| 633 | audio.play(track.path.clone()); | ||
| 634 | } | ||
| 635 | format!("Queued {} tracks from: {}\n", files.len(), path_str) | ||
| 636 | } | ||
| 569 | } | 637 | } |
| 638 | } else { | ||
| 639 | "Error accessing queue\n".to_string() | ||
| 570 | } | 640 | } |
| 571 | format!("Queued {} tracks from: {}\n", files.len(), path_str) | ||
| 572 | } | 641 | } |
| 573 | _ => format!("Error: No audio files found in '{}'\n", path_str), | 642 | _ => format!("Error: No audio files found in '{}'\n", path_str), |
| 574 | } | 643 | } |
| @@ -621,7 +690,7 @@ fn process_command( | |||
| 621 | for (display_idx, &track_idx) in indices.iter().enumerate() { | 690 | for (display_idx, &track_idx) in indices.iter().enumerate() { |
| 622 | if let Some(track) = q.tracks.get(track_idx) { | 691 | if let Some(track) = q.tracks.get(track_idx) { |
| 623 | let track_str = track.display_string(display_idx); | 692 | let track_str = track.display_string(display_idx); |
| 624 | if display_idx == current_position { | 693 | if track_idx == current_position { |
| 625 | output.push_str(&format!("> {} (playing)\n", track_str)); | 694 | output.push_str(&format!("> {} (playing)\n", track_str)); |
| 626 | } else { | 695 | } else { |
| 627 | output.push_str(&format!(" {}\n", track_str)); | 696 | output.push_str(&format!(" {}\n", track_str)); |
