use std::{
collections::{HashSet, VecDeque},
sync::{Arc, atomic::AtomicBool, mpsc::Sender},
};
use lunar_lib::database::{DatabaseEntry, DatabaseError};
use rand::{rngs::StdRng, seq::SliceRandom};
use selene_core::{
database::LibraryDb,
library::{
album::Album,
artist::{Artist, ArtistId},
collection::{Collectable, Collection, CollectionId},
track::{ResolvedTrack, Track},
},
};
use crate::{LoopMode, ShuffleMode, changed_state, event_handler::EventTx, player::PlayerEvent};
mod playlist_rng;
pub use playlist_rng::*;
mod core_impls;
pub(crate) trait ResolvedTrackExt {
#[cfg(feature = "mpris")]
#[must_use]
fn mpris_id(&self) -> mpris_server::TrackId;
fn new(track: Track, position: Option<usize>, db: &LibraryDb) -> Result<Self, DatabaseError>
where
Self: Sized;
fn from_queue(track: Track, db: &LibraryDb) -> Result<Self, DatabaseError>
where
Self: Sized;
fn from_tracklist(track: Track, id: usize, db: &LibraryDb) -> Result<Self, DatabaseError>
where
Self: Sized;
}
impl ResolvedTrackExt for ResolvedTrack {
#[cfg(feature = "mpris")]
fn mpris_id(&self) -> mpris_server::TrackId {
use mpris_server::zbus::zvariant::ObjectPath;
if let Some(position) = self.position {
ObjectPath::from_string_unchecked(format!(
"/org/mpris/MediaPlayer2/TrackList/{position}",
))
.into()
} else {
mpris_server::TrackId::NO_TRACK
}
}
fn new(track: Track, position: Option<usize>, db: &LibraryDb) -> Result<Self, DatabaseError> {
let album = track.metadata.album(db)?;
let album_artists = album
.as_ref()
.map(|a| a.artists().artists(db))
.transpose()?;
Ok(Self {
position,
album,
album_artists,
artists: track.metadata.artists(db)?,
track,
})
}
fn from_queue(track: Track, db: &LibraryDb) -> Result<Self, DatabaseError> {
Self::new(track, None, db)
}
fn from_tracklist(track: Track, id: usize, db: &LibraryDb) -> Result<Self, DatabaseError> {
Self::new(track, Some(id), db)
}
}
pub struct Playlist {
event_tx: Sender<PlayerEvent>,
rng: PlaylistRng,
queue: VecDeque<Arc<Track>>,
playlist: Vec<Playable>,
tracklist: Vec<Arc<Track>>,
tracklist_index: Option<usize>,
shuffle_mode: ShuffleMode,
loop_mode: LoopMode,
looping: Arc<AtomicBool>,
}
impl Playlist {
pub fn clear(&mut self) {
self.playlist.clear();
self.tracklist.clear();
self.tracklist_index = None;
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: Vec::new(),
position: self.tracklist_index,
});
}
fn rebuild_tracklist(&mut self) {
self.rng = PlaylistRng::new();
let mut rng = self.rng.current_rng();
let mut tracklist = self.build_tracklist(&mut rng);
if let Some(current) = self.tracklist_index.and_then(|i| self.tracklist.get(i))
&& let Some(new_pos) = tracklist.iter().position(|t| Arc::ptr_eq(t, current))
{
tracklist.swap(0, new_pos);
self.tracklist_index = Some(0);
} else {
self.tracklist_index = None;
}
self.tracklist = tracklist;
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
position: self.tracklist_index,
});
changed_state();
}
fn shuffle_tracklist(&mut self, rng: &mut StdRng) {
self.tracklist = self.build_tracklist(rng);
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
position: self.tracklist_index,
});
changed_state();
}
fn build_tracklist(&self, rng: &mut StdRng) -> Vec<Arc<Track>> {
let mut tracklist: Vec<Arc<Track>> = self
.playlist
.iter()
.flat_map(|p| p.flatten_shuffle(self.shuffle_mode, rng))
.collect();
if matches!(self.shuffle_mode, ShuffleMode::Full) {
tracklist.shuffle(rng);
}
tracklist
}
}
#[derive(Debug, Clone)]
pub struct PlayableAlbum {
album: Album,
tracks: Vec<Arc<Track>>,
}
impl PlayableAlbum {
fn from_album(album: Album, db: &LibraryDb) -> Result<PlayableAlbum, DatabaseError> {
let tracks = album.tracks(db)?.into_iter().map(Arc::new).collect();
Ok(Self { album, tracks })
}
}
#[derive(Debug, Clone)]
pub enum Playable {
Track {
track: Arc<Track>,
},
Album {
album: Box<PlayableAlbum>,
},
Artist {
artist: ArtistId,
tracks: Vec<Arc<Track>>,
albums: Vec<PlayableAlbum>,
},
Collection {
collection: CollectionId,
playables: Vec<Playable>,
},
}
impl Playable {
#[must_use]
pub fn flatten(&self) -> Vec<Arc<Track>> {
let mut buf = Vec::new();
let mut stack = vec![self];
while let Some(last) = stack.pop() {
match last {
Playable::Track { track } => buf.push(track.clone()),
Playable::Album { album } => buf.extend(album.tracks.iter().map(Arc::clone)),
Playable::Artist { tracks, albums, .. } => {
buf.extend(albums.iter().flat_map(|a| a.tracks.iter().map(Arc::clone)));
buf.extend(tracks.iter().map(Arc::clone));
}
Playable::Collection { playables, .. } => stack.extend(playables),
}
}
buf
}
#[must_use]
pub fn flatten_shuffle(&self, shuffle_mode: ShuffleMode, rng: &mut StdRng) -> Vec<Arc<Track>> {
let mut buf: Vec<Arc<Track>> = Vec::new();
let mut stack = vec![self];
while let Some(last) = stack.pop() {
match last {
Playable::Track { track } => buf.push(track.clone()),
Playable::Album { album, .. } => {
let mut album = album.tracks.iter().map(Arc::clone).collect::<Vec<_>>();
if matches!(
shuffle_mode,
ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
) {
album.shuffle(rng);
}
buf.extend(album);
}
Playable::Artist { tracks, albums, .. } => {
let mut items: Vec<_> = albums
.iter()
.map(|a| a.tracks.iter().map(Arc::clone).collect::<Vec<_>>())
.chain(tracks.iter().map(|t| vec![t.clone()]))
.collect();
if matches!(
shuffle_mode,
ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
) {
items.shuffle(rng);
}
buf.extend(items.into_iter().flat_map(|mut tracks| {
if matches!(
shuffle_mode,
ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
) {
tracks.shuffle(rng);
}
tracks
}));
}
Playable::Collection { playables, .. } => {
let mut playables = playables.iter().collect::<Vec<_>>();
if matches!(
shuffle_mode,
ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
) {
playables.shuffle(rng);
}
stack.extend(playables.into_iter().rev());
}
}
}
buf
}
pub fn from_collectable(
collectable: Collectable,
db: &LibraryDb,
) -> Result<Playable, DatabaseError> {
let playable = match collectable {
Collectable::Track(track_id) => {
let track = Track::db_get_from(track_id, db)?.ok_or(DatabaseError::MissingEntry)?;
Playable::Track {
track: Arc::new(track),
}
}
Collectable::Artist(artist_id) => {
let artist =
Artist::db_get_from(artist_id, db)?.ok_or(DatabaseError::MissingEntry)?;
let tracks = artist.all_tracks(db)?.into_iter().map(Arc::new).collect();
let albums: Vec<PlayableAlbum> = artist
.albums(db)?
.into_iter()
.map(|a| PlayableAlbum::from_album(a, db))
.collect::<Result<_, _>>()?;
Playable::Artist {
artist: artist_id,
tracks,
albums,
}
}
Collectable::Album(album_id) => {
let album = Album::db_get_from(album_id, db)?.ok_or(DatabaseError::MissingEntry)?;
Playable::Album {
album: Box::new(PlayableAlbum::from_album(album, db)?),
}
}
Collectable::Collection(collection_id) => {
struct Frame<I: Iterator<Item = Collectable>> {
collection_id: CollectionId,
remaining: I,
playables: Vec<Playable>,
ancestors: HashSet<CollectionId>,
}
let root = Collection::db_get_from(collection_id, db)?
.ok_or(DatabaseError::MissingEntry)?;
let mut frames = vec![Frame {
collection_id,
remaining: root.collectables(db)?.into_iter(),
playables: Vec::new(),
ancestors: HashSet::from([collection_id]),
}];
loop {
let frame = frames.last_mut().unwrap();
if let Some(item) = frame.remaining.next() {
match item {
Collectable::Collection(inner_id) => {
assert!(
!frame.ancestors.contains(&inner_id),
"Invalid collection: Cyclical reference"
);
let mut child_ancestors = frame.ancestors.clone();
child_ancestors.insert(inner_id);
let inner = Collection::db_get_from(inner_id, db)?
.ok_or(DatabaseError::MissingEntry)?;
frames.push(Frame {
collection_id: inner_id,
remaining: inner.collectables(db)?.into_iter(),
playables: Vec::new(),
ancestors: child_ancestors,
});
}
other => {
frame.playables.push(Playable::from_collectable(other, db)?);
}
}
} else {
let completed = frames.pop().unwrap();
let result = Playable::Collection {
collection: completed.collection_id,
playables: completed.playables,
};
match frames.last_mut() {
Some(parent) => parent.playables.push(result),
None => return Ok(result),
}
}
}
}
};
Ok(playable)
}
#[must_use]
pub fn to_collectable(&self) -> Collectable {
match self {
Playable::Track { track } => Collectable::Track(track.id()),
Playable::Album { album } => Collectable::Album(album.album.id()),
Playable::Artist { artist, .. } => Collectable::Artist(*artist),
Playable::Collection { collection, .. } => Collectable::Collection(*collection),
}
}
fn id_as_bytes(&self) -> [u8; 32] {
match self {
Playable::Track { track } => *track.id(),
Playable::Album { album, .. } => *album.album.id(),
Playable::Artist { artist, .. } => **artist,
Playable::Collection { collection, .. } => **collection,
}
}
}