From 0f00616dca071750381234b5c59c8ec1e71b28ed Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 8 May 2026 01:12:31 +0200 Subject: Fix shuffle first song not being shuffled update_shuffle_indices now shuffles all indices when position is 0 (no tracks have been played yet), so the first track played is random instead of always being tracks[0]. Directory playback now uses q.current() to pick the first track, resolving through shuffled indices when shuffle is active. Also pins notify-rust version in Cargo.toml and removes PLAN.md (superseded by TODO.md). --- Cargo.toml | 2 +- PLAN.md | 28 ---------------------------- TODO.md | 2 +- src/daemon.rs | 5 ++--- src/queue.rs | 4 +++- tests/queue.rs | 6 +++--- 6 files changed, 10 insertions(+), 37 deletions(-) delete mode 100644 PLAN.md diff --git a/Cargo.toml b/Cargo.toml index 8ba5ee0..3482486 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ dirs = "6.0" libc = "0.2.186" ctrlc = "3.5.2" rand = "0.10.1" -notify-rust = "4" +notify-rust = "4.17.0" [dev-dependencies] tempfile = "3.17" diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index d04e528..0000000 --- a/PLAN.md +++ /dev/null @@ -1,28 +0,0 @@ -# rsplayer Project Plan - -## Phase 1: Setup & Core ✓ -1. Initialize Rust project, add dependencies: `clap` (CLI/autocompletion), `rusqlite` (SQLite), `symphonia`/`cpal` (audio), `directories` (XDG paths), `serde`/`toml` (config), `tokio` (daemon async), `reqwest` (scrobbling). ✓ -2. Config stored at `$XDG_CONFIG_HOME/rsplayer/config.toml`: `music_paths` (array of directories to scan), `scan_interval` (optional periodic rescan interval), scrobbling credentials, daemon socket path. ✓ -3. SQLite library schema: `artists`, `albums`, `tracks` tables. Implement `rsp scan` to scan all paths in config's `music_paths`, populate/update database. ✓ - -## Phase 2: Daemon ✓ -1. Unix socket daemon (path: `$XDG_RUNTIME_DIR/rsplayer/daemon.sock`) for IPC. ✓ -2. Audio engine: Symphonia decode + CPAL output, support play/pause/stop/volume/seek. ✓ -3. Queue: in-memory, `last` (append to end, default), `now` (play immediately, ending current song playback), `next` (insert after current). ✓ -4. Shuffle modes: `none` (linear playback), `all` (shuffle all tracks), `album` (shuffle within album, structure ready). ✓ - -## Phase 3: CLI & Completion ✓ -1. Clap-based CLI with all subcommands. ✓ -2. Shell completions for bash/zsh/fish/powershell/elvish (`rsp completions`). PARTIALLY -3. `rsp status`: show player state, current track, progress, volume, next 2-3 queue items. ✓ -4. `rsp queue`: no arguments display current queue; with arguments add items (default `last` behavior). BUGGED -5. `rsp settings` subcommands: `get`, `set`, `list`, `unset` config values. ✓ -6. `rsp scan`: `--force` full rescan, `--paths` override config paths. Uses symphonia for metadata. ✓ -7. `rsp play`: flexible insert options (`-i`/`--now`, `-n`/`--next`, `-l`/`--last`). ✓ - -## Phase 4: Polish -1. [ ] Queue state persistence - Save queue to disk so it survives daemon restarts. -2. [ ] Album shuffle mode - Implement proper `ShuffleMode::Album` (shuffle tracks within same album/folder). -3. [ ] Scrobbling - Last.fm and ListenBrainz support via configured credentials. -4. [ ] Logging and error handling improvements. -5. [ ] Systemd service unit for auto-start. diff --git a/TODO.md b/TODO.md index 95d775c..1048440 100644 --- a/TODO.md +++ b/TODO.md @@ -64,6 +64,6 @@ - [ ] **Scrobbling (Last.fm/ListenBrainz)** - Add scrobbling support via configured credentials. -- [ ] **Shuffle first song** - First song is not being shuffled due to currently played track not being shuffled. +- [x] **Shuffle first song** - `update_shuffle_indices` now shuffles all indices when position is 0; directory playback uses `q.current()` instead of `files.first()`. - [ ] **In album shuffle** - Shuffle songs within albums, don't mix albums together (only applies to shuffle: album). diff --git a/src/daemon.rs b/src/daemon.rs index cca9b11..00bf35f 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -453,11 +453,10 @@ fn process_command( q.add_track(file.clone()); } if !audio.is_playing() - && let Some(track) = files.first() + && let Some(track) = q.current() { - let track = Track::from_path(track.clone()); audio.play(track.path.clone()); - notification::notify_track(&track); + notification::notify_track(track); } format!("Queued {} tracks from: {}\n", files.len(), path_str) } diff --git a/src/queue.rs b/src/queue.rs index 53b104f..dca8e3d 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -171,7 +171,9 @@ impl Queue { if let ShuffleMode::All = self.shuffle { let mut rng = rand::rng(); let pos = self.position.min(len.saturating_sub(1)); - if pos + 1 < len { + if pos == 0 && len > 1 { + self.shuffled_indices.shuffle(&mut rng); + } else if pos + 1 < len { self.shuffled_indices[pos + 1..].shuffle(&mut rng); } } diff --git a/tests/queue.rs b/tests/queue.rs index 0ceaba0..d3e923e 100644 --- a/tests/queue.rs +++ b/tests/queue.rs @@ -269,8 +269,6 @@ fn test_shuffle_all_navigates_in_shuffled_order() { add_track(&mut q, &format!("/music/song{}.mp3", i)); } q.set_shuffle(ShuffleMode::All); - // Current should still return the first track - assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3")); // Navigate through all tracks, should visit all 10 (in shuffled order) let mut visited = std::collections::HashSet::new(); visited.insert(q.current().unwrap().path.clone()); @@ -291,7 +289,9 @@ fn test_shuffle_all_current_stays_at_position_zero() { } q.set_shuffle(ShuffleMode::All); assert_eq!(q.position(), 0); - assert_eq!(q.shuffled_indices()[0], 0); + let mut sorted = q.shuffled_indices().to_vec(); + sorted.sort(); + assert_eq!(sorted, vec![0, 1, 2, 3, 4]); } #[test] -- cgit v1.2.3