diff options
| author | salmon <salmonik@tuta.io> | 2026-08-12 02:56:18 +0200 |
|---|---|---|
| committer | salmon <salmonik@tuta.io> | 2026-08-12 02:56:18 +0200 |
| commit | 8ec85d39c55769c564298782a51c6ec25dd6ba9f (patch) | |
| tree | a55bda29f2f12afbb64eb5ef05a920fa19ca4d3e /src/queue.rs | |
| parent | clean up readme and make install.sh activate rc.d service (diff) | |
| download | rsplayer-8ec85d39c55769c564298782a51c6ec25dd6ba9f.tar.gz rsplayer-8ec85d39c55769c564298782a51c6ec25dd6ba9f.zip | |
fix a bug where adding track using `now` when shuffle is on does not work as expected
Diffstat (limited to '')
| -rw-r--r-- | src/queue.rs | 87 |
1 files changed, 65 insertions, 22 deletions
diff --git a/src/queue.rs b/src/queue.rs index c3f8dbe..da79441 100644 --- a/src/queue.rs +++ b/src/queue.rs | |||
| @@ -5,7 +5,9 @@ | |||
| 5 | * https://codeberg.org/salmonik/RSPlayer | 5 | * https://codeberg.org/salmonik/RSPlayer |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | use rand::rngs::StdRng; | ||
| 8 | use rand::seq::SliceRandom; | 9 | use rand::seq::SliceRandom; |
| 10 | use rand::SeedableRng; | ||
| 9 | use std::path::{Path, PathBuf}; | 11 | use std::path::{Path, PathBuf}; |
| 10 | 12 | ||
| 11 | #[derive(Debug, Clone, PartialEq)] | 13 | #[derive(Debug, Clone, PartialEq)] |
| @@ -202,6 +204,7 @@ pub struct Queue { | |||
| 202 | position: usize, | 204 | position: usize, |
| 203 | shuffle: ShuffleMode, | 205 | shuffle: ShuffleMode, |
| 204 | shuffled_indices: Vec<usize>, | 206 | shuffled_indices: Vec<usize>, |
| 207 | rng: StdRng, | ||
| 205 | } | 208 | } |
| 206 | 209 | ||
| 207 | impl Default for Queue { | 210 | impl Default for Queue { |
| @@ -217,6 +220,20 @@ impl Queue { | |||
| 217 | position: 0, | 220 | position: 0, |
| 218 | shuffle: ShuffleMode::None, | 221 | shuffle: ShuffleMode::None, |
| 219 | shuffled_indices: Vec::new(), | 222 | shuffled_indices: Vec::new(), |
| 223 | rng: rand::make_rng(), | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | /// Construct a queue with a fixed RNG seed, making shuffle order fully | ||
| 228 | /// reproducible. Used by tests and handy for debugging a specific order. | ||
| 229 | #[allow(dead_code)] // used only by integration tests (bin crate does not link the lib) | ||
| 230 | pub fn with_seed(seed: u64) -> Self { | ||
| 231 | Queue { | ||
| 232 | tracks: Vec::new(), | ||
| 233 | position: 0, | ||
| 234 | shuffle: ShuffleMode::None, | ||
| 235 | shuffled_indices: Vec::new(), | ||
| 236 | rng: StdRng::seed_from_u64(seed), | ||
| 220 | } | 237 | } |
| 221 | } | 238 | } |
| 222 | 239 | ||
| @@ -225,7 +242,6 @@ impl Queue { | |||
| 225 | self.update_shuffle_indices(); | 242 | self.update_shuffle_indices(); |
| 226 | } | 243 | } |
| 227 | 244 | ||
| 228 | #[allow(dead_code)] | ||
| 229 | pub fn push_track(&mut self, track: Track) { | 245 | pub fn push_track(&mut self, track: Track) { |
| 230 | self.tracks.push(track); | 246 | self.tracks.push(track); |
| 231 | self.update_shuffle_indices(); | 247 | self.update_shuffle_indices(); |
| @@ -234,11 +250,27 @@ impl Queue { | |||
| 234 | pub fn insert_after_current(&mut self, path: PathBuf) { | 250 | pub fn insert_after_current(&mut self, path: PathBuf) { |
| 235 | if self.tracks.is_empty() { | 251 | if self.tracks.is_empty() { |
| 236 | self.tracks.push(Track::from_path(path)); | 252 | self.tracks.push(Track::from_path(path)); |
| 237 | } else { | 253 | self.update_shuffle_indices(); |
| 238 | let insert_idx = self.position + 1; | 254 | return; |
| 239 | self.tracks.insert(insert_idx, Track::from_path(path)); | ||
| 240 | } | 255 | } |
| 241 | self.update_shuffle_indices(); | 256 | let insert_idx = self.position + 1; |
| 257 | self.tracks.insert(insert_idx, Track::from_path(path)); | ||
| 258 | if self.shuffle == ShuffleMode::None { | ||
| 259 | self.update_shuffle_indices(); | ||
| 260 | return; | ||
| 261 | } | ||
| 262 | // Under shuffle, splice the new entry into the play order right after | ||
| 263 | // the current slot instead of re-shuffling the tail: the inserted | ||
| 264 | // track deterministically occupies slot `position + 1`, so "play | ||
| 265 | // now/next" is meaningful and `current()` agrees with what the daemon | ||
| 266 | // starts playing. The remaining play order is preserved (indices that | ||
| 267 | // shifted right in `tracks` are bumped), not re-randomized. | ||
| 268 | for v in self.shuffled_indices.iter_mut() { | ||
| 269 | if *v >= insert_idx { | ||
| 270 | *v += 1; | ||
| 271 | } | ||
| 272 | } | ||
| 273 | self.shuffled_indices.insert(self.position + 1, insert_idx); | ||
| 242 | } | 274 | } |
| 243 | 275 | ||
| 244 | pub fn track_at(&self, idx: usize) -> Option<&Track> { | 276 | pub fn track_at(&self, idx: usize) -> Option<&Track> { |
| @@ -309,10 +341,9 @@ impl Queue { | |||
| 309 | self.shuffled_indices = (0..len).collect(); | 341 | self.shuffled_indices = (0..len).collect(); |
| 310 | match self.shuffle { | 342 | match self.shuffle { |
| 311 | ShuffleMode::All => { | 343 | ShuffleMode::All => { |
| 312 | let mut rng = rand::rng(); | ||
| 313 | let pos = self.position.min(len.saturating_sub(1)); | 344 | let pos = self.position.min(len.saturating_sub(1)); |
| 314 | if pos + 1 < len { | 345 | if pos + 1 < len { |
| 315 | self.shuffled_indices[pos + 1..].shuffle(&mut rng); | 346 | self.shuffled_indices[pos + 1..].shuffle(&mut self.rng); |
| 316 | } | 347 | } |
| 317 | } | 348 | } |
| 318 | ShuffleMode::Album => { | 349 | ShuffleMode::Album => { |
| @@ -330,10 +361,14 @@ impl Queue { | |||
| 330 | self.shuffled_indices = (0..len).collect(); | 361 | self.shuffled_indices = (0..len).collect(); |
| 331 | 362 | ||
| 332 | if len > 1 { | 363 | if len > 1 { |
| 333 | let mut rng = rand::rng(); | ||
| 334 | let pos = self.position.min(len.saturating_sub(1)); | 364 | let pos = self.position.min(len.saturating_sub(1)); |
| 335 | if pos + 1 < len { | 365 | if pos + 1 < len { |
| 336 | self.shuffle_album_range(pos + 1..len, &mut rng); | 366 | Self::shuffle_album_range( |
| 367 | &self.tracks, | ||
| 368 | &mut self.shuffled_indices, | ||
| 369 | pos + 1..len, | ||
| 370 | &mut self.rng, | ||
| 371 | ); | ||
| 337 | } | 372 | } |
| 338 | } | 373 | } |
| 339 | } | 374 | } |
| @@ -352,7 +387,6 @@ impl Queue { | |||
| 352 | } | 387 | } |
| 353 | 388 | ||
| 354 | let range_start = pos + 1; | 389 | let range_start = pos + 1; |
| 355 | let mut rng = rand::rng(); | ||
| 356 | 390 | ||
| 357 | // Build album groups within the range, shuffling tracks within each group | 391 | // Build album groups within the range, shuffling tracks within each group |
| 358 | let mut groups: Vec<Vec<usize>> = Vec::new(); | 392 | let mut groups: Vec<Vec<usize>> = Vec::new(); |
| @@ -368,7 +402,7 @@ impl Queue { | |||
| 368 | } else if album != current_album { | 402 | } else if album != current_album { |
| 369 | // Shuffle tracks within this album group before adding it | 403 | // Shuffle tracks within this album group before adding it |
| 370 | if current_group.len() > 1 { | 404 | if current_group.len() > 1 { |
| 371 | current_group.shuffle(&mut rng); | 405 | current_group.shuffle(&mut self.rng); |
| 372 | } | 406 | } |
| 373 | groups.push(std::mem::take(&mut current_group)); | 407 | groups.push(std::mem::take(&mut current_group)); |
| 374 | current_group.push(i); | 408 | current_group.push(i); |
| @@ -379,13 +413,13 @@ impl Queue { | |||
| 379 | } | 413 | } |
| 380 | if !current_group.is_empty() { | 414 | if !current_group.is_empty() { |
| 381 | if current_group.len() > 1 { | 415 | if current_group.len() > 1 { |
| 382 | current_group.shuffle(&mut rng); | 416 | current_group.shuffle(&mut self.rng); |
| 383 | } | 417 | } |
| 384 | groups.push(current_group); | 418 | groups.push(current_group); |
| 385 | } | 419 | } |
| 386 | 420 | ||
| 387 | // Shuffle the album group order | 421 | // Shuffle the album group order |
| 388 | groups.shuffle(&mut rng); | 422 | groups.shuffle(&mut self.rng); |
| 389 | 423 | ||
| 390 | let mut new_indices: Vec<usize> = self.shuffled_indices[..=pos].to_vec(); | 424 | let mut new_indices: Vec<usize> = self.shuffled_indices[..=pos].to_vec(); |
| 391 | for group in &groups { | 425 | for group in &groups { |
| @@ -394,7 +428,12 @@ impl Queue { | |||
| 394 | self.shuffled_indices = new_indices; | 428 | self.shuffled_indices = new_indices; |
| 395 | } | 429 | } |
| 396 | 430 | ||
| 397 | fn shuffle_album_range(&mut self, range: std::ops::Range<usize>, rng: &mut impl rand::Rng) { | 431 | fn shuffle_album_range( |
| 432 | tracks: &[Track], | ||
| 433 | indices: &mut [usize], | ||
| 434 | range: std::ops::Range<usize>, | ||
| 435 | rng: &mut impl rand::Rng, | ||
| 436 | ) { | ||
| 398 | if range.start >= range.end { | 437 | if range.start >= range.end { |
| 399 | return; | 438 | return; |
| 400 | } | 439 | } |
| @@ -404,7 +443,7 @@ impl Queue { | |||
| 404 | let mut range_start: Option<usize> = None; | 443 | let mut range_start: Option<usize> = None; |
| 405 | 444 | ||
| 406 | for i in range.clone() { | 445 | for i in range.clone() { |
| 407 | let album = self.tracks.get(i).and_then(|t| t.album.as_deref()); | 446 | let album = tracks.get(i).and_then(|t| t.album.as_deref()); |
| 408 | match (current_album, range_start) { | 447 | match (current_album, range_start) { |
| 409 | (None, None) => { | 448 | (None, None) => { |
| 410 | current_album = album; | 449 | current_album = album; |
| @@ -424,7 +463,7 @@ impl Queue { | |||
| 424 | 463 | ||
| 425 | for (start, end) in album_ranges { | 464 | for (start, end) in album_ranges { |
| 426 | if start + 1 < end { | 465 | if start + 1 < end { |
| 427 | self.shuffled_indices[start..end].shuffle(rng); | 466 | indices[start..end].shuffle(rng); |
| 428 | } | 467 | } |
| 429 | } | 468 | } |
| 430 | } | 469 | } |
| @@ -498,13 +537,17 @@ impl Queue { | |||
| 498 | if self.position != 0 || self.tracks.len() <= 1 { | 537 | if self.position != 0 || self.tracks.len() <= 1 { |
| 499 | return; | 538 | return; |
| 500 | } | 539 | } |
| 501 | let mut rng = rand::rng(); | ||
| 502 | match self.shuffle { | 540 | match self.shuffle { |
| 503 | ShuffleMode::All => { | 541 | ShuffleMode::All => { |
| 504 | self.shuffled_indices.shuffle(&mut rng); | 542 | self.shuffled_indices.shuffle(&mut self.rng); |
| 505 | } | 543 | } |
| 506 | ShuffleMode::Album => { | 544 | ShuffleMode::Album => { |
| 507 | self.shuffle_album_range(0..self.tracks.len(), &mut rng); | 545 | Self::shuffle_album_range( |
| 546 | &self.tracks, | ||
| 547 | &mut self.shuffled_indices, | ||
| 548 | 0..self.tracks.len(), | ||
| 549 | &mut self.rng, | ||
| 550 | ); | ||
| 508 | } | 551 | } |
| 509 | ShuffleMode::Albums => { | 552 | ShuffleMode::Albums => { |
| 510 | let mut groups: Vec<Vec<usize>> = Vec::new(); | 553 | let mut groups: Vec<Vec<usize>> = Vec::new(); |
| @@ -519,7 +562,7 @@ impl Queue { | |||
| 519 | current_album = album; | 562 | current_album = album; |
| 520 | } else if album != current_album { | 563 | } else if album != current_album { |
| 521 | if current_group.len() > 1 { | 564 | if current_group.len() > 1 { |
| 522 | current_group.shuffle(&mut rng); | 565 | current_group.shuffle(&mut self.rng); |
| 523 | } | 566 | } |
| 524 | groups.push(std::mem::take(&mut current_group)); | 567 | groups.push(std::mem::take(&mut current_group)); |
| 525 | current_group.push(i); | 568 | current_group.push(i); |
| @@ -530,12 +573,12 @@ impl Queue { | |||
| 530 | } | 573 | } |
| 531 | if !current_group.is_empty() { | 574 | if !current_group.is_empty() { |
| 532 | if current_group.len() > 1 { | 575 | if current_group.len() > 1 { |
| 533 | current_group.shuffle(&mut rng); | 576 | current_group.shuffle(&mut self.rng); |
| 534 | } | 577 | } |
| 535 | groups.push(current_group); | 578 | groups.push(current_group); |
| 536 | } | 579 | } |
| 537 | 580 | ||
| 538 | groups.shuffle(&mut rng); | 581 | groups.shuffle(&mut self.rng); |
| 539 | 582 | ||
| 540 | self.shuffled_indices = groups.concat(); | 583 | self.shuffled_indices = groups.concat(); |
| 541 | } | 584 | } |
