use std::{
collections::HashMap,
error::Error,
sync::{atomic::Ordering, Arc},
time::Duration,
};
use tracing::{error, info, warn};
use crate::{
playlist::{self, Playlist},
songs::{self, Song, Songs},
};
use super::{Player, PlaylistDirection};
impl Player {
pub async fn play_song_from(
&self,
song_name: &str,
start_time: Duration,
) -> Result<Option<Arc<Song>>, Box<dyn Error>> {
if !*self.init_done_tx.borrow() {
return Err("Hardware is still initializing".into());
}
let mut join = self.join.lock().await;
if join.is_some() {
info!("Player is already playing a song.");
return Ok(None);
}
let all_songs = self
.get_all_songs_playlist()
.ok_or_else(|| -> Box<dyn Error> { "all_songs playlist not available".into() })?;
if all_songs.navigate_to(song_name).is_none() {
return Err(format!("Song '{}' not found", song_name).into());
}
*self.active_playlist.write() = "all_songs".to_string();
self.play_from_locked(start_time, &mut join).await
}
pub(super) async fn navigate(&self, direction: PlaylistDirection) -> Option<Arc<Song>> {
let join = self.join.lock().await;
if join.is_some() {
if self.is_current_song_looping() {
info!("Breaking out of song loop via {} navigation.", direction);
self.fade_out_current_audio();
self.loop_break.store(true, Ordering::Relaxed);
if let Some(ref handles) = *join {
handles.cancel.cancel();
}
return self.get_playlist().current();
}
let current = self.get_playlist().current();
if let Some(ref song) = current {
info!(
current_song = song.name(),
"Can't go to {}, player is active.", direction
);
}
return current;
}
let playlist = self.get_playlist();
let song = match direction {
PlaylistDirection::Next => playlist.next()?,
PlaylistDirection::Prev => playlist.prev()?,
};
self.emit_song_change(&song);
drop(join);
self.load_song_samples(&song);
Some(song)
}
pub async fn next(&self) -> Option<Arc<Song>> {
self.navigate(PlaylistDirection::Next).await
}
pub async fn prev(&self) -> Option<Arc<Song>> {
self.navigate(PlaylistDirection::Prev).await
}
pub async fn switch_to_playlist(&self, name: &str) -> Result<(), String> {
{
let join = self.join.lock().await;
if join.is_some() {
if let Some(current) = self.get_playlist().current() {
info!(
current_song = current.name(),
"Can't switch to {}, player is active.", name
);
}
return Err("Cannot switch playlist while playing".to_string());
}
}
{
let playlists = self.playlists.read();
if !playlists.contains_key(name) {
return Err(format!("Playlist '{}' not found", name));
}
}
*self.active_playlist.write() = name.to_string();
if name != "all_songs" {
*self.persisted_playlist.write() = name.to_string();
if let Some(store) = self.config_store() {
if let Err(e) = store.set_active_playlist(name.to_string()).await {
warn!("Failed to persist active playlist: {}", e);
}
}
}
if let Some(song) = self.get_playlist().current() {
self.emit_song_change(&song);
}
Ok(())
}
pub fn persisted_playlist_name(&self) -> String {
self.persisted_playlist.read().clone()
}
pub fn list_playlists(&self) -> Vec<String> {
let playlists = self.playlists.read();
let mut names: Vec<String> = playlists.keys().cloned().collect();
names.sort();
names
}
pub fn playlists_snapshot(&self) -> HashMap<String, Arc<Playlist>> {
self.playlists.read().clone()
}
pub fn get_all_songs_playlist(&self) -> Option<Arc<Playlist>> {
let playlists = self.playlists.read();
let result = playlists.get("all_songs").cloned();
if result.is_none() {
error!("all_songs playlist missing from player state");
}
result
}
pub fn get_playlist(&self) -> Arc<Playlist> {
let name = self.active_playlist.read().clone();
let playlists = self.playlists.read();
match playlists.get(&name).or_else(|| playlists.get("all_songs")) {
Some(playlist) => playlist.clone(),
None => {
error!("No playlist available (not even all_songs) — returning empty fallback");
drop(playlists);
let empty_songs = Arc::new(Songs::new(std::collections::HashMap::new()));
playlist::from_songs(empty_songs).expect("empty playlist construction cannot fail")
}
}
}
pub fn songs(&self) -> Arc<Songs> {
let playlists = self.playlists.read();
match playlists.get("all_songs") {
Some(playlist) => playlist.registry().clone(),
None => {
error!("all_songs playlist missing from player state");
Arc::new(Songs::new(std::collections::HashMap::new()))
}
}
}
pub fn reload_songs(
&self,
songs_path: &std::path::Path,
playlists_dir: Option<&std::path::Path>,
legacy_playlist_path: Option<&std::path::Path>,
) {
let new_songs = match songs::get_all_songs(songs_path) {
Ok(s) => s,
Err(e) => {
warn!("Failed to rescan songs: {}", e);
return;
}
};
let new_playlists =
match super::load_playlists(playlists_dir, legacy_playlist_path, new_songs.clone()) {
Ok(p) => p,
Err(e) => {
warn!("Failed to rebuild playlists: {}", e);
return;
}
};
{
let mut active = self.active_playlist.write();
if !new_playlists.contains_key(active.as_str()) {
*active = "all_songs".to_string();
}
}
*self.playlists.write() = new_playlists;
info!("Reloaded song state");
}
}