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