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