selene-daemon 0.5.7

Official music player daemon for Selene
Documentation
use std::{
    fmt::Display,
    sync::{
        Arc, Mutex, PoisonError,
        atomic::{AtomicU32, Ordering},
        mpsc::{Receiver, RecvError, SendError, Sender},
    },
};

use bitflags::bitflags;
use blake3::Hash;
use lunar_lib::{
    config::ConfigError,
    database::{Database, DatabaseError},
};
use rubato::ResamplerConstructionError;
use selene_core::{
    database::LibraryDb,
    library::{
        collection::Collectable,
        track::{ResolvedTrack, TrackId},
    },
    media_container::ContainerFormat,
    symphonia_helpers::raw_decoder::DecodingError,
};
use serde::{Deserialize, Serialize};
use symphonia::core::errors::Error as SymphoniaError;
use thiserror::Error;

use crate::{
    PlayerEvent,
    decoder::{DecoderCommand, DecoderTx},
    event_handler::EventTx,
    player::playback::{CpalError, DeviceConfig},
    playlist::{AtomicPlaybackStatus, LoopMode, PlaybackStatus, Playlist, ShuffleMode},
};

mod opened_decoder;
pub use opened_decoder::*;

pub mod playback;

mod ipc;
pub use ipc::*;

mod thread;

#[derive(Debug, Error)]
pub enum PlayerError {
    #[error("{0}")]
    Io(#[from] std::io::Error),

    #[error("{0}")]
    Config(#[from] ConfigError),

    #[error("{0}")]
    Cpal(#[from] CpalError),

    #[error("{0}")]
    Symphonia(#[from] SymphoniaError),

    #[error("{0}")]
    Database(#[from] DatabaseError),

    #[error("{0}")]
    Decoder(#[from] DecodingError),

    #[error("{0}")]
    Resampler(#[from] ResamplerConstructionError),

    #[error("Player attempted to play an unsupported container: '{0:?}'")]
    UnsupportedContainer(ContainerFormat),

    #[error("A critical thread panicked holding an interior mutex")]
    CriticalMutexPoisoned,

    #[error("A sender or receiver to a critical thread disconnected")]
    CriticalThreadDisconnected,
}

impl<T> From<PoisonError<T>> for PlayerError {
    fn from(_value: PoisonError<T>) -> Self {
        Self::CriticalMutexPoisoned
    }
}

impl From<RecvError> for PlayerError {
    fn from(_value: RecvError) -> Self {
        Self::CriticalThreadDisconnected
    }
}

impl<T> From<SendError<T>> for PlayerError {
    fn from(_value: SendError<T>) -> Self {
        Self::CriticalThreadDisconnected
    }
}

pub struct Player {
    rx: Receiver<PlayerRequest>,
    decoder_tx: Sender<DecoderCommand>,

    event_tx: Sender<PlayerEvent>,

    playlist: Playlist,

    device_config: Arc<Mutex<DeviceConfig>>,
    playback_state: Arc<AtomicPlaybackStatus>,
    volume: Arc<AtomicU32>,
}

impl Player {
    /// Sets or increments the volume to the input volume, clamps between `[0..1]`
    fn set_volume(&mut self, volume: f32, increment: bool) -> f32 {
        let volume = if increment {
            f32::from_bits(self.volume.load(Ordering::Relaxed)) + volume
        } else {
            volume
        }
        .clamp(0.0, 1.0);

        self.event_tx.event(PlayerEvent::VolumeChanged { volume });
        self.volume.store(volume.to_bits(), Ordering::Relaxed);

        volume
    }

    pub fn load(&self, playable: ResolvedTrack) -> Result<(), PlayerError> {
        let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
        let decoder = OpenedDecoder::new(playable, output_sample_rate)?;
        self.decoder_tx.load(decoder)?;
        Ok(())
    }

    pub fn preload(&self, playable: ResolvedTrack) -> Result<(), PlayerError> {
        let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
        let decoder = OpenedDecoder::new(playable, output_sample_rate)?;
        self.decoder_tx.preload(decoder)?;
        Ok(())
    }

    fn replace_decoders(&mut self, pop: bool) -> Result<bool, PlayerError> {
        let db = LibraryDb::open().unwrap();
        let load = if pop {
            self.playlist.pop_next(&db)?
        } else {
            self.playlist.current(&db)?
        };
        let preload = self.playlist.peek_next(&db)?;

        if let Some(load) = load {
            let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
            let load = OpenedDecoder::new(load, output_sample_rate)?;
            if let Some(preload) = preload {
                let preload = OpenedDecoder::new(preload, output_sample_rate)?;
                self.decoder_tx.load_and_preload(load, preload)?;
                return Ok(true);
            }

            self.decoder_tx.load(load)?;
            Ok(true)
        } else {
            self.decoder_tx.stop()?;
            Ok(false)
        }
    }
}

bitflags! {
    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
    pub struct PlayerQueryFlags: u16 {
        const PLAYBACK_STATE        = 1 << 0;

        const CURRENTLY_PLAYING     = 1 << 1;

        const VOLUME                = 1 << 2;
        const TIME                  = 1 << 3;

        const QUEUE                 = 1 << 4;
        const QUEUE_STATE           = 1 << 5;

        const PLAYLIST              = 1 << 6;
        const PLAYLIST_STATE        = 1 << 7;

        const TRACKLIST             = 1 << 8;
        const TRACKLIST_POSITION    = 1 << 9;

        const SHUFFLE_MODE          = 1 << 10;
        const LOOP_MODE             = 1 << 11;
    }
}

/// Wrapper around [`Option`] for ciborium ser/de workaround
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QueryWrapper<T> {
    None,
    Some(T),
}

impl<T> QueryWrapper<T> {
    pub fn into_option(self) -> Option<T> {
        match self {
            QueryWrapper::None => None,
            QueryWrapper::Some(t) => Some(t),
        }
    }

    pub fn as_option(&self) -> Option<&T> {
        match self {
            QueryWrapper::None => None,
            QueryWrapper::Some(t) => Some(t),
        }
    }
}

impl<T> From<Option<T>> for QueryWrapper<T> {
    fn from(value: Option<T>) -> Self {
        match value {
            Some(t) => QueryWrapper::Some(t),
            None => QueryWrapper::None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct QueryResult {
    pub playback_state: Option<PlaybackStatus>,

    pub currently_playing: Option<QueryWrapper<Arc<ResolvedTrack>>>,

    pub volume: Option<f32>,
    pub time: Option<QueryWrapper<f64>>,

    pub queue: Option<Vec<TrackId>>,
    pub queue_state: Option<Hash>,

    pub playlist: Option<Vec<Collectable>>,
    pub playlist_state: Option<Hash>,

    pub tracklist: Option<Vec<TrackId>>,
    pub tracklist_position: Option<QueryWrapper<usize>>,

    pub shuffle_mode: Option<ShuffleMode>,
    pub loop_mode: Option<LoopMode>,
}

impl Display for QueryResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(playback_state) = self.playback_state {
            writeln!(f, "Playback State: {playback_state}")?;
        }

        if let Some(tracklist_track) = &self.currently_playing {
            if let Some(tracklist_track) = tracklist_track.as_option() {
                writeln!(
                    f,
                    "Currently Playing: {}",
                    tracklist_track.track.id().to_selene_id()
                )?;
            } else {
                f.write_str("Currently Playing: None\n")?;
            }
        }

        if let Some(volume) = self.volume {
            writeln!(f, "Volume: {volume}")?;
        }

        if let Some(time) = &self.time {
            let time = time.as_option().unwrap_or(&0.0);
            writeln!(f, "Time: {time}")?;
        }

        if let Some(queue) = &self.queue {
            writeln!(
                f,
                "Queue: {}",
                queue
                    .iter()
                    .map(TrackId::to_selene_id)
                    .collect::<Vec<_>>()
                    .join(";")
            )?;
        }

        if let Some(state) = self.queue_state {
            writeln!(f, "Queue State: {state}")?;
        }

        if let Some(playlist) = &self.playlist {
            writeln!(
                f,
                "Playlist: {}",
                playlist
                    .iter()
                    .map(Collectable::to_selene_id)
                    .collect::<Vec<_>>()
                    .join(";")
            )?;
        }

        if let Some(state) = self.playlist_state {
            writeln!(f, "State: {state}")?;
        }

        if let Some(shuffle_mode) = self.shuffle_mode {
            writeln!(f, "Shuffle Mode: {shuffle_mode}")?;
        }

        if let Some(loop_mode) = self.loop_mode {
            writeln!(f, "Loop Mode: {loop_mode}")?;
        }

        Ok(())
    }
}