use flume::Receiver;
use moosicbox_audio_decoder::{DecodeError, unsync::decode};
use symphonia::core::{
audio::AudioBuffer, codecs::DecoderOptions, formats::FormatOptions, io::MediaSourceStream,
meta::MetadataOptions, probe::Hint,
};
use thiserror::Error;
impl From<std::io::Error> for PlaybackError {
fn from(err: std::io::Error) -> Self {
Self::Symphonia(symphonia::core::errors::Error::IoError(err))
}
}
#[derive(Debug, Error)]
pub enum PlaybackError {
#[error(transparent)]
Decode(#[from] DecodeError),
#[error(transparent)]
Symphonia(#[from] symphonia::core::errors::Error),
}
#[allow(clippy::too_many_arguments)]
pub fn play_media_source(
media_source_stream: MediaSourceStream,
hint: &Hint,
enable_gapless: bool,
verify: bool,
track_num: Option<usize>,
seek: Option<f64>,
) -> Result<Receiver<AudioBuffer<f32>>, PlaybackError> {
let format_opts = FormatOptions {
enable_gapless,
..Default::default()
};
let metadata_opts = MetadataOptions::default();
match symphonia::default::get_probe().format(
hint,
media_source_stream,
&format_opts,
&metadata_opts,
) {
Ok(probed) => {
let seek_time = seek;
let decode_opts = DecoderOptions { verify };
Ok(decode(probed.format, track_num, seek_time, decode_opts)?)
}
Err(err) => {
log::info!("the input is not supported: {err:?}");
Err(PlaybackError::Symphonia(err))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use moosicbox_audio_decoder::AudioDecodeError;
#[test_log::test]
fn test_playback_error_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "test file not found");
let playback_error: PlaybackError = io_error.into();
assert!(matches!(playback_error, PlaybackError::Symphonia(_)));
assert!(playback_error.to_string().contains("test file not found"));
}
#[test_log::test]
fn test_playback_error_from_decode_error() {
let decode_error = DecodeError::AudioDecode(AudioDecodeError::StreamClosed);
let playback_error: PlaybackError = decode_error.into();
assert!(matches!(playback_error, PlaybackError::Decode(_)));
assert!(!playback_error.to_string().is_empty());
}
#[test_log::test]
fn test_playback_error_display_variants() {
let error = PlaybackError::Decode(DecodeError::AudioDecode(AudioDecodeError::PlayStream));
assert!(!error.to_string().is_empty());
let debug_str = format!("{error:?}");
assert!(!debug_str.is_empty());
}
#[test_log::test]
fn test_playback_error_decode_multiple_variants() {
let errors = [
AudioDecodeError::OpenStream,
AudioDecodeError::PlayStream,
AudioDecodeError::StreamClosed,
AudioDecodeError::StreamEnd,
AudioDecodeError::Interrupt,
];
for error in errors {
let decode_error = DecodeError::AudioDecode(error);
let playback_error: PlaybackError = decode_error.into();
assert!(matches!(playback_error, PlaybackError::Decode(_)));
assert!(!playback_error.to_string().is_empty());
}
}
}