selene-daemon 0.6.0

Official music player daemon for Selene
Documentation
use std::{
    sync::{
        Arc, Mutex,
        atomic::{AtomicBool, AtomicU32, Ordering},
        mpsc::{Sender, TryRecvError, channel},
    },
    time::Instant,
};

use lunar_lib::{database::Database, debug, error};
use selene_core::{database::LibraryDb, library::track::ResolvedTrack};

use crate::{
    LoopMode, SHUTDOWN, STATE_CHANGED,
    config::daemon_config,
    decoder::{DecoderEvent, DecoderHandle},
    event_handler::EventHandler,
    player::{
        AtomicPlaybackStatus, PlaybackStatus, Player, PlayerError, PlayerRequest,
        playback::DeviceConfig, state::PlayerState,
    },
    playlist::Playlist,
    wait,
};

// Thread start impls
impl Player {
    fn new() -> Result<(Sender<PlayerRequest>, Self), PlayerError> {
        let state = PlayerState::load();

        let (player_tx, rx) = channel();

        let event_tx = EventHandler::open(player_tx.clone())?;

        state.trigger_events(&event_tx);

        let volume = Arc::new(AtomicU32::new(state.volume.to_bits()));
        let playback_state = Arc::new(AtomicPlaybackStatus::new(PlaybackStatus::Stopped));
        let looping = Arc::new(AtomicBool::new(matches!(
            state.loop_mode,
            LoopMode::RepeatTrack
        )));

        let playlist = Playlist::new(event_tx.clone(), looping.clone(), &state)?;

        let device_config = Arc::new(Mutex::new(DeviceConfig::default_config()?));

        let decoder_tx = DecoderHandle::open(
            player_tx.clone(),
            event_tx.clone(),
            device_config.clone(),
            playback_state.clone(),
            volume.clone(),
            looping,
        )?;

        Ok((
            player_tx,
            Self {
                rx,
                decoder_tx,

                event_tx,

                playlist,
                device_config,
                playback_state,

                volume,
            },
        ))
    }

    /// Opens a new [`Player`] and runs it on the current thread
    pub fn start() -> Result<(), PlayerError> {
        let (_, player) = Self::new()?;
        player.run()?;
        SHUTDOWN.store(true, Ordering::Relaxed);
        Ok(())
    }

    fn run(mut self) -> Result<(), PlayerError> {
        debug!("Engine thread started");

        self.replace_decoders(false)?;

        let mut last_state_save = Instant::now();

        loop {
            if SHUTDOWN.load(Ordering::Relaxed) {
                return Ok(());
            }

            loop {
                match self.rx.try_recv() {
                    Ok(PlayerRequest::Command(command)) => self.run_command(*command)?,
                    Ok(PlayerRequest::DecoderEvent(DecoderEvent::PreloadConsumed)) => {
                        let db = LibraryDb::open().unwrap();
                        self.playlist.pop_next(&db)?;
                        if let Some(peek_next) = self.playlist.peek_next(&db)? {
                            self.preload(peek_next)?;
                        }
                    }
                    Ok(PlayerRequest::DecoderEvent(DecoderEvent::Scrobble {
                        track,
                        start_time,
                    })) => {
                        scrobble(track, start_time);
                    }
                    Ok(PlayerRequest::DecoderEvent(DecoderEvent::NowPlaying { track })) => {
                        now_playing(track);
                    }
                    Err(TryRecvError::Empty) => break,
                    Err(TryRecvError::Disconnected) => {
                        return Ok(());
                    }
                }
            }

            if STATE_CHANGED.load(Ordering::Relaxed)
                && last_state_save.elapsed().as_secs_f64() > 5.0
            {
                last_state_save = Instant::now();
                let _ = PlayerState::save(&self);
                STATE_CHANGED.store(false, Ordering::Release);
            }

            wait();
            continue;
        }
    }
}

fn scrobble(track: Arc<ResolvedTrack>, start_time: u64) {
    if !daemon_config().main.scrobbling {
        return;
    }

    #[cfg(feature = "lastfm")]
    {
        use selene_core::scrobbling::lastfm::last_fm_client;

        if let Some(last_fm_client) = last_fm_client() {
            tokio::runtime::Handle::current().spawn(async move {
                if let Err(err) = last_fm_client.scrobble(&track, start_time, true).await {
                    error!("LastFM scrobble error: {err}")
                }
            });
        }
    }
}

fn now_playing(track: Arc<ResolvedTrack>) {
    if !daemon_config().main.scrobbling {
        return;
    }

    #[cfg(feature = "lastfm")]
    {
        use selene_core::scrobbling::lastfm::last_fm_client;

        if let Some(last_fm_client) = last_fm_client() {
            tokio::runtime::Handle::current().spawn(async move {
                if let Err(err) = last_fm_client.now_playing(&track).await {
                    error!("LastFM now_playing error: {err}")
                }
            });
        }
    }
}