mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-21 14:50:59 +00:00
improve documentation and tests of ByteRange
This commit is contained in:
parent
651db2e18b
commit
5972216323
6 changed files with 764 additions and 138 deletions
|
@ -153,8 +153,8 @@ impl MediaPlaylistBuilder {
|
|||
}
|
||||
|
||||
// CHECK: `#EXT-X-BYTE-RANGE`
|
||||
if let Some(tag) = s.byte_range() {
|
||||
if tag.to_range().start().is_none() {
|
||||
if let Some(range) = s.byte_range() {
|
||||
if range.start().is_none() {
|
||||
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
|
||||
if last_uri != s.uri() {
|
||||
return Err(Error::invalid_input());
|
||||
|
|
|
@ -120,7 +120,7 @@ mod tests {
|
|||
MediaSegment::builder()
|
||||
//.keys(vec![ExtXKey::empty()])
|
||||
.map(ExtXMap::new("https://www.example.com/"))
|
||||
.byte_range(ExtXByteRange::new(20, Some(5)))
|
||||
.byte_range(ExtXByteRange::from(5..25))
|
||||
//.date_range() // TODO!
|
||||
.discontinuity(ExtXDiscontinuity)
|
||||
.inf(ExtInf::new(Duration::from_secs(4)))
|
||||
|
|
|
@ -1,39 +1,120 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
use derive_more::{AsMut, AsRef, Deref, DerefMut, From};
|
||||
|
||||
use crate::types::{ByteRange, ProtocolVersion};
|
||||
use crate::utils::tag;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.2.2. EXT-X-BYTERANGE]
|
||||
/// Indicates that a [`MediaSegment`] is a sub-range of the resource identified
|
||||
/// by its `URI`.
|
||||
///
|
||||
/// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range
|
||||
/// of the resource identified by its `URI`.
|
||||
/// # Example
|
||||
///
|
||||
/// [`Media Segment`]: crate::MediaSegment
|
||||
/// [4.4.2.2. EXT-X-BYTERANGE]:
|
||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
|
||||
#[derive(Deref, DerefMut, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
/// Constructing an [`ExtXByteRange`]:
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// assert_eq!(ExtXByteRange::from(22..55), ExtXByteRange::from(22..=54));
|
||||
/// ```
|
||||
///
|
||||
/// It is also possible to omit the start, in which case it assumes that the
|
||||
/// [`ExtXByteRange`] starts at the byte after the end of the previous
|
||||
/// [`ExtXByteRange`] or 0 if there is no previous one.
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// assert_eq!(ExtXByteRange::from(..55), ExtXByteRange::from(..=54));
|
||||
/// ```
|
||||
///
|
||||
/// [`MediaSegment`]: crate::MediaSegment
|
||||
#[derive(
|
||||
AsRef, AsMut, From, Deref, DerefMut, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
#[from(forward)]
|
||||
pub struct ExtXByteRange(ByteRange);
|
||||
|
||||
impl ExtXByteRange {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
|
||||
|
||||
/// Makes a new [`ExtXByteRange`] tag.
|
||||
/// Adds `num` to the `start` and `end` of the range.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// let byte_range = ExtXByteRange::new(20, Some(5));
|
||||
/// let range = ExtXByteRange::from(10..22);
|
||||
/// let nrange = range.saturating_add(5);
|
||||
///
|
||||
/// assert_eq!(nrange.len(), range.len());
|
||||
/// assert_eq!(nrange.start(), range.start().map(|c| c + 5));
|
||||
/// ```
|
||||
pub const fn new(length: usize, start: Option<usize>) -> Self {
|
||||
Self(ByteRange::new(length, start))
|
||||
}
|
||||
///
|
||||
/// # Overflow
|
||||
///
|
||||
/// If the range is saturated it will not overflow and instead
|
||||
/// stay at it's current value.
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// let range = ExtXByteRange::from(5..usize::max_value());
|
||||
///
|
||||
/// // this would cause the end to overflow
|
||||
/// let nrange = range.saturating_add(1);
|
||||
///
|
||||
/// // but the range remains unchanged
|
||||
/// assert_eq!(range, nrange);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The length of the range will remain unchanged,
|
||||
/// if the `start` is `Some`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_add(self, num: usize) -> Self { Self(self.0.saturating_add(num)) }
|
||||
|
||||
/// Converts the [`ExtXByteRange`] to a [`ByteRange`].
|
||||
/// Subtracts `num` from the `start` and `end` of the range.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// let range = ExtXByteRange::from(10..22);
|
||||
/// let nrange = range.saturating_sub(5);
|
||||
///
|
||||
/// assert_eq!(nrange.len(), range.len());
|
||||
/// assert_eq!(nrange.start(), range.start().map(|c| c - 5));
|
||||
/// ```
|
||||
///
|
||||
/// # Underflow
|
||||
///
|
||||
/// If the range is saturated it will not underflow and instead stay
|
||||
/// at it's current value.
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// let range = ExtXByteRange::from(0..10);
|
||||
///
|
||||
/// // this would cause the start to underflow
|
||||
/// let nrange = range.saturating_sub(1);
|
||||
///
|
||||
/// // but the range remains unchanged
|
||||
/// assert_eq!(range, nrange);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The length of the range will remain unchanged,
|
||||
/// if the `start` is `Some`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_sub(self, num: usize) -> Self { Self(self.0.saturating_sub(num)) }
|
||||
|
||||
/// Returns a shared reference to the underlying [`ByteRange`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -41,10 +122,14 @@ impl ExtXByteRange {
|
|||
/// # use hls_m3u8::tags::ExtXByteRange;
|
||||
/// use hls_m3u8::types::ByteRange;
|
||||
///
|
||||
/// let byte_range = ExtXByteRange::new(20, Some(5));
|
||||
/// let range: ByteRange = byte_range.to_range();
|
||||
/// assert_eq!(
|
||||
/// ExtXByteRange::from(2..11).as_byte_range(),
|
||||
/// &ByteRange::from(2..11)
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn to_range(&self) -> ByteRange { self.0 }
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_byte_range(&self) -> &ByteRange { &self.0 }
|
||||
}
|
||||
|
||||
/// This tag requires [`ProtocolVersion::V4`].
|
||||
|
@ -52,6 +137,48 @@ impl RequiredVersion for ExtXByteRange {
|
|||
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 }
|
||||
}
|
||||
|
||||
impl Into<ByteRange> for ExtXByteRange {
|
||||
fn into(self) -> ByteRange { self.0 }
|
||||
}
|
||||
|
||||
impl<T> Sub<T> for ExtXByteRange
|
||||
where
|
||||
ByteRange: Sub<T, Output = ByteRange>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn sub(self, rhs: T) -> Self::Output { Self(self.0.sub(rhs)) }
|
||||
}
|
||||
|
||||
impl<T> SubAssign<T> for ExtXByteRange
|
||||
where
|
||||
ByteRange: SubAssign<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, other: T) { self.0.sub_assign(other); }
|
||||
}
|
||||
|
||||
impl<T> Add<T> for ExtXByteRange
|
||||
where
|
||||
ByteRange: Add<T, Output = ByteRange>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn add(self, rhs: T) -> Self::Output { Self(self.0.add(rhs)) }
|
||||
}
|
||||
|
||||
impl<T> AddAssign<T> for ExtXByteRange
|
||||
where
|
||||
ByteRange: AddAssign<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: T) { self.0.add_assign(other); }
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtXByteRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
|
@ -77,57 +204,52 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let byte_range = ExtXByteRange::new(0, Some(5));
|
||||
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:0@5".to_string());
|
||||
|
||||
let byte_range = ExtXByteRange::new(99999, Some(2));
|
||||
assert_eq!(
|
||||
byte_range.to_string(),
|
||||
"#EXT-X-BYTERANGE:99999@2".to_string()
|
||||
ExtXByteRange::from(2..15).to_string(),
|
||||
"#EXT-X-BYTERANGE:13@2".to_string()
|
||||
);
|
||||
|
||||
let byte_range = ExtXByteRange::new(99999, None);
|
||||
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:99999".to_string());
|
||||
assert_eq!(
|
||||
ExtXByteRange::from(..22).to_string(),
|
||||
"#EXT-X-BYTERANGE:22".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let byte_range = ExtXByteRange::new(99999, Some(2));
|
||||
assert_eq!(
|
||||
byte_range,
|
||||
"#EXT-X-BYTERANGE:99999@2".parse::<ExtXByteRange>().unwrap()
|
||||
ExtXByteRange::from(2..15),
|
||||
"#EXT-X-BYTERANGE:13@2".parse().unwrap()
|
||||
);
|
||||
|
||||
let byte_range = ExtXByteRange::new(99999, None);
|
||||
assert_eq!(
|
||||
byte_range,
|
||||
"#EXT-X-BYTERANGE:99999".parse::<ExtXByteRange>().unwrap()
|
||||
ExtXByteRange::from(..22),
|
||||
"#EXT-X-BYTERANGE:22".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deref() {
|
||||
let byte_range = ExtXByteRange::new(0, Some(22));
|
||||
let byte_range = ExtXByteRange::from(0..22);
|
||||
|
||||
assert_eq!(byte_range.length(), 0);
|
||||
assert_eq!(byte_range.start(), Some(22));
|
||||
assert_eq!(byte_range.len(), 22);
|
||||
assert_eq!(byte_range.start(), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deref_mut() {
|
||||
let mut byte_range = ExtXByteRange::new(0, Some(22));
|
||||
let mut byte_range = ExtXByteRange::from(10..110);
|
||||
|
||||
byte_range.set_length(100);
|
||||
byte_range.set_start(Some(50));
|
||||
|
||||
assert_eq!(byte_range.length(), 100);
|
||||
assert_eq!(byte_range.len(), 60);
|
||||
assert_eq!(byte_range.start(), Some(50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_required_version() {
|
||||
assert_eq!(
|
||||
ExtXByteRange::new(20, Some(5)).required_version(),
|
||||
ExtXByteRange::from(5..20).required_version(),
|
||||
ProtocolVersion::V4
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,13 +44,10 @@ pub struct ExtXMap {
|
|||
/// # use hls_m3u8::tags::ExtXMap;
|
||||
/// use hls_m3u8::types::ByteRange;
|
||||
///
|
||||
/// let mut map = ExtXMap::with_range(
|
||||
/// "https://prod.mediaspace.com/init.bin",
|
||||
/// ByteRange::new(9, Some(2)),
|
||||
/// );
|
||||
/// let mut map = ExtXMap::with_range("https://prod.mediaspace.com/init.bin", ..9);
|
||||
///
|
||||
/// map.set_range(Some(ByteRange::new(1, None)));
|
||||
/// assert_eq!(map.range(), Some(ByteRange::new(1, None)));
|
||||
/// map.set_range(Some(2..5));
|
||||
/// assert_eq!(map.range(), Some(ByteRange::from(2..5)));
|
||||
/// ```
|
||||
#[shorthand(enable(copy))]
|
||||
range: Option<ByteRange>,
|
||||
|
@ -85,15 +82,12 @@ impl ExtXMap {
|
|||
/// # use hls_m3u8::tags::ExtXMap;
|
||||
/// use hls_m3u8::types::ByteRange;
|
||||
///
|
||||
/// let map = ExtXMap::with_range(
|
||||
/// "https://prod.mediaspace.com/init.bin",
|
||||
/// ByteRange::new(9, Some(2)),
|
||||
/// );
|
||||
/// ExtXMap::with_range("https://prod.mediaspace.com/init.bin", 2..11);
|
||||
/// ```
|
||||
pub fn with_range<T: Into<String>>(uri: T, range: ByteRange) -> Self {
|
||||
pub fn with_range<I: Into<String>, B: Into<ByteRange>>(uri: I, range: B) -> Self {
|
||||
Self {
|
||||
uri: uri.into(),
|
||||
range: Some(range),
|
||||
range: Some(range.into()),
|
||||
keys: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +167,7 @@ mod test {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))).to_string(),
|
||||
ExtXMap::with_range("foo", ByteRange::from(2..11)).to_string(),
|
||||
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\"".to_string(),
|
||||
);
|
||||
}
|
||||
|
@ -186,11 +180,11 @@ mod test {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))),
|
||||
ExtXMap::with_range("foo", ByteRange::from(2..11)),
|
||||
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\"".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))),
|
||||
ExtXMap::with_range("foo", ByteRange::from(2..11)),
|
||||
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\",UNKNOWN=IGNORED"
|
||||
.parse()
|
||||
.unwrap()
|
||||
|
@ -201,7 +195,7 @@ mod test {
|
|||
fn test_required_version() {
|
||||
assert_eq!(ExtXMap::new("foo").required_version(), ProtocolVersion::V6);
|
||||
assert_eq!(
|
||||
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))).required_version(),
|
||||
ExtXMap::with_range("foo", ByteRange::from(2..11)).required_version(),
|
||||
ProtocolVersion::V6
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,67 +1,394 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use core::convert::TryInto;
|
||||
use core::fmt;
|
||||
use core::ops::{
|
||||
Add, AddAssign, Bound, Range, RangeBounds, RangeInclusive, RangeTo, RangeToInclusive, Sub,
|
||||
SubAssign,
|
||||
};
|
||||
use core::str::FromStr;
|
||||
|
||||
use shorthand::ShortHand;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Byte range.
|
||||
/// A range of bytes, which can be seen as either `..end` or `start..end`.
|
||||
///
|
||||
/// See: [4.3.2.2. EXT-X-BYTERANGE]
|
||||
/// It can be constructed from `..end` and `start..end`:
|
||||
///
|
||||
/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
|
||||
/// ```
|
||||
/// use hls_m3u8::types::ByteRange;
|
||||
///
|
||||
/// let range = ByteRange::from(10..20);
|
||||
/// let range = ByteRange::from(..20);
|
||||
/// ```
|
||||
#[derive(ShortHand, Copy, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
||||
#[shorthand(enable(must_use))]
|
||||
#[shorthand(enable(must_use, copy), disable(option_as_ref, set))]
|
||||
pub struct ByteRange {
|
||||
/// The length of the range.
|
||||
/// Returns the `start` of the [`ByteRange`], if there is one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// #
|
||||
/// let mut range = ByteRange::new(20, Some(3));
|
||||
/// # assert_eq!(range.length(), 20);
|
||||
///
|
||||
/// range.set_length(10);
|
||||
/// assert_eq!(range.length(), 10);
|
||||
/// assert_eq!(ByteRange::from(0..5).start(), Some(0));
|
||||
/// assert_eq!(ByteRange::from(..5).start(), None);
|
||||
/// ```
|
||||
length: usize,
|
||||
/// The start of the range.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// #
|
||||
/// let mut range = ByteRange::new(20, None);
|
||||
/// # assert_eq!(range.start(), None);
|
||||
///
|
||||
/// range.set_start(Some(3));
|
||||
/// assert_eq!(range.start(), Some(3));
|
||||
/// ```
|
||||
//
|
||||
// this is a workaround until this issue is fixed:
|
||||
// https://github.com/Luro02/shorthand/issues/20
|
||||
#[shorthand(enable(copy), disable(option_as_ref))]
|
||||
start: Option<usize>,
|
||||
/// Returns the `end` of the [`ByteRange`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// assert_eq!(ByteRange::from(0..5).start(), Some(0));
|
||||
/// assert_eq!(ByteRange::from(..5).start(), None);
|
||||
/// ```
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl ByteRange {
|
||||
/// Creates a new [`ByteRange`].
|
||||
/// Changes the length of the [`ByteRange`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// ByteRange::new(22, Some(12));
|
||||
/// let mut range = ByteRange::from(0..5);
|
||||
/// range.set_len(2);
|
||||
///
|
||||
/// assert_eq!(range, ByteRange::from(0..2));
|
||||
///
|
||||
/// range.set_len(200);
|
||||
/// assert_eq!(range, ByteRange::from(0..200));
|
||||
/// ```
|
||||
pub const fn new(length: usize, start: Option<usize>) -> Self { Self { length, start } }
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The `start` will not be changed.
|
||||
pub fn set_len(&mut self, new_len: usize) {
|
||||
// the new_len can be either greater or smaller than `self.len()`.
|
||||
// if new_len is larger `checked_sub` will return `None`
|
||||
if let Some(value) = self.len().checked_sub(new_len) {
|
||||
self.end -= value;
|
||||
} else {
|
||||
self.end += new_len.saturating_sub(self.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the `start` of the [`ByteRange`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// assert_eq!(ByteRange::from(0..5).set_start(Some(5)).start(), Some(5));
|
||||
/// assert_eq!(ByteRange::from(..5).set_start(Some(2)).start(), Some(2));
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic, if the `new_start` is larger, than the
|
||||
/// [`end`](ByteRange::end).
|
||||
pub fn set_start(&mut self, new_start: Option<usize>) -> &mut Self {
|
||||
if new_start.map_or(false, |s| s > self.end) {
|
||||
panic!("attempt to make the start larger than the end");
|
||||
}
|
||||
|
||||
self.start = new_start;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds `num` to the `start` and `end` of the range.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(10..22);
|
||||
/// let nrange = range.saturating_add(5);
|
||||
///
|
||||
/// assert_eq!(nrange.len(), range.len());
|
||||
/// assert_eq!(nrange.start(), range.start().map(|c| c + 5));
|
||||
/// ```
|
||||
///
|
||||
/// # Overflow
|
||||
///
|
||||
/// If the range is saturated it will not overflow and instead stay
|
||||
/// at it's current value.
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(5..usize::max_value());
|
||||
///
|
||||
/// // this would cause the end to overflow
|
||||
/// let nrange = range.saturating_add(1);
|
||||
///
|
||||
/// // but the range remains unchanged
|
||||
/// assert_eq!(range, nrange);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The length of the range will remain unchanged,
|
||||
/// if the `start` is `Some`.
|
||||
#[must_use]
|
||||
pub fn saturating_add(mut self, num: usize) -> Self {
|
||||
if let Some(start) = self.start {
|
||||
// add the number to the start
|
||||
if let (Some(start), Some(end)) = (start.checked_add(num), self.end.checked_add(num)) {
|
||||
self.start = Some(start);
|
||||
self.end = end;
|
||||
} else {
|
||||
// it is ensured at construction that the start will never be larger than the
|
||||
// end. This clause can therefore be only reached if the end overflowed.
|
||||
// -> It is only possible to add `usize::max_value() - end` to the start.
|
||||
if let Some(start) = start.checked_add(usize::max_value() - self.end) {
|
||||
self.start = Some(start);
|
||||
self.end = usize::max_value();
|
||||
} else {
|
||||
// both end + start overflowed -> do not change anything
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.end = self.end.saturating_add(num);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Subtracts `num` from the `start` and `end` of the range.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(10..22);
|
||||
/// let nrange = range.saturating_sub(5);
|
||||
///
|
||||
/// assert_eq!(nrange.len(), range.len());
|
||||
/// assert_eq!(nrange.start(), range.start().map(|c| c - 5));
|
||||
/// ```
|
||||
///
|
||||
/// # Underflow
|
||||
///
|
||||
/// If the range is saturated it will not underflow and instead stay
|
||||
/// at it's current value.
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(0..10);
|
||||
///
|
||||
/// // this would cause the start to underflow
|
||||
/// let nrange = range.saturating_sub(1);
|
||||
///
|
||||
/// // but the range remains unchanged
|
||||
/// assert_eq!(range, nrange);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The length of the range will remain unchanged,
|
||||
/// if the `start` is `Some`.
|
||||
#[must_use]
|
||||
pub fn saturating_sub(mut self, num: usize) -> Self {
|
||||
if let Some(start) = self.start {
|
||||
// subtract the number from the start
|
||||
if let (Some(start), Some(end)) = (start.checked_sub(num), self.end.checked_sub(num)) {
|
||||
self.start = Some(start);
|
||||
self.end = end;
|
||||
} else {
|
||||
// it is ensured at construction that the start will never be larger, than the
|
||||
// end so this clause will only be reached, if the start underflowed.
|
||||
// -> can at most subtract `start` from `end`
|
||||
if let Some(end) = self.end.checked_sub(start) {
|
||||
self.start = Some(0);
|
||||
self.end = end;
|
||||
} else {
|
||||
// both end + start underflowed
|
||||
// -> do not change anything
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.end = self.end.saturating_sub(num);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the length, which is calculated by subtracting the `end` from
|
||||
/// the `start`. If the `start` is `None` a 0 is assumed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(1..16);
|
||||
///
|
||||
/// assert_eq!(range.len(), 15);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize { self.end.saturating_sub(self.start.unwrap_or(0)) }
|
||||
|
||||
/// Returns `true` if the length is zero.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::ByteRange;
|
||||
/// let range = ByteRange::from(12..12);
|
||||
///
|
||||
/// assert_eq!(range.is_empty(), true);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
||||
}
|
||||
|
||||
impl Sub<usize> for ByteRange {
|
||||
type Output = Self;
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
Self {
|
||||
start: self.start.map(|lhs| lhs - rhs),
|
||||
end: self.end - rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<usize> for ByteRange {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, other: usize) { *self = <Self as Sub<usize>>::sub(*self, other); }
|
||||
}
|
||||
|
||||
impl Add<usize> for ByteRange {
|
||||
type Output = Self;
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self {
|
||||
start: self.start.map(|lhs| lhs + rhs),
|
||||
end: self.end + rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<usize> for ByteRange {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: usize) { *self = <Self as Add<usize>>::add(*self, other); }
|
||||
}
|
||||
|
||||
macro_rules! impl_from_ranges {
|
||||
( $( $type:tt ),* ) => {
|
||||
$(
|
||||
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
|
||||
impl From<Range<$type>> for ByteRange {
|
||||
fn from(range: Range<$type>) -> Self {
|
||||
if range.start > range.end {
|
||||
panic!("the range start must be smaller than the end");
|
||||
}
|
||||
|
||||
Self {
|
||||
start: Some(range.start as usize),
|
||||
end: range.end as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
|
||||
impl From<RangeInclusive<$type>> for ByteRange {
|
||||
fn from(range: RangeInclusive<$type>) -> Self {
|
||||
let (start, end) = range.into_inner();
|
||||
|
||||
if start > end {
|
||||
panic!("the range start must be smaller than the end");
|
||||
}
|
||||
|
||||
Self {
|
||||
start: Some(start as usize),
|
||||
end: (end as usize).saturating_add(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
|
||||
impl From<RangeTo<$type>> for ByteRange {
|
||||
fn from(range: RangeTo<$type>) -> Self {
|
||||
Self {
|
||||
start: None,
|
||||
end: range.end as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
|
||||
impl From<RangeToInclusive<$type>> for ByteRange {
|
||||
fn from(range: RangeToInclusive<$type>) -> Self {
|
||||
Self {
|
||||
start: None,
|
||||
end: (range.end as usize).saturating_add(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with generics as soon as overlapping trait implementations are
|
||||
// stable (`Into<i64> for usize` is reserved for upstream crates ._.)
|
||||
impl_from_ranges![u64, u32, u16, u8, usize, i32];
|
||||
|
||||
#[must_use]
|
||||
impl RangeBounds<usize> for ByteRange {
|
||||
fn start_bound(&self) -> Bound<&usize> {
|
||||
if let Some(start) = &self.start {
|
||||
Bound::Included(start)
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end_bound(&self) -> Bound<&usize> { Bound::Excluded(&self.end) }
|
||||
}
|
||||
|
||||
/// This conversion will fail if the start of the [`ByteRange`] is `Some`.
|
||||
impl TryInto<RangeTo<usize>> for ByteRange {
|
||||
type Error = Error;
|
||||
|
||||
fn try_into(self) -> Result<RangeTo<usize>, Self::Error> {
|
||||
if self.start.is_some() {
|
||||
return Err(Error::custom("A `RangeTo` (`..end`) does not have a start"));
|
||||
}
|
||||
|
||||
Ok(RangeTo { end: self.end })
|
||||
}
|
||||
}
|
||||
|
||||
/// This conversion will fail if the start of the [`ByteRange`] is `None`.
|
||||
impl TryInto<Range<usize>> for ByteRange {
|
||||
type Error = Error;
|
||||
|
||||
fn try_into(self) -> Result<Range<usize>, Self::Error> {
|
||||
if self.start.is_none() {
|
||||
return Err(Error::custom(
|
||||
"A `Range` (`start..end`) has to have a start.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Range {
|
||||
start: self.start.unwrap(),
|
||||
end: self.end,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ByteRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.length)?;
|
||||
write!(f, "{}", self.len())?;
|
||||
|
||||
if let Some(value) = self.start {
|
||||
write!(f, "@{}", value)?;
|
||||
|
@ -77,17 +404,20 @@ impl FromStr for ByteRange {
|
|||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut input = input.splitn(2, '@');
|
||||
|
||||
let length = input
|
||||
.next()
|
||||
.ok_or_else(|| Error::custom("missing length"))?;
|
||||
let length = length.parse().map_err(|e| Error::parse_int(length, e))?;
|
||||
let length = input.next().unwrap();
|
||||
let length = length
|
||||
.parse::<usize>()
|
||||
.map_err(|e| Error::parse_int(length, e))?;
|
||||
|
||||
let start = input
|
||||
.next()
|
||||
.map(|v| v.parse().map_err(|e| Error::parse_int(v, e)))
|
||||
.map(|v| v.parse::<usize>().map_err(|e| Error::parse_int(v, e)))
|
||||
.transpose()?;
|
||||
|
||||
Ok(Self::new(length, start))
|
||||
Ok(Self {
|
||||
start,
|
||||
end: start.unwrap_or(0) + length,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,59 +427,239 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
#[should_panic = "the range start must be smaller than the end"]
|
||||
fn test_from_range_panic() { let _ = ByteRange::from(6..0); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "the range start must be smaller than the end"]
|
||||
fn test_from_range_inclusive_panic() { let _ = ByteRange::from(6..=0); }
|
||||
|
||||
#[test]
|
||||
fn test_from_ranges() {
|
||||
assert_eq!(ByteRange::from(1..10), ByteRange::from(1..=9));
|
||||
assert_eq!(ByteRange::from(..10), ByteRange::from(..=9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_bounds() {
|
||||
assert_eq!(ByteRange::from(0..10).start_bound(), Bound::Included(&0));
|
||||
assert_eq!(ByteRange::from(..10).start_bound(), Bound::Unbounded);
|
||||
|
||||
assert_eq!(ByteRange::from(0..10).end_bound(), Bound::Excluded(&10));
|
||||
assert_eq!(ByteRange::from(..10).end_bound(), Bound::Excluded(&10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_into() {
|
||||
assert_eq!(ByteRange::from(1..4).try_into(), Ok(1..4));
|
||||
assert_eq!(ByteRange::from(..4).try_into(), Ok(..4));
|
||||
|
||||
assert!(TryInto::<RangeTo<usize>>::try_into(ByteRange::from(1..4)).is_err());
|
||||
assert!(TryInto::<Range<usize>>::try_into(ByteRange::from(..4)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_assign() {
|
||||
let mut range = ByteRange::from(5..10);
|
||||
range += 5;
|
||||
|
||||
assert_eq!(range, ByteRange::from(10..15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "attempt to add with overflow"]
|
||||
fn test_add_assign_panic() {
|
||||
let mut range = ByteRange::from(4..usize::max_value());
|
||||
range += 5;
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_assign() {
|
||||
let mut range = ByteRange::from(10..20);
|
||||
range -= 5;
|
||||
|
||||
assert_eq!(range, ByteRange::from(5..15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "attempt to subtract with overflow"]
|
||||
fn test_sub_assign_panic() {
|
||||
let mut range = ByteRange::from(4..10);
|
||||
range -= 5;
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "attempt to make the start larger than the end"]
|
||||
fn test_set_start() { let _ = ByteRange::from(4..10).set_start(Some(11)); }
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
// normal addition
|
||||
assert_eq!(ByteRange::from(5..10) + 5, ByteRange::from(10..15));
|
||||
assert_eq!(ByteRange::from(..10) + 5, ByteRange::from(..15));
|
||||
|
||||
// adding 0
|
||||
assert_eq!(ByteRange::from(5..10) + 0, ByteRange::from(5..10));
|
||||
assert_eq!(ByteRange::from(..10) + 0, ByteRange::from(..10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "attempt to add with overflow"]
|
||||
fn test_add_panic() { let _ = ByteRange::from(usize::max_value()..usize::max_value()) + 1; }
|
||||
|
||||
#[test]
|
||||
fn test_sub() {
|
||||
// normal subtraction
|
||||
assert_eq!(ByteRange::from(5..10) - 4, ByteRange::from(1..6));
|
||||
assert_eq!(ByteRange::from(..10) - 4, ByteRange::from(..6));
|
||||
|
||||
// subtracting 0
|
||||
assert_eq!(ByteRange::from(0..0) - 0, ByteRange::from(0..0));
|
||||
assert_eq!(ByteRange::from(2..3) - 0, ByteRange::from(2..3));
|
||||
|
||||
assert_eq!(ByteRange::from(..0) - 0, ByteRange::from(..0));
|
||||
assert_eq!(ByteRange::from(..3) - 0, ByteRange::from(..3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "attempt to subtract with overflow"]
|
||||
fn test_sub_panic() { let _ = ByteRange::from(0..0) - 1; }
|
||||
|
||||
#[test]
|
||||
fn test_saturating_add() {
|
||||
// normal addition
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 0,
|
||||
start: Some(5),
|
||||
}
|
||||
.to_string(),
|
||||
"0@5".to_string()
|
||||
ByteRange::from(5..10).saturating_add(5),
|
||||
ByteRange::from(10..15)
|
||||
);
|
||||
assert_eq!(
|
||||
ByteRange::from(..10).saturating_add(5),
|
||||
ByteRange::from(..15)
|
||||
);
|
||||
|
||||
// adding 0
|
||||
assert_eq!(
|
||||
ByteRange::from(6..11).saturating_add(0),
|
||||
ByteRange::from(6..11)
|
||||
);
|
||||
assert_eq!(
|
||||
ByteRange::from(..11).saturating_add(0),
|
||||
ByteRange::from(..11)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 99999,
|
||||
start: Some(2),
|
||||
}
|
||||
.to_string(),
|
||||
"99999@2".to_string()
|
||||
ByteRange::from(0..0).saturating_add(0),
|
||||
ByteRange::from(0..0)
|
||||
);
|
||||
assert_eq!(ByteRange::from(..0).saturating_add(0), ByteRange::from(..0));
|
||||
|
||||
// overflow
|
||||
assert_eq!(
|
||||
ByteRange::from(usize::max_value()..usize::max_value()).saturating_add(1),
|
||||
ByteRange::from(usize::max_value()..usize::max_value())
|
||||
);
|
||||
assert_eq!(
|
||||
ByteRange::from(..usize::max_value()).saturating_add(1),
|
||||
ByteRange::from(..usize::max_value())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 99999,
|
||||
start: None,
|
||||
}
|
||||
.to_string(),
|
||||
"99999".to_string()
|
||||
ByteRange::from(usize::max_value() - 5..usize::max_value()).saturating_add(1),
|
||||
ByteRange::from(usize::max_value() - 5..usize::max_value())
|
||||
);
|
||||
|
||||
// overflow, but something can be added to the range:
|
||||
assert_eq!(
|
||||
ByteRange::from(usize::max_value() - 5..usize::max_value() - 3).saturating_add(4),
|
||||
ByteRange::from(usize::max_value() - 2..usize::max_value())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ByteRange::from(..usize::max_value() - 3).saturating_add(4),
|
||||
ByteRange::from(..usize::max_value())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturating_sub() {
|
||||
// normal subtraction
|
||||
assert_eq!(
|
||||
ByteRange::from(5..10).saturating_sub(4),
|
||||
ByteRange::from(1..6)
|
||||
);
|
||||
|
||||
// subtracting 0
|
||||
assert_eq!(
|
||||
ByteRange::from(0..0).saturating_sub(0),
|
||||
ByteRange::from(0..0)
|
||||
);
|
||||
assert_eq!(
|
||||
ByteRange::from(2..3).saturating_sub(0),
|
||||
ByteRange::from(2..3)
|
||||
);
|
||||
|
||||
// the start underflows
|
||||
assert_eq!(
|
||||
ByteRange::from(0..5).saturating_sub(4),
|
||||
ByteRange::from(0..5)
|
||||
);
|
||||
|
||||
// the start underflows, but one can still subtract something from it
|
||||
assert_eq!(
|
||||
ByteRange::from(1..5).saturating_sub(2),
|
||||
ByteRange::from(0..4)
|
||||
);
|
||||
|
||||
// both start and end underflow
|
||||
assert_eq!(
|
||||
ByteRange::from(1..3).saturating_sub(5),
|
||||
ByteRange::from(0..2)
|
||||
);
|
||||
|
||||
// both start + end are 0 + underflow
|
||||
assert_eq!(
|
||||
ByteRange::from(0..0).saturating_sub(1),
|
||||
ByteRange::from(0..0)
|
||||
);
|
||||
|
||||
// half open ranges:
|
||||
assert_eq!(ByteRange::from(..6).saturating_sub(2), ByteRange::from(..4));
|
||||
assert_eq!(ByteRange::from(..5).saturating_sub(0), ByteRange::from(..5));
|
||||
assert_eq!(ByteRange::from(..0).saturating_sub(0), ByteRange::from(..0));
|
||||
|
||||
assert_eq!(ByteRange::from(..0).saturating_sub(1), ByteRange::from(..0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(ByteRange::from(0..5).to_string(), "5@0".to_string());
|
||||
|
||||
assert_eq!(
|
||||
ByteRange::from(2..100001).to_string(),
|
||||
"99999@2".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(ByteRange::from(..99999).to_string(), "99999".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(ByteRange::from(2..22), "20@2".parse().unwrap());
|
||||
|
||||
assert_eq!(ByteRange::from(..300), "300".parse().unwrap());
|
||||
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 99999,
|
||||
start: Some(2),
|
||||
},
|
||||
"99999@2".parse::<ByteRange>().unwrap()
|
||||
ByteRange::from_str("a"),
|
||||
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 99999,
|
||||
start: Some(2),
|
||||
},
|
||||
"99999@2".parse::<ByteRange>().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ByteRange {
|
||||
length: 99999,
|
||||
start: None,
|
||||
},
|
||||
"99999".parse::<ByteRange>().unwrap()
|
||||
ByteRange::from_str("1@a"),
|
||||
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
|
||||
);
|
||||
|
||||
assert!("".parse::<ByteRange>().is_err());
|
||||
|
|
|
@ -13,19 +13,19 @@ fn test_media_playlist_with_byterange() {
|
|||
.segments(vec![
|
||||
MediaSegment::builder()
|
||||
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||
.byte_range(ExtXByteRange::new(75232, Some(0)))
|
||||
.byte_range(ExtXByteRange::from(0..75232))
|
||||
.uri("video.ts")
|
||||
.build()
|
||||
.unwrap(),
|
||||
MediaSegment::builder()
|
||||
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||
.byte_range(ExtXByteRange::new(82112, Some(752321)))
|
||||
.byte_range(ExtXByteRange::from(752321..82112 + 752321))
|
||||
.uri("video.ts")
|
||||
.build()
|
||||
.unwrap(),
|
||||
MediaSegment::builder()
|
||||
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||
.byte_range(ExtXByteRange::new(69864, None))
|
||||
.byte_range(ExtXByteRange::from(..69864))
|
||||
.uri("video.ts")
|
||||
.build()
|
||||
.unwrap(),
|
||||
|
|
Loading…
Reference in a new issue