From 863b4017714130f042f993e7c9d0f3f8adee22bf Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 2 Nov 2026 13:12:48 +0100 Subject: state: persist the queue and pick up where playback stopped The queue, its shuffle order and whether playback was running are written to ~/.local/state/rsplayer/state.json whenever they change, and on shutdown, then restored at daemon start. Tracks that disappeared from disk are dropped instead of failing on play. `rsp resume`/`rsp toggle` now start the restored track when nothing is loaded into the engine yet, which is the state right after a restart with a queue that was paused. --- src/queue.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'src/queue.rs') diff --git a/src/queue.rs b/src/queue.rs index 17f2143..c985755 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -8,9 +8,11 @@ use rand::SeedableRng; use rand::rngs::StdRng; use rand::seq::SliceRandom; +use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum ShuffleMode { None, All, @@ -94,7 +96,7 @@ pub fn extract_metadata(path: &Path) -> Option { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Track { pub path: PathBuf, pub title: Option, @@ -199,6 +201,18 @@ impl Track { } } +/// A queue captured for persistence. +/// +/// `shuffled_indices` carries the current play order, so restoring a shuffled +/// queue keeps the order it was played in instead of reshuffling the tail. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QueueSnapshot { + pub tracks: Vec, + pub position: usize, + pub shuffle: ShuffleMode, + pub shuffled_indices: Vec, +} + pub struct Queue { tracks: Vec, position: usize, @@ -591,4 +605,42 @@ impl Queue { self.position = 0; self.shuffled_indices.clear(); } + + /// Everything needed to rebuild this queue exactly. + pub fn snapshot(&self) -> QueueSnapshot { + QueueSnapshot { + tracks: self.tracks.clone(), + position: self.position, + shuffle: self.shuffle, + shuffled_indices: self.shuffled_indices.clone(), + } + } + + pub fn from_snapshot(snapshot: QueueSnapshot) -> Self { + let mut queue = Queue::new(); + queue.tracks = snapshot.tracks; + queue.shuffle = snapshot.shuffle; + queue.position = if queue.tracks.is_empty() { + 0 + } else { + snapshot.position.min(queue.tracks.len() - 1) + }; + // Trust the saved play order only when it still describes this queue; + // otherwise derive a new one instead of playing stale indices. + if queue.shuffle != ShuffleMode::None + && snapshot.shuffled_indices.len() == queue.tracks.len() + && Self::is_permutation(&snapshot.shuffled_indices) + { + queue.shuffled_indices = snapshot.shuffled_indices; + } else { + queue.update_shuffle_indices(); + } + queue + } + + fn is_permutation(indices: &[usize]) -> bool { + let mut sorted = indices.to_vec(); + sorted.sort_unstable(); + sorted.iter().enumerate().all(|(i, &idx)| i == idx) + } } -- cgit v1.2.3