Skip to main content

chilen_backend/playback/
state.rs

1use std::{
2    fs::{File, read},
3    io::Write,
4    path::PathBuf,
5    sync::{Arc, LazyLock, RwLock},
6    thread,
7    time::Duration,
8};
9
10use log::{error, trace};
11use rmp_serde::{Deserializer, Serializer};
12use serde::{Deserialize, Serialize};
13
14use rand::seq::SliceRandom;
15
16use crate::{
17    Error, Event,
18    music_lib::{CACHE_DIR, state::Track, tracks_from_hashes},
19    playback::{LoopState, PlaybackState, PlayerVolume, ShuffleState},
20};
21
22#[cfg(feature = "mpris")]
23use crate::playback::mpris;
24
25/// Data structure used to store playback state on the disc and in the RAM at runtime.
26#[derive(Debug, Clone, Default, PartialEq)]
27pub struct PlayerState {
28    /// The index of the current track.
29    ///
30    /// It can either point to the `tracks` variable or `shuffled_tracks` is shuffle is supported
31    /// and set to [`ShuffleState::On`].
32    pub position: usize,
33    pub player_position: Duration,
34    pub player_volume: PlayerVolume,
35    pub playback_state: PlaybackState,
36    pub tracks: Vec<Track>,
37    pub shuffled_tracks: Vec<Track>,
38    pub shuffle_state: ShuffleState,
39    pub loop_state: LoopState,
40}
41
42impl TryFrom<PlayerStateRaw> for PlayerState {
43    type Error = Error;
44    fn try_from(value: PlayerStateRaw) -> Result<Self, Self::Error> {
45        Ok(Self {
46            position: value.position,
47            player_position: value.player_position,
48            player_volume: value.player_volume,
49            playback_state: PlaybackState::Stopped,
50            tracks: tracks_from_hashes(value.track_hashes)?
51                .into_iter()
52                .map(|t| t.as_ref().clone())
53                .collect(),
54            shuffled_tracks: tracks_from_hashes(value.shuffled_track_hashes)?
55                .into_iter()
56                .map(|t| t.as_ref().clone())
57                .collect(),
58            shuffle_state: value.shuffle_state,
59            loop_state: value.loop_state,
60        })
61    }
62}
63
64impl PlayerState {
65    fn on_track_changed(&self) {
66        crate::send_event(Event::PlayerStateChanged(self.clone()));
67
68        #[cfg(feature = "mpris")]
69        {
70            use mpris_server::{Metadata, Property};
71
72            let properties = vec![
73                match self.current() {
74                    Some(track) => Property::Metadata(track.get_meta(self.position)),
75                    None => Property::Metadata(Metadata::new()),
76                },
77                Property::CanGoPrevious(self.can_go_previous()),
78                Property::CanGoNext(self.can_go_next()),
79            ];
80            mpris::update_properties(properties);
81        }
82    }
83
84    pub(crate) fn on_playback_state_changed(&self) {
85        trace!("Playback state changed: {}", self.playback_state);
86
87        crate::send_event(Event::PlayerStateChanged(self.clone()));
88
89        #[cfg(feature = "mpris")]
90        {
91            use mpris_server::Property;
92            mpris::update_properties(vec![
93                Property::PlaybackStatus(mpris::playback_state_2_mpris(&self.playback_state)),
94                Property::CanPlay(self.can_play()),
95                Property::CanPause(self.can_pause()),
96                Property::CanSeek(self.can_seek()),
97            ]);
98        }
99    }
100
101    #[cfg(feature = "mpris")]
102    pub(crate) fn get_mpris_properties(&self) -> Vec<mpris_server::Property> {
103        use mpris_server::{Metadata, Property};
104
105        vec![
106            Property::PlaybackStatus(mpris::playback_state_2_mpris(&self.playback_state)),
107            Property::LoopStatus(mpris::loop_state_2_mpris(&self.loop_state)),
108            Property::Shuffle(self.shuffle_state.into()),
109            Property::Volume(self.player_volume.get()),
110            Property::CanGoNext(self.can_go_next()),
111            Property::CanGoPrevious(self.can_go_previous()),
112            Property::CanPlay(self.can_play()),
113            Property::CanPause(self.can_pause()),
114            Property::CanSeek(self.can_seek()),
115            match self.current() {
116                Some(track) => Property::Metadata(track.get_meta(self.position)),
117                None => Property::Metadata(Metadata::new()),
118            },
119        ]
120    }
121
122    pub fn set_tracks(&mut self, tracks: Vec<Track>) {
123        self.position = 0;
124        self.tracks = tracks;
125        self.set_playback_state(PlaybackState::Stopped);
126        if self.shuffle_state == ShuffleState::On {
127            self.shuffle();
128        }
129        self.on_track_changed();
130    }
131
132    pub fn append_tracks(&mut self, tracks: &mut Vec<Track>) {
133        self.tracks.append(tracks);
134        if self.shuffle_state == ShuffleState::On {
135            self.shuffle();
136        }
137        crate::send_event(Event::PlayerStateChanged(self.clone()));
138        #[cfg(feature = "mpris")]
139        {
140            use mpris_server::{Metadata, Property};
141
142            mpris::update_properties(vec![
143                match self.current() {
144                    Some(track) => Property::Metadata(track.get_meta(self.position)),
145                    None => Property::Metadata(Metadata::new()),
146                },
147                Property::CanGoPrevious(self.can_go_previous()),
148                Property::CanGoNext(self.can_go_next()),
149                Property::CanPlay(self.can_play()),
150                Property::CanPause(self.can_pause()),
151            ]);
152        }
153    }
154
155    /// Shuffle the queue without changing the current track.
156    pub fn shuffle(&mut self) {
157        if self.tracks.is_empty() {
158            use log::warn;
159
160            warn!("Refusing to shuffle an empty queue");
161            return;
162        }
163        // Maybe a position check here is necessary to prevent panics?
164        let mut tracks = self.tracks.clone();
165        let prev_pos = self.position;
166        let track = tracks.swap_remove(prev_pos);
167        let mut rng = rand::rng();
168        tracks.shuffle(&mut rng);
169        tracks.insert(prev_pos, track);
170        self.shuffled_tracks = tracks;
171        crate::send_event(Event::PlayerStateChanged(self.clone()));
172    }
173
174    pub fn set_shuffle_state(&mut self, shuffle_state: ShuffleState) {
175        if self.shuffle_state != shuffle_state {
176            if shuffle_state == ShuffleState::Off
177                && let Some(track) = self.current().cloned()
178            {
179                match self.tracks.iter().position(|t| *t == track) {
180                    Some(pos) => self.position = pos,
181                    None => {
182                        log::warn!(
183                            "Could not find the previous track in the queue, this should never happen"
184                        );
185                        thread::spawn(crate::playback::stop);
186                    }
187                }
188            }
189            self.shuffle_state = shuffle_state;
190            crate::send_event(Event::PlayerStateChanged(self.clone()));
191            #[cfg(feature = "mpris")]
192            {
193                use mpris_server::Property;
194
195                let properties = vec![
196                    Property::Shuffle(self.shuffle_state.into()),
197                    Property::CanGoPrevious(self.can_go_previous()),
198                    Property::CanGoNext(self.can_go_next()),
199                ];
200                mpris::update_properties(properties);
201            }
202        }
203    }
204
205    pub fn increment_player_position(&mut self, duration: Duration) {
206        self.player_position += duration;
207        crate::send_event(Event::PlayerStateChanged(self.clone()));
208        #[cfg(feature = "mpris")]
209        {
210            use mpris_server::{Metadata, Property};
211
212            let meta = match self.current() {
213                Some(track) => track.get_meta(self.position),
214                None => Metadata::new(),
215            };
216            mpris::update_properties(vec![Property::Metadata(meta)]);
217            mpris::set_position(self.player_position);
218        }
219    }
220
221    pub fn set_player_position(&mut self, player_position: Duration) {
222        if self.player_position != player_position {
223            self.player_position = player_position;
224            crate::send_event(Event::PlayerStateChanged(self.clone()));
225            #[cfg(feature = "mpris")]
226            {
227                use mpris_server::{Metadata, Property};
228
229                let meta = match self.current() {
230                    Some(track) => track.get_meta(self.position),
231                    None => Metadata::new(),
232                };
233                mpris::update_properties(vec![Property::Metadata(meta)]);
234                mpris::set_position(player_position);
235            }
236        }
237    }
238
239    pub fn set_player_volume(&mut self, player_volume: PlayerVolume) {
240        if self.player_volume != player_volume {
241            self.player_volume = player_volume;
242            crate::send_event(Event::PlayerStateChanged(self.clone()));
243            #[cfg(feature = "mpris")]
244            {
245                use mpris_server::Property;
246
247                let properties = vec![Property::Volume(self.player_volume.get())];
248                mpris::update_properties(properties);
249            }
250        }
251    }
252
253    pub fn set_loop_state(&mut self, loop_state: LoopState) {
254        if self.loop_state != loop_state {
255            self.loop_state = loop_state;
256            crate::send_event(Event::PlayerStateChanged(self.clone()));
257            #[cfg(feature = "mpris")]
258            {
259                use mpris_server::Property;
260
261                let properties = vec![
262                    Property::LoopStatus(mpris::loop_state_2_mpris(&self.loop_state)),
263                    Property::CanGoPrevious(self.can_go_previous()),
264                    Property::CanGoNext(self.can_go_next()),
265                ];
266                mpris::update_properties(properties);
267            }
268        }
269    }
270
271    pub fn set_playback_state(&mut self, playback_state: PlaybackState) {
272        if self.playback_state != playback_state {
273            self.playback_state = playback_state;
274            if self.playback_state == PlaybackState::Stopped {
275                self.set_player_position(Duration::default());
276            }
277            self.on_playback_state_changed();
278        }
279    }
280
281    pub fn is_empty(&self) -> bool {
282        match self.shuffle_state {
283            ShuffleState::Off => self.tracks.is_empty(),
284            ShuffleState::On => self.shuffled_tracks.is_empty(),
285        }
286    }
287
288    pub fn current(&self) -> Option<&Track> {
289        match self.shuffle_state {
290            ShuffleState::Off => {
291                if self.position < self.tracks.len() {
292                    return Some(&self.tracks[self.position]);
293                }
294            }
295            ShuffleState::On => {
296                if self.position < self.shuffled_tracks.len() {
297                    return Some(&self.shuffled_tracks[self.position]);
298                }
299            }
300        }
301        None
302    }
303
304    pub fn play_track(&mut self, index: usize) -> Option<&Track> {
305        if index < self.tracks.len() {
306            self.position = index;
307            self.on_track_changed();
308            self.current()
309        } else {
310            None
311        }
312    }
313
314    #[cfg(feature = "mpris")]
315    pub fn can_seek(&self) -> bool {
316        self.playback_state != PlaybackState::Stopped
317    }
318
319    #[cfg(feature = "mpris")]
320    pub fn can_play(&self) -> bool {
321        match self.shuffle_state {
322            ShuffleState::Off => {
323                self.position < self.tracks.len() && self.playback_state != PlaybackState::Playing
324            }
325            ShuffleState::On => {
326                self.position < self.shuffled_tracks.len()
327                    && self.playback_state != PlaybackState::Playing
328            }
329        }
330    }
331
332    #[cfg(feature = "mpris")]
333    pub fn can_pause(&self) -> bool {
334        self.playback_state == PlaybackState::Playing
335    }
336
337    pub fn can_go_next(&self) -> bool {
338        match self.loop_state {
339            LoopState::Off => match self.shuffle_state {
340                ShuffleState::Off => {
341                    !self.tracks.is_empty() && self.position < self.tracks.len() - 1
342                }
343                ShuffleState::On => {
344                    !self.shuffled_tracks.is_empty()
345                        && self.position < self.shuffled_tracks.len() - 1
346                }
347            },
348            _ => match self.shuffle_state {
349                ShuffleState::Off => !self.tracks.is_empty(),
350                ShuffleState::On => !self.shuffled_tracks.is_empty(),
351            },
352        }
353    }
354
355    pub fn next_track(&mut self) -> Option<&Track> {
356        match self.loop_state {
357            LoopState::Off => {
358                let tracks = match self.shuffle_state {
359                    ShuffleState::Off => &self.tracks,
360                    ShuffleState::On => &self.shuffled_tracks,
361                };
362                if !tracks.is_empty() && self.position < tracks.len() - 1 {
363                    self.position += 1;
364                    self.on_track_changed();
365                    return self.current();
366                }
367                None
368            }
369            LoopState::Track => {
370                self.on_track_changed();
371                self.current()
372            }
373            LoopState::Playlist => {
374                let tracks = match self.shuffle_state {
375                    ShuffleState::Off => &self.tracks,
376                    ShuffleState::On => &self.shuffled_tracks,
377                };
378                if tracks.is_empty() {
379                    None
380                } else if !tracks.is_empty() && self.position < tracks.len() - 1 {
381                    self.position += 1;
382                    self.on_track_changed();
383                    self.current()
384                } else {
385                    self.position = 0;
386                    self.on_track_changed();
387                    self.current()
388                }
389            }
390        }
391    }
392
393    pub fn can_go_previous(&self) -> bool {
394        match self.loop_state {
395            LoopState::Off => match self.shuffle_state {
396                ShuffleState::Off => !self.tracks.is_empty() && self.position > 0,
397                ShuffleState::On => !self.shuffled_tracks.is_empty() && self.position > 0,
398            },
399            _ => match self.shuffle_state {
400                ShuffleState::Off => !self.tracks.is_empty(),
401                ShuffleState::On => !self.shuffled_tracks.is_empty(),
402            },
403        }
404    }
405
406    pub fn previous_track(&mut self) -> Option<&Track> {
407        match self.loop_state {
408            LoopState::Off => {
409                let tracks = match self.shuffle_state {
410                    ShuffleState::Off => &self.tracks,
411                    ShuffleState::On => &self.shuffled_tracks,
412                };
413                if self.position > 0 && !tracks.is_empty() {
414                    self.position -= 1;
415                    self.on_track_changed();
416                    self.current()
417                } else {
418                    None
419                }
420            }
421            LoopState::Track => {
422                self.on_track_changed();
423                self.current()
424            }
425            LoopState::Playlist => {
426                let tracks = match self.shuffle_state {
427                    ShuffleState::Off => &self.tracks,
428                    ShuffleState::On => &self.shuffled_tracks,
429                };
430                if tracks.is_empty() {
431                    None
432                } else if self.position > 0 {
433                    self.position -= 1;
434                    self.on_track_changed();
435                    self.current()
436                } else if !tracks.is_empty() {
437                    self.position = tracks.len() - 1;
438                    self.on_track_changed();
439                    self.current()
440                } else {
441                    None
442                }
443            }
444        }
445    }
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
449struct PlayerStateRaw {
450    position: usize,
451    player_position: Duration,
452    player_volume: PlayerVolume,
453    track_hashes: Vec<u64>,
454    shuffled_track_hashes: Vec<u64>,
455    shuffle_state: ShuffleState,
456    loop_state: LoopState,
457}
458
459impl From<PlayerState> for PlayerStateRaw {
460    fn from(value: PlayerState) -> Self {
461        let track_hashes = Track::hash_tracks(&value.tracks);
462        let shuffled_track_hashes = Track::hash_tracks(&value.shuffled_tracks);
463        Self {
464            position: value.position,
465            player_position: value.player_position,
466            player_volume: value.player_volume,
467            track_hashes,
468            shuffled_track_hashes,
469            shuffle_state: value.shuffle_state,
470            loop_state: value.loop_state,
471        }
472    }
473}
474
475static STATE_FILE: LazyLock<PathBuf> = LazyLock::new(|| {
476    let mut data = CACHE_DIR.read().unwrap().clone().unwrap();
477    data.push("player_state");
478    data
479});
480
481pub(crate) static PLAYER_STATE: LazyLock<Arc<RwLock<Option<PlayerState>>>> =
482    LazyLock::new(|| Arc::new(RwLock::new(None)));
483
484pub(crate) fn unwrap_state_ref(maybe_state: Option<&PlayerState>) -> Result<&PlayerState, Error> {
485    match maybe_state {
486        Some(state) => Ok(state),
487        None => Err(Error::StateNotInitialized),
488    }
489}
490
491pub(crate) fn unwrap_state_mut(
492    maybe_state: Option<&mut PlayerState>,
493) -> Result<&mut PlayerState, Error> {
494    match maybe_state {
495        Some(state) => Ok(state),
496        None => Err(Error::StateNotInitialized),
497    }
498}
499
500fn save_state(state: PlayerState) -> Result<(), String> {
501    let state_raw: PlayerStateRaw = state.into();
502    let state_file = STATE_FILE.clone();
503
504    let mut data = Vec::new();
505    if let Err(e) = state_raw.serialize(&mut Serializer::new(&mut data)) {
506        error!("Could not serialize the player state: {e}");
507        return Err(e.to_string());
508    }
509
510    let mut file = match File::create(state_file) {
511        Ok(file) => file,
512        Err(e) => {
513            error!("Could not open the player state cache in write-only mode: {e}");
514            return Err(e.to_string());
515        }
516    };
517
518    match file.write_all(&data) {
519        Ok(_) => {
520            trace!("Saved player state to cache");
521            Ok(())
522        }
523        Err(e) => {
524            error!("Could not write to the player state cache: {e}");
525            Err(e.to_string())
526        }
527    }
528}
529
530pub(crate) fn background_save_state(state: PlayerState) {
531    thread::spawn(|| {
532        if let Err(e) = save_state(state) {
533            error!("Could not save player state to cache: {e}");
534        }
535    });
536}
537
538pub(crate) fn restore_state_from_cache() -> Result<PlayerState, String> {
539    let state_file = STATE_FILE.clone();
540
541    trace!("Restoring player state from {state_file:?}");
542
543    let state_exists = match state_file.try_exists() {
544        Ok(exists) => exists,
545        Err(e) => {
546            error!("Could not check if the player state file exists: {e}");
547            return Err(e.to_string());
548        }
549    };
550
551    if state_exists {
552        let data = match read(state_file) {
553            Ok(data) => data,
554            Err(e) => {
555                error!("Could not read the player state cache: {e}");
556                return Err(e.to_string());
557            }
558        };
559
560        let state_raw = match PlayerStateRaw::deserialize(&mut Deserializer::from_read_ref(&data)) {
561            Ok(data) => data,
562            Err(e) => {
563                error!("Could not decode the contents of the player state file: {e}");
564                return Err(e.to_string());
565            }
566        };
567
568        match <PlayerStateRaw as TryInto<PlayerState>>::try_into(state_raw) {
569            Ok(mut state) => {
570                state.playback_state = PlaybackState::Stopped;
571                Ok(state)
572            }
573            Err(e) => {
574                error!("Could not restore player state from cache: {e}");
575                Err(e.to_string())
576            }
577        }
578    } else {
579        Ok(PlayerState::default())
580    }
581}