1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-22 07:10:59 +00:00

some improvements

This commit is contained in:
Luro02 2020-02-14 13:05:18 +01:00
parent 25f9691c75
commit 8cced1ac53
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
16 changed files with 331 additions and 152 deletions

View file

@ -35,6 +35,6 @@ script:
after_success: |
# this does require a -Z flag for Doctests, which is unstable!
if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then
cargo tarpaulin --run-types Tests Doctests --out Xml
cargo tarpaulin --ignore-panics --ignore-tests --run-types Tests Doctests --out Xml
bash <(curl -s https://codecov.io/bash)
fi

View file

@ -1,7 +1,9 @@
use core::convert::TryFrom;
use core::fmt;
use core::iter::FusedIterator;
use core::str::FromStr;
use derive_more::Display;
use crate::tags;
use crate::Error;
@ -33,16 +35,14 @@ impl<'a> Iterator for Lines<'a> {
}
}
impl<'a> FusedIterator for Lines<'a> {}
impl<'a> From<&'a str> for Lines<'a> {
fn from(buffer: &'a str) -> Self {
Self {
lines: buffer.lines().filter_map(|line| {
if line.trim().is_empty() {
None
} else {
Some(line.trim())
}
}),
lines: buffer
.lines()
.filter_map(|line| Some(line.trim()).filter(|v| !v.is_empty())),
}
}
}
@ -55,7 +55,8 @@ pub(crate) enum Line<'a> {
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Display)]
#[display(fmt = "{}")]
pub(crate) enum Tag<'a> {
ExtXVersion(tags::ExtXVersion),
ExtInf(tags::ExtInf),
@ -80,34 +81,6 @@ pub(crate) enum Tag<'a> {
Unknown(&'a str),
}
impl<'a> fmt::Display for Tag<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Self::ExtXVersion(value) => value.fmt(f),
Self::ExtInf(value) => value.fmt(f),
Self::ExtXByteRange(value) => value.fmt(f),
Self::ExtXDiscontinuity(value) => value.fmt(f),
Self::ExtXKey(value) => value.fmt(f),
Self::ExtXMap(value) => value.fmt(f),
Self::ExtXProgramDateTime(value) => value.fmt(f),
Self::ExtXDateRange(value) => value.fmt(f),
Self::ExtXTargetDuration(value) => value.fmt(f),
Self::ExtXMediaSequence(value) => value.fmt(f),
Self::ExtXDiscontinuitySequence(value) => value.fmt(f),
Self::ExtXEndList(value) => value.fmt(f),
Self::ExtXPlaylistType(value) => value.fmt(f),
Self::ExtXIFramesOnly(value) => value.fmt(f),
Self::ExtXMedia(value) => value.fmt(f),
Self::VariantStream(value) => value.fmt(f),
Self::ExtXSessionData(value) => value.fmt(f),
Self::ExtXSessionKey(value) => value.fmt(f),
Self::ExtXIndependentSegments(value) => value.fmt(f),
Self::ExtXStart(value) => value.fmt(f),
Self::Unknown(value) => value.fmt(f),
}
}
}
impl<'a> TryFrom<&'a str> for Tag<'a> {
type Error = Error;

View file

@ -15,8 +15,9 @@ use crate::utils::tag;
use crate::{Error, RequiredVersion};
/// The master playlist describes all of the available variants for your
/// content. Each variant is a version of the stream at a particular bitrate
/// and is contained in a separate playlist.
/// content.
/// Each variant is a version of the stream at a particular bitrate and is
/// contained in a separate playlist.
#[derive(ShortHand, Debug, Clone, Builder, PartialEq)]
#[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))]
@ -67,21 +68,24 @@ pub struct MasterPlaylist {
/// This tag is optional.
#[builder(default)]
variants: Vec<VariantStream>,
/// The [`ExtXSessionData`] tags of the playlist.
/// The [`ExtXSessionData`] tag allows arbitrary session data to be
/// carried in a [`MasterPlaylist`].
///
/// # Note
///
/// This tag is optional.
#[builder(default)]
session_data: Vec<ExtXSessionData>,
/// The [`ExtXSessionKey`] tags of the playlist.
/// This is a list of [`ExtXSessionKey`]s, that allows the client to preload
/// these keys without having to read the [`MediaPlaylist`]s first.
///
/// # Note
///
/// This tag is optional.
#[builder(default)]
session_keys: Vec<ExtXSessionKey>,
/// A list of tags that are unknown.
/// This is a list of all tags that could not be identified while parsing
/// the input.
///
/// # Note
///
@ -140,7 +144,7 @@ impl MasterPlaylistBuilder {
}
fn validate_stream_inf(&self, value: &[VariantStream]) -> crate::Result<()> {
let mut has_none_closed_captions = false;
let mut closed_captions_none = false;
for t in value {
if let VariantStream::ExtXStreamInf {
@ -170,31 +174,24 @@ impl MasterPlaylistBuilder {
if let Some(closed_captions) = &closed_captions {
match &closed_captions {
ClosedCaptions::GroupId(group_id) => {
if closed_captions_none {
return Err(Error::custom(
"If one ClosedCaptions is None all have to be None!",
));
}
if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
return Err(Error::unmatched_group(group_id));
}
}
ClosedCaptions::None => {
has_none_closed_captions = true;
_ => {
if !closed_captions_none {
closed_captions_none = true;
}
}
}
}
}
if has_none_closed_captions
&& !value.iter().all(|t| {
if let VariantStream::ExtXStreamInf {
closed_captions, ..
} = &t
{
closed_captions == &Some(ClosedCaptions::None)
} else {
false
}
})
{
return Err(Error::invalid_input());
}
Ok(())
@ -218,6 +215,8 @@ impl MasterPlaylistBuilder {
let mut set = HashSet::new();
if let Some(value) = &self.session_data {
set.reserve(value.len());
for t in value {
if !set.insert((t.data_id(), t.language())) {
return Err(Error::custom(format!("Conflict: {}", t)));
@ -264,20 +263,20 @@ impl fmt::Display for MasterPlaylist {
writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
}
for t in &self.media {
writeln!(f, "{}", t)?;
for value in &self.media {
writeln!(f, "{}", value)?;
}
for t in &self.variants {
writeln!(f, "{}", t)?;
for value in &self.variants {
writeln!(f, "{}", value)?;
}
for t in &self.session_data {
writeln!(f, "{}", t)?;
for value in &self.session_data {
writeln!(f, "{}", value)?;
}
for t in &self.session_keys {
writeln!(f, "{}", t)?;
for value in &self.session_keys {
writeln!(f, "{}", value)?;
}
if let Some(value) = &self.independent_segments {
@ -288,8 +287,8 @@ impl fmt::Display for MasterPlaylist {
writeln!(f, "{}", value)?;
}
for t in &self.unknown_tags {
writeln!(f, "{}", t)?;
for value in &self.unknown_tags {
writeln!(f, "{}", value)?;
}
Ok(())
@ -382,40 +381,194 @@ impl FromStr for MasterPlaylist {
#[cfg(test)]
mod tests {
use super::*;
use crate::types::StreamData;
use pretty_assertions::assert_eq;
#[test]
fn test_parser() {
"#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/low/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/lo_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/hi_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
http://example.com/high/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio/index.m3u8\n"
assert_eq!(
concat!(
"#EXTM3U\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/low/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/lo_mid/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/hi_mid/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n",
"http://example.com/high/index.m3u8\n",
"#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n",
"http://example.com/audio/index.m3u8\n"
)
.parse::<MasterPlaylist>()
.unwrap();
.unwrap(),
MasterPlaylist::builder()
.variants(vec![
VariantStream::ExtXStreamInf {
uri: "http://example.com/low/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(150000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/lo_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(240000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/hi_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(440000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/high/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(640000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((640, 360))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/audio/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(64000)
.codecs("mp4a.40.5")
.build()
.unwrap()
},
])
.build()
.unwrap()
);
}
#[test]
fn test_display() {
let input = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/low/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/lo_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/hi_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
http://example.com/high/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio/index.m3u8\n";
let playlist = input.parse::<MasterPlaylist>().unwrap();
assert_eq!(playlist.to_string(), input);
assert_eq!(
concat!(
"#EXTM3U\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/low/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/lo_mid/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/hi_mid/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n",
"http://example.com/high/index.m3u8\n",
"#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n",
"http://example.com/audio/index.m3u8\n"
)
.to_string(),
MasterPlaylist::builder()
.variants(vec![
VariantStream::ExtXStreamInf {
uri: "http://example.com/low/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(150000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/lo_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(240000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/hi_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(440000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/high/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(640000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((640, 360))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/audio/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(64000)
.codecs("mp4a.40.5")
.build()
.unwrap()
},
])
.build()
.unwrap()
.to_string()
);
}
}

View file

@ -41,9 +41,7 @@ pub struct MediaSegment {
}
impl MediaSegment {
/// Returns a builder for a [`MasterPlaylist`].
///
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// Returns a builder for a [`MediaSegment`].
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
}
@ -131,12 +129,14 @@ mod tests {
.build()
.unwrap()
.to_string(),
"#EXT-X-KEY:METHOD=NONE\n\
#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"
concat!(
"#EXT-X-KEY:METHOD=NONE\n",
"#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()
);
}

View file

@ -9,10 +9,10 @@ use crate::{Error, RequiredVersion};
///
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
/// Playlist file.
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].
/// It is the at the start of every [`MediaPlaylist`] and [`MasterPlaylist`].
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// [`M3U`]: https://en.wikipedia.org/wiki/M3U
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]

View file

@ -8,7 +8,7 @@ use crate::{Error, RequiredVersion};
/// # [4.3.1.2. EXT-X-VERSION]
///
/// The [`ExtXVersion`] tag indicates the compatibility version of the
/// [`Master Playlist`] or [`Media Playlist`] file.
/// [`MasterPlaylist`] or [`MediaPlaylist`] file.
/// It applies to the entire Playlist.
///
/// # Examples
@ -40,8 +40,8 @@ use crate::{Error, RequiredVersion};
/// );
/// ```
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ExtXVersion(ProtocolVersion);

View file

@ -11,16 +11,16 @@ use crate::{Error, RequiredVersion};
/// # [4.4.5.1. EXT-X-MEDIA]
///
/// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s,
/// The [`ExtXMedia`] tag is used to relate [`MediaPlaylist`]s,
/// that contain alternative renditions of the same content.
///
/// For example, three [`ExtXMedia`] tags can be used to identify audio-only
/// [`Media Playlist`]s, that contain English, French, and Spanish renditions
/// [`MediaPlaylist`]s, that contain English, French, and Spanish renditions
/// of the same presentation. Or, two [`ExtXMedia`] tags can be used to
/// identify video-only [`Media Playlist`]s that show two different camera
/// identify video-only [`MediaPlaylist`]s that show two different camera
/// angles.
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.4.5.1. EXT-X-MEDIA]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.1
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash)]
@ -35,7 +35,7 @@ pub struct ExtXMedia {
/// This attribute is **required**.
#[shorthand(enable(copy))]
media_type: MediaType,
/// An `URI` to a [`Media Playlist`].
/// An `URI` to a [`MediaPlaylist`].
///
/// # Note
///
@ -44,7 +44,7 @@ pub struct ExtXMedia {
/// - This attribute is **not allowed**, if the [`MediaType`] is
/// [`MediaType::ClosedCaptions`].
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
#[builder(setter(strip_option), default)]
uri: Option<String>,
/// The identifier that specifies the group to which the rendition
@ -112,9 +112,9 @@ pub struct ExtXMedia {
#[builder(default)]
is_forced: bool,
/// An [`InStreamId`] specifies a rendition within the
/// segments in the [`Media Playlist`].
/// segments in the [`MediaPlaylist`].
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
#[builder(setter(strip_option), default)]
#[shorthand(enable(copy))]
instream_id: Option<InStreamId>,

View file

@ -31,9 +31,9 @@ pub enum SessionData {
/// # [4.3.4.4. EXT-X-SESSION-DATA]
///
/// The [`ExtXSessionData`] tag allows arbitrary session data to be
/// carried in a [`Master Playlist`].
/// carried in a [`MasterPlaylist`].
///
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
#[derive(ShortHand, Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
#[builder(setter(into))]
@ -58,7 +58,7 @@ pub struct ExtXSessionData {
///
/// This field is required.
data: SessionData,
/// The `language` attribute identifies the language of [`SessionData`].
/// The `language` attribute identifies the language of the [`SessionData`].
/// See [rfc5646](https://tools.ietf.org/html/rfc5646).
#[builder(setter(into, strip_option), default)]
language: Option<String>,

View file

@ -9,13 +9,13 @@ use crate::{Error, RequiredVersion};
/// # [4.3.4.5. EXT-X-SESSION-KEY]
///
/// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s
/// to be specified in a [`Master Playlist`]. This allows the client to
/// preload these keys without having to read the [`Media Playlist`]s
/// The [`ExtXSessionKey`] tag allows encryption keys from [`MediaPlaylist`]s
/// to be specified in a [`MasterPlaylist`]. This allows the client to
/// preload these keys without having to read the [`MediaPlaylist`]s
/// first.
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
#[derive(Deref, DerefMut, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey(DecryptionKey);

View file

@ -11,11 +11,13 @@ use crate::RequiredVersion;
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///
/// The [`ExtXDiscontinuitySequence`] tag allows synchronization between
/// different Renditions of the same Variant Stream or different Variant
/// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s.
/// different renditions of the same [`VariantStream`] or different
/// [`VariantStream`]s that have [`ExtXDiscontinuity`] tags in their
/// [`MediaPlaylist`]s.
///
/// [`VariantStream`]: crate::tags::VariantStream
/// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.3
#[derive(ShortHand, Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]

View file

@ -7,11 +7,11 @@ use crate::{Error, RequiredVersion};
/// # [4.4.3.4. EXT-X-ENDLIST]
///
/// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be
/// added to the [`Media Playlist`] file.
/// The [`ExtXEndList`] tag indicates, that no more [`MediaSegment`]s will be
/// added to the [`MediaPlaylist`] file.
///
/// [`Media Segment`]: crate::MediaSegment
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaSegment`]: crate::MediaSegment
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.4.3.4. EXT-X-ENDLIST]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]

View file

@ -65,7 +65,10 @@ impl RequiredVersion for ExtXMediaSequence {
}
impl fmt::Display for ExtXMediaSequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) }
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//
write!(f, "{}{}", Self::PREFIX, self.0)
}
}
impl FromStr for ExtXMediaSequence {

View file

@ -8,22 +8,22 @@ use crate::{Error, RequiredVersion};
/// # [4.3.3.5. EXT-X-PLAYLIST-TYPE]
///
/// The [`ExtXPlaylistType`] tag provides mutability information about the
/// [`Media Playlist`]. It applies to the entire [`Media Playlist`].
/// [`MediaPlaylist`]. It applies to the entire [`MediaPlaylist`].
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ExtXPlaylistType {
/// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s
/// can only be added to the end of the [`Media Playlist`].
/// If the [`ExtXPlaylistType`] is Event, [`MediaSegment`]s
/// can only be added to the end of the [`MediaPlaylist`].
///
/// [`Media Segment`]: crate::MediaSegment
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaSegment`]: crate::MediaSegment
/// [`MediaPlaylist`]: crate::MediaPlaylist
Event,
/// If the [`ExtXPlaylistType`] is Video On Demand (Vod),
/// the [`Media Playlist`] cannot change.
/// the [`MediaPlaylist`] cannot change.
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`MediaPlaylist`]: crate::MediaPlaylist
Vod,
}

View file

@ -1,11 +1,14 @@
//! The tags in this section can appear in either Master Playlists or
//! Media Playlists. If one of these tags appears in a Master Playlist,
//! it should not appear in any Media Playlist referenced by that Master
//! Playlist. A tag that appears in both must have the same value;
//! otherwise, clients should ignore the value in the Media Playlist(s).
//! The tags in this section can appear in either [`MasterPlaylist`]s or
//! [`MediaPlaylist`]s. If one of these tags appears in a [`MasterPlaylist`],
//! it should not appear in any [`MediaPlaylist`] referenced by that
//! [`MasterPlaylist`]. A tag that appears in both must have the same value;
//! otherwise, clients should ignore the value in the [`MediaPlaylist`](s).
//!
//! These tags must not appear more than once in a Playlist. If a tag
//! appears more than once, clients must fail to parse the Playlist.
//!
//! [`MediaPlaylist`]: crate::MediaPlaylist
//! [`MasterPlaylist`]: crate::MasterPlaylist
mod independent_segments;
mod start;

View file

@ -87,6 +87,7 @@ pub trait Encrypted {
if self.keys().is_empty() {
return false;
}
self.keys()
.iter()
.any(|k| k.method() != EncryptionMethod::None)
@ -140,7 +141,7 @@ pub trait RequiredVersion {
impl<T: RequiredVersion> RequiredVersion for Vec<T> {
fn required_version(&self) -> ProtocolVersion {
self.iter()
.map(|v| v.required_version())
.map(RequiredVersion::required_version)
.max()
// return ProtocolVersion::V1, if the iterator is empty:
.unwrap_or_default()
@ -150,8 +151,26 @@ impl<T: RequiredVersion> RequiredVersion for Vec<T> {
impl<T: RequiredVersion> RequiredVersion for Option<T> {
fn required_version(&self) -> ProtocolVersion {
self.iter()
.map(|v| v.required_version())
.map(RequiredVersion::required_version)
.max()
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_required_version_trait() {
struct Example;
impl RequiredVersion for Example {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V3 }
}
assert_eq!(Example.required_version(), ProtocolVersion::V3);
assert_eq!(Example.introduced_version(), ProtocolVersion::V3);
}
}

View file

@ -86,6 +86,32 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_iv_from_str() {
assert_eq!(
parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(),
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF
]
);
assert_eq!(
parse_iv_from_str("0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(),
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF
]
);
// missing `0x` at the start:
assert!(parse_iv_from_str("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err());
// too small:
assert!(parse_iv_from_str("0xFF").is_err());
// too large:
assert!(parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err());
}
#[test]
fn test_parse_yes_or_no() {
assert!(parse_yes_or_no("YES").unwrap());