use std::{
io::{ErrorKind, Read, Seek},
num::NonZeroUsize,
time::Duration,
};
use kithara_stream::{
NotReadyCause, PendingReason, 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>,
},
PastEof { duration: Duration },
}
#[derive(Debug)]
pub enum DecoderChunkOutcome {
Chunk(PcmChunk),
Pending(PendingReason),
Eof,
}
impl DecoderChunkOutcome {
#[must_use]
pub fn as_chunk(&self) -> Option<&PcmChunk> {
match self {
Self::Chunk(chunk) => Some(chunk),
_ => None,
}
}
#[must_use]
pub fn into_chunk(self) -> Option<PcmChunk> {
match self {
Self::Chunk(chunk) => Some(chunk),
_ => None,
}
}
#[must_use]
pub fn is_chunk(&self) -> bool {
matches!(self, Self::Chunk(_))
}
#[must_use]
pub fn is_eof(&self) -> bool {
matches!(self, Self::Eof)
}
#[must_use]
pub fn is_pending(&self) -> bool {
matches!(self, Self::Pending(_))
}
}
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 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);
}