1use crate::{MediaTime, TimeRangeError};
2use serde::{Deserialize, Serialize};
3
4#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, Hash, Serialize)]
6#[serde(try_from = "Wire")]
7#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
8pub struct TimeRange {
9 start: MediaTime,
10 end: MediaTime,
11}
12#[derive(Deserialize)]
13#[serde(deny_unknown_fields)]
14#[cfg_attr(
15 feature = "schema",
16 derive(schemars::JsonSchema),
17 schemars(rename = "TimeRange")
18)]
19struct Wire {
21 start: MediaTime,
23 end: MediaTime,
25}
26impl TryFrom<Wire> for TimeRange {
27 type Error = TimeRangeError;
28 fn try_from(wire: Wire) -> Result<Self, Self::Error> {
29 Self::new(wire.start, wire.end)
30 }
31}
32impl TimeRange {
33 pub const fn new(start: MediaTime, end: MediaTime) -> Result<Self, TimeRangeError> {
38 if end.0 < start.0 {
39 Err(TimeRangeError::EndBeforeStart)
40 } else {
41 Ok(Self { start, end })
42 }
43 }
44 #[must_use]
46 pub const fn start(self) -> MediaTime {
47 self.start
48 }
49 #[must_use]
51 pub const fn end(self) -> MediaTime {
52 self.end
53 }
54 #[must_use]
56 pub const fn is_empty(self) -> bool {
57 self.start.0 == self.end.0
58 }
59 #[must_use]
61 pub const fn duration(self) -> Option<MediaTime> {
62 self.end.checked_sub(self.start)
63 }
64 #[must_use]
66 pub const fn contains(self, time: MediaTime) -> bool {
67 self.start.0 <= time.0 && time.0 < self.end.0
68 }
69 #[must_use]
72 pub const fn intersects(self, other: Self) -> bool {
73 self.intersection(other).is_some()
74 }
75 #[must_use]
77 pub const fn intersection(self, other: Self) -> Option<Self> {
78 let start = if self.start.0 > other.start.0 {
79 self.start
80 } else {
81 other.start
82 };
83 let end = if self.end.0 < other.end.0 {
84 self.end
85 } else {
86 other.end
87 };
88 if start.0 < end.0 {
89 Some(Self { start, end })
90 } else {
91 None
92 }
93 }
94}
95#[cfg(test)]
96mod tests {
97 use super::*;
98 #[test]
99 fn half_open() {
100 let range = TimeRange::new(MediaTime(1), MediaTime(2)).unwrap();
101 assert!(range.contains(MediaTime(1)));
102 assert!(!range.contains(MediaTime(2)));
103 }
104
105 #[test]
106 fn ranges_intersect_where_they_share_an_instant() {
107 let range = |start, end| TimeRange::new(MediaTime(start), MediaTime(end)).unwrap();
108 assert!(!range(5, 5).intersects(range(0, 10)));
110 assert!(!range(0, 10).intersects(range(5, 5)));
111 assert!(!range(0, 0).intersects(range(0, 10)));
112 assert!(!range(5, 5).intersects(range(5, 5)));
113 assert!(!range(0, 5).intersects(range(5, 10)));
115 assert!(range(0, 6).intersects(range(5, 10)));
116 assert!(range(2, 3).intersects(range(0, 10)));
117 let ranges = (-3..4)
118 .flat_map(|start| (start..4).map(move |end| range(start, end)))
119 .collect::<Vec<_>>();
120 for &a in &ranges {
121 for &b in &ranges {
122 let shared = (-3..4).any(|t| a.contains(MediaTime(t)) && b.contains(MediaTime(t)));
123 assert_eq!(a.intersects(b), shared, "{a:?} {b:?}");
124 assert_eq!(a.intersection(b).is_some(), shared, "{a:?} {b:?}");
125 }
126 }
127 }
128}