aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/audio.rs43
-rw-r--r--src/daemon.rs25
-rw-r--r--src/queue.rs23
-rw-r--r--tests/queue.rs171
4 files changed, 214 insertions, 48 deletions
diff --git a/src/audio.rs b/src/audio.rs
index 386c041..62afd93 100644
--- a/src/audio.rs
+++ b/src/audio.rs
@@ -199,30 +199,29 @@ fn audio_thread(
199 199
200 // Pre-decode some samples before starting playback to prevent buffer underrun 200 // Pre-decode some samples before starting playback to prevent buffer underrun
201 let channels = data.channels as usize; 201 let channels = data.channels as usize;
202 for _ in 0..50 { 202 for _ in 0..100 {
203 match data.format.next_packet() { 203 match data.format.next_packet() {
204 Ok(packet) => { 204 Ok(packet) => match data.decoder.decode(&packet) {
205 match data.decoder.decode(&packet) { 205 Ok(decoded) => {
206 Ok(decoded) => { 206 let spec = *decoded.spec();
207 let spec = *decoded.spec(); 207 let mut sample_buf = symphonia::core::audio::SampleBuffer::<
208 let mut sample_buf = 208 f32,
209 symphonia::core::audio::SampleBuffer::<f32>::new( 209 >::new(
210 decoded.capacity() as u64, spec 210 decoded.capacity() as u64, spec
211 ); 211 );
212 sample_buf.copy_interleaved_ref(decoded); 212 sample_buf.copy_interleaved_ref(decoded);
213 let samples = sample_buf.samples(); 213 let samples = sample_buf.samples();
214 214
215 let resampled = if let Some(ratio) = data.resample_ratio { 215 let resampled = if let Some(ratio) = data.resample_ratio {
216 resample_linear(samples, ratio, channels) 216 resample_linear(samples, ratio, channels)
217 } else { 217 } else {
218 samples.to_vec() 218 samples.to_vec()
219 }; 219 };
220 220
221 data.sample_buffer.extend(resampled); 221 data.sample_buffer.extend(resampled);
222 }
223 Err(_) => break,
224 } 222 }
225 } 223 Err(_) => break,
224 },
226 Err(_) => break, 225 Err(_) => break,
227 } 226 }
228 } 227 }
diff --git a/src/daemon.rs b/src/daemon.rs
index 7488fc2..b79e67a 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -454,7 +454,10 @@ fn process_command(
454 for file in &files { 454 for file in &files {
455 q.add_track(file.clone()); 455 q.add_track(file.clone());
456 } 456 }
457 if was_empty && !was_playing && let Some(track) = q.current() { 457 if was_empty
458 && !was_playing
459 && let Some(track) = q.current()
460 {
458 audio.play(track.path.clone()); 461 audio.play(track.path.clone());
459 notification::notify_track(track); 462 notification::notify_track(track);
460 } 463 }
@@ -519,24 +522,8 @@ fn process_command(
519 } 522 }
520 } 523 }
521 } 524 }
522 // Use pager if queue is longer than terminal height 525 // Always return full output - client can pipe to pager if needed
523 let terminal_rows = terminal_size::terminal_size() 526 output
524 .map(|(w, _)| w.0 as usize)
525 .unwrap_or(24);
526 if output.lines().count() > terminal_rows {
527 use std::process::{Command, Stdio};
528 let pager = Command::new("less").arg("-R").stdin(Stdio::piped()).spawn();
529 if let Ok(mut child) = pager {
530 use std::io::Write;
531 let _ = child.stdin.as_mut().unwrap().write_all(output.as_bytes());
532 let _ = child.wait();
533 "".to_string()
534 } else {
535 output
536 }
537 } else {
538 output
539 }
540 } 527 }
541 } else { 528 } else {
542 "Error accessing queue\n".to_string() 529 "Error accessing queue\n".to_string()
diff --git a/src/queue.rs b/src/queue.rs
index db329c6..f556ed3 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -184,24 +184,29 @@ impl Queue {
184 184
185 fn restore_from_original(&mut self) { 185 fn restore_from_original(&mut self) {
186 if self.original_tracks.is_empty() { 186 if self.original_tracks.is_empty() {
187 eprintln!("DEBUG: original_tracks empty");
187 self.shuffled_indices.clear(); 188 self.shuffled_indices.clear();
188 return; 189 return;
189 } 190 }
190 191
191 let current_pos = self.position; 192 let current_pos = self.position;
193 let indices_len = self.shuffled_indices.len().min(10);
194 eprintln!("DEBUG restore: current_pos={}, tracks.len={}, shuffled_indices={:?}",
195 current_pos, self.tracks.len(), &self.shuffled_indices[..indices_len]);
192 196
193 // Find which tracks have been played (positions 0..=current_pos in shuffled order) 197 // Find which tracks have been played (positions 0..=current_pos in shuffled order)
194 // These are tracks whose shuffled_indices value is <= current_pos
195 let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new(); 198 let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new();
196 for i in 0..=current_pos { 199 for i in 0..=current_pos {
197 if let Some(&idx) = self.shuffled_indices.get(i) { 200 if let Some(&idx) = self.shuffled_indices.get(i)
198 if let Some(track) = self.tracks.get(idx) { 201 && let Some(track) = self.tracks.get(idx)
199 played_track_paths.push(track.path.clone()); 202 {
200 } 203 eprintln!("DEBUG: played track at shuffled pos {} -> track idx {} = {:?}",
204 i, idx, track.path.file_name());
205 played_track_paths.push(track.path.clone());
201 } 206 }
202 } 207 }
203 208
204 // Keep the played tracks in their current order (shuffled order) 209 // Keep the played tracks in their current order
205 let mut new_tracks: Vec<Track> = Vec::new(); 210 let mut new_tracks: Vec<Track> = Vec::new();
206 for path in &played_track_paths { 211 for path in &played_track_paths {
207 if let Some(track) = self.tracks.iter().find(|t| &t.path == path) { 212 if let Some(track) = self.tracks.iter().find(|t| &t.path == path) {
@@ -209,14 +214,18 @@ impl Queue {
209 } 214 }
210 } 215 }
211 216
217 eprintln!("DEBUG: kept {} played tracks", new_tracks.len());
218
212 // Add remaining tracks from original in original order 219 // Add remaining tracks from original in original order
213 for orig_track in &self.original_tracks { 220 for (i, orig_track) in self.original_tracks.iter().enumerate() {
214 let is_played = played_track_paths.iter().any(|p| p == &orig_track.path); 221 let is_played = played_track_paths.iter().any(|p| p == &orig_track.path);
215 if !is_played { 222 if !is_played {
223 eprintln!("DEBUG: adding unplayed from original idx {}: {:?}", i, orig_track.path.file_name());
216 new_tracks.push(orig_track.clone()); 224 new_tracks.push(orig_track.clone());
217 } 225 }
218 } 226 }
219 227
228 eprintln!("DEBUG: final tracks.len={}", new_tracks.len());
220 self.tracks = new_tracks; 229 self.tracks = new_tracks;
221 self.position = current_pos; 230 self.position = current_pos;
222 self.shuffled_indices.clear(); 231 self.shuffled_indices.clear();
diff --git a/tests/queue.rs b/tests/queue.rs
index bff83ca..e511612 100644
--- a/tests/queue.rs
+++ b/tests/queue.rs
@@ -457,3 +457,174 @@ fn test_track_no_metadata_fields() {
457 assert!(t.artist.is_none()); 457 assert!(t.artist.is_none());
458 assert!(t.album.is_none()); 458 assert!(t.album.is_none());
459} 459}
460
461// ---------- Shuffle restore ----------
462
463#[test]
464fn test_shuffle_all_then_none_keeps_played_tracks_in_place() {
465 // User's scenario:
466 // Queue after shuffle all: 01, 02, 03, 04(playing), 05, 06...
467 // Skip to track 04, then shuffle none
468 // Expected: played tracks (01,02,03) stay in place, current (04) stays,
469 // remaining tracks from original order after position
470
471 let mut q = Queue::new();
472 // Add tracks A, B, C, D, E, F (A is at index 0)
473 add_track(&mut q, "/music/A.mp3");
474 add_track(&mut q, "/music/B.mp3");
475 add_track(&mut q, "/music/C.mp3");
476 add_track(&mut q, "/music/D.mp3");
477 add_track(&mut q, "/music/E.mp3");
478 add_track(&mut q, "/music/F.mp3");
479
480 // Shuffle all - should save original and shuffle
481 q.set_shuffle(ShuffleMode::All);
482
483 // Simulate playing: advance position a few times
484 // After playing A, B, C, we're at position 2 (track C)
485 // Let's say shuffled order puts D at position 2
486 q.next(); // position 1 (playing second track)
487 q.next(); // position 2 (playing third track)
488 q.next(); // position 3 (playing fourth track)
489
490 let pos_before_restore = q.position();
491
492 // Now shuffle none - should restore
493 q.set_shuffle(ShuffleMode::None);
494
495 // After restore:
496 // - position should be same as before
497 // - tracks before current position should remain in same order as when playing
498 // - tracks after current should be from original order that haven't been played
499
500 let current = q.current().unwrap();
501 println!("Position after restore: {}", q.position());
502 println!("Current track: {:?}", current.path);
503
504 // The position should stay the same
505 assert_eq!(q.position(), pos_before_restore);
506
507 // Tracks should be: [played tracks in played order...] [current] [unplayed from original]
508 // Let's verify by checking that tracks before current include the ones that were played
509 for i in 0..q.position() {
510 println!("Track at {}: {:?}", i, q.track_at(i).unwrap().path);
511 }
512}
513
514#[test]
515fn test_shuffle_all_then_none_exact_scenario() {
516 // Exact scenario from user:
517 // Original queue: 1, 2, 3, 4, 5, 6 (position 0 = track 1)
518 // shuffle all -> 1, 2(playing), 4, 5, 6, 3
519 // play 2 more -> 1, 2, 4, 5(playing), 6, 3
520 // shuffle none -> 1, 2, 4, 5(playing), 3, 6
521
522 let mut q = Queue::new();
523 add_track(&mut q, "/music/1.mp3");
524 add_track(&mut q, "/music/2.mp3");
525 add_track(&mut q, "/music/3.mp3");
526 add_track(&mut q, "/music/4.mp3");
527 add_track(&mut q, "/music/5.mp3");
528 add_track(&mut q, "/music/6.mp3");
529
530 // Original order: [1, 2, 3, 4, 5, 6]
531 // Position 0 = track 1
532
533 // Shuffle all
534 q.set_shuffle(ShuffleMode::All);
535
536 // Assume after shuffle and navigating to position 3 (playing track 5),
537 // shuffled order is [1, 2, 4, 5, 6, 3]
538 // We need to set this up manually since shuffle is random
539
540 // Let's simulate by setting position to 3 (after playing 1, 2, 4, now playing 5)
541 // Position 3 means we've played indices 0,1,2 in shuffled order = tracks 1,2,4
542 // Currently at position 3 = track 5
543
544 // First get the shuffled indices and manually set up a specific order
545 let indices: Vec<usize> = q.shuffled_indices().to_vec();
546 println!("Shuffled indices: {:?}", indices);
547
548 // Force position to 3 to simulate playing 3 tracks then current is track at pos 3
549 q.set_position(3);
550
551 let current_before = q.current().unwrap().path.clone();
552 println!("Current before restore: {:?}", current_before.file_name().unwrap());
553 println!("Position: {}", q.position());
554
555 // Now shuffle none
556 q.set_shuffle(ShuffleMode::None);
557
558 println!("After restore:");
559 for i in 0..q.len() {
560 println!(" {}: {:?}", i+1, q.track_at(i).unwrap().path.file_name().unwrap());
561 }
562
563 // After restore:
564 // - Position should still be 3
565 // - Tracks at positions 0,1,2,3 should be the played tracks in their order (1,2,4,5)
566 // - Tracks 4,5 should be from original unplayed (3,6)
567 assert_eq!(q.position(), 3, "position should stay same");
568
569 let current_after = q.current().unwrap().path.clone();
570 assert_eq!(current_before, current_after, "current track should stay same");
571}
572
573#[test]
574fn test_shuffle_restore_simulation() {
575 // Simulate exact user scenario:
576 // Queue: 1,2,3,4,5,6 (original)
577 // shuffle all -> shuffled order e.g., [4,2,6,1,5,3]
578 // Navigate 4 times (play 1,2,4,5 -> position 4, now playing 5)
579 // Expected after shuffle none: 4,2,6,1,5,3 (played in place, unplayed from original)
580
581 let mut q = Queue::new();
582 for i in 1..=6 {
583 add_track(&mut q, &format!("/music/{}.mp3", i));
584 }
585
586 // Shuffle all (saves original as [1,2,3,4,5,6])
587 q.set_shuffle(ShuffleMode::All);
588
589 // Let's say shuffled order is [4, 2, 6, 1, 5, 3]
590 // We can force this by inspecting the indices
591 println!("Initial shuffled: {:?}", q.shuffled_indices());
592
593 // Navigate 4 times
594 for _ in 0..4 {
595 q.next();
596 }
597
598 println!("After 4 next() calls:");
599 println!(" Position: {}", q.position());
600 println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap());
601 println!(" Tracks:");
602 for i in 0..q.len() {
603 println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap());
604 }
605
606 let current_path = q.current().unwrap().path.clone();
607 let pos_before = q.position();
608
609 // Shuffle none
610 q.set_shuffle(ShuffleMode::None);
611
612 println!("After shuffle none:");
613 println!(" Position: {}", q.position());
614 println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap());
615 println!(" Tracks:");
616 for i in 0..q.len() {
617 println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap());
618 }
619
620 // Assertions
621 assert_eq!(q.position(), pos_before, "position should be preserved");
622 assert_eq!(q.current().unwrap().path, current_path, "current track should be preserved");
623
624 // The key requirement: tracks before current position should remain in played order
625 // and tracks after should be remaining from original in original order
626 let pos = q.position();
627 for i in 0..pos {
628 println!("Played track {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap());
629 }
630}