use std::{
collections::VecDeque,
fmt::Display,
sync::mpsc::{Sender, channel},
};
use blake3::Hash;
use lunar_lib::trace;
use rand::{rng, seq::SliceRandom};
use serde::{Deserialize, Serialize};
use symphonia::core::errors::{Error as SymphoniaError, SeekErrorKind};
use crate::{
IpcActionError, IpcCommand, PlayerEvent, PlayerResponse,
daemon::DaemonError,
player::{OpenedDecoder, Player},
playlist::{LoopMode, Playable, PlayableTrack, ShuffleMode},
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum PlayerCommand {
SetPlaying(bool),
TogglePlaying,
Stop,
TracklistSeek {
index: isize,
increment: bool,
},
Seek {
seconds: f64,
increment: bool,
},
SetVolume {
volume: f32,
increment: bool,
},
QueueExtend(Vec<Playable>),
QueueSet {
tracks: VecDeque<PlayableTrack>,
expected_state: Hash,
},
QueueShuffle,
QueueClear,
PlaylistExtend(Vec<Playable>),
PlaylistSet {
playables: Vec<Playable>,
expected_state: Hash,
},
PlaylistClear,
GetState,
GetPlaying,
PlaylistGetLoopMode,
Flush,
PlaylistSetLoopMode {
loop_mode: LoopMode,
},
TracklistRebuild,
GetTime,
GetVolume,
QueueGet,
PlaylistGet,
PlaylistSetShuffleMode {
shuffle_mode: ShuffleMode,
},
PlaylistGetShuffleMode,
TracklistGet,
PlaylistGetState,
QueueGetState,
GetPlayingConnectorId,
GetIsPlaying,
Play {
playable: Playable,
},
}
impl Display for PlayerCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlayerCommand::SetPlaying(_) => f.write_str("SetPlaying"),
PlayerCommand::TogglePlaying => f.write_str("TogglePlaying"),
PlayerCommand::Stop => f.write_str("Stop"),
PlayerCommand::TracklistSeek { .. } => f.write_str("TracklistSeek"),
PlayerCommand::Seek { .. } => f.write_str("Seek"),
PlayerCommand::SetVolume { .. } => f.write_str("SetVolume"),
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::GetState => f.write_str("GetState"),
PlayerCommand::GetPlaying => f.write_str("GetPlaying"),
PlayerCommand::PlaylistGetLoopMode => f.write_str("PlaylistGetLoopMode"),
PlayerCommand::Flush => f.write_str("Flush"),
PlayerCommand::PlaylistSetLoopMode { .. } => f.write_str("PlaylistSetLoopMode"),
PlayerCommand::TracklistRebuild => f.write_str("TracklistRebuild"),
PlayerCommand::GetTime => f.write_str("GetTime"),
PlayerCommand::GetVolume => f.write_str("GetVolume"),
PlayerCommand::QueueGet => f.write_str("QueueGet"),
PlayerCommand::PlaylistGet => f.write_str("PlaylistGet"),
PlayerCommand::PlaylistSetShuffleMode { .. } => f.write_str("PlaylistSetShuffleMode"),
PlayerCommand::PlaylistGetShuffleMode => f.write_str("PlaylistGetShuffleMode"),
PlayerCommand::TracklistGet => f.write_str("TracklistGet"),
PlayerCommand::PlaylistGetState => f.write_str("PlaylistGetState"),
PlayerCommand::QueueGetState => f.write_str("QueueGetState"),
PlayerCommand::GetPlayingConnectorId => f.write_str("GetPlayingConnectorId"),
PlayerCommand::GetIsPlaying => f.write_str("GetIsPlaying"),
PlayerCommand::Play { .. } => f.write_str("Play"),
}
}
}
impl PlayerCommand {
pub fn from_ipc(value: IpcCommand) -> Option<Self> {
let command = match value {
IpcCommand::SetPlaying(playing) => PlayerCommand::SetPlaying(playing),
IpcCommand::TogglePlaying => PlayerCommand::TogglePlaying,
IpcCommand::TracklistSeek { index, increment } => {
PlayerCommand::TracklistSeek { index, increment }
}
IpcCommand::Seek { seconds, increment } => PlayerCommand::Seek { seconds, increment },
IpcCommand::SetVolume { volume, increment } => {
PlayerCommand::SetVolume { volume, increment }
}
IpcCommand::QueueExtend(playables) => PlayerCommand::QueueExtend(playables),
IpcCommand::QueueSet {
tracks: new_state,
expected_state,
} => PlayerCommand::QueueSet {
tracks: new_state,
expected_state,
},
IpcCommand::QueueShuffle => PlayerCommand::QueueShuffle,
IpcCommand::QueueClear => PlayerCommand::QueueClear,
IpcCommand::PlaylistExtend(playables) => PlayerCommand::PlaylistExtend(playables),
IpcCommand::PlaylistSet {
playables: new_state,
expected_state,
} => PlayerCommand::PlaylistSet {
playables: new_state,
expected_state,
},
IpcCommand::PlaylistClear => PlayerCommand::PlaylistClear,
IpcCommand::GetState => PlayerCommand::GetState,
IpcCommand::GetPlaying => PlayerCommand::GetPlaying,
IpcCommand::GetTime => PlayerCommand::GetTime,
IpcCommand::GetVolume => PlayerCommand::GetVolume,
IpcCommand::QueueGet => PlayerCommand::QueueGet,
IpcCommand::PlaylistGet => PlayerCommand::PlaylistGet,
IpcCommand::PlaylistSetShuffleMode { shuffle_mode } => {
PlayerCommand::PlaylistSetShuffleMode { shuffle_mode }
}
IpcCommand::PlaylistGetShuffleMode => PlayerCommand::PlaylistGetShuffleMode,
IpcCommand::TracklistRebuild => PlayerCommand::TracklistRebuild,
IpcCommand::PlaylistSetLoopMode { loop_mode } => {
PlayerCommand::PlaylistSetLoopMode { loop_mode }
}
IpcCommand::PlaylistGetLoopMode => PlayerCommand::PlaylistGetLoopMode,
IpcCommand::Flush => PlayerCommand::Flush,
IpcCommand::TracklistGet => PlayerCommand::TracklistGet,
IpcCommand::QueueGetState => PlayerCommand::QueueGetState,
IpcCommand::PlaylistGetState => PlayerCommand::PlaylistGetState,
IpcCommand::Play { playable } => PlayerCommand::Play { playable },
IpcCommand::Stop => PlayerCommand::Stop,
IpcCommand::Disconnect => return None,
};
Some(command)
}
}
pub enum PlayerRequest {
Quit,
Mpris {
command: Box<PlayerCommand>,
},
Ipc {
command: Box<PlayerCommand>,
callback: Sender<Result<PlayerResponse, IpcActionError>>,
},
}
pub trait PlayerTx {
fn request_ipc(&self, command: PlayerCommand) -> Result<PlayerResponse, IpcActionError>;
fn request_mpris(&self, command: PlayerCommand);
fn quit(&self);
}
impl PlayerTx for Sender<PlayerRequest> {
fn request_ipc(&self, command: PlayerCommand) -> Result<PlayerResponse, IpcActionError> {
let command_display = command.to_string();
trace!("IPC Request: Sending command '{command_display}' to the player");
let (tx, rx) = channel();
self.send(PlayerRequest::Ipc {
command: Box::new(command),
callback: tx,
})
.map_err(|_| IpcActionError::Disconnected)?;
let response = rx
.recv()
.map_err(|_| IpcActionError::Disconnected)
.flatten();
trace!(
"Player responded to '{command_display}': {response}",
response = response
.as_ref()
.map(PlayerResponse::to_string)
.unwrap_or_else(IpcActionError::to_string)
);
response
}
fn request_mpris(&self, command: PlayerCommand) {
trace!("MPRIS Request: Sending command '{command}' to the player");
let _ = self.send(PlayerRequest::Mpris {
command: Box::new(command),
});
}
fn quit(&self) {
let _ = self.send(PlayerRequest::Quit);
}
}
impl Player {
pub(crate) fn run_command(
&mut self,
command: PlayerCommand,
) -> Result<Result<PlayerResponse, IpcActionError>, DaemonError> {
match command {
PlayerCommand::Flush => (),
PlayerCommand::GetState => todo!(),
PlayerCommand::SetPlaying(playing) => self.set_is_playing(playing),
PlayerCommand::GetIsPlaying => {
return Ok(Ok(PlayerResponse::IsPlaying(self.is_playing)));
}
PlayerCommand::TogglePlaying => {
self.set_is_playing(!self.is_playing);
return Ok(Ok(PlayerResponse::IsPlaying(self.is_playing)));
}
PlayerCommand::TracklistSeek { index, increment } => {
let seeked_to = self.playlist.tracklist_seek(index, increment);
self.replace_decoders()?;
return Ok(Ok(PlayerResponse::TracklistPosition {
index: seeked_to,
max: self.playlist.tracklist().len(),
}));
}
PlayerCommand::PlaylistSet {
expected_state,
playables: new_state,
} => {
if expected_state == self.playlist.playlist_hash() {
self.playlist.set_playlist(new_state);
} else {
return Ok(Err(IpcActionError::StateMismatch));
}
}
PlayerCommand::PlaylistExtend(playable_groups) => {
self.playlist.extend(playable_groups);
}
PlayerCommand::PlaylistGet => {
return Ok(Ok(PlayerResponse::Playlist(
self.playlist.playlist().to_vec(),
)));
}
PlayerCommand::PlaylistClear => {
self.playlist.clear();
}
PlayerCommand::PlaylistSetShuffleMode { shuffle_mode } => {
self.playlist.shuffle_mode = shuffle_mode;
self.playlist.rebuild_tracklist();
self.event(PlayerEvent::ShuffleModeChanged { shuffle_mode });
return Ok(Ok(PlayerResponse::Tracklist(
self.playlist.tracklist().to_vec(),
)));
}
PlayerCommand::PlaylistGetShuffleMode => {
return Ok(Ok(PlayerResponse::ShuffleMode(self.playlist.shuffle_mode)));
}
PlayerCommand::PlaylistGetLoopMode => {
return Ok(Ok(PlayerResponse::LoopMode(self.playlist.loop_mode)));
}
PlayerCommand::PlaylistSetLoopMode { loop_mode } => {
self.playlist.loop_mode = loop_mode;
self.event(PlayerEvent::LoopModeChanged { loop_mode });
}
PlayerCommand::QueueSet {
expected_state,
tracks,
} => {
if expected_state == self.playlist.queue_hash() {
self.playlist.queue = tracks;
} else {
return Ok(Err(IpcActionError::StateMismatch));
}
}
PlayerCommand::QueueGet => {
return Ok(Ok(PlayerResponse::Queue(
self.playlist.queue.clone().into(),
)));
}
PlayerCommand::QueueExtend(playable_groups) => {
self.playlist.queue_extend(playable_groups);
}
PlayerCommand::QueueShuffle => {
let mut shuffled: Vec<_> = self.playlist.queue.drain(..).collect();
shuffled.shuffle(&mut rng());
self.playlist.queue = shuffled.into();
}
PlayerCommand::QueueClear => self.playlist.queue.clear(),
PlayerCommand::TracklistGet => {
return Ok(Ok(PlayerResponse::Tracklist(
self.playlist.tracklist().to_vec(),
)));
}
PlayerCommand::TracklistRebuild => {
self.playlist.rebuild_tracklist();
return Ok(Ok(PlayerResponse::Tracklist(
self.playlist.tracklist().to_vec(),
)));
}
PlayerCommand::GetPlaying => {
let playing = self
.decoder
.as_ref()
.map(|d| Box::new(d.decoded_from.clone()));
return Ok(Ok(PlayerResponse::Playing(playing)));
}
PlayerCommand::Play { playable } => {
self.playlist.set_playlist(std::iter::once(playable));
self.replace_decoders()?;
self.set_is_playing(true);
}
PlayerCommand::Stop => {
self.decoder = None;
self.event(PlayerEvent::CurrentlyPlayingChanged {
currently_playing: None,
});
self.set_is_playing(false);
self.preload = None;
}
PlayerCommand::SetVolume { volume, increment } => {
let vol = self.set_volume(volume, increment);
return Ok(Ok(PlayerResponse::Volume(vol)));
}
PlayerCommand::GetVolume => {
return Ok(Ok(PlayerResponse::Volume(self.volume)));
}
PlayerCommand::Seek { seconds, increment } => {
if let Some(opened_decoder) = &mut self.decoder {
match opened_decoder.seek(seconds, increment) {
Ok(time) => {
self.event(PlayerEvent::SeekOccured { time });
return Ok(Ok(PlayerResponse::Time(Some(time))));
}
Err(SymphoniaError::SeekError(SeekErrorKind::OutOfRange)) => {
self.consume_preload()?;
return Ok(Ok(PlayerResponse::Time(None)));
}
Err(other) => return Err(other.into()),
};
};
}
PlayerCommand::GetTime => {
return Ok(Ok(PlayerResponse::Time(
self.decoder.as_ref().map(OpenedDecoder::time),
)));
}
PlayerCommand::PlaylistGetState => {
return Ok(Ok(PlayerResponse::PlaylistState(
self.playlist.playlist_hash(),
)));
}
PlayerCommand::QueueGetState => {
return Ok(Ok(PlayerResponse::QueueState(self.playlist.queue_hash())));
}
PlayerCommand::GetPlayingConnectorId => {
todo!()
}
}
trace!("Player finished running command");
Ok(Ok(PlayerResponse::Done))
}
}