selene-core 0.4.2

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{fs, sync::LazyLock};

use symphonia::{
    core::{
        audio::{SampleBuffer, SignalSpec},
        codecs::{CODEC_TYPE_NULL, CodecRegistry, Decoder, DecoderOptions},
        errors::Error as SymphoniaError,
        formats::{FormatOptions, FormatReader},
        io::{MediaSourceStream, MediaSourceStreamOptions},
        probe::Probe,
    },
    default::{
        codecs::{FlacDecoder, MpaDecoder, PcmDecoder, VorbisDecoder},
        formats::{FlacReader, MpaReader, OggReader, WavReader},
    },
};
use thiserror::Error;

use crate::media_container::{ContainerFormat, MediaContainer};

/// Symphonia codec registry for all supported codecs
pub static SYMPHONIA_CODEC_REGISTRY: LazyLock<CodecRegistry> = LazyLock::new(|| {
    let mut codecs = CodecRegistry::new();
    codecs.register_all::<FlacDecoder>();
    codecs.register_all::<MpaDecoder>();
    codecs.register_all::<VorbisDecoder>();
    codecs.register_all::<PcmDecoder>();
    codecs
});

pub static SYMPHONIA_PROBE: LazyLock<Probe> = LazyLock::new(|| {
    let mut probe = Probe::default();
    probe.register_all::<FlacReader>();
    probe.register_all::<OggReader>();
    probe.register_all::<MpaReader>();
    probe.register_all::<WavReader>();
    probe
});

#[derive(Debug, Error)]
pub enum DecodingError {
    #[error("{0}")]
    Io(#[from] std::io::Error),

    #[error("{0}")]
    Symphonia(#[from] SymphoniaError),

    #[error("Player attempted to play an unsupported container: '{0:?}'")]
    UnsupportedContainer(ContainerFormat),
}

pub struct RawDecoder {
    pub stream: symphonia::core::formats::Track,
    pub format_reader: Box<dyn FormatReader>,
    pub decoder: Box<dyn Decoder>,
}

pub struct DecodedPacket {
    pub sample_buf: SampleBuffer<f32>,
    pub frame_count: usize,
    pub spec: SignalSpec,
}

impl RawDecoder {
    pub fn from_container(
        container: &MediaContainer,
        buffer_len: usize,
    ) -> Result<Self, DecodingError> {
        let file = fs::File::open(container.path())?;

        let options = MediaSourceStreamOptions { buffer_len };
        let mss = MediaSourceStream::new(Box::new(file), options);

        let fmt_opts = FormatOptions {
            enable_gapless: true,
            ..Default::default()
        };

        let format_reader: Box<dyn FormatReader> = match container.container() {
            ContainerFormat::Flac => Box::new(FlacReader::try_new(mss, &fmt_opts)?),
            ContainerFormat::Mpa => Box::new(MpaReader::try_new(mss, &fmt_opts)?),
            ContainerFormat::Ogg => Box::new(OggReader::try_new(mss, &fmt_opts)?),
            ContainerFormat::Wav => Box::new(WavReader::try_new(mss, &fmt_opts)?),
            other => return Err(DecodingError::UnsupportedContainer(*other)),
        };

        let track = format_reader
            .tracks()
            .iter()
            .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
            .expect("Playable file does not have any supported codecs");
        let dec_opts: DecoderOptions = Default::default();

        let decoder = SYMPHONIA_CODEC_REGISTRY.make(&track.codec_params, &dec_opts)?;

        Ok(Self {
            stream: track.clone(),
            format_reader,
            decoder,
        })
    }

    pub fn decode_next_packet(&mut self) -> Result<Option<DecodedPacket>, DecodingError> {
        loop {
            let packet = match self.format_reader.next_packet() {
                Ok(packet) => packet,
                Err(SymphoniaError::ResetRequired) => {
                    self.decoder.reset();
                    continue;
                }
                Err(SymphoniaError::IoError(err))
                    if matches!(err.kind(), std::io::ErrorKind::UnexpectedEof) =>
                {
                    return Ok(None);
                }
                Err(err) => return Err(DecodingError::Symphonia(err)),
            };

            while !self.format_reader.metadata().is_latest() {
                self.format_reader.metadata().pop();
            }

            if packet.track_id() != self.stream.id {
                continue;
            }

            let decoded = match self.decoder.decode(&packet) {
                Ok(decoded) => decoded,
                Err(SymphoniaError::DecodeError(_)) => continue,
                Err(SymphoniaError::IoError(_)) => continue,
                Err(err) => return Err(DecodingError::Symphonia(err)),
            };

            let frame_count = decoded.frames();
            let spec = *decoded.spec();

            let mut sample_buf = SampleBuffer::<f32>::new(frame_count as u64, spec);
            sample_buf.copy_interleaved_ref(decoded);

            return Ok(Some(DecodedPacket {
                sample_buf,
                frame_count,
                spec,
            }));
        }
    }
}