nsm/crates/player/src/lib.rs
2026-07-13 09:24:20 +03:00

152 lines
4.5 KiB
Rust

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<PlayerCommand>,
evt_rx: Receiver<PlayerEvent>,
disconnected_reported: std::cell::Cell<bool>,
}
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<QueueItem>, 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<String>, url: impl Into<String>) -> 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<PlayerEvent> {
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()
}
}