use std::{
collections::{HashSet, VecDeque},
fmt::Display,
sync::{
atomic::{AtomicU8, Ordering},
mpsc::Sender,
},
};
use blake3::Hash;
use lunar_lib::database::{DatabaseEntry, DatabaseError};
use rand::{rng, seq::SliceRandom};
use selene_core::{
database::LibraryDb,
library::{
album::Album,
artist::{Artist, ArtistId},
collection::{Collectable, Collection, CollectionId},
track::Track,
},
};
use serde::{Deserialize, Serialize};
use crate::{PlayerEvent, event_handler::EventTx};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedTrack {
pub track: Track,
pub position: usize,
pub album: Option<Album>,
pub album_artists: Option<Vec<Artist>>,
pub artists: Vec<Artist>,
}
impl ResolvedTrack {
#[cfg(feature = "mpris")]
#[must_use]
pub fn mpris_id(&self) -> mpris_server::TrackId {
use mpris_server::zbus::zvariant::ObjectPath;
ObjectPath::from_string_unchecked(format!(
"/org/mpris/MediaPlayer2/TrackList/{}",
self.position,
))
.into()
}
pub fn from_tracklist(track: Track, id: 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: id,
album,
album_artists,
artists: track.metadata.artists(db)?,
track,
})
}
}
#[derive(Debug)]
pub struct Playlist {
event_tx: Sender<PlayerEvent>,
pub queue: VecDeque<Track>,
playlist: Vec<Playable>,
tracklist: Vec<Track>,
tracklist_index: Option<usize>,
pub shuffle_mode: ShuffleMode,
pub loop_mode: LoopMode,
}
impl Playlist {
#[must_use]
pub fn new(event_tx: Sender<PlayerEvent>) -> Self {
Self {
event_tx,
queue: VecDeque::new(),
playlist: Vec::new(),
tracklist: Vec::new(),
tracklist_index: None,
shuffle_mode: ShuffleMode::None,
loop_mode: LoopMode::None,
}
}
pub fn clear(&mut self) {
self.playlist.clear();
self.tracklist.clear();
self.tracklist_index = None;
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: Vec::new(),
});
}
#[must_use]
pub fn position(&self) -> (usize, usize) {
(self.tracklist_index.unwrap_or(0), self.tracklist.len())
}
pub fn peek_next(&self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
if self.tracklist.is_empty() {
return Ok(None);
}
let track = match self.loop_mode {
LoopMode::None | LoopMode::LoopAndReshuffle => {
let i = self.tracklist_index.map_or(0, |i| i + 1);
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
ResolvedTrack::from_tracklist(track.clone(), i, db)?
}
LoopMode::Loop => {
let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
ResolvedTrack::from_tracklist(track.clone(), i, db)?
}
LoopMode::RepeatTrack => {
let Some(i) = self.tracklist_index else {
return Ok(None);
};
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
ResolvedTrack::from_tracklist(track.clone(), i, db)?
}
};
Ok(Some(track))
}
pub fn current(&mut self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
let Some(i) = self.tracklist_index else {
return Ok(None);
};
let resolved = ResolvedTrack::from_tracklist(self.tracklist[i].clone(), i, db)?;
Ok(Some(resolved))
}
pub fn pop_next(&mut self, db: &LibraryDb) -> Result<Option<ResolvedTrack>, DatabaseError> {
if self.tracklist.is_empty() {
return Ok(None);
}
let track = match self.loop_mode {
LoopMode::None => {
let i = self.tracklist_index.map_or(0, |i| i + 1);
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
self.tracklist_index = Some(i);
resolved
}
LoopMode::Loop => {
let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
self.tracklist_index = Some(i);
resolved
}
LoopMode::LoopAndReshuffle => {
let i = self.tracklist_index.map_or(0, |i| i + 1) % self.tracklist.len();
if i == 0 {
self.rebuild_tracklist();
}
let Some(track) = self.tracklist.get(i) else {
return Ok(None);
};
let resolved = ResolvedTrack::from_tracklist(track.clone(), i, db)?;
self.tracklist_index = Some(i);
resolved
}
LoopMode::RepeatTrack => {
let Some(i) = self.tracklist_index else {
return Ok(None);
};
ResolvedTrack::from_tracklist(self.tracklist[i].clone(), i, db)?
}
};
Ok(Some(track))
}
pub fn rebuild_tracklist(&mut self) {
let mut index_map: Vec<usize> = (0..self.playlist.len()).collect();
if matches!(
self.shuffle_mode,
ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
) {
index_map.shuffle(&mut rng());
}
let mut tracklist: Vec<_> = index_map
.into_iter()
.flat_map(|i| self.playlist[i].clone().flatten_shuffle(self.shuffle_mode))
.collect();
if matches!(self.shuffle_mode, ShuffleMode::Full) {
tracklist.shuffle(&mut rng());
}
self.tracklist_index = None;
self.tracklist = tracklist;
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self
.tracklist()
.iter()
.map(selene_core::library::track::Track::id)
.collect(),
});
}
pub fn tracklist_seek(&mut self, to: isize, increment: bool) -> usize {
let to = if increment {
self.tracklist_index.unwrap_or(0) as isize + to
} else {
to
};
let len = self.tracklist.len() as isize;
match self.loop_mode {
LoopMode::None => {
self.tracklist_index = Some(to.clamp(0, len) as usize);
}
LoopMode::Loop => {
self.tracklist_index = Some(to.rem_euclid(len) as usize);
}
LoopMode::LoopAndReshuffle => {
if to >= len && !matches!(self.shuffle_mode, ShuffleMode::None) {
self.rebuild_tracklist();
}
self.tracklist_index = Some(to.rem_euclid(len) as usize);
}
LoopMode::RepeatTrack => {
if !increment {
self.tracklist_index = Some(to.clamp(0, len) as usize);
}
}
}
self.tracklist_index.unwrap()
}
#[must_use]
pub fn playlist(&self) -> &[Playable] {
&self.playlist
}
pub fn set_playlist(&mut self, playlist: impl IntoIterator<Item = Playable>) {
self.playlist = playlist.into_iter().collect();
self.rebuild_tracklist();
}
pub fn queue_extend(&mut self, with: impl IntoIterator<Item = Playable>) {
self.queue.extend(
with.into_iter()
.flat_map(|p| p.flatten_shuffle(ShuffleMode::None)),
);
}
#[must_use]
pub fn tracklist(&self) -> &[Track] {
&self.tracklist
}
#[must_use]
pub fn queue_hash(&self) -> Hash {
let mut hash_seed = Vec::with_capacity(self.queue.len() * 32);
for track in &self.queue {
hash_seed.extend(*track.id());
}
blake3::hash(&hash_seed)
}
#[must_use]
pub fn playlist_hash(&self) -> Hash {
let mut hash_seed = Vec::with_capacity(self.playlist.len() * 32);
for playable in &self.playlist {
hash_seed.extend(playable.id_as_bytes());
}
blake3::hash(&hash_seed)
}
}
impl Extend<Playable> for Playlist {
fn extend<T: IntoIterator<Item = Playable>>(&mut self, iter: T) {
let mut vec: Vec<_> = iter.into_iter().collect();
if matches!(
self.shuffle_mode,
ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
) {
vec.shuffle(&mut rng());
}
self.tracklist.extend(
vec.iter()
.cloned()
.flat_map(|p| p.flatten_shuffle(self.shuffle_mode)),
);
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self
.tracklist()
.iter()
.map(selene_core::library::track::Track::id)
.collect(),
});
self.playlist.extend(vec);
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayableAlbum {
album: Album,
tracks: Vec<Track>,
}
impl PlayableAlbum {
fn from_album(album: Album, db: &LibraryDb) -> Result<PlayableAlbum, DatabaseError> {
let tracks = album.tracks(db)?;
Ok(Self { album, tracks })
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Playable {
Track {
track: Box<Track>,
},
Album {
album: Box<PlayableAlbum>,
},
Artist {
artist: ArtistId,
tracks: Vec<Track>,
albums: Vec<PlayableAlbum>,
},
Collection {
collection: CollectionId,
playables: Vec<Playable>,
},
}
impl Playable {
#[must_use]
pub fn flatten_shuffle(self, shuffle_mode: ShuffleMode) -> Vec<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),
Playable::Album { mut album, .. } => {
if matches!(
shuffle_mode,
ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
) {
album.tracks.shuffle(&mut rng());
}
buf.extend(album.tracks);
}
Playable::Artist {
mut tracks,
mut albums,
..
} => {
if matches!(
shuffle_mode,
ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
) {
albums.shuffle(&mut rng());
}
buf.extend(albums.into_iter().flat_map(|mut a| {
if matches!(
shuffle_mode,
ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
) {
a.tracks.shuffle(&mut rng());
}
a.tracks
}));
if matches!(
shuffle_mode,
ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
) {
tracks.shuffle(&mut rng());
}
buf.extend(tracks);
}
Playable::Collection { mut playables, .. } => {
if matches!(
shuffle_mode,
ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
) {
playables.shuffle(&mut rng());
}
stack.extend(playables);
}
}
}
if matches!(shuffle_mode, ShuffleMode::Full) {
buf.shuffle(&mut rng());
}
buf
}
#[must_use]
pub fn flatten(self) -> Vec<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),
Playable::Album { album } => buf.extend(album.tracks),
Playable::Artist { tracks, albums, .. } => {
buf.extend(albums.into_iter().flat_map(|a| a.tracks));
buf.extend(tracks);
}
Playable::Collection { playables, .. } => stack.extend(playables),
}
}
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: Box::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)?;
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,
}
}
pub fn display(&self) -> Result<String, DatabaseError> {
match self {
Playable::Track { track } => Ok(format!("{} (Track)", track.metadata.safe_title())),
Playable::Album { album, .. } => Ok(format!("{} (Album)", album.album.name())),
Playable::Artist { artist, .. } => {
let artist = Artist::db_get(*artist)?.ok_or(DatabaseError::MissingEntry)?;
Ok(format!("{} (Artist)", artist.name()))
}
Playable::Collection { collection, .. } => {
let collection =
Collection::db_get(*collection)?.ok_or(DatabaseError::MissingEntry)?;
Ok(format!("{} (Collection)", collection.name))
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ShuffleMode {
None,
CollectionsOnly,
TracksOnly,
CollectionsAndTracks,
Full,
}
impl Display for ShuffleMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ShuffleMode::None => f.write_str("None"),
ShuffleMode::CollectionsOnly => f.write_str("Collections Only"),
ShuffleMode::TracksOnly => f.write_str("Tracks Only"),
ShuffleMode::CollectionsAndTracks => f.write_str("Collections And Tracks"),
ShuffleMode::Full => f.write_str("Full"),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum LoopMode {
None,
Loop,
LoopAndReshuffle,
RepeatTrack,
}
impl Display for LoopMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoopMode::None => f.write_str("None"),
LoopMode::Loop => f.write_str("Loop"),
LoopMode::LoopAndReshuffle => f.write_str("Loop And Reshuffle"),
LoopMode::RepeatTrack => f.write_str("Repeat Track"),
}
}
}
#[repr(u8)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum PlaybackStatus {
Playing,
Paused,
Stopped,
}
impl Display for PlaybackStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlaybackStatus::Playing => f.write_str("Playing"),
PlaybackStatus::Paused => f.write_str("Paused"),
PlaybackStatus::Stopped => f.write_str("Stopped"),
}
}
}
pub struct AtomicPlaybackStatus(AtomicU8);
impl AtomicPlaybackStatus {
#[must_use]
pub fn new(state: PlaybackStatus) -> Self {
Self(AtomicU8::new(state as u8))
}
pub fn load(&self, order: Ordering) -> PlaybackStatus {
match self.0.load(order) {
0 => PlaybackStatus::Playing,
1 => PlaybackStatus::Paused,
2 => PlaybackStatus::Stopped,
_ => unreachable!(),
}
}
pub fn store(&self, state: PlaybackStatus, order: Ordering) {
self.0.store(state as u8, order);
}
}