use std::path::{Path, PathBuf};
use std::time::Duration;
use ff_format::{AudioFrame, AudioStreamInfo, ContainerInfo, NetworkOptions, SampleFormat};
use crate::audio::decoder_inner::AudioDecoderInner;
use crate::error::DecodeError;
#[derive(Debug)]
pub struct AudioDecoderBuilder {
path: PathBuf,
output_format: Option<SampleFormat>,
output_sample_rate: Option<u32>,
output_channels: Option<u32>,
network_opts: Option<NetworkOptions>,
}
impl AudioDecoderBuilder {
pub(crate) fn new(path: PathBuf) -> Self {
Self {
path,
output_format: None,
output_sample_rate: None,
output_channels: None,
network_opts: None,
}
}
#[must_use]
pub fn output_format(mut self, format: SampleFormat) -> Self {
self.output_format = Some(format);
self
}
#[must_use]
pub fn output_sample_rate(mut self, sample_rate: u32) -> Self {
self.output_sample_rate = Some(sample_rate);
self
}
#[must_use]
pub fn output_channels(mut self, channels: u32) -> Self {
self.output_channels = Some(channels);
self
}
#[must_use]
pub fn network(mut self, opts: NetworkOptions) -> Self {
self.network_opts = Some(opts);
self
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn get_output_format(&self) -> Option<SampleFormat> {
self.output_format
}
#[must_use]
pub fn get_output_sample_rate(&self) -> Option<u32> {
self.output_sample_rate
}
#[must_use]
pub fn get_output_channels(&self) -> Option<u32> {
self.output_channels
}
pub fn build(self) -> Result<AudioDecoder, DecodeError> {
let is_network_url = self.path.to_str().is_some_and(crate::network::is_url);
if !is_network_url && !self.path.exists() {
return Err(DecodeError::FileNotFound {
path: self.path.clone(),
});
}
let (inner, stream_info, container_info) = AudioDecoderInner::new(
&self.path,
self.output_format,
self.output_sample_rate,
self.output_channels,
self.network_opts,
)?;
Ok(AudioDecoder {
path: self.path,
inner,
stream_info,
container_info,
fused: false,
})
}
}
pub struct AudioDecoder {
path: PathBuf,
inner: AudioDecoderInner,
stream_info: AudioStreamInfo,
container_info: ContainerInfo,
fused: bool,
}
impl AudioDecoder {
pub fn open(path: impl AsRef<Path>) -> AudioDecoderBuilder {
AudioDecoderBuilder::new(path.as_ref().to_path_buf())
}
#[must_use]
pub fn stream_info(&self) -> &AudioStreamInfo {
&self.stream_info
}
#[must_use]
pub fn sample_rate(&self) -> u32 {
self.stream_info.sample_rate()
}
#[must_use]
pub fn channels(&self) -> u32 {
self.stream_info.channels()
}
#[must_use]
pub fn duration(&self) -> Duration {
self.stream_info.duration().unwrap_or(Duration::ZERO)
}
#[must_use]
pub fn duration_opt(&self) -> Option<Duration> {
self.stream_info.duration()
}
#[must_use]
pub fn container_info(&self) -> &ContainerInfo {
&self.container_info
}
#[must_use]
pub fn position(&self) -> Duration {
self.inner.position()
}
#[must_use]
pub fn is_eof(&self) -> bool {
self.inner.is_eof()
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
self.inner.decode_one()
}
pub fn decode_all(&mut self) -> Result<Vec<u8>, DecodeError> {
let mut buffer = Vec::new();
while let Some(frame) = self.decode_one()? {
for plane in frame.planes() {
buffer.extend_from_slice(plane);
}
}
Ok(buffer)
}
pub fn decode_range(&mut self, start: Duration, end: Duration) -> Result<Vec<u8>, DecodeError> {
if start >= end {
return Err(DecodeError::DecodingFailed {
timestamp: Some(start),
reason: format!(
"Invalid time range: start ({start:?}) must be before end ({end:?})"
),
});
}
self.seek(start, crate::SeekMode::Keyframe)?;
let mut buffer = Vec::new();
while let Some(frame) = self.decode_one()? {
let frame_time = frame.timestamp().as_duration();
if frame_time >= end {
break;
}
if frame_time >= start {
for plane in frame.planes() {
buffer.extend_from_slice(plane);
}
}
}
Ok(buffer)
}
pub fn seek(&mut self, position: Duration, mode: crate::SeekMode) -> Result<(), DecodeError> {
if self.inner.is_live() {
return Err(DecodeError::SeekNotSupported);
}
self.inner.seek(position, mode)
}
#[must_use]
pub fn is_live(&self) -> bool {
self.inner.is_live()
}
pub fn flush(&mut self) {
self.inner.flush();
}
}
impl Iterator for AudioDecoder {
type Item = Result<AudioFrame, DecodeError>;
fn next(&mut self) -> Option<Self::Item> {
if self.fused {
return None;
}
match self.decode_one() {
Ok(Some(frame)) => Some(Ok(frame)),
Ok(None) => None,
Err(e) => {
self.fused = true;
Some(Err(e))
}
}
}
}
impl std::iter::FusedIterator for AudioDecoder {}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_builder_default_values() {
let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"));
assert_eq!(builder.path(), Path::new("test.mp3"));
assert!(builder.get_output_format().is_none());
assert!(builder.get_output_sample_rate().is_none());
assert!(builder.get_output_channels().is_none());
}
#[test]
fn test_builder_output_format() {
let builder =
AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_format(SampleFormat::F32);
assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
}
#[test]
fn test_builder_output_sample_rate() {
let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_sample_rate(48000);
assert_eq!(builder.get_output_sample_rate(), Some(48000));
}
#[test]
fn test_builder_output_channels() {
let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_channels(2);
assert_eq!(builder.get_output_channels(), Some(2));
}
#[test]
fn test_builder_chaining() {
let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"))
.output_format(SampleFormat::F32)
.output_sample_rate(48000)
.output_channels(2);
assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
assert_eq!(builder.get_output_sample_rate(), Some(48000));
assert_eq!(builder.get_output_channels(), Some(2));
}
#[test]
fn test_decoder_open() {
let builder = AudioDecoder::open("audio.mp3");
assert_eq!(builder.path(), Path::new("audio.mp3"));
}
#[test]
fn test_build_file_not_found() {
let result = AudioDecoder::open("nonexistent_file_12345.mp3").build();
assert!(result.is_err());
match result {
Err(DecodeError::FileNotFound { path }) => {
assert!(
path.to_string_lossy()
.contains("nonexistent_file_12345.mp3")
);
}
Err(e) => panic!("Expected FileNotFound error, got: {e:?}"),
Ok(_) => panic!("Expected error, got Ok"),
}
}
#[test]
fn network_setter_should_store_options() {
let opts = NetworkOptions::default();
let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).network(opts.clone());
assert_eq!(builder.network_opts, Some(opts));
}
#[test]
fn build_should_bypass_file_existence_check_for_network_url() {
let result = AudioDecoder::open("http://192.0.2.1/nonexistent.aac").build();
assert!(
!matches!(result, Err(DecodeError::FileNotFound { .. })),
"FileNotFound must not be returned for network URLs"
);
}
}