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 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 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 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 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 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 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 #[must_use]
279 pub fn playlist(&self) -> &[Playable] {
280 &self.playlist
281 }
282
283 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 #[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 #[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 None,
607
608 CollectionsOnly,
610
611 TracksOnly,
613
614 CollectionsAndTracks,
616
617 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 None,
638
639 Loop,
641
642 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}