use crate::{MediaTime, TimeRangeError};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, Hash, Serialize)]
#[serde(try_from = "Wire")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TimeRange {
start: MediaTime,
end: MediaTime,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
derive(schemars::JsonSchema),
schemars(rename = "TimeRange")
)]
struct Wire {
start: MediaTime,
end: MediaTime,
}
impl TryFrom<Wire> for TimeRange {
type Error = TimeRangeError;
fn try_from(wire: Wire) -> Result<Self, Self::Error> {
Self::new(wire.start, wire.end)
}
}
impl TimeRange {
pub const fn new(start: MediaTime, end: MediaTime) -> Result<Self, TimeRangeError> {
if end.0 < start.0 {
Err(TimeRangeError::EndBeforeStart)
} else {
Ok(Self { start, end })
}
}
#[must_use]
pub const fn start(self) -> MediaTime {
self.start
}
#[must_use]
pub const fn end(self) -> MediaTime {
self.end
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.start.0 == self.end.0
}
#[must_use]
pub const fn duration(self) -> Option<MediaTime> {
self.end.checked_sub(self.start)
}
#[must_use]
pub const fn contains(self, time: MediaTime) -> bool {
self.start.0 <= time.0 && time.0 < self.end.0
}
#[must_use]
pub const fn intersects(self, other: Self) -> bool {
self.intersection(other).is_some()
}
#[must_use]
pub const fn intersection(self, other: Self) -> Option<Self> {
let start = if self.start.0 > other.start.0 {
self.start
} else {
other.start
};
let end = if self.end.0 < other.end.0 {
self.end
} else {
other.end
};
if start.0 < end.0 {
Some(Self { start, end })
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn half_open() {
let range = TimeRange::new(MediaTime(1), MediaTime(2)).unwrap();
assert!(range.contains(MediaTime(1)));
assert!(!range.contains(MediaTime(2)));
}
#[test]
fn ranges_intersect_where_they_share_an_instant() {
let range = |start, end| TimeRange::new(MediaTime(start), MediaTime(end)).unwrap();
assert!(!range(5, 5).intersects(range(0, 10)));
assert!(!range(0, 10).intersects(range(5, 5)));
assert!(!range(0, 0).intersects(range(0, 10)));
assert!(!range(5, 5).intersects(range(5, 5)));
assert!(!range(0, 5).intersects(range(5, 10)));
assert!(range(0, 6).intersects(range(5, 10)));
assert!(range(2, 3).intersects(range(0, 10)));
let ranges = (-3..4)
.flat_map(|start| (start..4).map(move |end| range(start, end)))
.collect::<Vec<_>>();
for &a in &ranges {
for &b in &ranges {
let shared = (-3..4).any(|t| a.contains(MediaTime(t)) && b.contains(MediaTime(t)));
assert_eq!(a.intersects(b), shared, "{a:?} {b:?}");
assert_eq!(a.intersection(b).is_some(), shared, "{a:?} {b:?}");
}
}
}
}