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]
71 pub const fn intersects(self, other: Self) -> bool {
72 self.intersection(other).is_some()
73 }
74 #[must_use]
76 pub const fn intersection(self, other: Self) -> Option<Self> {
77 let start = if self.start.0 > other.start.0 {
78 self.start
79 } else {
80 other.start
81 };
82 let end = if self.end.0 < other.end.0 {
83 self.end
84 } else {
85 other.end
86 };
87 if start.0 < end.0 {
88 Some(Self { start, end })
89 } else {
90 None
91 }
92 }
93}
94#[cfg(test)]
95mod tests {
96 use super::*;
97 #[test]
98 fn half_open() {
99 let range = TimeRange::new(MediaTime(1), MediaTime(2)).unwrap();
100 assert!(range.contains(MediaTime(1)));
101 assert!(!range.contains(MediaTime(2)));
102 }
103
104 #[test]
105 fn ranges_intersect_where_they_share_an_instant() {
106 let range = |start, end| TimeRange::new(MediaTime(start), MediaTime(end)).unwrap();
107 assert!(!range(5, 5).intersects(range(0, 10)));
108 assert!(!range(0, 10).intersects(range(5, 5)));
109 assert!(!range(0, 0).intersects(range(0, 10)));
110 assert!(!range(5, 5).intersects(range(5, 5)));
111 assert!(!range(0, 5).intersects(range(5, 10)));
112 assert!(range(0, 6).intersects(range(5, 10)));
113 assert!(range(2, 3).intersects(range(0, 10)));
114 let ranges = (-3..4)
115 .flat_map(|start| (start..4).map(move |end| range(start, end)))
116 .collect::<Vec<_>>();
117 for &a in &ranges {
118 for &b in &ranges {
119 let shared = (-3..4).any(|t| a.contains(MediaTime(t)) && b.contains(MediaTime(t)));
120 assert_eq!(a.intersects(b), shared, "{a:?} {b:?}");
121 assert_eq!(a.intersection(b).is_some(), shared, "{a:?} {b:?}");
122 }
123 }
124 }
125}