use std::{
fs::File,
io::BufReader,
sync::Arc,
time::{Duration, Instant},
};
use color_eyre::{Result, eyre::bail};
use crossbeam::{
channel::{Receiver, Sender},
utils::Backoff,
};
use rodio::{Decoder, OutputStream, Sink};
use serde::{Deserialize, Serialize};
use tracing::{debug, error, info};
use crate::{database::models::tracks::Track, playback::notifications::now_playing};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum PlayerEvent {
TrackChanged(Track),
TrackProgress(Duration),
TrackPlayingStatus(bool),
CurrentVolume(f32),
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum PlayerCommand {
Create(Track, f32),
Play,
Pause,
Toggle,
Resume,
Clear,
Volume,
SetVolume(f32),
Position,
SetPosition(Duration),
}
pub struct Player {
_stream: OutputStream,
sink: Arc<Sink>,
player_event_tx: Sender<PlayerEvent>,
player_cmd_rx: Receiver<PlayerCommand>,
}
impl Player {
pub fn new(
player_event_tx: Sender<PlayerEvent>,
player_cmd_rx: Receiver<PlayerCommand>,
) -> Result<Self> {
let backoff = Backoff::new();
let timeout = Instant::now() + Duration::from_secs(10);
let (stream, sink) = loop {
match OutputStream::try_default() {
Ok((stream, handle)) => match Sink::try_new(&handle) {
Ok(sink) => break (stream, sink),
Err(err) => {
error!("Sink creation failed: {}", err);
}
},
Err(err) => {
error!("Audio device not available: {}", err);
}
}
if Instant::now() > timeout {
bail!("Timed out waiting for audio device");
}
backoff.snooze();
};
info!("Audio device found!");
Ok(Self {
_stream: stream,
sink: Arc::new(sink),
player_event_tx,
player_cmd_rx,
})
}
pub fn create(&self) {
while let Ok(command) = self.player_cmd_rx.recv() {
if let Err(err) = self.handle_command(&command) {
error!(
"Processing player command {:?} failed with error {}",
command, err
);
}
}
}
fn create_player_track(&self, track: &Track, volume: &f32) -> Result<()> {
if !self.sink.empty() {
self.sink.clear();
}
let track_file_path = track.path.clone();
debug!("Appended file {:?} to sink, and playing", track.path);
let file = File::open(track_file_path)?;
let decoder = Decoder::new(BufReader::new(file))?;
self.sink.append(decoder);
self.sink.set_volume(*volume);
self.sink.play();
self.player_event_tx
.send(PlayerEvent::TrackChanged(track.clone()))?;
now_playing(&track.name);
Ok(())
}
fn handle_command(&self, command: &PlayerCommand) -> Result<()> {
debug!("Player received command: {:?}", command);
match command {
PlayerCommand::Create(track, volume) => {
self.create_player_track(track, volume)?;
}
PlayerCommand::Play => {
self.sink.play();
}
PlayerCommand::Toggle => {
let is_paused = self.sink.is_paused();
if is_paused {
self.sink.play();
} else {
self.sink.pause();
}
self.player_event_tx
.send(PlayerEvent::TrackPlayingStatus(is_paused))?;
}
PlayerCommand::Pause => {
self.sink.pause();
}
PlayerCommand::Resume => {
if self.sink.is_paused() {
self.sink.play();
} else {
debug!("No track to resume");
}
}
PlayerCommand::Clear => {
self.sink.clear();
}
PlayerCommand::Volume => {
let volume = self.sink.volume();
self.player_event_tx
.send(PlayerEvent::CurrentVolume(volume))?;
}
PlayerCommand::SetVolume(volume_value) => {
self.sink.set_volume(*volume_value);
self.player_event_tx
.send(PlayerEvent::CurrentVolume(*volume_value))?;
}
PlayerCommand::Position => {
let position = self.sink.get_pos();
self.player_event_tx
.send(PlayerEvent::TrackProgress(position))?;
}
PlayerCommand::SetPosition(duration) => {
if let Err(err) = self.sink.try_seek(*duration) {
bail!("Failed to set duration: {:?}", err);
};
let position = self.sink.get_pos();
self.player_event_tx
.send(PlayerEvent::TrackProgress(position))?;
}
}
Ok(())
}
}