use crate::TrackId;
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;
use broadcast_common::{Demand, Stage, Timestamp};
use crate::error::{Error, Result};
use crate::ll_dash::build_chunk;
use crate::pipeline::{FragmentTrackData, Sample, TrackSpec, build_init_segment};
use crate::segmenter::{
MAX_PENDING_SAMPLES_PER_TRACK, MediaClock, choose_anchor, no_sync_sample_error,
};
#[derive(Debug, Clone)]
pub struct PartInfo {
pub bytes: Vec<u8>,
pub duration: f64,
pub independent: bool,
pub segment_seq: u32,
pub part_index: u32,
}
#[derive(Debug, Clone)]
pub struct SegmentInfo {
pub bytes: Vec<u8>,
pub duration: f64,
pub segment_seq: u32,
pub part_count: u32,
}
#[derive(Debug, Clone)]
pub struct CutPlan {
pub segment_seq: u32,
pub segment_start: i64,
pub timescale: u32,
pub parts: Vec<PartCut>,
}
#[derive(Debug, Clone)]
pub struct PartCut {
pub part_index: u32,
pub start: i64,
pub timescale: u32,
}
impl CutPlan {
pub fn new(segment_seq: u32, segment_start: i64, timescale: u32, parts: Vec<PartCut>) -> Self {
Self {
segment_seq,
segment_start,
timescale,
parts,
}
}
}
impl PartCut {
pub fn new(part_index: u32, start: i64, timescale: u32) -> Self {
Self {
part_index,
start,
timescale,
}
}
fn effective_timescale(&self, plan_timescale: u32) -> u32 {
if self.timescale == 0 {
plan_timescale
} else {
self.timescale
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PartCutReady {
pub msn: u32,
pub part_index: u32,
pub start_dts: i64,
pub timescale: u32,
}
struct TrackState {
spec: TrackSpec,
pending: Vec<Sample>,
part_start: usize,
part_base_decode: u64,
seg_base_decode: u64,
part_clock: MediaClock,
seg_clock: MediaClock,
origin_applied: bool,
gap_pending: bool,
}
pub struct LlHlsSegmenter {
tracks: Vec<TrackState>,
movie_timescale: u32,
anchor: usize,
target_ticks: u64,
part_target_ticks: u64,
part_target_ms: u32,
part_trigger_ticks: u64,
frame_probe: FrameProbe,
anchor_seg_dur: u64,
anchor_part_dur: u64,
anchor_clock: MediaClock,
next_seq: u32,
current_segment: u32,
next_part_index: u32,
ready: VecDeque<LlHlsStageOutput>,
external_cuts: bool,
timeline_origin: Option<(i64, u32)>,
record_cuts: bool,
current_cuts: Vec<PartCut>,
ready_cuts: VecDeque<CutPlan>,
record_part_cuts: bool,
ready_part_cuts: VecDeque<PartCutReady>,
current_plan: Option<CutPlan>,
incremental: bool,
part_deferred: bool,
inc_next_part: usize,
inc_part_count: u32,
}
impl LlHlsSegmenter {
pub fn with_part_target(
tracks: Vec<TrackSpec>,
movie_timescale: u32,
target_duration_secs: f64,
part_target_ms: u32,
) -> Result<Self> {
if tracks.is_empty() {
return Err(Error::InvalidInput(
"ll-hls 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",
));
}
if part_target_ms == 0 {
return Err(Error::InvalidInput("part_target_ms must be >= 1"));
}
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 anchor = choose_anchor(tracks.iter().map(|t| &t.config))?;
let anchor_timescale = tracks[anchor].timescale as f64;
let target_ticks = ((target_duration_secs * anchor_timescale) as u64).max(1);
let part_target_ticks =
((part_target_ms as u64 * tracks[anchor].timescale as u64) / 1000).max(1);
let tracks = tracks
.into_iter()
.map(|spec| TrackState {
spec,
pending: Vec::new(),
part_start: 0,
part_base_decode: 0,
seg_base_decode: 0,
part_clock: MediaClock::new(),
seg_clock: MediaClock::new(),
origin_applied: false,
gap_pending: false,
})
.collect();
Ok(Self {
tracks,
movie_timescale,
anchor,
target_ticks,
part_target_ticks,
part_target_ms,
part_trigger_ticks: part_target_ticks,
frame_probe: FrameProbe::default(),
anchor_seg_dur: 0,
anchor_part_dur: 0,
anchor_clock: MediaClock::new(),
next_seq: 1,
current_segment: 1,
next_part_index: 0,
ready: VecDeque::new(),
external_cuts: false,
timeline_origin: None,
record_cuts: false,
current_cuts: Vec::new(),
ready_cuts: VecDeque::new(),
record_part_cuts: false,
ready_part_cuts: VecDeque::new(),
current_plan: None,
incremental: false,
inc_next_part: 0,
inc_part_count: 0,
part_deferred: false,
})
}
pub fn part_target_secs(&self) -> f64 {
self.part_target_ticks as f64 / self.tracks[self.anchor].spec.timescale.max(1) as f64
}
pub fn part_target_ms(&self) -> u32 {
self.part_target_ms
}
pub fn next_sequence_numbers(&self) -> (u32, u32) {
(self.next_seq, self.current_segment)
}
#[doc(hidden)]
pub fn with_part_target_at(
tracks: Vec<TrackSpec>,
movie_timescale: u32,
target_duration_secs: f64,
part_target_ms: u32,
next_seq: u32,
current_segment: u32,
) -> Result<Self> {
let mut seg = Self::with_part_target(
tracks,
movie_timescale,
target_duration_secs,
part_target_ms,
)?;
seg.next_seq = next_seq;
seg.current_segment = current_segment;
seg.next_part_index = 0;
Ok(seg)
}
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: TrackId, 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 self.external_cuts {
if sample.dts.is_none() {
return Err(Error::InvalidInput(
"follower segmenter push: sample has no dts",
));
}
if self.tracks[idx].pending.len() >= MAX_PENDING_SAMPLES_PER_TRACK {
return Err(Error::InvalidInput(
"follower segmenter pending buffer full: no boundary received within \
MAX_PENDING_SAMPLES_PER_TRACK samples",
));
}
self.tracks[idx].pending.push(sample);
return self.try_emit_inc_parts();
}
if idx == self.anchor
&& sample.flags.is_sync
&& self.anchor_seg_dur >= self.target_ticks
&& !self.tracks[self.anchor].pending.is_empty()
{
self.finish_segment()?;
}
if self.tracks[idx].pending.len() >= MAX_PENDING_SAMPLES_PER_TRACK {
return Err(no_sync_sample_error());
}
if idx == self.anchor && self.part_deferred {
self.part_deferred = false;
self.emit_part(false)?;
}
if idx == self.anchor {
let elapsed = self.anchor_clock.tick(&sample);
self.anchor_seg_dur += elapsed;
self.anchor_part_dur += elapsed;
self.observe_frame(elapsed);
}
self.tracks[idx].pending.push(sample);
if idx == self.anchor
&& !self.frame_probe.is_measuring()
&& self.anchor_part_dur >= self.part_trigger_ticks
{
if self.would_end_with_empty_anchor_tail() {
self.part_deferred = true;
} else {
self.emit_part(false)?;
}
}
Ok(())
}
pub fn follow(&mut self, plan: CutPlan) -> Result<()> {
if self.incremental {
self.close_incremental_segment(Some((plan.segment_start, plan.timescale)))?;
}
if plan.timescale == 0 {
return Err(Error::InvalidInput("follow: plan timescale must be > 0"));
}
if self.tracks.iter().any(|t| t.spec.timescale == 0) {
return Err(Error::InvalidInput("follow: track timescale must be > 0"));
}
let mut previous_part: Option<(i64, u32)> = None;
for p in &plan.parts {
let ts = p.effective_timescale(plan.timescale);
if let Some((start, prev_ts)) = previous_part
&& cmp_time(p.start, ts, start, prev_ts).is_lt()
{
return Err(Error::InvalidInput(
"follow: part starts must be non-decreasing",
));
}
previous_part = Some((p.start, ts));
}
let cut_on_own = !self.ready.is_empty()
|| self
.tracks
.iter()
.any(|t| t.part_start > 0 || t.seg_base_decode > 0);
if !self.external_cuts && cut_on_own {
return Err(Error::InvalidInput(
"follow: segmenter already cut on its own; call follow before the first cut",
));
}
if let Some(current) = &self.current_plan
&& (plan.segment_seq <= current.segment_seq
|| cmp_time(
plan.segment_start,
plan.timescale,
current.segment_start,
current.timescale,
)
.is_lt())
{
return Err(Error::InvalidInput(
"follow: plan must advance the previous plan's sequence number and start",
));
}
self.external_cuts = true;
if let Some(current) = self.current_plan.take() {
let end = (plan.segment_start, plan.timescale);
self.cut_follower_segment(¤t, Some(end))?;
}
self.current_segment = plan.segment_seq;
self.current_plan = Some(plan);
Ok(())
}
pub fn follow_part(
&mut self,
segment_seq: u32,
part_index: u32,
start: i64,
timescale: u32,
) -> Result<()> {
if timescale == 0 {
return Err(Error::InvalidInput("follow_part: timescale must be > 0"));
}
if self.tracks.iter().any(|t| t.spec.timescale == 0) {
return Err(Error::InvalidInput(
"follow_part: track timescale must be > 0",
));
}
if !self.incremental {
let cut_on_own = !self.ready.is_empty()
|| self
.tracks
.iter()
.any(|t| t.part_start > 0 || t.seg_base_decode > 0);
if !self.external_cuts && cut_on_own {
return Err(Error::InvalidInput(
"follow_part: segmenter already cut on its own; call follow_part before the first cut",
));
}
if self.current_plan.is_some() {
return Err(Error::InvalidInput(
"follow_part: segmenter is following whole-segment plans",
));
}
}
if self.incremental
&& let Some(open) = &self.current_plan
{
if segment_seq < open.segment_seq {
return Err(Error::InvalidInput(
"follow_part: segment_seq must not go backwards",
));
}
if segment_seq > open.segment_seq {
self.close_incremental_segment(Some((start, timescale)))?;
}
}
if !self.incremental {
self.external_cuts = true;
self.incremental = true;
self.inc_next_part = 0;
self.inc_part_count = 0;
self.current_segment = segment_seq;
self.current_plan = Some(CutPlan::new(segment_seq, start, timescale, Vec::new()));
}
let plan = self
.current_plan
.as_mut()
.ok_or(Error::InvalidInput("follow_part: no open segment"))?;
if let Some(last) = plan.parts.last()
&& cmp_time(
start,
timescale,
last.start,
last.effective_timescale(plan.timescale),
)
.is_lt()
{
return Err(Error::InvalidInput(
"follow_part: part starts must be non-decreasing",
));
}
plan.parts.push(PartCut::new(part_index, start, timescale));
self.try_emit_inc_parts()
}
fn try_emit_inc_parts(&mut self) -> Result<()> {
while let Some((segment_seq, part_index, next)) = self.next_inc_part() {
let anchor = &self.tracks[self.anchor];
let threshold = ceil_rescale(next.0, next.1, anchor.spec.timescale);
let reached = anchor.pending[anchor.part_start..]
.iter()
.any(|s| follower_dts(s) >= threshold);
if !reached {
break;
}
self.emit_inc_part(segment_seq, part_index, Some(next))?;
self.inc_next_part += 1;
}
Ok(())
}
fn next_inc_part(&self) -> Option<(u32, u32, (i64, u32))> {
if !self.incremental {
return None;
}
let plan = self.current_plan.as_ref()?;
let part = plan.parts.get(self.inc_next_part)?;
let next = plan.parts.get(self.inc_next_part + 1)?;
Some((
plan.segment_seq,
part.part_index,
(next.start, next.effective_timescale(plan.timescale)),
))
}
fn emit_inc_part(
&mut self,
segment_seq: u32,
part_index: u32,
end: Option<(i64, u32)>,
) -> Result<bool> {
let take_ends: Vec<usize> = self
.tracks
.iter()
.map(|t| match end {
Some((value, ts)) => {
let threshold = ceil_rescale(value, ts, t.spec.timescale);
t.part_start
+ t.pending[t.part_start..].partition_point(|s| follower_dts(s) < threshold)
}
None => t.pending.len(),
})
.collect();
let active: Vec<bool> = self
.tracks
.iter()
.zip(&take_ends)
.map(|(t, &e)| e > t.part_start)
.collect();
if !active.contains(&true) {
return Ok(false);
}
self.apply_follower_origin(&active);
let frags: Vec<FragmentTrackData<'_>> = self
.tracks
.iter()
.zip(&take_ends)
.filter(|&(t, &e)| e > t.part_start)
.map(|(t, &e)| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.part_base_decode,
samples: &t.pending[t.part_start..e],
})
.collect();
let bytes = build_chunk(self.next_seq, &frags, false)?;
self.next_seq += 1;
let independent = frags
.first()
.is_some_and(|f| f.samples.iter().any(|s| s.flags.is_sync));
let first_ti = active
.iter()
.position(|&a| a)
.ok_or(Error::InvalidInput("follow_part: part has no track"))?;
let duration = span_secs(
&self.tracks[first_ti],
self.tracks[first_ti].part_start,
take_ends[first_ti],
);
for (t, &e) in self.tracks.iter_mut().zip(&take_ends) {
let mut clock = t.part_clock;
let ticks: u64 = t.pending[t.part_start..e]
.iter()
.map(|s| clock.tick(s))
.sum();
t.part_clock = clock;
t.part_base_decode += ticks;
t.part_start = e;
}
self.inc_part_count += 1;
self.ready.push_back(LlHlsStageOutput::Part(PartInfo {
bytes,
duration,
independent,
segment_seq,
part_index,
}));
Ok(true)
}
fn close_incremental_segment(&mut self, end: Option<(i64, u32)>) -> Result<()> {
let Some(plan) = self.current_plan.take() else {
self.incremental = false;
return Ok(());
};
let starts: Vec<(u32, (i64, u32))> = plan
.parts
.iter()
.map(|p| {
(
p.part_index,
(p.start, p.effective_timescale(plan.timescale)),
)
})
.collect();
for k in self.inc_next_part..starts.len() {
let part_end = starts.get(k + 1).map(|s| s.1).or(end);
self.emit_inc_part(plan.segment_seq, starts[k].0, part_end)?;
}
let splits: Vec<usize> = self
.tracks
.iter()
.map(|t| match end {
Some((value, ts)) => {
let threshold = ceil_rescale(value, ts, t.spec.timescale);
t.pending.partition_point(|s| follower_dts(s) < threshold)
}
None => t.pending.len(),
})
.collect();
let part_count = self.inc_part_count;
self.incremental = false;
self.inc_next_part = 0;
self.inc_part_count = 0;
self.finish_follower_segment(plan.segment_seq, &splits, part_count)
}
pub fn flush(&mut self) -> Result<()> {
if self.incremental {
return self.close_incremental_segment(None);
}
if self.external_cuts {
return match self.current_plan.take() {
Some(current) => self.cut_follower_segment(¤t, None),
None if self.tracks.iter().any(|t| !t.pending.is_empty()) => Err(
Error::InvalidInput("flush: follower has buffered samples but no cut plan"),
),
None => Ok(()),
};
}
if self.tracks.iter().any(|t| !t.pending.is_empty()) {
self.finish_segment()?;
}
Ok(())
}
fn cut_follower_segment(&mut self, plan: &CutPlan, end: Option<(i64, u32)>) -> Result<()> {
let part_starts: Vec<(u32, i64, u32)> = if plan.parts.is_empty() {
vec![(0, plan.segment_start, plan.timescale)]
} else {
plan.parts
.iter()
.map(|p| (p.part_index, p.start, p.effective_timescale(plan.timescale)))
.collect()
};
let nparts = part_starts.len();
let mut bounds: Vec<Vec<usize>> = Vec::with_capacity(self.tracks.len());
for t in &self.tracks {
let before = |limit: Option<(i64, u32)>, upto: usize| match limit {
Some((value, ts)) => {
let threshold = ceil_rescale(value, ts, t.spec.timescale);
t.pending[..upto].partition_point(|s| follower_dts(s) < threshold)
}
None => upto,
};
let split = before(end, t.pending.len());
let mut b = Vec::with_capacity(nparts + 1);
b.push(0);
for &(_, start, ts) in &part_starts[1..] {
b.push(before(Some((start, ts)), split));
}
b.push(split);
bounds.push(b);
}
let active: Vec<bool> = bounds.iter().map(|b| b[nparts] > 0).collect();
self.apply_follower_origin(&active);
let mut cumulative: Vec<Vec<u64>> = Vec::with_capacity(self.tracks.len());
for (t, b) in self.tracks.iter().zip(&bounds) {
let mut clock = MediaClock::resumed_at(t.seg_clock.last_dts());
let mut cum = Vec::with_capacity(b[nparts] + 1);
cum.push(0u64);
for s in &t.pending[..b[nparts]] {
let last = cum[cum.len() - 1];
cum.push(last + clock.tick(s));
}
cumulative.push(cum);
}
let seg_seq = plan.segment_seq;
let mut emitted_parts: u32 = 0;
for (k, &(part_index, _, _)) in part_starts.iter().enumerate() {
let frags: Vec<FragmentTrackData<'_>> = self
.tracks
.iter()
.enumerate()
.filter(|(ti, _)| bounds[*ti][k + 1] > bounds[*ti][k])
.map(|(ti, t)| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.seg_base_decode + cumulative[ti][bounds[ti][k]],
samples: &t.pending[bounds[ti][k]..bounds[ti][k + 1]],
})
.collect();
let Some(first) = frags.first() else {
continue;
};
let bytes = build_chunk(self.next_seq, &frags, false)?;
self.next_seq += 1;
let first_ti = self
.tracks
.iter()
.position(|t| t.spec.track_id == first.track_id)
.ok_or(Error::InvalidInput("follow: part track vanished"))?;
let independent = first.samples.iter().any(|s| s.flags.is_sync);
let duration = span_secs(
&self.tracks[first_ti],
bounds[first_ti][k],
bounds[first_ti][k + 1],
);
emitted_parts += 1;
self.ready.push_back(LlHlsStageOutput::Part(PartInfo {
bytes,
duration,
independent,
segment_seq: seg_seq,
part_index,
}));
}
let splits: Vec<usize> = bounds.iter().map(|b| b[nparts]).collect();
self.finish_follower_segment(seg_seq, &splits, emitted_parts)
}
fn apply_follower_origin(&mut self, active: &[bool]) {
let Some((origin, origin_ts)) = self.timeline_origin else {
return;
};
for (t, &is_active) in self.tracks.iter_mut().zip(active) {
if !is_active || (t.origin_applied && !t.gap_pending) {
continue;
}
t.origin_applied = true;
t.gap_pending = false;
let first = follower_dts(&t.pending[0]);
let offset = i128::from(first) * i128::from(origin_ts)
- i128::from(origin) * i128::from(t.spec.timescale);
let ticks = offset / i128::from(origin_ts);
t.seg_base_decode = u64::try_from(ticks).unwrap_or(0);
t.part_base_decode = t.seg_base_decode;
}
}
fn finish_follower_segment(
&mut self,
segment_seq: u32,
splits: &[usize],
part_count: u32,
) -> Result<()> {
for (t, &split) in self.tracks.iter_mut().zip(splits) {
if split == 0 {
t.gap_pending = true;
}
}
let Some(first_ti) = splits.iter().position(|&b| b > 0) else {
return Ok(());
};
let seg_frags: Vec<FragmentTrackData<'_>> = self
.tracks
.iter()
.zip(splits)
.filter(|&(_, &b)| b > 0)
.map(|(t, &b)| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.seg_base_decode,
samples: &t.pending[..b],
})
.collect();
let bytes = crate::pipeline::build_media_segment(self.next_seq, &seg_frags)?;
self.next_seq += 1;
let duration = span_secs(&self.tracks[first_ti], 0, splits[first_ti]);
self.ready.push_back(LlHlsStageOutput::Segment(SegmentInfo {
bytes,
duration,
segment_seq,
part_count,
}));
for (t, &split) in self.tracks.iter_mut().zip(splits) {
if split == 0 {
continue;
}
let mut clock = MediaClock::resumed_at(t.seg_clock.last_dts());
let ticks: u64 = t.pending[..split].iter().map(|s| clock.tick(s)).sum();
t.pending.drain(..split);
t.seg_base_decode += ticks;
t.part_base_decode = t.seg_base_decode;
t.part_start = 0;
t.seg_clock = clock;
t.part_clock = clock;
}
Ok(())
}
pub fn record_cuts(&mut self) {
self.record_cuts = true;
}
pub fn set_timeline_origin(&mut self, origin_dts: i64, origin_timescale: u32) {
if origin_timescale != 0 {
self.timeline_origin = Some((origin_dts, origin_timescale));
}
}
pub fn take_ready_cut_plans(&mut self) -> Vec<CutPlan> {
self.ready_cuts.drain(..).collect()
}
pub fn record_part_cuts(&mut self) {
self.record_part_cuts = true;
}
pub fn take_ready_part_cuts(&mut self) -> Vec<PartCutReady> {
self.ready_part_cuts.drain(..).collect()
}
pub fn take_ready_parts(&mut self) -> Vec<PartInfo> {
let mut parts = Vec::new();
let mut remaining = VecDeque::with_capacity(self.ready.len());
for item in self.ready.drain(..) {
match item {
LlHlsStageOutput::Part(p) => parts.push(p),
other => remaining.push_back(other),
}
}
self.ready = remaining;
parts
}
pub fn take_ready_segments(&mut self) -> Vec<SegmentInfo> {
let mut segments = Vec::new();
let mut remaining = VecDeque::with_capacity(self.ready.len());
for item in self.ready.drain(..) {
match item {
LlHlsStageOutput::Segment(s) => segments.push(s),
other => remaining.push_back(other),
}
}
self.ready = remaining;
segments
}
fn finish_segment(&mut self) -> Result<()> {
let segment_start = {
let t = &self.tracks[self.anchor];
t.pending
.first()
.and_then(|s| s.dts)
.unwrap_or_else(|| ticks_to_i64(t.seg_base_decode))
};
let has_tail = self.tracks.iter().any(|t| t.part_start < t.pending.len());
if has_tail {
self.emit_part(true)?;
}
let seg_seq = self.next_seq;
let seg_bytes = {
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.seg_base_decode,
samples: &t.pending,
})
.collect();
crate::pipeline::build_media_segment(seg_seq, &frags)?
};
self.next_seq += 1;
let seg_duration =
self.anchor_seg_dur as f64 / self.tracks[self.anchor].spec.timescale.max(1) as f64;
self.ready.push_back(LlHlsStageOutput::Segment(SegmentInfo {
bytes: seg_bytes,
duration: seg_duration,
segment_seq: self.current_segment,
part_count: self.next_part_index,
}));
if self.record_cuts {
let cuts = core::mem::take(&mut self.current_cuts);
self.ready_cuts.push_back(CutPlan::new(
self.current_segment,
segment_start,
self.tracks[self.anchor].spec.timescale,
cuts,
));
}
for t in &mut self.tracks {
let clock = &mut t.seg_clock;
let dur: u64 = t.pending.iter().map(|s| clock.tick(s)).sum();
t.seg_base_decode += dur;
t.part_base_decode = t.seg_base_decode;
t.pending.clear();
t.part_start = 0;
}
self.anchor_seg_dur = 0;
self.anchor_part_dur = 0;
self.part_deferred = false;
self.current_segment += 1;
self.next_part_index = 0;
Ok(())
}
fn observe_frame(&mut self, elapsed: u64) {
let Some(grid) = self.frame_probe.observe(elapsed) else {
return;
};
let spread = grid.longest - grid.shortest;
let hi = grid.longest + spread;
let lo = grid.shortest.saturating_sub(spread);
if lo == 0 || lo * PERCENT_SCALE < hi * PART_MIN_PERCENT {
return;
}
let floor_frames = self.part_target_ticks / hi;
if floor_frames >= 1
&& floor_frames * lo * PERCENT_SCALE >= self.part_target_ticks * PART_MIN_PERCENT
{
if (floor_frames - 1) * hi < floor_frames * lo {
self.part_trigger_ticks = floor_frames * lo;
}
return;
}
let frames = self.part_target_ticks.div_ceil(hi).max(1);
if (frames - 1) * hi >= frames * lo {
return;
}
let timescale = u64::from(self.tracks[self.anchor].spec.timescale.max(1));
let ms = (frames * hi * MILLIS_PER_SEC).div_ceil(timescale);
let Ok(ms) = u32::try_from(ms) else {
return;
};
self.part_target_ms = ms;
self.part_target_ticks = u64::from(ms) * timescale / MILLIS_PER_SEC;
self.part_trigger_ticks = frames * lo;
}
fn would_end_with_empty_anchor_tail(&self) -> bool {
if self.anchor_seg_dur < self.target_ticks {
return false;
}
let t = &self.tracks[self.anchor];
let span = self.anchor_part_dur.min(self.part_target_ticks);
take_span(t, span) >= t.pending.len()
}
fn emit_part(&mut self, final_part: bool) -> Result<()> {
let anchor = self.anchor;
let part_start = {
let t = &self.tracks[anchor];
t.pending
.get(t.part_start)
.and_then(|s| s.dts)
.unwrap_or_else(|| ticks_to_i64(t.part_base_decode))
};
let anchor_scale = self.tracks[anchor].spec.timescale.max(1) as u64;
let part_span = if final_part {
u64::MAX
} else {
self.anchor_part_dur.min(self.part_target_ticks)
};
let take_ends: Vec<usize> = self
.tracks
.iter()
.map(|t| {
if final_part {
return t.pending.len();
}
let track_scale = t.spec.timescale.max(1) as u64;
let track_span = if part_span == u64::MAX {
u64::MAX
} else {
part_span.saturating_mul(track_scale) / anchor_scale
};
take_span(t, track_span)
})
.collect();
if take_ends
.iter()
.zip(&self.tracks)
.all(|(&end, t)| end == t.part_start)
{
return Ok(());
}
let anchor_state = &self.tracks[anchor];
let independent = anchor_state.pending[anchor_state.part_start..take_ends[anchor]]
.iter()
.any(|s| s.flags.is_sync);
let anchor_span_ticks: u64 = {
let t = &self.tracks[anchor];
let clock = &mut MediaClock::resumed_at(t.part_clock.last_dts());
t.pending[t.part_start..take_ends[anchor]]
.iter()
.map(|s| clock.tick(s))
.sum()
};
if anchor_span_ticks == 0 {
return Ok(());
}
let part_secs = anchor_span_ticks as f64 / self.tracks[anchor].spec.timescale.max(1) as f64;
let seq = self.next_seq;
let part_bytes = {
let frags: Vec<FragmentTrackData<'_>> = self
.tracks
.iter()
.zip(&take_ends)
.filter(|&(t, &end)| end > t.part_start)
.map(|(t, &end)| FragmentTrackData {
track_id: t.spec.track_id,
base_media_decode_time: t.part_base_decode,
samples: &t.pending[t.part_start..end],
})
.collect();
build_chunk(seq, &frags, false)?
};
self.next_seq += 1;
for (t, &end) in self.tracks.iter_mut().zip(&take_ends) {
let clock = &mut t.part_clock;
let mut dur: u64 = 0;
for s in &t.pending[t.part_start..end] {
dur += clock.tick(s);
}
t.part_base_decode += dur;
t.part_start = end;
}
let part_index = self.next_part_index;
self.next_part_index += 1;
self.anchor_part_dur = {
let t = &self.tracks[anchor];
let mut clock = MediaClock::resumed_at(t.part_clock.last_dts());
t.pending[t.part_start..]
.iter()
.map(|s| clock.tick(s))
.sum()
};
self.ready.push_back(LlHlsStageOutput::Part(PartInfo {
bytes: part_bytes,
duration: part_secs,
independent,
segment_seq: self.current_segment,
part_index,
}));
if self.record_cuts {
self.current_cuts.push(PartCut::new(
part_index,
part_start,
self.tracks[anchor].spec.timescale,
));
}
if self.record_part_cuts {
self.ready_part_cuts.push_back(PartCutReady {
msn: self.current_segment,
part_index,
start_dts: part_start,
timescale: self.tracks[anchor].spec.timescale,
});
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum LlHlsStageOutput {
Part(PartInfo),
Segment(SegmentInfo),
}
impl Stage for LlHlsSegmenter {
type In<'a> = (TrackId, Sample);
type Out = LlHlsStageOutput;
type Error = Error;
fn feed(&mut self, (track_id, sample): Self::In<'_>, _now: Timestamp) -> Result<()> {
self.push(track_id, sample)
}
fn poll(&mut self) -> Option<Self::Out> {
self.ready.pop_front()
}
fn finish(&mut self) -> Result<()> {
self.flush()
}
fn next_deadline(&self) -> Option<Timestamp> {
None
}
fn on_deadline(&mut self, _now: Timestamp) {}
fn demand(&self) -> Demand {
if self
.tracks
.iter()
.any(|t| t.pending.len() >= MAX_PENDING_SAMPLES_PER_TRACK)
{
Demand::saturated()
} else {
Demand::default()
}
}
}
fn take_span(t: &TrackState, target_ticks: u64) -> usize {
if t.part_start >= t.pending.len() {
return t.part_start;
}
let mut clock = MediaClock::resumed_at(t.part_clock.last_dts());
let mut span: u64 = 0;
let mut end = t.part_start;
for s in &t.pending[t.part_start..] {
let next = clock.tick(s);
if end > t.part_start && span + next > target_ticks {
break;
}
span += next;
end += 1;
}
end
}
const MILLIS_PER_SEC: u64 = 1000;
const PERCENT_SCALE: u64 = 100;
const PART_MIN_PERCENT: u64 = 85;
const FRAME_PROBE_FRAMES: u32 = 8;
const FRAME_PROBE_GIVE_UP: u32 = 16;
#[derive(Debug, Clone, Copy)]
struct FrameGrid {
shortest: u64,
longest: u64,
}
#[derive(Debug, Clone, Copy, Default)]
enum FrameProbe {
#[default]
Idle,
Measuring {
seen: u32,
frames: u32,
shortest: u64,
longest: u64,
},
Done,
}
impl FrameProbe {
fn is_measuring(&self) -> bool {
matches!(self, Self::Idle | Self::Measuring { .. })
}
fn observe(&mut self, elapsed: u64) -> Option<FrameGrid> {
let (mut seen, mut frames, mut shortest, mut longest) = match *self {
Self::Idle => (0, 0, u64::MAX, 0),
Self::Measuring {
seen,
frames,
shortest,
longest,
} => (seen, frames, shortest, longest),
Self::Done => return None,
};
seen += 1;
if elapsed > 0 {
frames += 1;
shortest = shortest.min(elapsed);
longest = longest.max(elapsed);
}
if frames >= FRAME_PROBE_FRAMES {
*self = Self::Done;
return Some(FrameGrid { shortest, longest });
}
if seen >= FRAME_PROBE_GIVE_UP {
*self = Self::Done;
return None;
}
*self = Self::Measuring {
seen,
frames,
shortest,
longest,
};
None
}
}
fn ticks_to_i64(ticks: u64) -> i64 {
i64::try_from(ticks).unwrap_or(i64::MAX)
}
fn ceil_rescale(value: i64, from_ts: u32, to_ts: u32) -> i64 {
let numerator = i128::from(value) * i128::from(to_ts);
let from = i128::from(from_ts);
let floor = numerator.div_euclid(from);
let ceil = if numerator.rem_euclid(from) == 0 {
floor
} else {
floor + 1
};
i64::try_from(ceil).unwrap_or(if ceil < 0 { i64::MIN } else { i64::MAX })
}
fn cmp_time(a: i64, a_ts: u32, b: i64, b_ts: u32) -> core::cmp::Ordering {
(i128::from(a) * i128::from(b_ts)).cmp(&(i128::from(b) * i128::from(a_ts)))
}
fn follower_dts(sample: &Sample) -> i64 {
sample.dts.unwrap_or(i64::MIN)
}
fn span_secs(track: &TrackState, from: usize, to: usize) -> f64 {
let mut clock = MediaClock::resumed_at(
from.checked_sub(1)
.and_then(|i| track.pending.get(i))
.and_then(|s| s.dts)
.or(track.seg_clock.last_dts()),
);
let ticks: u64 = track.pending[from..to].iter().map(|s| clock.tick(s)).sum();
ticks as f64 / f64::from(track.spec.timescale.max(1))
}