mod decode; mod engine; mod eq; mod leveler; mod queue; mod resample; mod spectrum; pub mod types; use std::path::PathBuf; use std::sync::mpsc::{Receiver, Sender, TryRecvError}; pub use types::*; pub use spectrum::SPECTRUM_BANDS; /// Хэндл для управления плеером из UI-слоя. Само воспроизведение работает /// в фоновом потоке; здесь только неблокирующая отправка команд и приём событий. pub struct Player { cmd_tx: Sender, evt_rx: Receiver, disconnected_reported: std::cell::Cell, } impl Player { pub fn new() -> Self { let (cmd_tx, evt_rx) = engine::spawn(); Player { cmd_tx, evt_rx, disconnected_reported: std::cell::Cell::new(false), } } fn send(&self, cmd: PlayerCommand) -> bool { let ok = self.cmd_tx.send(cmd).is_ok(); if !ok { self.disconnected_reported.set(true); } ok } /// true, если фоновый поток движка ещё жив (канал команд не разорван). pub fn is_alive(&self) -> bool { !self.disconnected_reported.get() } pub fn open_single(&self, path: PathBuf) -> bool { self.send(PlayerCommand::OpenPath(path, OpenMode::SingleTrack)) } pub fn open_album(&self, path: PathBuf) -> bool { self.send(PlayerCommand::OpenPath(path, OpenMode::WholeAlbum)) } pub fn set_queue(&self, tracks: Vec, start_at: usize) -> bool { self.send(PlayerCommand::SetQueue(tracks, start_at)) } pub fn enqueue(&self, path: PathBuf) -> bool { self.send(PlayerCommand::Enqueue(path)) } pub fn play_radio(&self, name: impl Into, url: impl Into) -> bool { self.send(PlayerCommand::PlayRadio { name: name.into(), url: url.into(), }) } pub fn toggle_mute(&self) -> bool { self.send(PlayerCommand::ToggleMute) } pub fn toggle_fade_out(&self) -> bool { self.send(PlayerCommand::ToggleFadeOut) } pub fn toggle_loudness_norm(&self) -> bool { self.send(PlayerCommand::ToggleLoudnessNorm) } pub fn play_pause(&self) -> bool { self.send(PlayerCommand::PlayPause) } pub fn stop(&self) -> bool { self.send(PlayerCommand::Stop) } pub fn next(&self) -> bool { self.send(PlayerCommand::Next) } pub fn previous(&self) -> bool { self.send(PlayerCommand::Previous) } pub fn seek(&self, position: std::time::Duration) -> bool { self.send(PlayerCommand::Seek(position)) } pub fn set_speed(&self, speed: Speed) -> bool { self.send(PlayerCommand::SetSpeed(speed.0)) } pub fn toggle_shuffle(&self) -> bool { self.send(PlayerCommand::ToggleShuffle) } pub fn cycle_repeat(&self) -> bool { self.send(PlayerCommand::CycleRepeat) } pub fn set_eq_band(&self, band: usize, gain_db: f32) -> bool { self.send(PlayerCommand::SetEqBand(band, gain_db)) } pub fn reset_eq(&self) -> bool { self.send(PlayerCommand::ResetEq) } /// Неблокирующий опрос событий — вызывать из UI event loop каждый тик. /// Если фоновый поток движка завершился (например, аудиоустройство пропало), /// это будет один раз отражено явной ошибкой вместо молчаливой потери команд. pub fn poll_events(&self) -> Vec { let mut out = Vec::new(); loop { match self.evt_rx.try_recv() { Ok(e) => out.push(e), Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => { if !self.disconnected_reported.get() { out.push(PlayerEvent::Error( "Player engine stopped running (audio device disconnected?). \ Commands will no longer work — restart the app to resume playback." .to_string(), )); self.disconnected_reported.set(true); } break; } } } out } } impl Default for Player { fn default() -> Self { Self::new() } }