use std::{
fmt::{Debug, Display},
sync::{atomic::Ordering, mpsc::Sender},
};
use blake3::Hash;
use selene_core::{
database::LibraryDb,
library::{collectable::Collectable, track::TrackId},
};
use serde::{Deserialize, Serialize};
use crate::{
LoopMode, ShuffleMode,
config::daemon_config,
decoder::{DecoderEvent, DecoderTx},
player::{PlaybackStatus, Player, PlayerError, PlayerQueryFlags, QueryResult},
playlist::{Playable, PlayingTrack},
};
#[derive(Debug, Clone)]
pub(crate) enum PlayerCommand {
Flush {
callback: Sender<()>,
},
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>>>,
},
QueueExtend {
with: Vec<Playable>,
},
QueueSet {
to: Vec<Playable>,
expected_state: Hash,
callback: Option<Sender<bool>>,
},
QueueShuffle,
QueueClear,
PlaylistExtend {
items: Vec<Playable>,
},
PlaylistSet {
playables: Vec<Playable>,
expected_state: Hash,
callback: Option<Sender<bool>>,
},
PlaylistClear,
PlaylistSetShuffleMode {
shuffle_mode: ShuffleMode,
},
PlaylistSetLoopMode {
loop_mode: LoopMode,
},
TracklistSeek {
index: isize,
increment: bool,
callback: Option<Sender<Option<usize>>>,
},
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::GetState { .. } => f.write_str("GetState"),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub enum PlayerEvent {
CurrentlyPlayingChanged {
currently_playing: PlayingTrack,
},
PlaybackIsPlayingChanged {
is_playing: bool,
changed_at: f64,
},
PlaybackStopped,
ShuffleModeChanged {
shuffle_mode: ShuffleMode,
},
LoopModeChanged {
loop_mode: LoopMode,
},
VolumeChanged {
volume: f32,
},
SeekOccured {
time: f64,
},
QueueChanged {
queue: Vec<TrackId>,
},
PlaylistChanged {
playlist: Vec<Collectable>,
},
TracklistChanged {
tracklist: Vec<TrackId>,
position: Option<usize>,
},
Shutdown,
}
impl Display for PlayerEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlayerEvent::CurrentlyPlayingChanged { .. } => f.write_str("CurrentlyPlayingChanged"),
PlayerEvent::PlaybackIsPlayingChanged { .. } => f.write_str("PlaybackIsPlayingChanged"),
PlayerEvent::PlaybackStopped => f.write_str("PlaybackStopped"),
PlayerEvent::ShuffleModeChanged { .. } => f.write_str("ShuffleModeChanged"),
PlayerEvent::LoopModeChanged { .. } => f.write_str("LoopModeChanged"),
PlayerEvent::VolumeChanged { .. } => f.write_str("VolumeChanged"),
PlayerEvent::SeekOccured { .. } => f.write_str("SeekOccured"),
PlayerEvent::QueueChanged { .. } => f.write_str("QueueChanged"),
PlayerEvent::PlaylistChanged { .. } => f.write_str("PlaylistChanged"),
PlayerEvent::TracklistChanged { .. } => f.write_str("TracklistChanged"),
PlayerEvent::Shutdown => f.write_str("Shutdown"),
}
}
}
pub(crate) enum PlayerRequest {
DecoderEvent(DecoderEvent),
Command(Box<PlayerCommand>),
}
pub(crate) 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,
db: &LibraryDb,
) -> Result<(), PlayerError> {
match command {
PlayerCommand::Flush { callback } => {
let _ = callback.send(());
}
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()?;
if let Some((track, pos)) = currently_playing.map(|c| (c.source, c.position)) {
query.currently_playing = Some(Some(track.id()));
query.currently_playing_index = Some(pos);
} else {
query.currently_playing = Some(None);
query.currently_playing_index = Some(None);
}
}
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()?);
}
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());
}
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);
}
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);
}
}
PlayerCommand::QueueSet {
expected_state,
to,
callback,
} => {
let equals = expected_state == self.playlist.queue_hash();
if equals {
self.playlist.set_queue(&to);
self.try_preload(db)?;
}
if let Some(callback) = callback {
let _ = callback.send(equals);
}
}
PlayerCommand::QueueExtend { with } => {
self.playlist.extend_queue(&with);
self.try_preload(db)?;
}
PlayerCommand::QueueShuffle => {
self.playlist.shuffle_queue();
self.try_preload(db)?;
}
PlayerCommand::QueueClear => {
self.playlist.clear_queue();
self.try_preload(db)?;
}
PlayerCommand::PlaylistSet {
expected_state,
playables,
callback,
} => {
let equals = expected_state == self.playlist.playlist_hash();
if equals {
self.playlist.set_playlist(playables);
self.try_preload(db)?;
}
if let Some(callback) = callback {
let _ = callback.send(equals);
}
}
PlayerCommand::PlaylistExtend { items } => {
self.playlist.extend_playlist(items);
self.try_preload(db)?;
}
PlayerCommand::PlaylistClear => {
self.playlist.clear();
self.try_preload(db)?;
}
PlayerCommand::PlaylistSetShuffleMode { shuffle_mode } => {
self.playlist.set_shuffle_mode(shuffle_mode);
self.try_preload(db)?;
}
PlayerCommand::PlaylistSetLoopMode { loop_mode } => {
self.playlist.set_loop_mode(loop_mode);
}
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 => {
if matches!(self.playlist.loop_mode(), LoopMode::RepeatTrack) {
self.decoder_tx.seek(0.0, false)?;
} else {
self.decoder_tx.skip()?;
}
}
PlayerCommand::Previous => {
if matches!(self.playlist.loop_mode(), LoopMode::RepeatTrack) {
self.decoder_tx.seek(0.0, false)?;
} else if let Some(restart_on_previous_thresh) =
daemon_config().playback.previous_restart_thresh
&& let Some(time) = self.decoder_tx.get_time()?
&& time >= restart_on_previous_thresh
{
self.decoder_tx.seek(0.0, false)?;
} else {
self.playlist.tracklist_seek(-1, true);
self.replace_decoders(false)?;
}
}
}
Ok(())
}
}