use std::{
collections::VecDeque,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc::Sender,
},
};
use blake3::Hash;
use lunar_lib::database::{DatabaseError, DbHandle};
use rand::seq::SliceRandom;
use selene_core::{database::LibraryDb, library::track::Track};
use crate::{
LoopMode, ShuffleMode, changed_state,
event_handler::EventTx,
player::{PlayerError, PlayerEvent, PlayerState},
playlist::{Playable, PlayingTrack, Playlist, PlaylistRng},
};
impl Playlist {
#[must_use]
pub fn new(
event_tx: Sender<PlayerEvent>,
looping: Arc<AtomicBool>,
state: &PlayerState,
) -> Result<Self, PlayerError> {
let db = DbHandle::<LibraryDb>::open()?;
let (playlist, tracklist) = state.playlist_tracklist(&db)?;
Ok(Self {
event_tx,
rng: PlaylistRng::new(),
queue: VecDeque::new(),
playlist,
tracklist,
tracklist_index: state.tracklist_index,
shuffle_mode: ShuffleMode::None,
loop_mode: LoopMode::None,
looping,
})
}
pub fn set_shuffle_mode(&mut self, shuffle_mode: ShuffleMode) {
self.shuffle_mode = shuffle_mode;
self.rebuild_tracklist();
self.event_tx
.event(PlayerEvent::ShuffleModeChanged { shuffle_mode });
changed_state();
}
pub fn set_loop_mode(&mut self, loop_mode: LoopMode) {
self.loop_mode = loop_mode;
self.looping.store(
matches!(loop_mode, LoopMode::RepeatTrack),
Ordering::Relaxed,
);
self.event_tx
.event(PlayerEvent::LoopModeChanged { loop_mode });
changed_state();
}
}
impl Playlist {
#[must_use]
pub fn playlist(&self) -> &[Playable] {
&self.playlist
}
#[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)
}
pub fn set_playlist(&mut self, playlist: impl IntoIterator<Item = Playable>) {
self.playlist = playlist.into_iter().collect();
self.rebuild_tracklist();
}
pub fn extend_playlist(&mut self, items: impl IntoIterator<Item = Playable>) {
let items: Vec<Playable> = items.into_iter().collect();
let mut rng = self.rng.current_rng();
let mut tracklist_items: Vec<Arc<Track>> = items
.iter()
.flat_map(|p| p.flatten_shuffle(self.shuffle_mode, &mut rng))
.collect();
if matches!(self.shuffle_mode, ShuffleMode::Full) {
tracklist_items.shuffle(&mut rng);
}
self.playlist.extend(items);
self.tracklist.extend(tracklist_items);
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
position: self.tracklist_index,
});
self.rng = PlaylistRng::new();
changed_state();
}
}
impl Playlist {
pub fn queue(&self) -> &VecDeque<Arc<Track>> {
&self.queue
}
#[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)
}
pub fn set_queue<'a>(&mut self, items: impl IntoIterator<Item = &'a Playable>) {
self.queue = items.into_iter().flat_map(Playable::flatten).collect();
}
pub fn shuffle_queue(&mut self) {
let mut queue: Vec<Arc<Track>> = std::mem::take(&mut self.queue).into();
queue.shuffle(&mut rand::rng());
self.queue = queue.into();
}
pub fn clear_queue(&mut self) {
self.queue.clear();
}
pub fn extend_queue<'a>(&mut self, with: impl IntoIterator<Item = &'a Playable>) {
self.queue
.extend(with.into_iter().flat_map(Playable::flatten));
}
}
impl Playlist {
#[must_use]
pub fn position(&self) -> Option<usize> {
self.tracklist_index
}
#[must_use]
pub fn shuffle_mode(&self) -> ShuffleMode {
self.shuffle_mode
}
#[must_use]
pub fn loop_mode(&self) -> LoopMode {
self.loop_mode
}
#[must_use]
pub fn tracklist(&self) -> &[Arc<Track>] {
&self.tracklist
}
#[must_use]
pub fn rng(&self) -> PlaylistRng {
self.rng
}
}
impl Playlist {
pub fn current(&mut self, db: &LibraryDb) -> Result<Option<PlayingTrack>, DatabaseError> {
let Some(index) = self.position() else {
return Ok(None);
};
let track = PlayingTrack::from_tracklist(self.tracklist[index].clone(), index, db)?;
Ok(Some(track))
}
fn peek_next_raw(&self) -> Option<(usize, bool)> {
if self.tracklist.is_empty() {
return None;
}
let len = self.tracklist.len();
let result = match self.loop_mode {
LoopMode::None | LoopMode::RepeatTrack => {
let next_i = self.tracklist_index.map_or(0, |i| i + 1);
if next_i >= len {
return None;
}
(next_i, false)
}
LoopMode::Loop => (self.tracklist_index.map_or(0, |i| (i + 1) % len), false),
LoopMode::LoopAndReshuffle => {
let next_i = self.tracklist_index.map_or(0, |i| i + 1);
(next_i % len, next_i >= len)
}
};
Some(result)
}
pub fn peek_next(&self, db: &LibraryDb) -> Result<Option<PlayingTrack>, DatabaseError> {
if let Some(front) = self.queue.front().cloned() {
return Ok(Some(PlayingTrack::from_queue(front, db)?));
}
let Some((index, new_cycle)) = self.peek_next_raw() else {
return Ok(None);
};
let track = if new_cycle {
let tracklist = self.build_tracklist(
&mut self
.rng
.find_cycle_rng(self.rng.current_cycle().wrapping_add(1)),
);
PlayingTrack::from_tracklist(tracklist[index].clone(), index, db)?
} else {
PlayingTrack::from_tracklist(self.tracklist[index].clone(), index, db)?
};
Ok(Some(track))
}
pub fn pop_next(&mut self, db: &LibraryDb) -> Result<Option<PlayingTrack>, DatabaseError> {
if let Some(front) = self.queue.pop_front() {
return Ok(Some(PlayingTrack::from_queue(front, db)?));
}
let Some((index, new_cycle)) = self.peek_next_raw() else {
return Ok(None);
};
let track = if new_cycle {
let mut rng = self.rng.increment();
let tracklist = self.build_tracklist(&mut rng);
let track = PlayingTrack::from_tracklist(tracklist[index].clone(), index, db)?;
self.tracklist = tracklist;
self.event_tx.event(PlayerEvent::TracklistChanged {
tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
position: self.tracklist_index,
});
track
} else {
PlayingTrack::from_tracklist(self.tracklist[index].clone(), index, db)?
};
self.tracklist_index = Some(self.tracklist_index.map_or(0, |i| i + 1));
changed_state();
Ok(Some(track))
}
pub fn tracklist_seek(&mut self, to: isize, increment: bool) -> Option<usize> {
let len = self.tracklist.len();
if len == 0 {
return None;
}
let i = if increment {
self.tracklist_index
.map_or(0, |i| (i as isize).saturating_add(to))
} else {
to.max(0)
};
self.tracklist_index = match self.loop_mode {
LoopMode::None | LoopMode::RepeatTrack => {
let i = i.clamp(0, len as isize) as usize;
if i >= len {
return None;
}
Some(i)
}
LoopMode::Loop => Some(i.rem_euclid(len as isize) as usize),
LoopMode::LoopAndReshuffle => {
let current_cycle = self.rng.current_cycle();
let cycles_delta = i.div_euclid(len as isize);
let new_cycle = if cycles_delta > 0 {
current_cycle + 1
} else {
current_cycle.wrapping_add_signed(cycles_delta as i64)
};
if new_cycle > current_cycle {
let mut rng = self.rng.increment();
self.shuffle_tracklist(&mut rng);
} else if new_cycle < current_cycle {
self.rng.set_cycle(new_cycle);
let mut rng = self.rng.find_cycle_rng(new_cycle);
self.shuffle_tracklist(&mut rng);
}
Some(i.rem_euclid(len as isize) as usize)
}
};
changed_state();
self.tracklist_index
}
}