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 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 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 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 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 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 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 #[must_use]
281 pub fn playlist(&self) -> &[Playable] {
282 &self.playlist
283 }
284
285 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 #[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 #[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 None,
609
610 CollectionsOnly,
612
613 TracksOnly,
615
616 CollectionsAndTracks,
618
619 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 None,
640
641 Loop,
643
644 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}