use kithara_decode::{
Decoder, GaplessMode, GaplessOutput, GaplessTrimmer, PcmChunk, duration_for_frames,
};
use kithara_platform::time::Duration;
use kithara_stream::MediaInfo;
type GaplessOutputIter = <GaplessOutput as IntoIterator>::IntoIter;
pub(crate) struct GaplessStage {
trimmer: GaplessTrimmer,
pending: Option<GaplessOutputIter>,
}
impl GaplessStage {
#[must_use]
pub(crate) fn build(
decoder: &dyn Decoder,
mode: GaplessMode,
media_info: Option<&MediaInfo>,
) -> Self {
let trimmer = match mode {
GaplessMode::MediaOnly => decoder
.track_info()
.gapless
.map_or_else(GaplessTrimmer::disabled, GaplessTrimmer::from),
GaplessMode::CodecPriming => decoder.track_info().gapless.map_or_else(
|| resolve_codec_priming(decoder, media_info),
GaplessTrimmer::from,
),
GaplessMode::SilenceTrim(params) => decoder.track_info().gapless.map_or_else(
|| GaplessTrimmer::silence_trim(params),
GaplessTrimmer::from,
),
_ => GaplessTrimmer::disabled(),
};
Self {
trimmer,
pending: None,
}
}
pub(crate) fn flush(&mut self) {
let output = self.trimmer.flush();
self.replace_pending(output);
}
#[must_use]
pub(crate) fn next(&mut self) -> Option<PcmChunk> {
let pending = self.pending.as_mut()?;
let next = pending.next();
if pending.len() == 0 {
self.pending = None;
}
next
}
pub(crate) fn notify_seek(&mut self) {
self.trimmer.notify_seek();
self.pending = None;
}
pub(crate) fn push(&mut self, chunk: PcmChunk) {
let output = self.trimmer.push(chunk);
self.replace_pending(output);
}
fn replace_pending(&mut self, output: GaplessOutput) {
debug_assert!(
self.pending
.as_ref()
.is_none_or(|pending| pending.len() == 0)
);
self.pending = (!output.is_empty()).then(|| output.into_iter());
}
}
fn resolve_codec_priming(decoder: &dyn Decoder, media_info: Option<&MediaInfo>) -> GaplessTrimmer {
let Some(codec) = media_info.and_then(|info| info.codec) else {
return GaplessTrimmer::disabled();
};
let frames = decoder.default_priming_frames(codec);
if frames == 0 {
GaplessTrimmer::disabled()
} else {
GaplessTrimmer::codec_priming(frames, decoder.spec().sample_rate)
}
}
#[must_use]
pub(crate) fn visible_duration(decoder: &dyn Decoder, mode: GaplessMode) -> Option<Duration> {
let raw = decoder.duration()?;
if matches!(mode, GaplessMode::Disabled) {
return Some(raw);
}
let Some(info) = decoder.track_info().gapless else {
return Some(raw);
};
let trim_frames = info.leading_frames.saturating_add(info.trailing_frames);
if trim_frames == 0 {
return Some(raw);
}
if decoder.spec().sample_rate == 0 {
return Some(raw);
}
let trim = duration_for_frames(decoder.spec().sample_rate, trim_frames);
Some(raw.saturating_sub(trim))
}