use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum AudioCommand {
LoadFile(PathBuf),
LoadBytes {
data: Vec<u8>,
format: String,
},
LoadUrl(String),
Play,
Pause,
Stop,
Seek(Duration),
SetVolume(f32),
SetSpeed(f32),
ToggleMute,
SetLoop(bool),
}
#[derive(Debug, Clone, Default)]
pub struct AudioState {
pub playing: bool,
pub paused: bool,
pub muted: bool,
pub position: Duration,
pub duration: Option<Duration>,
pub volume: f32,
pub speed: f32,
pub looping: bool,
pub error: Option<String>,
}
impl AudioState {
pub fn new() -> Self {
Self {
volume: 1.0,
speed: 1.0,
..Default::default()
}
}
pub fn progress(&self) -> f32 {
match self.duration {
Some(duration) if !duration.is_zero() => {
self.position.as_secs_f32() / duration.as_secs_f32()
}
_ => 0.0,
}
}
}
#[derive(Debug, Clone)]
pub enum AudioEvent {
Started,
Paused,
Stopped,
Finished,
PositionChanged(Duration),
DurationDetermined(Duration),
VolumeChanged(f32),
Error(String),
StateChanged(AudioState),
}
pub trait AudioBackend: Send {
fn send(&self, command: AudioCommand) -> Result<(), AudioError>;
fn state(&self) -> AudioState;
fn supports_streaming(&self) -> bool {
false
}
fn supports_format(&self, format: &str) -> bool;
}
#[derive(Debug, Clone)]
pub enum AudioError {
FileNotFound(PathBuf),
UnsupportedFormat(String),
DecodeError(String),
PlaybackError(String),
NetworkError(String),
BackendUnavailable,
Other(String),
}
impl std::fmt::Display for AudioError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileNotFound(path) => write!(f, "Audio file not found: {}", path.display()),
Self::UnsupportedFormat(fmt) => write!(f, "Unsupported audio format: {}", fmt),
Self::DecodeError(msg) => write!(f, "Audio decode error: {}", msg),
Self::PlaybackError(msg) => write!(f, "Audio playback error: {}", msg),
Self::NetworkError(msg) => write!(f, "Network error: {}", msg),
Self::BackendUnavailable => write!(f, "Audio backend unavailable"),
Self::Other(msg) => write!(f, "Audio error: {}", msg),
}
}
}
impl std::error::Error for AudioError {}
#[derive(Debug, Default)]
pub struct NoOpAudioPlayer {
state: AudioState,
}
impl NoOpAudioPlayer {
pub fn new() -> Self {
Self {
state: AudioState::new(),
}
}
}
impl AudioBackend for NoOpAudioPlayer {
fn send(&self, _command: AudioCommand) -> Result<(), AudioError> {
Ok(())
}
fn state(&self) -> AudioState {
self.state.clone()
}
fn supports_format(&self, _format: &str) -> bool {
false
}
}
pub mod utils {
use super::*;
pub fn format_duration(duration: Duration) -> String {
let total_secs = duration.as_secs();
let minutes = total_secs / 60;
let seconds = total_secs % 60;
format!("{:02}:{:02}", minutes, seconds)
}
pub fn format_duration_long(duration: Duration) -> String {
let total_secs = duration.as_secs();
let hours = total_secs / 3600;
let minutes = (total_secs % 3600) / 60;
let seconds = total_secs % 60;
if hours > 0 {
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
} else {
format!("{:02}:{:02}", minutes, seconds)
}
}
pub fn progress_to_duration(progress: f32, total: Duration) -> Duration {
Duration::from_secs_f32(progress * total.as_secs_f32())
}
}
pub mod formats {
pub const MP3: &str = "mp3";
pub const WAV: &str = "wav";
pub const OGG: &str = "ogg";
pub const FLAC: &str = "flac";
pub const AAC: &str = "aac";
pub const WEBM: &str = "webm";
pub fn all() -> &'static [&'static str] {
&[MP3, WAV, OGG, FLAC, AAC, WEBM]
}
}