use std::{
sync::{
Arc, Mutex,
atomic::{AtomicU32, Ordering},
mpsc::{Sender, TryRecvError, channel},
},
thread,
};
use lunar_lib::{database::Database, debug, error, trace};
use selene_core::{database::LibraryDb, library::track::ResolvedTrack};
use crate::{
SHUTDOWN,
decoder::{DecoderEvent, DecoderHandle},
event_handler::EventHandler,
player::{Player, PlayerError, PlayerRequest, playback::DeviceConfig},
playlist::{AtomicPlaybackStatus, PlaybackStatus, Playlist},
wait,
};
impl Player {
fn new() -> Result<(Sender<PlayerRequest>, Self), PlayerError> {
let (player_tx, rx) = channel();
let event_tx = EventHandler::open(player_tx.clone())?;
let volume = Arc::new(AtomicU32::new(1.0_f32.to_bits()));
let playback_state = Arc::new(AtomicPlaybackStatus::new(PlaybackStatus::Stopped));
let playlist = Playlist::new(event_tx.clone());
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(),
)?;
Ok((
player_tx,
Self {
rx,
decoder_tx,
event_tx,
playlist,
device_config,
playback_state,
volume,
},
))
}
pub fn open() -> Result<Sender<PlayerRequest>, PlayerError> {
let (player_tx, player) = Self::new()?;
thread::spawn(move || {
if let Err(err) = player.run() {
error!("Engine failed with error: {err}");
}
SHUTDOWN.store(true, Ordering::Relaxed);
});
Ok(player_tx)
}
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");
loop {
if SHUTDOWN.load(Ordering::Relaxed) {
return Ok(());
}
loop {
match self.rx.try_recv() {
Ok(PlayerRequest::Command(command)) => self.run_command(*command)?,
Ok(PlayerRequest::DecoderEvent(DecoderEvent::PreloadConsumed)) => {
let db = LibraryDb::open().unwrap();
self.playlist.pop_next(&db)?;
if let Some(peek_next) = self.playlist.peek_next(&db)? {
self.preload(peek_next)?;
}
}
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(());
}
}
}
wait();
continue;
}
}
}
fn scrobble(track: Arc<ResolvedTrack>, start_time: u64) {
trace!(
"Received 'scrobble' for: {} | time: {}",
track.track.metadata.safe_title(),
start_time
);
#[cfg(feature = "lastfm")]
{
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, start_time, true).await {
error!("LastFM scrobble error: {err}")
}
});
}
}
}
fn now_playing(track: Arc<ResolvedTrack>) {
trace!(
"Received 'now_playing' for: {}",
track.track.metadata.safe_title()
);
#[cfg(feature = "lastfm")]
{
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).await {
error!("LastFM now_playing error: {err}")
}
});
}
}
}