Skip to main content

selene_daemon/
playlist.rs

1use std::{
2    collections::{HashSet, VecDeque},
3    fmt::Display,
4    sync::{
5        atomic::{AtomicU8, Ordering},
6        mpsc::Sender,
7    },
8};
9
10use blake3::Hash;
11use lunar_lib::database::{DatabaseEntry, DatabaseError};
12use rand::{rng, seq::SliceRandom};
13use selene_core::{
14    database::LibraryDb,
15    library::{
16        album::Album,
17        artist::{Artist, ArtistId},
18        collection::{Collectable, Collection, CollectionId},
19        track::Track,
20    },
21};
22use serde::{Deserialize, Serialize};
23
24use crate::{PlayerEvent, event_handler::EventTx};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ResolvedTrack {
28    pub track: Track,
29    pub position: usize,
30
31    pub album: Option<Album>,
32    pub album_artists: Option<Vec<Artist>>,
33
34    pub artists: Vec<Artist>,
35}
36
37impl ResolvedTrack {
38    #[cfg(feature = "mpris")]
39    #[must_use]
40    pub fn mpris_id(&self) -> mpris_server::TrackId {
41        use mpris_server::zbus::zvariant::ObjectPath;
42
43        ObjectPath::from_string_unchecked(format!(
44            "/org/mpris/MediaPlayer2/TrackList/{}",
45            self.position,
46        ))
47        .into()
48    }
49
50    pub fn from_tracklist(track: Track, id: usize, db: &LibraryDb) -> Result<Self, DatabaseError> {
51        let album = track.metadata.album(db)?;
52        let album_artists = album
53            .as_ref()
54            .map(|a| a.artists().artists(db))
55            .transpose()?;
56
57        Ok(Self {
58            position: id,
59            album,
60            album_artists,
61            artists: track.metadata.artists(db)?,
62            track,
63        })
64    }
65}
66
67#[derive(Debug)]
68pub struct Playlist {
69    event_tx: Sender<PlayerEvent>,
70
71    pub queue: VecDeque<Track>,
72    playlist: Vec<Playable>,
73    tracklist: Vec<Track>,
74    tracklist_index: Option<usize>,
75
76    pub shuffle_mode: ShuffleMode,
77    pub loop_mode: LoopMode,
78}
79
80impl Playlist {
81    #[must_use]
82    pub fn new(event_tx: Sender<PlayerEvent>) -> Self {
83        Self {
84            event_tx,
85
86            queue: VecDeque::new(),
87            playlist: Vec::new(),
88            tracklist: Vec::new(),
89            tracklist_index: None,
90
91            shuffle_mode: ShuffleMode::None,
92            loop_mode: LoopMode::None,
93        }
94    }
95
96    /// Clears the playlist and the tracklist
97    pub fn clear(&mut self) {
98        self.playlist.clear();
99        self.tracklist.clear();
100        self.tracklist_index = None;
101        self.event_tx.event(PlayerEvent::TracklistChanged {
102            tracklist: Vec::new(),
103        });
104    }
105
106    #[must_use]
107    pub fn position(&self) -> (usize, usize) {
108        (self.tracklist_index.unwrap_or(0), self.tracklist.len())
109    }
110
111    /// Peeks at the next item
112    ///
113    /// If this function returns none, it means the end of the track list has been reached, and the next consumption will follow the [`LoopMode`]
114    pub fn peek_next(&self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
115        if self.tracklist.is_empty() {
116            return Ok(None);
117        }
118
119        let track = match self.loop_mode {
120            LoopMode::None | LoopMode::LoopAndReshuffle => {
121                let i = self.tracklist_index.map_or(0, |i| i + 1);
122
123                let Some(track) = self.tracklist.get(i) else {
124                    return Ok(None);
125                };
126                ResolvedTrack::from_tracklist(track.clone(), i, db)?
127            }
128            LoopMode::Loop => {
129                let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
130
131                let Some(track) = self.tracklist.get(i) else {
132                    return Ok(None);
133                };
134                ResolvedTrack::from_tracklist(track.clone(), i, db)?
135            }
136            LoopMode::RepeatTrack => {
137                let Some(i) = self.tracklist_index else {
138                    return Ok(None);
139                };
140                let Some(track) = self.tracklist.get(i) else {
141                    return Ok(None);
142                };
143                ResolvedTrack::from_tracklist(track.clone(), i, db)?
144            }
145        };
146
147        Ok(Some(track))
148    }
149
150    /// Returns the item at the current index
151    pub fn current(&mut self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
152        let Some(i) = self.tracklist_index else {
153            return Ok(None);
154        };
155        let resolved = ResolvedTrack::from_tracklist(self.tracklist[i].clone(), i, db)?;
156        Ok(Some(resolved))
157    }
158
159    /// Moves to the next item and returns it
160    ///
161    /// If this function returns none, it means there is nothing left to play or nothing to play
162    ///
163    /// This function will skip tracks without a container.
164    pub fn pop_next(&mut self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
165        if self.tracklist.is_empty() {
166            return Ok(None);
167        }
168
169        let track = match self.loop_mode {
170            LoopMode::None => {
171                let i = self.tracklist_index.map_or(0, |i| i + 1);
172
173                let Some(track) = self.tracklist.get(i) else {
174                    return Ok(None);
175                };
176                let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
177
178                self.tracklist_index = Some(i);
179                resolved
180            }
181            LoopMode::Loop => {
182                let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
183                let Some(track) = self.tracklist.get(i) else {
184                    return Ok(None);
185                };
186                let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
187                self.tracklist_index = Some(i);
188                resolved
189            }
190            LoopMode::LoopAndReshuffle => {
191                let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
192
193                if i == 0 {
194                    self.rebuild_tracklist();
195                }
196
197                let Some(track) = self.tracklist.get(i) else {
198                    return Ok(None);
199                };
200                let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
201                self.tracklist_index = Some(i);
202                resolved
203            }
204            LoopMode::RepeatTrack => {
205                let Some(i) = self.tracklist_index else {
206                    return Ok(None);
207                };
208                ResolvedTrack::from_tracklist(self.tracklist[i].clone(), i, db)?
209            }
210        };
211
212        Ok(Some(track))
213    }
214
215    /// Rebuilds the interior tracklist
216    pub fn rebuild_tracklist(&mut self) {
217        let mut index_map: Vec<usize> = (0..self.playlist.len()).collect();
218        if matches!(
219            self.shuffle_mode,
220            ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
221        ) {
222            index_map.shuffle(&mut rng());
223        }
224
225        let mut tracklist: Vec<_> = index_map
226            .into_iter()
227            .flat_map(|i| self.playlist[i].clone().flatten_shuffle(self.shuffle_mode))
228            .collect();
229
230        if matches!(self.shuffle_mode, ShuffleMode::Full) {
231            tracklist.shuffle(&mut rng());
232        }
233
234        self.tracklist_index = None;
235        self.tracklist = tracklist;
236        self.event_tx.event(PlayerEvent::TracklistChanged {
237            tracklist: self
238                .tracklist()
239                .iter()
240                .map(selene_core::library::track::Track::id)
241                .collect(),
242        });
243    }
244
245    /// If the looping mode is set to `Loop`, this function will modulo the input
246    ///
247    /// If the looping mode is set to `LoopAndReshuffle`, this function will modulo the input and reshuffle the tracklist
248    pub fn tracklist_seek(&mut self, to: isize, increment: bool) -> usize {
249        let to = if increment {
250            self.tracklist_index.unwrap_or(0) as isize + to
251        } else {
252            to
253        };
254
255        let len = self.tracklist.len() as isize;
256
257        match self.loop_mode {
258            LoopMode::None => {
259                self.tracklist_index = Some(to.clamp(0, len) as usize);
260            }
261            LoopMode::Loop => {
262                self.tracklist_index = Some(to.rem_euclid(len) as usize);
263            }
264            LoopMode::LoopAndReshuffle => {
265                if to >= len && !matches!(self.shuffle_mode, ShuffleMode::None) {
266                    self.rebuild_tracklist();
267                }
268                self.tracklist_index = Some(to.rem_euclid(len) as usize);
269            }
270            LoopMode::RepeatTrack => {
271                if !increment {
272                    self.tracklist_index = Some(to.clamp(0, len) as usize);
273                }
274            }
275        }
276        self.tracklist_index.unwrap()
277    }
278
279    /// Gets the current state of the playlist
280    #[must_use]
281    pub fn playlist(&self) -> &[Playable] {
282        &self.playlist
283    }
284
285    /// Sets the current state of the playlist
286    pub fn set_playlist(&mut self, playlist: impl IntoIterator<Item = Playable>) {
287        self.playlist = playlist.into_iter().collect();
288        self.rebuild_tracklist();
289    }
290
291    pub fn queue_extend(&mut self, with: impl IntoIterator<Item = Playable>) {
292        self.queue.extend(
293            with.into_iter()
294                .flat_map(|p| p.flatten_shuffle(ShuffleMode::None)),
295        );
296    }
297
298    #[must_use]
299    pub fn tracklist(&self) -> &[Track] {
300        &self.tracklist
301    }
302
303    // Returns the hash of the queue
304    #[must_use]
305    pub fn queue_hash(&self) -> Hash {
306        let mut hash_seed = Vec::with_capacity(self.queue.len() * 32);
307        for track in &self.queue {
308            hash_seed.extend(*track.id());
309        }
310        blake3::hash(&hash_seed)
311    }
312
313    // Returns the hash of the playlist
314    #[must_use]
315    pub fn playlist_hash(&self) -> Hash {
316        let mut hash_seed = Vec::with_capacity(self.playlist.len() * 32);
317        for playable in &self.playlist {
318            hash_seed.extend(playable.id_as_bytes());
319        }
320        blake3::hash(&hash_seed)
321    }
322}
323
324impl Extend<Playable> for Playlist {
325    fn extend<T: IntoIterator<Item = Playable>>(&mut self, iter: T) {
326        let mut vec: Vec<_> = iter.into_iter().collect();
327        if matches!(
328            self.shuffle_mode,
329            ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
330        ) {
331            vec.shuffle(&mut rng());
332        }
333
334        self.tracklist.extend(
335            vec.iter()
336                .cloned()
337                .flat_map(|p| p.flatten_shuffle(self.shuffle_mode)),
338        );
339        self.event_tx.event(PlayerEvent::TracklistChanged {
340            tracklist: self
341                .tracklist()
342                .iter()
343                .map(selene_core::library::track::Track::id)
344                .collect(),
345        });
346        self.playlist.extend(vec);
347    }
348}
349
350#[derive(Debug, Serialize, Deserialize, Clone)]
351pub struct PlayableAlbum {
352    album: Album,
353    tracks: Vec<Track>,
354}
355impl PlayableAlbum {
356    fn from_album(album: Album, db: &LibraryDb) -> Result<PlayableAlbum, DatabaseError> {
357        let tracks = album.tracks(db)?;
358        Ok(Self { album, tracks })
359    }
360}
361
362#[derive(Debug, Serialize, Deserialize, Clone)]
363pub enum Playable {
364    Track {
365        track: Box<Track>,
366    },
367    Album {
368        album: Box<PlayableAlbum>,
369    },
370    Artist {
371        artist: ArtistId,
372        tracks: Vec<Track>,
373        albums: Vec<PlayableAlbum>,
374    },
375    Collection {
376        collection: CollectionId,
377        playables: Vec<Playable>,
378    },
379}
380
381impl Playable {
382    #[must_use]
383    pub fn flatten_shuffle(self, shuffle_mode: ShuffleMode) -> Vec<Track> {
384        let mut buf = Vec::new();
385        let mut stack = vec![self];
386
387        while let Some(last) = stack.pop() {
388            match last {
389                Playable::Track { track } => buf.push(*track),
390                Playable::Album { mut album, .. } => {
391                    if matches!(
392                        shuffle_mode,
393                        ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
394                    ) {
395                        album.tracks.shuffle(&mut rng());
396                    }
397
398                    buf.extend(album.tracks);
399                }
400                Playable::Artist {
401                    mut tracks,
402                    mut albums,
403                    ..
404                } => {
405                    if matches!(
406                        shuffle_mode,
407                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
408                    ) {
409                        albums.shuffle(&mut rng());
410                    }
411
412                    buf.extend(albums.into_iter().flat_map(|mut a| {
413                        if matches!(
414                            shuffle_mode,
415                            ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
416                        ) {
417                            a.tracks.shuffle(&mut rng());
418                        }
419
420                        a.tracks
421                    }));
422
423                    if matches!(
424                        shuffle_mode,
425                        ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
426                    ) {
427                        tracks.shuffle(&mut rng());
428                    }
429
430                    buf.extend(tracks);
431                }
432                Playable::Collection { mut playables, .. } => {
433                    if matches!(
434                        shuffle_mode,
435                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
436                    ) {
437                        playables.shuffle(&mut rng());
438                    }
439
440                    stack.extend(playables);
441                }
442            }
443        }
444
445        if matches!(shuffle_mode, ShuffleMode::Full) {
446            buf.shuffle(&mut rng());
447        }
448
449        buf
450    }
451
452    #[must_use]
453    pub fn flatten(self) -> Vec<Track> {
454        let mut buf = Vec::new();
455        let mut stack = vec![self];
456
457        while let Some(last) = stack.pop() {
458            match last {
459                Playable::Track { track } => buf.push(*track),
460                Playable::Album { album } => buf.extend(album.tracks),
461                Playable::Artist { tracks, albums, .. } => {
462                    buf.extend(albums.into_iter().flat_map(|a| a.tracks));
463                    buf.extend(tracks);
464                }
465                Playable::Collection { playables, .. } => stack.extend(playables),
466            }
467        }
468
469        buf
470    }
471
472    pub fn from_collectable(
473        collectable: Collectable,
474        db: &LibraryDb,
475    ) -> Result<Playable, DatabaseError> {
476        let playable = match collectable {
477            Collectable::Track(track_id) => {
478                let track = Track::db_get_from(track_id, db)?.ok_or(DatabaseError::MissingEntry)?;
479                Playable::Track {
480                    track: Box::new(track),
481                }
482            }
483            Collectable::Artist(artist_id) => {
484                let artist =
485                    Artist::db_get_from(artist_id, db)?.ok_or(DatabaseError::MissingEntry)?;
486
487                let tracks = artist.all_tracks(db)?;
488
489                let albums: Vec<PlayableAlbum> = artist
490                    .albums(db)?
491                    .into_iter()
492                    .map(|a| PlayableAlbum::from_album(a, db))
493                    .collect::<Result<_, _>>()?;
494
495                Playable::Artist {
496                    artist: artist_id,
497                    tracks,
498                    albums,
499                }
500            }
501            Collectable::Album(album_id) => {
502                let album = Album::db_get_from(album_id, db)?.ok_or(DatabaseError::MissingEntry)?;
503
504                Playable::Album {
505                    album: Box::new(PlayableAlbum::from_album(album, db)?),
506                }
507            }
508            Collectable::Collection(collection_id) => {
509                struct Frame<I: Iterator<Item = Collectable>> {
510                    collection_id: CollectionId,
511                    remaining: I,
512                    playables: Vec<Playable>,
513                    ancestors: HashSet<CollectionId>,
514                }
515
516                let root = Collection::db_get_from(collection_id, db)?
517                    .ok_or(DatabaseError::MissingEntry)?;
518                let mut frames = vec![Frame {
519                    collection_id,
520                    remaining: root.collectables(db)?.into_iter(),
521                    playables: Vec::new(),
522                    ancestors: HashSet::from([collection_id]),
523                }];
524
525                loop {
526                    let frame = frames.last_mut().unwrap();
527                    if let Some(item) = frame.remaining.next() {
528                        match item {
529                            Collectable::Collection(inner_id) => {
530                                assert!(
531                                    !frame.ancestors.contains(&inner_id),
532                                    "Invalid collection: Cyclical reference"
533                                );
534
535                                let mut child_ancestors = frame.ancestors.clone();
536                                child_ancestors.insert(inner_id);
537
538                                let inner = Collection::db_get_from(inner_id, db)?
539                                    .ok_or(DatabaseError::MissingEntry)?;
540                                frames.push(Frame {
541                                    collection_id: inner_id,
542                                    remaining: inner.collectables(db)?.into_iter(),
543                                    playables: Vec::new(),
544                                    ancestors: child_ancestors,
545                                });
546                            }
547                            other => {
548                                frame.playables.push(Playable::from_collectable(other, db)?);
549                            }
550                        }
551                    } else {
552                        let completed = frames.pop().unwrap();
553                        let result = Playable::Collection {
554                            collection: completed.collection_id,
555                            playables: completed.playables,
556                        };
557                        match frames.last_mut() {
558                            Some(parent) => parent.playables.push(result),
559                            None => return Ok(result),
560                        }
561                    }
562                }
563            }
564        };
565        Ok(playable)
566    }
567
568    #[must_use]
569    pub fn to_collectable(&self) -> Collectable {
570        match self {
571            Playable::Track { track } => Collectable::Track(track.id()),
572            Playable::Album { album } => Collectable::Album(album.album.id()),
573            Playable::Artist { artist, .. } => Collectable::Artist(*artist),
574            Playable::Collection { collection, .. } => Collectable::Collection(*collection),
575        }
576    }
577
578    fn id_as_bytes(&self) -> [u8; 32] {
579        match self {
580            Playable::Track { track } => *track.id(),
581            Playable::Album { album, .. } => *album.album.id(),
582            Playable::Artist { artist, .. } => **artist,
583            Playable::Collection { collection, .. } => **collection,
584        }
585    }
586
587    pub fn display(&self) -> Result<String, DatabaseError> {
588        match self {
589            Playable::Track { track } => Ok(format!("{} (Track)", track.metadata.safe_title())),
590            Playable::Album { album, .. } => Ok(format!("{} (Album)", album.album.name())),
591            Playable::Artist { artist, .. } => {
592                let artist = Artist::db_get(*artist)?.ok_or(DatabaseError::MissingEntry)?;
593                Ok(format!("{} (Artist)", artist.name()))
594            }
595            Playable::Collection { collection, .. } => {
596                let collection =
597                    Collection::db_get(*collection)?.ok_or(DatabaseError::MissingEntry)?;
598                Ok(format!("{} (Collection)", collection.name))
599            }
600        }
601    }
602}
603
604#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
605#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
606pub enum ShuffleMode {
607    /// Tracks are not shuffled, they are played as is
608    None,
609
610    /// Only collections are shuffled, meaning the collections are shuffled, but not the contents
611    CollectionsOnly,
612
613    /// Randomize tracks only, meaning the contents of collections are shuffled, but not the collections themselves
614    TracksOnly,
615
616    /// Randomize collection order and the tracks inside the collection
617    CollectionsAndTracks,
618
619    /// Randomizes the order of everything
620    Full,
621}
622
623impl Display for ShuffleMode {
624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625        match self {
626            ShuffleMode::None => f.write_str("None"),
627            ShuffleMode::CollectionsOnly => f.write_str("Collections Only"),
628            ShuffleMode::TracksOnly => f.write_str("Tracks Only"),
629            ShuffleMode::CollectionsAndTracks => f.write_str("Collections And Tracks"),
630            ShuffleMode::Full => f.write_str("Full"),
631        }
632    }
633}
634
635#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
636#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
637pub enum LoopMode {
638    /// Disables looping
639    None,
640
641    /// When the end of the tracklist is reached, loop back to the beginning
642    Loop,
643
644    /// When the end of the tracklist is reached, reshuffle the tracklist using the shuffle mode and loop
645    LoopAndReshuffle,
646    RepeatTrack,
647}
648
649impl Display for LoopMode {
650    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651        match self {
652            LoopMode::None => f.write_str("None"),
653            LoopMode::Loop => f.write_str("Loop"),
654            LoopMode::LoopAndReshuffle => f.write_str("Loop And Reshuffle"),
655            LoopMode::RepeatTrack => f.write_str("Repeat Track"),
656        }
657    }
658}
659
660#[repr(u8)]
661#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
662pub enum PlaybackStatus {
663    Playing,
664    Paused,
665    Stopped,
666}
667
668impl Display for PlaybackStatus {
669    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
670        match self {
671            PlaybackStatus::Playing => f.write_str("Playing"),
672            PlaybackStatus::Paused => f.write_str("Paused"),
673            PlaybackStatus::Stopped => f.write_str("Stopped"),
674        }
675    }
676}
677
678pub struct AtomicPlaybackStatus(AtomicU8);
679
680impl AtomicPlaybackStatus {
681    #[must_use]
682    pub fn new(state: PlaybackStatus) -> Self {
683        Self(AtomicU8::new(state as u8))
684    }
685
686    pub fn load(&self, order: Ordering) -> PlaybackStatus {
687        match self.0.load(order) {
688            0 => PlaybackStatus::Playing,
689            1 => PlaybackStatus::Paused,
690            2 => PlaybackStatus::Stopped,
691            _ => unreachable!(),
692        }
693    }
694
695    pub fn store(&self, state: PlaybackStatus, order: Ordering) {
696        self.0.store(state as u8, order);
697    }
698}