1use transmux::SegmentIndexBox;
18
19use super::AddressError;
20use super::base_index::{self, IndexedSegment};
21use super::list;
22use super::template::{self, TemplateError, TemplateFault, TemplateUse};
23use super::timeline::TimelineIndex;
24use crate::error::Unsupported;
25use crate::timing::availability::{
26 init_available_from, segment_available_from_ticks, segment_available_until_ticks, ticks_to_us,
27};
28use broadcast_common::timescale::{Rounding, from_micros};
29use broadcast_dash::inherit::{ListInfo, ResolvedMulti, SegmentInfo, TemplateInfo};
30use broadcast_dash::{BaseInfo, ByteRange, UrlType};
31
32const MICROS: i128 = 1_000_000;
33
34fn us_to_ticks_ceil(us: i128, timescale: u64) -> u64 {
35 u64::try_from(from_micros(us.max(0), timescale, Rounding::Ceil)).unwrap_or(u64::MAX)
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40#[non_exhaustive]
41pub struct DynamicContext {
42 pub availability_start_us: i128,
44 pub time_shift_buffer_depth_us: Option<i128>,
47 pub availability_time_offset_us: i128,
50 pub horizon_utc_us: Option<i128>,
53}
54
55impl DynamicContext {
56 #[must_use]
58 pub fn new(availability_start_us: i128, time_shift_buffer_depth_us: Option<i128>) -> Self {
59 Self {
60 availability_start_us,
61 time_shift_buffer_depth_us,
62 availability_time_offset_us: 0,
63 horizon_utc_us: None,
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
70#[non_exhaustive]
71pub struct PlanInput<'a> {
72 pub representation_id: &'a str,
74 pub bandwidth: u64,
76 pub info: &'a SegmentInfo,
78 pub period_start_us: i128,
80 pub period_end_us: Option<i128>,
82 pub dynamic: Option<DynamicContext>,
84 pub index: Option<(&'a SegmentIndexBox, u64)>,
86}
87
88impl<'a> PlanInput<'a> {
89 #[must_use]
91 pub fn new(representation_id: &'a str, bandwidth: u64, info: &'a SegmentInfo) -> Self {
92 Self {
93 representation_id,
94 bandwidth,
95 info,
96 period_start_us: 0,
97 period_end_us: None,
98 dynamic: None,
99 index: None,
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106#[non_exhaustive]
107pub struct SegmentRef {
108 pub index: u64,
110 pub number: u64,
112 pub time_ticks: u64,
114 pub duration_ticks: u64,
116 pub timescale: u64,
118 pub start_us: i128,
121 pub duration_us: i128,
123 pub reference: String,
126 pub byte_range: Option<ByteRange>,
128 pub available_from_us: Option<i128>,
130 pub available_until_us: Option<i128>,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136#[non_exhaustive]
137pub struct ResourceRef {
138 pub reference: String,
140 pub byte_range: Option<ByteRange>,
142 pub available_from_us: Option<i128>,
144 pub available_until_us: Option<i128>,
146}
147
148#[derive(Debug, Clone)]
149struct Fixed {
150 number: u64,
151 time: u64,
152 start_ticks: i128,
153 duration: u64,
154 reference: String,
155 range: Option<ByteRange>,
156}
157
158#[derive(Debug, Clone)]
159enum Kind {
160 Duration {
161 d: u64,
162 start_number: u64,
163 count: Option<u64>,
164 last_duration: u64,
165 },
166 Timeline(TimelineIndex),
167 Fixed(Vec<Fixed>),
168}
169
170#[derive(Debug, Clone)]
171struct Avail {
172 ast_us: i128,
173 ato_us: i128,
174 tsb_us: Option<i128>,
175}
176
177#[derive(Debug, Clone)]
178struct MediaTemplate {
179 template: String,
180 representation_id: String,
181 bandwidth: u64,
182}
183
184#[derive(Debug, Clone)]
186pub struct SegmentPlan {
187 kind: Kind,
188 timescale: u64,
189 pto: u64,
190 period_start_us: i128,
191 avail: Option<Avail>,
192 media: Option<MediaTemplate>,
193 init: Option<(String, Option<ByteRange>)>,
194 index: Option<(String, Option<ByteRange>)>,
195 len_cap: Option<u64>,
196}
197
198fn fault(template: &str, f: TemplateFault) -> AddressError {
199 AddressError::InvalidTemplate {
200 template: template.to_owned(),
201 fault: f,
202 }
203}
204
205fn reject_sequences(used: TemplateUse) -> Result<(), AddressError> {
206 if used.sub_number {
207 Err(AddressError::Unsupported(Unsupported::SegmentSequence))
208 } else {
209 Ok(())
210 }
211}
212
213fn url_ref(u: &UrlType) -> (String, Option<ByteRange>) {
214 (u.source_url.clone().unwrap_or_default(), u.range)
215}
216
217fn period_end_media_ticks(
218 multi: &ResolvedMulti,
219 input: &PlanInput<'_>,
220 period_duration_ticks: Option<u64>,
221) -> Option<u64> {
222 let pto = multi.presentation_time_offset;
223 if let Some(pd) = period_duration_ticks {
224 return Some(pd.saturating_add(pto));
225 }
226 let dynamic = input.dynamic.as_ref()?;
227 let horizon = dynamic.horizon_utc_us?;
228 let rel = horizon
229 .saturating_sub(dynamic.availability_start_us)
230 .saturating_sub(input.period_start_us);
231 Some(us_to_ticks_ceil(rel, multi.timescale).saturating_add(pto))
232}
233
234impl SegmentPlan {
235 pub fn build(input: &PlanInput<'_>) -> Result<Self, AddressError> {
242 let avail = input.dynamic.as_ref().map(|d| Avail {
243 ast_us: d.availability_start_us,
244 ato_us: d.availability_time_offset_us,
245 tsb_us: d.time_shift_buffer_depth_us,
246 });
247 let mut plan = match input.info {
248 SegmentInfo::Template(t) => Self::from_template(input, t)?,
249 SegmentInfo::List(l) => Self::from_list(input, l)?,
250 SegmentInfo::Base(b) => Self::from_base(input, b)?,
251 _ => return Err(AddressError::UnsupportedFeature),
252 };
253 plan.avail = avail;
254 plan.period_start_us = input.period_start_us;
255 Ok(plan)
256 }
257
258 fn period_duration_us(input: &PlanInput<'_>) -> Option<i128> {
259 input
260 .period_end_us
261 .map(|e| e.saturating_sub(input.period_start_us).max(0))
262 }
263
264 fn from_template(input: &PlanInput<'_>, t: &TemplateInfo) -> Result<Self, AddressError> {
265 let media = t
266 .media
267 .as_deref()
268 .ok_or(AddressError::MissingMediaTemplate)?;
269 let used = template::scan_media(media).map_err(|f| fault(media, f))?;
270 reject_sequences(used)?;
271 let mut init = t.multi.base.initialization.as_ref().map(url_ref);
272 if let Some(tpl) = &t.initialization {
273 let used = template::scan_initialization(tpl).map_err(|f| fault(tpl, f))?;
274 reject_sequences(used)?;
275 init = Some((
276 template::substitute(tpl, input.representation_id, None, None, input.bandwidth)
277 .map_err(|e| fault(tpl, e.fault()))?,
278 None,
279 ));
280 }
281 let mut index = t.multi.base.representation_index.as_ref().map(url_ref);
282 if let Some(tpl) = &t.index {
283 let used = template::scan(tpl).map_err(|f| fault(tpl, f))?;
284 reject_sequences(used)?;
285 if !used.number && !used.time {
286 index = Some((
287 template::substitute(tpl, input.representation_id, None, None, input.bandwidth)
288 .map_err(|e| fault(tpl, e.fault()))?,
289 None,
290 ));
291 }
292 }
293 let m = &t.multi;
294 let ts = m.timescale;
295 let pd_us = Self::period_duration_us(input);
296 let pd_ticks = pd_us.map(|d| us_to_ticks_ceil(d, ts));
297 let (kind, len_cap) = if let Some(tl) = &m.timeline {
298 let end = period_end_media_ticks(m, input, pd_ticks);
299 let idx = TimelineIndex::build(tl, m.start_number, end)?;
300 let cap = m.end_number.map(|en| idx.count_up_to_number(en));
301 (Kind::Timeline(idx), cap)
302 } else if let Some(d) = m.duration {
303 let mut count = pd_us.map(|dur| {
304 let num = dur.saturating_mul(i128::from(ts));
305 let den = i128::from(d).saturating_mul(MICROS);
306 let c = num.div_euclid(den) + i128::from(num.rem_euclid(den) != 0);
307 u64::try_from(c).unwrap_or(u64::MAX)
308 });
309 if let Some(en) = m.end_number {
310 let cap = if en < m.start_number {
311 0
312 } else {
313 en.saturating_sub(m.start_number).saturating_add(1)
314 };
315 count = Some(count.map_or(cap, |c| c.min(cap)));
316 }
317 let last_duration = match (count, pd_ticks) {
318 (Some(c), Some(pd)) if c > 0 => {
319 let full = (c - 1).saturating_mul(d);
320 pd.saturating_sub(full).clamp(1, d)
321 }
322 _ => d,
323 };
324 (
325 Kind::Duration {
326 d,
327 start_number: m.start_number,
328 count,
329 last_duration,
330 },
331 None,
332 )
333 } else {
334 let seg = Fixed {
335 number: m.start_number,
336 time: 0,
337 start_ticks: 0,
338 duration: pd_ticks.unwrap_or(0),
339 reference: String::new(),
340 range: None,
341 };
342 (Kind::Fixed(vec![seg]), None)
343 };
344 Ok(Self {
345 kind,
346 timescale: ts,
347 pto: m.presentation_time_offset,
348 period_start_us: 0,
349 avail: None,
350 media: Some(MediaTemplate {
351 template: media.to_owned(),
352 representation_id: input.representation_id.to_owned(),
353 bandwidth: input.bandwidth,
354 }),
355 init,
356 index,
357 len_cap,
358 })
359 }
360
361 fn from_list(input: &PlanInput<'_>, l: &ListInfo) -> Result<Self, AddressError> {
362 let m = &l.multi;
363 let ts = m.timescale;
364 let pd_ticks = Self::period_duration_us(input).map(|d| us_to_ticks_ceil(d, ts));
365 let timeline = match &m.timeline {
366 Some(tl) => {
367 let end = period_end_media_ticks(m, input, pd_ticks);
368 Some(TimelineIndex::build(tl, m.start_number, end)?)
369 }
370 None => None,
371 };
372 let segs = list::expand(l, timeline.as_ref(), pd_ticks)?;
373 let pto = i128::from(m.presentation_time_offset);
374 let fixed = segs
375 .into_iter()
376 .map(|s| Fixed {
377 number: s.number,
378 time: s.time,
379 start_ticks: if timeline.is_some() {
380 i128::from(s.time) - pto
381 } else {
382 i128::from(s.time)
383 },
384 duration: s.duration,
385 reference: s.reference,
386 range: s.range,
387 })
388 .collect();
389 Ok(Self {
390 kind: Kind::Fixed(fixed),
391 timescale: ts,
392 pto: m.presentation_time_offset,
393 period_start_us: 0,
394 avail: None,
395 media: None,
396 init: m.base.initialization.as_ref().map(url_ref),
397 index: m.base.representation_index.as_ref().map(url_ref),
398 len_cap: None,
399 })
400 }
401
402 fn from_base(input: &PlanInput<'_>, b: &BaseInfo) -> Result<Self, AddressError> {
403 let init = b.initialization.as_ref().map(url_ref);
404 let index = b
405 .representation_index
406 .as_ref()
407 .map(url_ref)
408 .or_else(|| b.index_range.map(|r| (String::new(), Some(r))));
409 let pto = b.presentation_time_offset.unwrap_or(0);
410 let base_ts = b.timescale.unwrap_or(1);
411 if let Some(r) = &b.index_range {
412 let (sidx, anchor) = input.index.ok_or(AddressError::IndexRequired)?;
413 let _ = base_index::anchor_after(r)?;
414 let ts = u64::from(sidx.timescale).max(1);
415 let segs: Vec<IndexedSegment> = base_index::segments(sidx, anchor)?;
416 let fixed = segs
417 .into_iter()
418 .enumerate()
419 .map(|(i, s)| Fixed {
420 number: i as u64 + 1,
421 time: s.time,
422 start_ticks: i128::from(s.time) - i128::from(pto),
423 duration: s.duration,
424 reference: String::new(),
425 range: Some(s.range),
426 })
427 .collect();
428 return Ok(Self {
429 kind: Kind::Fixed(fixed),
430 timescale: ts,
431 pto,
432 period_start_us: 0,
433 avail: None,
434 media: None,
435 init,
436 index,
437 len_cap: None,
438 });
439 }
440 let pd_ticks = Self::period_duration_us(input).map(|d| us_to_ticks_ceil(d, base_ts));
441 let seg = Fixed {
442 number: 1,
443 time: 0,
444 start_ticks: 0,
445 duration: pd_ticks.unwrap_or(0),
446 reference: String::new(),
447 range: None,
448 };
449 Ok(Self {
450 kind: Kind::Fixed(vec![seg]),
451 timescale: base_ts,
452 pto,
453 period_start_us: 0,
454 avail: None,
455 media: None,
456 init,
457 index,
458 len_cap: None,
459 })
460 }
461
462 #[must_use]
464 pub fn timescale(&self) -> u64 {
465 self.timescale
466 }
467
468 #[must_use]
470 pub fn presentation_time_offset(&self) -> u64 {
471 self.pto
472 }
473
474 #[must_use]
476 pub fn period_start_us(&self) -> i128 {
477 self.period_start_us
478 }
479
480 #[must_use]
482 pub fn is_dynamic(&self) -> bool {
483 self.avail.is_some()
484 }
485
486 #[must_use]
489 pub fn len(&self) -> Option<u64> {
490 let raw = match &self.kind {
491 Kind::Duration { count, .. } => *count,
492 Kind::Timeline(t) => Some(t.len()),
493 Kind::Fixed(v) => Some(v.len() as u64),
494 };
495 match (raw, self.len_cap) {
496 (Some(a), Some(b)) => Some(a.min(b)),
497 (a, None) => a,
498 (None, b) => b,
499 }
500 }
501
502 #[must_use]
504 pub fn is_empty(&self) -> bool {
505 self.len() == Some(0)
506 }
507
508 fn reference(
509 &self,
510 number: u64,
511 time: u64,
512 fixed: Option<&str>,
513 ) -> Result<String, AddressError> {
514 match (&self.media, fixed) {
515 (Some(m), _) => template::substitute(
516 &m.template,
517 &m.representation_id,
518 Some(number),
519 Some(time),
520 m.bandwidth,
521 )
522 .map_err(|e| match e {
523 TemplateError::MissingNumber => AddressError::MissingTemplateValue {
524 template: m.template.clone(),
525 which: "number",
526 },
527 TemplateError::MissingTime => AddressError::MissingTemplateValue {
528 template: m.template.clone(),
529 which: "time",
530 },
531 TemplateError::Invalid(f) => fault(&m.template, f),
532 _ => AddressError::UnsupportedFeature,
533 }),
534 (None, Some(r)) => Ok(r.to_owned()),
535 (None, None) => Ok(String::new()),
536 }
537 }
538
539 pub fn get(&self, index: u64) -> Result<Option<SegmentRef>, AddressError> {
546 if self.len().is_some_and(|n| index >= n) {
547 return Ok(None);
548 }
549 let (number, time, start_ticks, duration, reference, range) = match &self.kind {
550 Kind::Duration {
551 d,
552 start_number,
553 count,
554 last_duration,
555 } => {
556 let Some(start) = index.checked_mul(*d) else {
557 return Err(AddressError::Overflow);
558 };
559 let dur = if count.is_some_and(|c| index + 1 == c) {
560 *last_duration
561 } else {
562 *d
563 };
564 let number = start_number.saturating_add(index);
565 (
566 number,
567 start,
568 i128::from(start),
569 dur,
570 self.reference(number, start, None)?,
571 None,
572 )
573 }
574 Kind::Timeline(t) => {
575 let Some(s) = t.segment(index) else {
576 return Ok(None);
577 };
578 (
579 s.number,
580 s.time,
581 i128::from(s.time) - i128::from(self.pto),
582 s.duration,
583 self.reference(s.number, s.time, None)?,
584 None,
585 )
586 }
587 Kind::Fixed(v) => {
588 let Some(f) = usize::try_from(index).ok().and_then(|i| v.get(i)) else {
589 return Ok(None);
590 };
591 (
592 f.number,
593 f.time,
594 f.start_ticks,
595 f.duration,
596 self.reference(f.number, f.time, Some(&f.reference))?,
597 f.range,
598 )
599 }
600 };
601 let start_us = ticks_to_us(start_ticks, self.timescale);
602 let duration_us = ticks_to_us(i128::from(duration), self.timescale);
603 let (available_from_us, available_until_us) = match &self.avail {
604 Some(a) => (
605 Some(segment_available_from_ticks(
606 a.ast_us,
607 self.period_start_us,
608 start_ticks,
609 i128::from(duration),
610 self.timescale,
611 a.ato_us,
612 )),
613 segment_available_until_ticks(
614 a.ast_us,
615 self.period_start_us,
616 start_ticks,
617 i128::from(duration),
618 self.timescale,
619 a.tsb_us,
620 ),
621 ),
622 None => (None, None),
623 };
624 Ok(Some(SegmentRef {
625 index,
626 number,
627 time_ticks: time,
628 duration_ticks: duration,
629 timescale: self.timescale,
630 start_us,
631 duration_us,
632 reference,
633 byte_range: range,
634 available_from_us,
635 available_until_us,
636 }))
637 }
638
639 pub fn iter(&self) -> impl Iterator<Item = SegmentRef> + '_ {
642 let mut i = 0u64;
643 std::iter::from_fn(move || {
644 let r = match self.get(i) {
645 Ok(Some(s)) => s,
646 _ => return None,
647 };
648 i += 1;
649 Some(r)
650 })
651 }
652
653 #[must_use]
655 pub fn init(&self) -> Option<ResourceRef> {
656 self.init
657 .as_ref()
658 .map(|(reference, range)| self.resource(reference, *range))
659 }
660
661 #[must_use]
664 pub fn index(&self) -> Option<ResourceRef> {
665 self.index
666 .as_ref()
667 .map(|(reference, range)| self.resource(reference, *range))
668 }
669
670 fn resource(&self, reference: &str, range: Option<ByteRange>) -> ResourceRef {
671 let from = self
672 .avail
673 .as_ref()
674 .map(|a| init_available_from(a.ast_us, self.period_start_us));
675 let until =
676 self.len()
677 .and_then(|n| n.checked_sub(1))
678 .and_then(|last| match self.get(last) {
679 Ok(Some(s)) => s.available_until_us,
680 _ => None,
681 });
682 ResourceRef {
683 reference: reference.to_owned(),
684 byte_range: range,
685 available_from_us: from,
686 available_until_us: until,
687 }
688 }
689
690 pub fn index_at_or_before(&self, period_time_us: i128) -> Result<Option<u64>, AddressError> {
693 let n = self.len();
694 let (mut lo, mut hi) = (0u64, n.unwrap_or(u64::MAX / 2));
695 while lo < hi {
696 let mid = lo + (hi - lo) / 2;
697 match self.get(mid) {
698 Ok(Some(s)) if s.start_us <= period_time_us => lo = mid + 1,
699 Ok(Some(_)) | Ok(None) | Err(_) => hi = mid,
700 }
701 }
702 Ok(lo.checked_sub(1))
703 }
704
705 pub fn available_from_us(&self, index: u64) -> Result<Option<i128>, AddressError> {
707 Ok(self.get(index)?.and_then(|s| s.available_from_us))
708 }
709
710 pub fn available_until_us(&self, index: u64) -> Result<Option<i128>, AddressError> {
712 Ok(self.get(index)?.and_then(|s| s.available_until_us))
713 }
714
715 pub(crate) fn leading_true(
719 &self,
720 bound: u64,
721 pred: impl Fn(&SegmentRef) -> bool,
722 ) -> Result<u64, AddressError> {
723 let (mut lo, mut hi) = (0u64, bound);
724 while lo < hi {
725 let mid = lo + (hi - lo) / 2;
726 match self.get(mid) {
727 Ok(Some(s)) if pred(&s) => lo = mid + 1,
728 Ok(Some(_)) | Ok(None) | Err(_) => hi = mid,
729 }
730 }
731 Ok(lo)
732 }
733
734 pub(crate) fn constant_duration(&self) -> Option<u64> {
735 match &self.kind {
736 Kind::Duration { d, .. } => Some(*d),
737 _ => None,
738 }
739 }
740
741 pub(crate) fn avail_parts(&self) -> Option<(i128, i128)> {
742 self.avail.as_ref().map(|a| (a.ast_us, a.ato_us))
743 }
744}