diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-10 00:29:01 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-10 00:29:01 +0200 |
| commit | 69172d7abe17d56b68a97465344467d8c34482b6 (patch) | |
| tree | 2420fc37047441a9c81c7b7a221e84842a3f9e4e | |
| parent | Fix shuffle restore - track played tracks via shuffled_indices, not position (diff) | |
| download | rsplayer-69172d7abe17d56b68a97465344467d8c34482b6.tar.gz rsplayer-69172d7abe17d56b68a97465344467d8c34482b6.zip | |
Fix queue display returning nothing - removed daemon pager, return full output instead. Increase pre-buffer to 100 packets.
| -rw-r--r-- | src/audio.rs | 43 | ||||
| -rw-r--r-- | src/daemon.rs | 25 | ||||
| -rw-r--r-- | src/queue.rs | 23 | ||||
| -rw-r--r-- | tests/queue.rs | 171 |
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] | ||
| 464 | fn 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] | ||
| 515 | fn 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] | ||
| 574 | fn 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 | } | ||
