use std::{
io::{ErrorKind, Read, Seek},
num::NonZeroUsize,
time::Duration,
};
use kithara_stream::{
AudioCodec, NotReadyCause, PendingReason, PrerollHint, StreamPending, StreamReadError,
VariantChangeError,
};
mod kithara {
pub(crate) use kithara_test_macros::mock;
}
use crate::{
error::DecodeResult,
types::{PcmChunk, PcmSpec, TrackMetadata},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputReadOutcome {
Bytes(NonZeroUsize),
Pending(PendingReason),
Eof,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecoderSeekOutcome {
Landed {
landed_at: Duration,
landed_frame: u64,
landed_byte: Option<u64>,
preroll: PrerollHint,
},
PastEof { duration: Duration },
}
#[derive(Debug)]
pub enum DecoderChunkOutcome {
Chunk(PcmChunk),
Pending(PendingReason),
Eof,
}
impl DecoderChunkOutcome {
#[must_use]
pub fn is_eof(&self) -> bool {
matches!(self, Self::Eof)
}
}
impl TryFrom<DecoderChunkOutcome> for PcmChunk {
type Error = DecoderChunkOutcome;
fn try_from(outcome: DecoderChunkOutcome) -> Result<Self, Self::Error> {
match outcome {
DecoderChunkOutcome::Chunk(chunk) => Ok(chunk),
other => Err(other),
}
}
}
pub trait DecoderInput: Read + Seek + Send + Sync {
fn try_read(&mut self, buf: &mut [u8]) -> Result<InputReadOutcome, StreamReadError> {
match Read::read(self, buf) {
Ok(0) => Ok(InputReadOutcome::Eof),
Ok(n) => {
Ok(NonZeroUsize::new(n).map_or(InputReadOutcome::Eof, InputReadOutcome::Bytes))
}
Err(e) => {
let stream_pending = e
.get_ref()
.and_then(|src| src.downcast_ref::<StreamPending>())
.map(|p| p.reason);
let pending = e
.get_ref()
.and_then(|src| src.downcast_ref::<PendingReason>())
.copied();
let variant = e
.get_ref()
.and_then(|src| src.downcast_ref::<VariantChangeError>())
.is_some();
let interrupted = e.kind() == ErrorKind::Interrupted;
match (stream_pending, pending, variant, interrupted) {
(Some(reason), _, _, _) | (_, Some(reason), _, _) => {
Ok(InputReadOutcome::Pending(reason))
}
(None, None, true, _) => {
Ok(InputReadOutcome::Pending(PendingReason::VariantChange))
}
(None, None, false, true) => Ok(InputReadOutcome::Pending(
PendingReason::NotReady(NotReadyCause::SourcePending),
)),
(None, None, false, false) => Err(StreamReadError::Source(e)),
}
}
}
}
}
impl<T: Read + Seek + Send + Sync + ?Sized> DecoderInput for T {}
pub(crate) type BoxedSource = Box<dyn DecoderInput>;
#[kithara::mock(api = DecoderMock)]
pub trait Decoder: Send + 'static {
fn default_priming_frames(&self, codec: AudioCodec) -> u64 {
AudioCodec::encoder_priming_frames(codec)
}
fn duration(&self) -> Option<Duration>;
fn metadata(&self) -> TrackMetadata {
TrackMetadata::default()
}
fn next_chunk(&mut self) -> DecodeResult<DecoderChunkOutcome>;
fn seek(&mut self, pos: Duration) -> DecodeResult<DecoderSeekOutcome>;
fn spec(&self) -> PcmSpec;
fn track_info(&self) -> crate::DecoderTrackInfo {
crate::DecoderTrackInfo::default()
}
fn update_byte_len(&self, len: u64);
}