diff --git a/.travis.yml b/.travis.yml index af43e8e..982e71a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/src/line.rs b/src/line.rs index c6ba9d2..1c675e4 100644 --- a/src/line.rs +++ b/src/line.rs @@ -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; diff --git a/src/master_playlist.rs b/src/master_playlist.rs index f906667..9d9906f 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -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, - /// 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, - /// 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, - /// 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,33 +174,26 @@ 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::() - .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::().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() + ); } } diff --git a/src/media_segment.rs b/src/media_segment.rs index ec7889c..884cc1e 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -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,13 +129,15 @@ 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" - .to_string() + 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() ); } } diff --git a/src/tags/basic/m3u.rs b/src/tags/basic/m3u.rs index e2a8b78..e5b78ea 100644 --- a/src/tags/basic/m3u.rs +++ b/src/tags/basic/m3u.rs @@ -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)] diff --git a/src/tags/basic/version.rs b/src/tags/basic/version.rs index dc867e8..8108cf7 100644 --- a/src/tags/basic/version.rs +++ b/src/tags/basic/version.rs @@ -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); diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index d122930..f83d9b6 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -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, /// 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, diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index 9e53e8b..75ad365 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -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, diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index ecbd270..9f0e1d8 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -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); diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index b1f777b..a0a0349 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -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)] diff --git a/src/tags/media_playlist/end_list.rs b/src/tags/media_playlist/end_list.rs index 3202870..e848be3 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.rs @@ -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)] diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index 0536af6..c7cb4ca 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -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 { diff --git a/src/tags/media_playlist/playlist_type.rs b/src/tags/media_playlist/playlist_type.rs index 69c2e52..f81ad10 100644 --- a/src/tags/media_playlist/playlist_type.rs +++ b/src/tags/media_playlist/playlist_type.rs @@ -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, } diff --git a/src/tags/shared/mod.rs b/src/tags/shared/mod.rs index ce2f2d2..052f0eb 100644 --- a/src/tags/shared/mod.rs +++ b/src/tags/shared/mod.rs @@ -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; diff --git a/src/traits.rs b/src/traits.rs index a80de7f..a92a9af 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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 RequiredVersion for Vec { 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 RequiredVersion for Vec { impl RequiredVersion for Option { 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); + } +} diff --git a/src/utils.rs b/src/utils.rs index 3b2a375..7ff9ddd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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());