selene-daemon 0.8.1

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

use bitflags::bitflags;
use blake3::Hash;
use lunar_lib::{
    config::ConfigError,
    database::{DatabaseError, DbHandle},
};
use rubato::ResamplerConstructionError;
use selene_core::{
    database::LibraryDb,
    library::{collectable::Collectable, track::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::{
    LoopMode, ShuffleMode, changed_state,
    decoder::{DecoderCommand, DecoderTx},
    event_handler::EventTx,
    player::playback::{CpalError, DeviceConfig},
    playlist::Playlist,
};

mod opened_decoder;
pub use opened_decoder::*;

pub mod playback;

mod ipc;
pub use ipc::*;

mod state;
pub(crate) use state::*;

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

        changed_state();

        volume
    }

    fn try_preload(&self, db: &LibraryDb) -> Result<(), PlayerError> {
        if let Some(playable) = self.playlist.peek_next(db)? {
            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)?;
        } else {
            self.decoder_tx.unpreload()?;
        }

        Ok(())
    }

    fn replace_decoders(&mut self, pop: bool) -> Result<bool, PlayerError> {
        let db = DbHandle::<LibraryDb>::open()?;
        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;
    }
}

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

    pub currently_playing: Option<Option<TrackId>>,
    pub currently_playing_index: Option<Option<usize>>,

    pub volume: Option<f32>,
    pub time: Option<Option<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<Option<usize>>,

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

#[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);
    }
}