use std::{
cell::LazyCell,
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU32, Ordering},
mpsc::{Sender, TryRecvError, channel},
},
time::Instant,
};
use lunar_lib::{database::DbHandle, debug};
use selene_core::database::LibraryDb;
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::{PlayingTrack, Playlist},
wait,
};
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,
},
))
}
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(());
}
{
let db = LazyCell::new(|| DbHandle::<LibraryDb>::open().unwrap());
loop {
match self.rx.try_recv() {
Ok(PlayerRequest::Command(command)) => self.run_command(*command, &db)?,
Ok(PlayerRequest::DecoderEvent(DecoderEvent::PreloadConsumed)) => {
self.playlist.pop_next(&db)?;
self.try_preload(&db)?;
}
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;
}
}
}
#[allow(unused_variables)]
fn scrobble(track: PlayingTrack, start_time: u64) {
if !daemon_config().main.scrobbling {
return;
}
#[cfg(feature = "lastfm")]
{
use lunar_lib::error;
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.source, start_time, true)
.await
{
error!("LastFM scrobble error: {err}");
}
});
}
}
}
#[allow(unused_variables)]
fn now_playing(track: PlayingTrack) {
if !daemon_config().main.scrobbling {
return;
}
#[cfg(feature = "lastfm")]
{
use lunar_lib::error;
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.source).await {
error!("LastFM now_playing error: {err}");
}
});
}
}
}