2019-09-13 14:06:52 +00:00
|
|
|
use std::fmt;
|
|
|
|
|
2019-09-14 19:21:44 +00:00
|
|
|
use derive_builder::Builder;
|
2020-02-02 12:38:11 +00:00
|
|
|
use shorthand::ShortHand;
|
2019-09-14 19:21:44 +00:00
|
|
|
|
2019-03-31 09:58:11 +00:00
|
|
|
use crate::tags::{
|
2018-10-04 11:18:56 +00:00
|
|
|
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
|
|
|
|
};
|
2020-03-23 12:34:26 +00:00
|
|
|
use crate::types::{DecryptionKey, ProtocolVersion};
|
|
|
|
use crate::{Decryptable, RequiredVersion};
|
2018-02-12 16:00:23 +00:00
|
|
|
|
2020-03-25 11:18:34 +00:00
|
|
|
/// A video is split into smaller chunks called [`MediaSegment`]s, which are
|
|
|
|
/// specified by a uri and optionally a byte range.
|
|
|
|
///
|
|
|
|
/// Each `MediaSegment` must carry the continuation of the encoded bitstream
|
|
|
|
/// from the end of the segment with the previous [`MediaSegment::number`],
|
|
|
|
/// where values in a series such as timestamps and continuity counters must
|
|
|
|
/// continue uninterrupted. The only exceptions are the first [`MediaSegment`]
|
|
|
|
/// ever to appear in a [`MediaPlaylist`] and [`MediaSegment`]s that are
|
|
|
|
/// explicitly signaled as discontinuities.
|
|
|
|
/// Unmarked media discontinuities can trigger playback errors.
|
|
|
|
///
|
|
|
|
/// Any `MediaSegment` that contains video should include enough information
|
|
|
|
/// to initialize a video decoder and decode a continuous set of frames that
|
|
|
|
/// includes the final frame in the segment; network efficiency is optimized if
|
|
|
|
/// there is enough information in the segment to decode all frames in the
|
|
|
|
/// segment.
|
|
|
|
///
|
|
|
|
/// For example, any `MediaSegment` containing H.264 video should
|
|
|
|
/// contain an Instantaneous Decoding Refresh (IDR); frames prior to the first
|
|
|
|
/// IDR will be downloaded but possibly discarded.
|
|
|
|
///
|
|
|
|
/// [`MediaPlaylist`]: crate::MediaPlaylist
|
|
|
|
#[derive(ShortHand, Debug, Clone, Builder, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
#[builder(setter(strip_option))]
|
2020-03-29 10:58:43 +00:00
|
|
|
#[shorthand(enable(must_use, skip))]
|
2019-09-14 19:21:44 +00:00
|
|
|
pub struct MediaSegment {
|
2020-03-25 11:18:34 +00:00
|
|
|
/// Each [`MediaSegment`] has a number, which allows synchronization between
|
|
|
|
/// different variants.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This number must not be specified, because it will be assigned
|
|
|
|
/// automatically by [`MediaPlaylistBuilder::segments`]. The first
|
|
|
|
/// [`MediaSegment::number`] in a [`MediaPlaylist`] will either be 0 or the
|
|
|
|
/// number returned by the [`ExtXDiscontinuitySequence`] if one is
|
|
|
|
/// provided.
|
|
|
|
/// The following segments will be the previous segment number + 1.
|
|
|
|
///
|
|
|
|
/// [`MediaPlaylistBuilder::segments`]:
|
|
|
|
/// crate::builder::MediaPlaylistBuilder::segments
|
|
|
|
/// [`MediaPlaylist`]: crate::MediaPlaylist
|
|
|
|
/// [`ExtXMediaSequence`]: crate::tags::ExtXMediaSequence
|
|
|
|
/// [`ExtXDiscontinuitySequence`]: crate::tags::ExtXDiscontinuitySequence
|
|
|
|
#[builder(default, setter(custom))]
|
2020-03-29 10:58:43 +00:00
|
|
|
#[shorthand(disable(set, skip))]
|
2020-03-25 11:18:34 +00:00
|
|
|
pub(crate) number: usize,
|
|
|
|
#[builder(default, setter(custom))]
|
|
|
|
pub(crate) explicit_number: bool,
|
|
|
|
/// This field specifies how to decrypt a [`MediaSegment`], which can only
|
|
|
|
/// be encrypted with one [`EncryptionMethod`], using one [`DecryptionKey`]
|
|
|
|
/// and [`DecryptionKey::iv`].
|
|
|
|
///
|
|
|
|
/// However, a server may offer multiple ways to retrieve that key by
|
|
|
|
/// providing multiple keys with different [`DecryptionKey::format`]s.
|
|
|
|
///
|
|
|
|
/// Any unencrypted segment that is preceded by an encrypted segment must
|
|
|
|
/// have an [`ExtXKey::empty`]. Otherwise, the client will misinterpret
|
|
|
|
/// those segments as encrypted.
|
|
|
|
///
|
|
|
|
/// The server may set the HTTP Expires header in the key response to
|
|
|
|
/// indicate the duration for which the key can be cached.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is optional and a missing value or an [`ExtXKey::empty()`]
|
|
|
|
/// indicates an unencrypted media segment.
|
|
|
|
///
|
|
|
|
/// [`ExtXMap`]: crate::tags::ExtXMap
|
|
|
|
/// [`KeyFormat`]: crate::types::KeyFormat
|
|
|
|
/// [`EncryptionMethod`]: crate::types::EncryptionMethod
|
|
|
|
#[builder(default, setter(into))]
|
|
|
|
pub keys: Vec<ExtXKey>,
|
|
|
|
/// This field specifies how to obtain the Media Initialization Section
|
|
|
|
/// required to parse the applicable `MediaSegment`s.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is optional, but should be specified for media segments in
|
|
|
|
/// playlists with an [`ExtXIFramesOnly`] tag when the first `MediaSegment`
|
|
|
|
/// in the playlist (or the first segment following a segment marked with
|
|
|
|
/// [`MediaSegment::has_discontinuity`]) does not immediately follow the
|
|
|
|
/// Media Initialization Section at the beginning of its resource.
|
|
|
|
///
|
|
|
|
/// [`ExtXIFramesOnly`]: crate::tags::ExtXIFramesOnly
|
2020-02-02 12:38:11 +00:00
|
|
|
#[builder(default)]
|
2020-03-25 11:18:34 +00:00
|
|
|
pub map: Option<ExtXMap>,
|
|
|
|
/// This field indicates that a `MediaSegment` is a sub-range of the
|
|
|
|
/// resource identified by its URI.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is optional.
|
|
|
|
#[builder(default, setter(into))]
|
|
|
|
pub byte_range: Option<ExtXByteRange>,
|
|
|
|
/// This field associates a date-range (i.e., a range of time defined by a
|
|
|
|
/// starting and ending date) with a set of attribute/value pairs.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is optional.
|
2019-09-14 19:21:44 +00:00
|
|
|
#[builder(default)]
|
2020-03-25 11:18:34 +00:00
|
|
|
pub date_range: Option<ExtXDateRange>,
|
|
|
|
/// This field indicates a discontinuity between the `MediaSegment` that
|
|
|
|
/// follows it and the one that preceded it.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is required if any of the following characteristics change:
|
|
|
|
/// - file format
|
|
|
|
/// - number, type, and identifiers of tracks
|
|
|
|
/// - timestamp, sequence
|
|
|
|
///
|
|
|
|
/// This field should be present if any of the following characteristics
|
|
|
|
/// change:
|
|
|
|
/// - encoding parameters
|
|
|
|
/// - encoding sequence
|
2019-09-14 19:21:44 +00:00
|
|
|
#[builder(default)]
|
2020-03-25 11:18:34 +00:00
|
|
|
pub has_discontinuity: bool,
|
|
|
|
/// This field associates the first sample of a media segment with an
|
|
|
|
/// absolute date and/or time.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is optional.
|
2019-09-14 19:21:44 +00:00
|
|
|
#[builder(default)]
|
2020-03-25 11:18:34 +00:00
|
|
|
pub program_date_time: Option<ExtXProgramDateTime>,
|
|
|
|
/// This field indicates the duration of a media segment.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is required.
|
|
|
|
#[builder(setter(into))]
|
2020-03-25 12:58:21 +00:00
|
|
|
pub duration: ExtInf,
|
2020-03-25 11:18:34 +00:00
|
|
|
/// The URI of a media segment.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// This field is required.
|
|
|
|
#[builder(setter(into))]
|
2020-03-29 10:58:43 +00:00
|
|
|
#[shorthand(enable(into), disable(skip))]
|
2019-09-21 10:11:36 +00:00
|
|
|
uri: String,
|
2018-02-12 16:00:23 +00:00
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2019-10-05 14:24:48 +00:00
|
|
|
impl MediaSegment {
|
2020-02-14 12:05:18 +00:00
|
|
|
/// Returns a builder for a [`MediaSegment`].
|
2020-03-25 11:18:34 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::MediaSegment;
|
|
|
|
/// use hls_m3u8::tags::ExtXMap;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let segment = MediaSegment::builder()
|
|
|
|
/// .map(ExtXMap::new("https://www.example.com/"))
|
|
|
|
/// .byte_range(5..25)
|
|
|
|
/// .has_discontinuity(true)
|
2020-03-25 12:58:21 +00:00
|
|
|
/// .duration(Duration::from_secs(4))
|
2020-03-25 11:18:34 +00:00
|
|
|
/// .uri("http://www.uri.com/")
|
|
|
|
/// .build()?;
|
|
|
|
/// # Ok::<(), String>(())
|
|
|
|
/// ```
|
2020-02-24 15:45:10 +00:00
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
2019-10-05 14:24:48 +00:00
|
|
|
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
|
|
|
|
}
|
|
|
|
|
2018-02-12 16:00:23 +00:00
|
|
|
impl MediaSegmentBuilder {
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Pushes an [`ExtXKey`] tag.
|
2020-02-16 11:50:52 +00:00
|
|
|
pub fn push_key<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
|
|
|
|
if let Some(keys) = &mut self.keys {
|
|
|
|
keys.push(value.into());
|
2019-09-14 19:21:44 +00:00
|
|
|
} else {
|
2019-10-04 09:02:21 +00:00
|
|
|
self.keys = Some(vec![value.into()]);
|
2018-02-12 16:00:23 +00:00
|
|
|
}
|
2020-02-16 11:50:52 +00:00
|
|
|
|
2018-02-12 16:00:23 +00:00
|
|
|
self
|
|
|
|
}
|
2020-03-25 11:18:34 +00:00
|
|
|
|
|
|
|
/// The number of a [`MediaSegment`]. Normally this should not be set
|
|
|
|
/// explicitly, because the [`MediaPlaylist::builder`] will automatically
|
|
|
|
/// apply the correct number.
|
|
|
|
///
|
|
|
|
/// [`MediaPlaylist::builder`]: crate::MediaPlaylist::builder
|
|
|
|
pub fn number(&mut self, value: Option<usize>) -> &mut Self {
|
|
|
|
self.number = value;
|
|
|
|
self.explicit_number = Some(value.is_some());
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
2018-02-12 16:00:23 +00:00
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2018-02-12 16:00:23 +00:00
|
|
|
impl fmt::Display for MediaSegment {
|
2020-04-09 06:43:13 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-03-25 11:18:34 +00:00
|
|
|
// NOTE: self.keys will be printed by the `MediaPlaylist` to prevent redundance.
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-02-16 11:50:52 +00:00
|
|
|
if let Some(value) = &self.map {
|
2019-09-14 19:42:06 +00:00
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-12 16:00:23 +00:00
|
|
|
}
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-02-16 11:50:52 +00:00
|
|
|
if let Some(value) = &self.byte_range {
|
2019-09-14 19:42:06 +00:00
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-14 19:47:44 +00:00
|
|
|
}
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-02-16 11:50:52 +00:00
|
|
|
if let Some(value) = &self.date_range {
|
2019-09-14 19:42:06 +00:00
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-14 19:47:44 +00:00
|
|
|
}
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-03-25 11:18:34 +00:00
|
|
|
if self.has_discontinuity {
|
|
|
|
writeln!(f, "{}", ExtXDiscontinuity)?;
|
2018-02-14 19:47:44 +00:00
|
|
|
}
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-02-16 11:50:52 +00:00
|
|
|
if let Some(value) = &self.program_date_time {
|
2019-09-14 19:42:06 +00:00
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-14 19:47:44 +00:00
|
|
|
}
|
2020-02-02 12:38:11 +00:00
|
|
|
|
2020-03-25 12:58:21 +00:00
|
|
|
writeln!(f, "{}", self.duration)?;
|
2018-02-12 16:00:23 +00:00
|
|
|
writeln!(f, "{}", self.uri)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2019-09-22 08:57:28 +00:00
|
|
|
impl RequiredVersion for MediaSegment {
|
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
2019-10-05 14:24:48 +00:00
|
|
|
required_version![
|
|
|
|
self.keys,
|
2020-02-16 11:50:52 +00:00
|
|
|
self.map,
|
|
|
|
self.byte_range,
|
|
|
|
self.date_range,
|
2020-03-25 11:18:34 +00:00
|
|
|
{
|
|
|
|
if self.has_discontinuity {
|
|
|
|
Some(ExtXDiscontinuity)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
},
|
2020-02-16 11:50:52 +00:00
|
|
|
self.program_date_time,
|
2020-03-25 12:58:21 +00:00
|
|
|
self.duration
|
2019-10-05 14:24:48 +00:00
|
|
|
]
|
2018-02-12 16:00:23 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-04 09:02:21 +00:00
|
|
|
|
2020-03-23 12:34:26 +00:00
|
|
|
impl Decryptable for MediaSegment {
|
|
|
|
fn keys(&self) -> Vec<&DecryptionKey> {
|
|
|
|
//
|
|
|
|
self.keys.iter().filter_map(ExtXKey::as_ref).collect()
|
|
|
|
}
|
2019-10-04 09:02:21 +00:00
|
|
|
}
|
2019-10-06 14:39:18 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-10-08 13:42:33 +00:00
|
|
|
use pretty_assertions::assert_eq;
|
2019-10-06 14:39:18 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_display() {
|
|
|
|
assert_eq!(
|
|
|
|
MediaSegment::builder()
|
2020-02-16 11:50:52 +00:00
|
|
|
.map(ExtXMap::new("https://www.example.com/"))
|
2020-02-23 17:56:41 +00:00
|
|
|
.byte_range(ExtXByteRange::from(5..25))
|
2020-03-25 11:18:34 +00:00
|
|
|
.has_discontinuity(true)
|
2020-03-25 12:58:21 +00:00
|
|
|
.duration(ExtInf::new(Duration::from_secs(4)))
|
2019-10-06 14:39:18 +00:00
|
|
|
.uri("http://www.uri.com/")
|
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.to_string(),
|
2020-02-14 12:05:18 +00:00
|
|
|
concat!(
|
|
|
|
"#EXT-X-MAP:URI=\"https://www.example.com/\"\n",
|
|
|
|
"#EXT-X-BYTERANGE:20@5\n",
|
|
|
|
"#EXT-X-DISCONTINUITY\n",
|
|
|
|
"#EXTINF:4,\n",
|
|
|
|
"http://www.uri.com/\n"
|
|
|
|
)
|
|
|
|
.to_string()
|
2019-10-06 14:39:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|