use std::{
collections::{HashSet, VecDeque},
fmt::Display,
};
use blake3::Hash;
use rand::{rng, seq::SliceRandom};
use selene_core::{
database::{DatabaseEntry, DatabaseError},
library::{
album::Album,
artist::{Artist, ArtistId},
collection::{Collectable, Collection, CollectionId},
track::{Track, TrackId, track_meta::TrackMeta},
},
media_container::MediaContainer,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracklistTrack {
pub playable: Box<PlayableTrack>,
pub id: usize,
}
impl TracklistTrack {
pub fn can_play(&self) -> bool {
self.playable.container.is_some()
}
#[cfg(feature = "mpris")]
pub fn mpris_id(&self) -> mpris_server::TrackId {
use mpris_server::zbus::zvariant::ObjectPath;
ObjectPath::from_string_unchecked(format!("/org/mpris/MediaPlayer2/TrackList/{}", self.id))
.into()
}
}
#[derive(Debug)]
pub struct Playlist {
pub queue: VecDeque<PlayableTrack>,
playlist: Vec<Playable>,
tracklist: Vec<TracklistTrack>,
tracklist_index: Option<usize>,
pub shuffle_mode: ShuffleMode,
pub loop_mode: LoopMode,
}
impl Default for Playlist {
fn default() -> Self {
Self::new()
}
}
impl Playlist {
pub fn new() -> Self {
Self {
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;
}
pub fn peek_next(&self) -> Option<&TracklistTrack> {
if let Some(tracklist_index) = self.tracklist_index {
self.tracklist.get(tracklist_index + 1)
} else {
self.tracklist.first()
}
}
pub fn current(&mut self) -> Option<&TracklistTrack> {
let i = self.tracklist_index?;
self.tracklist.get(i)
}
pub fn pop_next(&mut self) -> Option<&TracklistTrack> {
if self.tracklist.is_empty() {
return None;
}
match self.loop_mode {
LoopMode::None => {
let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);
for i in start..self.tracklist.len() {
if self.tracklist[i].can_play() {
self.tracklist_index = Some(i);
return Some(&self.tracklist[i]);
}
}
None
}
LoopMode::Loop => {
let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);
for n in 0..self.tracklist.len() {
let i = (start + n) % self.tracklist.len();
if self.tracklist[i].can_play() {
self.tracklist_index = Some(i);
return Some(&self.tracklist[i]);
}
}
None
}
LoopMode::LoopAndReshuffle => {
let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);
for n in 0..self.tracklist.len() {
let i = (start + n) % self.tracklist.len();
if i == 0 && n != 0 {
self.rebuild_tracklist();
}
if self.tracklist[i].can_play() {
self.tracklist_index = Some(i);
return Some(&self.tracklist[i]);
}
}
None
}
LoopMode::RepeatTrack => {
let i = self.tracklist_index?;
self.tracklist[i].can_play().then_some(&self.tracklist[i])
}
}
}
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
.into_iter()
.enumerate()
.map(|(i, t)| TracklistTrack {
playable: Box::new(t),
id: i,
})
.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 | LoopMode::RepeatTrack => {
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);
}
}
self.tracklist_index.unwrap()
}
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)),
);
}
pub fn tracklist(&self) -> &[TracklistTrack] {
&self.tracklist
}
pub fn queue_hash(&self) -> Hash {
let mut hash_seed = Vec::with_capacity(self.queue.len() * 32);
for playable in &self.queue {
hash_seed.extend(playable.track.as_bytes());
}
blake3::hash(&hash_seed)
}
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.hash().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))
.enumerate()
.map(|(i, p)| TracklistTrack {
playable: Box::new(p),
id: i,
}),
);
self.playlist.extend(vec);
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayableTrack {
container: Option<MediaContainer>,
metadata: TrackMeta,
track: TrackId,
}
impl PartialEq for PlayableTrack {
fn eq(&self, other: &Self) -> bool {
self.track == other.track
}
}
impl Eq for PlayableTrack {}
impl PlayableTrack {
pub fn container(&self) -> Option<&MediaContainer> {
self.container.as_ref()
}
pub fn metadata(&self) -> &TrackMeta {
&self.metadata
}
pub fn id(&self) -> TrackId {
self.track
}
pub fn from_track(track: &Track, fallback: bool) -> Self {
Self {
container: if fallback {
Some(
track
.lib_container()
.unwrap_or(track.src_container())
.clone(),
)
} else {
track.lib_container().cloned()
},
metadata: track.metadata.clone(),
track: track.id(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayableAlbum {
album: Album,
tracks: Vec<PlayableTrack>,
}
impl PlayableAlbum {
fn from_album(album: Album) -> Result<PlayableAlbum, DatabaseError> {
let tracks = album
.tracks()?
.iter()
.map(|t| PlayableTrack::from_track(t, false))
.collect();
Ok(Self { album, tracks })
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Playable {
Track {
track: Box<PlayableTrack>,
},
Album {
album: Box<PlayableAlbum>,
},
Artist {
artist: ArtistId,
tracks: Vec<PlayableTrack>,
albums: Vec<PlayableAlbum>,
},
Collection {
collection: CollectionId,
playables: Vec<Playable>,
},
}
impl Playable {
pub fn flatten_shuffle(self, shuffle_mode: ShuffleMode) -> Vec<PlayableTrack> {
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
}
pub fn flatten(self) -> Vec<PlayableTrack> {
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) -> Result<Playable, DatabaseError> {
let playable = match collectable {
Collectable::Track(track_id) => {
let track = Track::db_get(track_id)?.ok_or(DatabaseError::MissingEntry)?;
Playable::Track {
track: Box::new(PlayableTrack::from_track(&track, false)),
}
}
Collectable::Artist(artist_id) => {
let artist = Artist::db_get(artist_id)?.ok_or(DatabaseError::MissingEntry)?;
let tracks = artist
.all_tracks()?
.iter()
.map(|t| PlayableTrack::from_track(t, false))
.collect();
let albums: Vec<PlayableAlbum> = artist
.albums()?
.into_iter()
.map(PlayableAlbum::from_album)
.collect::<Result<_, _>>()?;
Playable::Artist {
artist: artist_id,
tracks,
albums,
}
}
Collectable::Album(album_id) => {
let album = Album::db_get(album_id)?.ok_or(DatabaseError::MissingEntry)?;
Playable::Album {
album: Box::new(PlayableAlbum::from_album(album)?),
}
}
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(collection_id)?.ok_or(DatabaseError::MissingEntry)?;
let mut frames = vec![Frame {
collection_id,
remaining: root.collectables.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) => {
if frame.ancestors.contains(&inner_id) {
panic!("Invalid collection: Cyclical reference")
}
let mut child_ancestors = frame.ancestors.clone();
child_ancestors.insert(inner_id);
let inner = Collection::db_get(inner_id)?
.ok_or(DatabaseError::MissingEntry)?;
frames.push(Frame {
collection_id: inner_id,
remaining: inner.collectables.into_iter(),
playables: Vec::new(),
ancestors: child_ancestors,
});
}
other => {
frame.playables.push(Playable::from_collectable(other)?);
}
}
} 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)
}
fn hash(&self) -> Hash {
match self {
Playable::Track { track } => *track.track,
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))
}
}
}
}
#[cfg(feature = "clap")]
use clap::ValueEnum;
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(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("RandomizeCollectionsOnly"),
ShuffleMode::TracksOnly => f.write_str("RandomizeTracksOnly"),
ShuffleMode::CollectionsAndTracks => f.write_str("RandomizeCollectionsAndTracks"),
ShuffleMode::Full => f.write_str("RandomizeFull"),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(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("LoopAndReshuffle"),
LoopMode::RepeatTrack => f.write_str("RepeatTrack"),
}
}
}