selene-daemon 0.5.7

Official music player daemon for Selene
Documentation
use std::{
    fmt::{Debug, Display},
    sync::{atomic::Ordering, mpsc::Sender},
};

use blake3::Hash;

use crate::{
    PlayerEvent,
    decoder::{DecoderEvent, DecoderTx},
    event_handler::EventTx,
    player::{Player, PlayerError, PlayerQueryFlags, QueryResult},
    playlist::{LoopMode, Playable, PlaybackStatus, ShuffleMode},
};

#[derive(Debug, Clone)]
pub enum PlayerCommand {
    // Generic
    Flush {
        callback: Sender<()>,
    },

    // Playback
    Play {
        playables: Vec<Playable>,
    },
    Next,
    Previous,
    SetIsPlaying {
        is_playing: bool,
    },
    TogglePlaying {
        callback: Option<Sender<PlaybackStatus>>,
    },
    Stop,
    SetVolume {
        volume: f32,
        increment: bool,
        callback: Option<Sender<f32>>,
    },
    Seek {
        seconds: f64,
        increment: bool,
        callback: Option<Sender<Option<f64>>>,
    },

    // The override queue
    QueueExtend {
        with: Vec<Playable>,
    },
    QueueSet {
        to: Vec<Playable>,
        expected_state: Hash,
        callback: Option<Sender<bool>>,
    },
    QueueShuffle,
    QueueClear,

    // The playlist
    PlaylistExtend {
        items: Vec<Playable>,
    },
    PlaylistSet {
        playables: Vec<Playable>,
        expected_state: Hash,
        callback: Option<Sender<bool>>,
    },
    PlaylistClear,
    PlaylistSetShuffleMode {
        shuffle_mode: ShuffleMode,
    },
    PlaylistSetLoopMode {
        loop_mode: LoopMode,
    },

    // Tracklist
    TracklistSeek {
        index: isize,
        increment: bool,
        callback: Option<Sender<Option<usize>>>,
    },
    TracklistRebuild,

    // Queries
    GetState {
        flags: PlayerQueryFlags,
        callback: Sender<QueryResult>,
    },
}

impl Display for PlayerCommand {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PlayerCommand::Flush { .. } => f.write_str("Flush"),
            PlayerCommand::Play { .. } => f.write_str("Play"),
            PlayerCommand::Next => f.write_str("Next"),
            PlayerCommand::Previous => f.write_str("Previous"),
            PlayerCommand::SetIsPlaying { .. } => f.write_str("SetIsPlaying"),
            PlayerCommand::TogglePlaying { .. } => f.write_str("TogglePlaying"),
            PlayerCommand::Stop => f.write_str("Stop"),
            PlayerCommand::SetVolume { .. } => f.write_str("SetVolume"),
            PlayerCommand::Seek { .. } => f.write_str("Seek"),
            PlayerCommand::QueueExtend { .. } => f.write_str("QueueExtend"),
            PlayerCommand::QueueSet { .. } => f.write_str("QueueSet"),
            PlayerCommand::QueueShuffle => f.write_str("QueueShuffle"),
            PlayerCommand::QueueClear => f.write_str("QueueClear"),
            PlayerCommand::PlaylistExtend { .. } => f.write_str("PlaylistExtend"),
            PlayerCommand::PlaylistSet { .. } => f.write_str("PlaylistSet"),
            PlayerCommand::PlaylistClear => f.write_str("PlaylistClear"),
            PlayerCommand::PlaylistSetShuffleMode { .. } => f.write_str("PlaylistSetShuffleMode"),
            PlayerCommand::PlaylistSetLoopMode { .. } => f.write_str("PlaylistSetLoopMode"),
            PlayerCommand::TracklistSeek { .. } => f.write_str("TracklistSeek"),
            PlayerCommand::TracklistRebuild => f.write_str("TracklistRebuild"),
            PlayerCommand::GetState { .. } => f.write_str("GetState"),
        }
    }
}

pub enum PlayerRequest {
    DecoderEvent(DecoderEvent),
    Command(Box<PlayerCommand>),
}

pub trait PlayerTx {
    fn decoder_event(&self, event: DecoderEvent) -> Result<(), PlayerError>;
    fn command(&self, command: PlayerCommand) -> Result<(), PlayerError>;
}

impl PlayerTx for Sender<PlayerRequest> {
    fn decoder_event(&self, event: DecoderEvent) -> Result<(), PlayerError> {
        self.send(PlayerRequest::DecoderEvent(event))?;
        Ok(())
    }

    fn command(&self, command: PlayerCommand) -> Result<(), PlayerError> {
        self.send(PlayerRequest::Command(Box::new(command)))?;
        Ok(())
    }
}

impl Player {
    pub(crate) fn run_command(&mut self, command: PlayerCommand) -> Result<(), PlayerError> {
        match command {
            // Generic
            PlayerCommand::Flush { callback } => {
                let _ = callback.send(());
            }

            // Queries
            PlayerCommand::GetState { flags, callback } => {
                let mut query = QueryResult::default();

                if flags.contains(PlayerQueryFlags::PLAYBACK_STATE) {
                    query.playback_state = Some(self.playback_state.load(Ordering::SeqCst));
                }

                if flags.contains(PlayerQueryFlags::CURRENTLY_PLAYING) {
                    let currently_playing = self.decoder_tx.get_playing()?;
                    query.currently_playing = Some(currently_playing.into());
                }

                if flags.contains(PlayerQueryFlags::VOLUME) {
                    query.volume = Some(f32::from_bits(self.volume.load(Ordering::SeqCst)));
                }

                if flags.contains(PlayerQueryFlags::TIME) {
                    query.time = Some(self.decoder_tx.get_time()?.into());
                }

                if flags.contains(PlayerQueryFlags::QUEUE) {
                    query.queue = Some(self.playlist.queue().iter().map(|t| t.id()).collect());
                }

                if flags.contains(PlayerQueryFlags::QUEUE_STATE) {
                    query.queue_state = Some(self.playlist.queue_hash());
                }

                if flags.contains(PlayerQueryFlags::PLAYLIST) {
                    query.playlist = Some(
                        self.playlist
                            .playlist()
                            .iter()
                            .map(Playable::to_collectable)
                            .collect(),
                    );
                }

                if flags.contains(PlayerQueryFlags::TRACKLIST) {
                    query.tracklist =
                        Some(self.playlist.tracklist().iter().map(|t| t.id()).collect());
                }

                if flags.contains(PlayerQueryFlags::TRACKLIST_POSITION) {
                    query.tracklist_position = Some(self.playlist.position().into());
                }

                if flags.contains(PlayerQueryFlags::PLAYLIST_STATE) {
                    query.playlist_state = Some(self.playlist.playlist_hash());
                }

                if flags.contains(PlayerQueryFlags::SHUFFLE_MODE) {
                    query.shuffle_mode = Some(self.playlist.shuffle_mode());
                }

                if flags.contains(PlayerQueryFlags::LOOP_MODE) {
                    query.loop_mode = Some(self.playlist.loop_mode);
                }

                let _ = callback.send(query);
            }

            // Playback
            PlayerCommand::Play {
                playables: playable,
            } => {
                self.playlist.set_playlist(playable);

                let loaded = self.replace_decoders(true)?;

                if loaded {
                    self.decoder_tx.set_playing(true)?;
                }
            }
            PlayerCommand::SetIsPlaying { is_playing } => {
                self.decoder_tx.set_playing(is_playing)?;
            }
            PlayerCommand::TogglePlaying { callback } => {
                let state = self.decoder_tx.toggle_playing()?;
                if let Some(callback) = callback {
                    let _ = callback.send(state);
                }
            }
            PlayerCommand::Stop => {
                self.decoder_tx.stop()?;
            }
            PlayerCommand::SetVolume {
                volume,
                increment,
                callback,
            } => {
                let vol = self.set_volume(volume, increment);
                if let Some(callback) = callback {
                    let _ = callback.send(vol);
                }
            }
            PlayerCommand::Seek {
                seconds,
                increment,
                callback,
            } => {
                let time = self.decoder_tx.seek(seconds, increment)?;
                if let Some(callback) = callback {
                    let _ = callback.send(time);
                }
            }

            // Queue
            PlayerCommand::QueueSet {
                expected_state,
                to,
                callback,
            } => {
                let equals = expected_state == self.playlist.queue_hash();
                if equals {
                    self.playlist.set_queue(&to);
                }
                if let Some(callback) = callback {
                    let _ = callback.send(equals);
                }
            }
            PlayerCommand::QueueExtend { with } => self.playlist.extend_queue(&with),
            PlayerCommand::QueueShuffle => self.playlist.shuffle_queue(),
            PlayerCommand::QueueClear => self.playlist.clear_queue(),

            // Playlist
            PlayerCommand::PlaylistSet {
                expected_state,
                playables,
                callback,
            } => {
                let equals = expected_state == self.playlist.playlist_hash();
                if equals {
                    self.playlist.set_playlist(playables);
                }

                if let Some(callback) = callback {
                    let _ = callback.send(equals);
                }
            }
            PlayerCommand::PlaylistExtend { items } => {
                self.playlist.extend_playlist(items);
            }
            PlayerCommand::PlaylistClear => self.playlist.clear(),
            PlayerCommand::PlaylistSetShuffleMode { shuffle_mode } => {
                self.playlist.set_shuffle_mode(shuffle_mode)
            }
            PlayerCommand::PlaylistSetLoopMode { loop_mode } => {
                self.playlist.loop_mode = loop_mode;
                self.event_tx
                    .event(PlayerEvent::LoopModeChanged { loop_mode });
            }

            // Tracklist
            PlayerCommand::TracklistSeek {
                index,
                increment,
                callback,
            } => {
                let position = self.playlist.tracklist_seek(index, increment);
                self.replace_decoders(false)?;
                if let Some(callback) = callback {
                    let _ = callback.send(position);
                }
            }
            PlayerCommand::Next => {
                self.decoder_tx.skip()?;
            }
            PlayerCommand::Previous => {
                self.playlist.tracklist_seek(-1, true);
                self.replace_decoders(false)?;
            }
            PlayerCommand::TracklistRebuild => {
                todo!()
            }
        }
        Ok(())
    }
}