selene-daemon 0.9.0-alpha.2

Official music player daemon for Selene
Documentation
use std::{
    collections::VecDeque,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};

use lunar_lib::{database::Entry, iterator_ext::IteratorExtensions};
use rand::seq::SliceRandom;
use selene_core::library::track::Track;

use crate::{
    LoopMode, ShuffleMode,
    playlist::{Playable, PlayingTrack, Playlist, PlaylistRng},
};

// Core impls
impl Playlist {
    pub(crate) fn new(looping: Arc<AtomicBool>) -> Self {
        Self {
            rng: PlaylistRng::new(),

            queue: VecDeque::new(),
            playlist: Vec::new(),
            tracklist: Vec::new(),
            tracklist_index: None,

            shuffle_mode: ShuffleMode::None,
            loop_mode: LoopMode::None,
            looping,
        }
    }

    pub(crate) fn set_shuffle_mode(&mut self, shuffle_mode: ShuffleMode) {
        self.shuffle_mode = shuffle_mode;
        self.rebuild_tracklist();
    }

    pub(crate) fn set_loop_mode(&mut self, loop_mode: LoopMode) {
        self.loop_mode = loop_mode;
        self.looping.store(
            matches!(loop_mode, LoopMode::RepeatTrack),
            Ordering::Relaxed,
        );
    }
}

// Playlist
impl Playlist {
    /// Gets the current state of the playlist
    #[must_use]
    pub(crate) fn playlist(&self) -> &[Playable] {
        &self.playlist
    }

    /// Sets the current state of the playlist
    pub(crate) fn set_playlist(&mut self, playlist: impl IntoIterator<Item = Playable>) {
        self.playlist = playlist.into_iter().collect();
        self.rebuild_tracklist();
    }

    /// Appends the input items to the end of the playlist and tracklist, shuffling them based off the current shuffle mode
    ///
    /// This function will reset the current RNG
    pub(crate) 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 = items
            .iter()
            .flat_map(|p| p.flatten_shuffle(self.shuffle_mode, &mut rng))
            .to_vec();

        if matches!(self.shuffle_mode, ShuffleMode::Full) {
            tracklist_items.shuffle(&mut rng);
        }

        self.playlist.extend(items);
        self.tracklist.extend(tracklist_items);
        self.rng = PlaylistRng::new();
    }
}

// Queue
impl Playlist {
    pub(crate) fn queue(&self) -> &VecDeque<Arc<Entry<Track>>> {
        &self.queue
    }

    pub(crate) fn set_queue<'a>(&mut self, items: impl IntoIterator<Item = &'a Playable>) {
        self.queue = items.into_iter().flat_map(Playable::flatten).collect();
    }

    pub(crate) fn shuffle_queue(&mut self) {
        let mut queue: Vec<Arc<Entry<Track>>> = std::mem::take(&mut self.queue).into();
        queue.shuffle(&mut rand::rng());
        self.queue = queue.into();
    }

    pub(crate) fn clear_queue(&mut self) {
        self.queue.clear();
    }

    pub(crate) fn extend_queue<'a>(&mut self, with: impl IntoIterator<Item = &'a Playable>) {
        self.queue
            .extend(with.into_iter().flat_map(Playable::flatten));
    }
}

// Accessors
impl Playlist {
    #[must_use]
    pub(crate) fn position(&self) -> Option<u32> {
        self.tracklist_index
    }

    #[must_use]
    pub(crate) fn shuffle_mode(&self) -> ShuffleMode {
        self.shuffle_mode
    }

    #[must_use]
    pub(crate) fn loop_mode(&self) -> LoopMode {
        self.loop_mode
    }

    #[must_use]
    pub(crate) fn tracklist(&self) -> &[Arc<Entry<Track>>] {
        &self.tracklist
    }
}

// Navigation
impl Playlist {
    /// Returns the item at the current index
    pub(crate) fn current(&mut self) -> Option<PlayingTrack> {
        let index = self.position()?;

        let track = PlayingTrack::from_tracklist(self.tracklist[index as usize].clone(), index);

        Some(track)
    }

    /// Peeks the next item in the track, returning its index, and if its part of a new cycle
    fn peek_next_raw(&self) -> Option<(u32, bool)> {
        if self.tracklist.is_empty() {
            return None;
        }
        let len = self.tracklist.len() as u32;

        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)
    }

    /// Peeks at the next item
    ///
    /// If this function returns none, there is nothing to play
    pub(crate) fn peek_next(&self) -> Option<PlayingTrack> {
        if let Some(front) = self.queue.front().cloned() {
            return Some(PlayingTrack::from_queue(front));
        }

        let (index, new_cycle) = self.peek_next_raw()?;

        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 as usize].clone(), index)
        } else {
            PlayingTrack::from_tracklist(self.tracklist[index as usize].clone(), index)
        };

        Some(track)
    }

    /// Moves to the next item and returns it
    ///
    /// If this function returns none, it means there is nothing left to play or nothing to play
    ///
    /// This function will skip tracks without a container.
    pub(crate) fn pop_next(&mut self) -> Option<PlayingTrack> {
        if let Some(front) = self.queue.pop_front() {
            return Some(PlayingTrack::from_queue(front));
        }

        let (index, new_cycle) = self.peek_next_raw()?;

        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 as usize].clone(), index);
            self.tracklist = tracklist;
            track
        } else {
            PlayingTrack::from_tracklist(self.tracklist[index as usize].clone(), index)
        };
        self.tracklist_index = Some(self.tracklist_index.map_or(0, |i| i + 1));

        Some(track)
    }

    /// Seeks within the tracklist
    ///
    /// If [`LoopMode::None`] is set, overflows will end the current cycle
    /// If [`LoopMode::Loop`] or [`LoopMode::LoopAndReshuffle`] is set, overflows will move cycles forward
    /// If [`LoopMode::RepeatTrack`] is set, this will move to that track and repeat it. Overflows will end the current cycle
    ///
    /// A cycle is one loop of the tracklist.
    pub(crate) fn tracklist_seek(&mut self, to: i32, increment: bool) -> Option<u32> {
        let len = self.tracklist.len();
        if len == 0 {
            return None;
        }

        let i = if increment {
            self.tracklist_index
                .map_or(0, |i| (i as i32).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 i32) as u32;
                if i >= len as u32 {
                    return None;
                }
                Some(i)
            }
            LoopMode::Loop => Some(i.rem_euclid(len as i32) as u32),
            LoopMode::LoopAndReshuffle => {
                let current_cycle = self.rng.current_cycle();
                let cycles_delta = i.div_euclid(len as i32);
                let new_cycle = current_cycle.wrapping_add_signed(i64::from(cycles_delta));

                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 i32) as u32)
            }
        };

        self.tracklist_index
    }
}