use rodio::buffer::SamplesBuffer;
use rodio::{Decoder, OutputStream, Sink, Source};
use std::io::Cursor;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
const CHUNK_MS: u64 = 60;
pub fn play_mp3(mp3_data: &[u8], stop: Arc<AtomicBool>, level: &AtomicU32) -> Result<(), String> {
let (_stream, stream_handle) =
OutputStream::try_default().map_err(|e| format!("Audio output error: {e}"))?;
let cursor = Cursor::new(mp3_data.to_vec());
let source = Decoder::new(cursor).map_err(|e| format!("MP3 decode error: {e}"))?;
let channels = source.channels();
let sample_rate = source.sample_rate();
let samples: Vec<f32> = source.convert_samples::<f32>().collect();
let sink = Sink::try_new(&stream_handle).map_err(|e| format!("Sink error: {e}"))?;
let frames = (sample_rate as u64 * CHUNK_MS / 1000).max(1) as usize;
let chunk_len = (frames * channels as usize).max(1);
for chunk in samples.chunks(chunk_len) {
if stop.load(Ordering::Relaxed) {
sink.stop();
level.store(0, Ordering::Relaxed);
return Ok(());
}
level.store(level_bits(chunk), Ordering::Relaxed);
sink.append(SamplesBuffer::new(channels, sample_rate, chunk.to_vec()));
std::thread::sleep(Duration::from_millis(CHUNK_MS));
}
while !sink.empty() && !stop.load(Ordering::Relaxed) {
std::thread::sleep(Duration::from_millis(20));
}
if stop.load(Ordering::Relaxed) {
sink.stop();
} else {
sink.sleep_until_end();
}
level.store(0, Ordering::Relaxed);
Ok(())
}
fn level_bits(chunk: &[f32]) -> u32 {
if chunk.is_empty() {
return 0f32.to_bits();
}
let sum: f32 = chunk.iter().map(|s| s * s).sum();
let rms = (sum / chunk.len() as f32).sqrt();
let normalized = if rms < 0.01 { 0.0 } else { rms.min(1.0) };
normalized.to_bits()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level_is_zero_for_silence_and_rises_with_amplitude() {
assert_eq!(f32::from_bits(level_bits(&[0.0; 64])), 0.0);
assert_eq!(f32::from_bits(level_bits(&[0.005; 64])), 0.0);
let quiet = f32::from_bits(level_bits(&[0.1; 64]));
let loud = f32::from_bits(level_bits(&[0.8; 64]));
assert!(quiet > 0.0 && quiet < loud, "{quiet} !< {loud}");
assert!((f32::from_bits(level_bits(&[1.0; 64])) - 1.0).abs() < 1e-6);
}
#[test]
fn level_of_an_empty_chunk_is_silence() {
assert_eq!(f32::from_bits(level_bits(&[])), 0.0);
}
}