use super::ring_buffer::RingBufferError;
use super::{BUFFER_BACKOFF_MICROS, RingBuffer, StreamConfig};
use parking_lot::Mutex;
use std::sync::Arc;
pub struct RealtimePlayer {
buffer: Arc<RingBuffer>,
stats: Arc<Mutex<PlaybackStats>>,
}
#[derive(Debug, Clone, Copy)]
pub struct PlaybackStats {
pub overrun_count: usize,
pub samples_played: usize,
pub fill_percentage: f32,
}
impl RealtimePlayer {
pub fn new(config: StreamConfig) -> Result<Self, RingBufferError> {
let buffer = Arc::new(RingBuffer::new(config.ring_buffer_size)?);
let stats = Arc::new(Mutex::new(PlaybackStats {
overrun_count: 0,
samples_played: 0,
fill_percentage: 0.0,
}));
Ok(RealtimePlayer { buffer, stats })
}
pub fn write_blocking(&self, samples: &[f32]) -> usize {
const MAX_RETRIES: u32 = 1000;
let mut total_written = 0;
let mut remaining = samples;
let mut retry_count = 0;
while !remaining.is_empty() && retry_count < MAX_RETRIES {
let written = self.buffer.write(remaining);
{
let mut stats = self.stats.lock();
stats.samples_played += written;
stats.fill_percentage = self.buffer.fill_percentage();
}
total_written += written;
if written == 0 {
std::thread::sleep(std::time::Duration::from_micros(BUFFER_BACKOFF_MICROS));
retry_count += 1;
} else {
remaining = &remaining[written..];
retry_count = 0;
}
}
total_written
}
pub fn get_stats(&self) -> PlaybackStats {
*self.stats.lock()
}
pub fn fill_percentage(&self) -> f32 {
self.buffer.fill_percentage()
}
pub fn get_buffer(&self) -> Arc<RingBuffer> {
Arc::clone(&self.buffer)
}
}
impl Drop for RealtimePlayer {
fn drop(&mut self) {
let stats = self.stats.lock();
println!(
"Playback complete: {} samples, {} overruns",
stats.samples_played, stats.overrun_count
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_config() {
let config = StreamConfig::stable(44100);
assert!(config.latency_ms() > 300.0);
}
#[test]
fn test_playback_stats() {
let stats = PlaybackStats {
overrun_count: 0,
samples_played: 44100,
fill_percentage: 0.5,
};
assert_eq!(stats.samples_played, 44100);
assert_eq!(stats.overrun_count, 0);
assert!(stats.fill_percentage > 0.4 && stats.fill_percentage < 0.6);
}
}