use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::pipeline::{
FragmentTrackData, Sample, TrackSpec, build_init_segment, build_media_segment,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SegmentMeta {
pub discontinuous: bool,
}
struct TrackState {
spec: TrackSpec,
pending: Vec<Sample>,
base_decode: u64,
}
pub struct Segmenter {
tracks: Vec<TrackState>,
movie_timescale: u32,
anchor: usize,
target_ticks: u64,
anchor_pending_dur: u64,
next_seq: u32,
ready: Vec<(Vec<u8>, SegmentMeta)>,
pending_discontinuity: bool,
last_init: Option<Vec<u8>>,
}
impl Segmenter {
pub fn new(
tracks: Vec<TrackSpec>,
movie_timescale: u32,
target_duration_secs: f64,
) -> Result<Self> {
if tracks.is_empty() {
return Err(Error::InvalidInput("segmenter needs at least one track"));
}
if !(target_duration_secs.is_finite() && target_duration_secs > 0.0) {
return Err(Error::InvalidInput(
"target_duration_secs must be positive and finite",
));
}
for (i, a) in tracks.iter().enumerate() {
if tracks[i + 1..].iter().any(|b| b.track_id == a.track_id) {
return Err(Error::InvalidInput("duplicate track_id"));
}
}
let tracks: Vec<TrackSpec> = tracks
.into_iter()
.filter(|t| !t.config.is_opaque_data())
.collect();
if tracks.is_empty() {
return Err(Error::InvalidInput(
"segmenter needs at least one carriable (non-Data) track",
));
}
let anchor = tracks.iter().position(|t| t.config.is_video()).unwrap_or(0);
let anchor_timescale = tracks[anchor].timescale as f64;
let target_ticks = (target_duration_secs * anchor_timescale) as u64;
let target_ticks = target_ticks.max(1);
let tracks = tracks
.into_iter()
.map(|spec| TrackState {
spec,
pending: Vec::new(),
base_decode: 0,
})
.collect();
Ok(Self {
tracks,
movie_timescale,
anchor,
target_ticks,
anchor_pending_dur: 0,
next_seq: 1,
ready: Vec::new(),
pending_discontinuity: false,
last_init: None,
})
}
pub fn init_segment(&self) -> Result<Vec<u8>> {
let specs: Vec<TrackSpec> = self.tracks.iter().map(|t| t.spec.clone()).collect();
build_init_segment(&specs, self.movie_timescale)
}
pub fn push(&mut self, track_id: u32, sample: Sample) -> Result<()> {
let idx = self
.tracks
.iter()
.position(|t| t.spec.track_id == track_id)
.ok_or(Error::InvalidInput("push: unknown track_id"))?;
if idx == self.anchor
&& sample.is_sync
&& self.anchor_pending_dur >= self.target_ticks
&& !self.tracks[self.anchor].pending.is_empty()
{
self.cut_segment()?;
}
if idx == self.anchor {
self.anchor_pending_dur += sample.duration as u64;
}
self.tracks[idx].pending.push(sample);
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
if self.tracks.iter().any(|t| !t.pending.is_empty()) {
self.cut_segment()?;
}
Ok(())
}
pub fn mark_discontinuity(&mut self) {
self.pending_discontinuity = true;
}
pub fn take_ready(&mut self) -> Vec<Vec<u8>> {
self.ready.drain(..).map(|(bytes, _meta)| bytes).collect()
}
pub fn take_ready_with_meta(&mut self) -> Vec<(Vec<u8>, SegmentMeta)> {
core::mem::take(&mut self.ready)
}
fn cut_segment(&mut self) -> Result<()> {
let seg = {
let frags: Vec<FragmentTrackData<'_>> = self
.tracks
.iter()
.filter(|t| !t.pending.is_empty())
.map(|t| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.base_decode,
samples: &t.pending,
})
.collect();
if frags.is_empty() {
return Ok(());
}
build_media_segment(self.next_seq, &frags)?
};
let current_init = build_init_segment(
&self
.tracks
.iter()
.map(|t| t.spec.clone())
.collect::<Vec<_>>(),
self.movie_timescale,
)?;
let init_changed = self
.last_init
.as_ref()
.map(|prev| prev != ¤t_init)
.unwrap_or(false); let discontinuous = self.pending_discontinuity || init_changed;
self.last_init = Some(current_init);
self.pending_discontinuity = false;
self.next_seq += 1;
for t in &mut self.tracks {
let dur: u64 = t.pending.iter().map(|s| s.duration as u64).sum();
t.base_decode += dur;
t.pending.clear();
}
self.anchor_pending_dur = 0;
self.ready.push((seg, SegmentMeta { discontinuous }));
Ok(())
}
}