pub mod format {
    use crate::common::types::AudioFormat;
    pub fn detect_format(header: &[u8]) -> AudioFormat {
        if header.len() < 4 {
            return AudioFormat::Unknown;
        }
        if header.starts_with(&[0x1A, 0x45, 0xDF, 0xA3]) {
            return AudioFormat::Webm;
        }
        if header.len() >= 8 && &header[4..8] == b"ftyp" {
            return AudioFormat::Mp4;
        }
        if header.starts_with(b"OggS") {
            return AudioFormat::Ogg;
        }
        if header.starts_with(b"fLaC") {
            return AudioFormat::Flac;
        }
        if header.starts_with(b"RIFF") && header.len() >= 12 && &header[8..12] == b"WAVE" {
            return AudioFormat::Wav;
        }
        if header.starts_with(b"ID3") {
            return AudioFormat::Mp3;
        }
        if header[0] == 0xFF {
            let b1 = header[1];
            let is_sync = (b1 & 0xE0) == 0xE0;
            let layer = (b1 >> 1) & 0x03;
            if is_sync && layer != 0 {
                return AudioFormat::Mp3;
            }
        }
        AudioFormat::Unknown
    }
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn detect_webm() {
            let hdr = [0x1A, 0x45, 0xDF, 0xA3, 0x00, 0x00, 0x00, 0x00];
            assert_eq!(detect_format(&hdr), AudioFormat::Webm);
        }
        #[test]
        fn detect_mp4() {
            let hdr = b"\x00\x00\x00\x1Cftypisom";
            assert_eq!(detect_format(hdr), AudioFormat::Mp4);
        }
        #[test]
        fn detect_ogg() {
            assert_eq!(detect_format(b"OggS\x00"), AudioFormat::Ogg);
        }
        #[test]
        fn detect_unknown() {
            assert_eq!(
                detect_format(&[0x00, 0x00, 0x00, 0x00]),
                AudioFormat::Unknown
            );
        }
        #[test]
        fn adts_not_mistaken_for_mp3() {
            let adts = [0xFF, 0xF1, 0x50, 0x80];
            assert_eq!(detect_format(&adts), AudioFormat::Unknown);
        }
        #[test]
        fn mp3_sync_word() {
            let mp3 = [0xFF, 0xFB, 0x90, 0x00];
            assert_eq!(detect_format(&mp3), AudioFormat::Mp3);
        }
    }
}
pub mod webm_opus {
    use symphonia::core::{
        codecs::CODEC_TYPE_OPUS,
        errors::Error,
        formats::{FormatOptions, FormatReader},
        io::{MediaSource, MediaSourceStream},
        meta::MetadataOptions,
        probe::Hint,
    };
    pub struct WebmOpusDemuxer {
        format: Box<dyn FormatReader>,
        track_id: u32,
    }
    impl WebmOpusDemuxer {
        pub fn open(source: Box<dyn MediaSource>) -> Result<Option<Self>, Error> {
            let mss = MediaSourceStream::new(source, Default::default());
            let mut hint = Hint::new();
            hint.with_extension("webm");
            let probed = symphonia::default::get_probe().format(
                &hint,
                mss,
                &FormatOptions::default(),
                &MetadataOptions::default(),
            )?;
            let format = probed.format;
            let track_id = format
                .tracks()
                .iter()
                .find(|t| t.codec_params.codec == CODEC_TYPE_OPUS)
                .map(|t| t.id);
            Ok(track_id.map(|track_id| Self { format, track_id }))
        }
        pub fn next_packet(&mut self) -> Result<Option<Vec<u8>>, Error> {
            loop {
                let packet = match self.format.next_packet() {
                    Ok(p) => p,
                    Err(Error::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
                        return Ok(None);
                    }
                    Err(e) => return Err(e),
                };
                if packet.track_id() != self.track_id {
                    continue;
                }
                return Ok(Some(packet.data.into_vec()));
            }
        }
    }
}
use crate::audio::constants::{MIXER_CHANNELS, TARGET_SAMPLE_RATE};
pub use crate::common::types::AudioFormat;
pub use format::detect_format;
use symphonia::core::{
    codecs::{CODEC_TYPE_NULL, Decoder, DecoderOptions},
    errors::Error,
    formats::{FormatOptions, FormatReader},
    io::{MediaSource, MediaSourceStream},
    meta::MetadataOptions,
    probe::Hint,
};
pub use webm_opus::WebmOpusDemuxer;
pub enum DemuxResult {
    Transcode {
        format: Box<dyn FormatReader>,
        track_id: u32,
        decoder: Box<dyn Decoder>,
        sample_rate: u32,
        channels: usize,
    },
}
pub fn open_format(
    source: Box<dyn MediaSource>,
    kind: Option<AudioFormat>,
) -> Result<DemuxResult, Error> {
    let mss = MediaSourceStream::new(source, Default::default());
    let mut hint = Hint::new();
    if let Some(k) = &kind {
        hint.with_extension(k.as_ext());
    }
    let probed = symphonia::default::get_probe().format(
        &hint,
        mss,
        &FormatOptions::default(),
        &MetadataOptions::default(),
    )?;
    let format = probed.format;
    let track = format
        .tracks()
        .iter()
        .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
        .ok_or_else(|| {
            Error::IoError(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "no audio track found",
            ))
        })?;
    let track_id = track.id;
    let codec = track.codec_params.codec;
    let sample_rate = track.codec_params.sample_rate.unwrap_or(TARGET_SAMPLE_RATE);
    let channels = track
        .codec_params
        .channels
        .map(|c| c.count())
        .unwrap_or(MIXER_CHANNELS);
    let decoder: Box<dyn Decoder> = if codec == symphonia::core::codecs::CODEC_TYPE_OPUS {
        Box::new(
            crate::audio::codec::opus_decoder::OpusCodecDecoder::try_new(
                &track.codec_params,
                &DecoderOptions::default(),
            )?,
        )
    } else {
        symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default())?
    };
    Ok(DemuxResult::Transcode {
        format,
        track_id,
        decoder,
        sample_rate,
        channels,
    })
}
