timed_metadata/
timeline.rs1use crate::anchor::TimeAnchor;
4use crate::convert::{EmsgConfig, scte35_to_daterange, scte35_to_emsg};
5use crate::daterange::DateRange;
6use crate::error::{Error, Result};
7use crate::event::{MediaTime, TimedEvent};
8use alloc::vec::Vec;
9use broadcast_common::traits::Parse;
10use scte35_splice::SpliceInfoSection;
11
12pub const PTS_WRAP: u64 = broadcast_common::clock33::WRAP_33BIT;
14
15#[derive(Debug, Default)]
17pub struct Timeline {
18 anchor: Option<TimeAnchor>,
19 unroller: PtsUnroller,
20}
21
22impl Timeline {
23 pub fn new() -> Self {
25 Self::default()
26 }
27 pub fn with_anchor(anchor: TimeAnchor) -> Self {
29 Timeline {
30 anchor: Some(anchor),
31 unroller: PtsUnroller::default(),
32 }
33 }
34 pub fn set_anchor(&mut self, anchor: TimeAnchor) {
36 self.anchor = Some(anchor);
37 }
38
39 pub fn push_scte35(&mut self, bytes: &[u8]) -> Result<TimedEvent> {
41 let section = SpliceInfoSection::parse(bytes)?;
42 let mut ev = TimedEvent::from_scte35(§ion, bytes)?;
43 if let Some(MediaTime(pts33)) = ev.at {
44 let abs = self.unroller.unroll(pts33);
45 ev.at = Some(MediaTime(abs));
46 }
47 Ok(ev)
48 }
49
50 pub fn to_daterange(&self, ev: &TimedEvent) -> Result<DateRange> {
52 let anchor = self.anchor.as_ref().ok_or(Error::MissingAnchor)?;
53 scte35_to_daterange(ev, anchor)
54 }
55
56 pub fn to_emsg(&self, ev: &TimedEvent, cfg: &EmsgConfig) -> Result<Vec<u8>> {
58 match &ev.source {
59 crate::event::SourcePayload::Scte35 { raw } => scte35_to_emsg(raw, cfg),
60 crate::event::SourcePayload::Emsg { .. } => Err(Error::AttrParse(
61 alloc::string::String::from("event is not SCTE-35-sourced"),
62 )),
63 }
64 }
65}
66
67#[derive(Debug, Default)]
85pub(crate) struct PtsUnroller {
86 state: Option<(u64, i128)>,
88}
89
90impl PtsUnroller {
91 pub(crate) fn unroll(&mut self, raw33: u64) -> u64 {
92 let unwrapped = match self.state {
93 Some((prev_raw, prev_unwrapped)) => {
94 broadcast_common::clock33::unwrap_delta(prev_unwrapped, prev_raw, raw33)
95 }
96 None => raw33 as i128,
97 };
98 self.state = Some((raw33, unwrapped));
99 unwrapped.max(0) as u64
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 fn splice_2002() -> alloc::vec::Vec<u8> {
108 let hex = "FC302100000000000000FFF01005000007D27FEF7F7E0020F580C0000000000088B9661D";
109 (0..hex.len())
110 .step_by(2)
111 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
112 .collect()
113 }
114
115 #[test]
116 fn push_scte35_returns_event() {
117 let mut tl = Timeline::new();
118 let ev = tl.push_scte35(&splice_2002()).unwrap();
119 assert_eq!(ev.id, Some(2002));
120 }
121
122 #[test]
123 fn to_daterange_without_anchor_errors() {
124 let tl = Timeline::new();
125 let ev = Timeline::new().push_scte35(&splice_2002()).unwrap();
126 assert!(matches!(
127 tl.to_daterange(&ev),
128 Err(crate::Error::MissingAnchor)
129 ));
130 }
131
132 #[test]
133 fn wrap_unroll_adds_one_epoch() {
134 let mut u = PtsUnroller {
136 state: Some(((1u64 << 33) - 10, ((1u64 << 33) - 10) as i128)),
137 };
138 assert_eq!(u.unroll(5), 5 + (1u64 << 33));
139 }
140
141 #[test]
142 fn wrap_unroll_forward_delta_keeps_epoch() {
143 let mut u = PtsUnroller {
145 state: Some((1_000, 1_000)),
146 };
147 assert_eq!(u.unroll(2_000), 2_000);
148 let mut u2 = PtsUnroller::default();
150 assert_eq!(u2.unroll(42), 42);
151 }
152
153 #[test]
162 fn wrap_unroll_backward_reorder_across_origin_does_not_leap_forward() {
163 let mut u = PtsUnroller::default();
164 assert_eq!(u.unroll(2), 2);
165 let second = u.unroll((1u64 << 33) - 3);
166 assert!(
167 second < 1000,
168 "expected a small value from a 5-tick backward reorder, got {second}"
169 );
170 }
171}