152 lines
4.5 KiB
Rust
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()
|
|
}
|
|
}
|