diff --git a/src/attribute.rs b/src/attribute.rs index 98db989..f4dda8a 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -90,11 +90,11 @@ fn split(value: &str, terminator: char) -> Vec { temp_string.push(c); } k if (k == terminator) => { - if !inside_quotes { + if inside_quotes { + temp_string.push(c); + } else { result.push(temp_string); temp_string = String::new(); - } else { - temp_string.push(c); } } _ => { diff --git a/src/error.rs b/src/error.rs index cd13c38..42b8df8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use failure::{Backtrace, Context, Fail}; /// This crate specific `Result` type. pub type Result = std::result::Result; -/// The ErrorKind. +/// The [`ErrorKind`]. #[derive(Debug, Fail, Clone, PartialEq, Eq)] pub enum ErrorKind { #[fail(display = "ChronoParseError: {}", _0)] @@ -72,6 +72,10 @@ pub enum ErrorKind { /// An attribute is missing. MissingAttribute(String), + #[fail(display = "Unexpected Attribute: {:?}", _0)] + /// An unexpected value. + UnexpectedAttribute(String), + /// Hints that destructuring should not be exhaustive. /// /// This enum may grow additional variants, so this makes sure clients @@ -121,6 +125,10 @@ impl Error { Self::from(ErrorKind::MissingValue(value.to_string())) } + pub(crate) fn unexpected_attribute(value: T) -> Self { + Self::from(ErrorKind::UnexpectedAttribute(value.to_string())) + } + pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) } diff --git a/src/line.rs b/src/line.rs index 67a3724..660b808 100644 --- a/src/line.rs +++ b/src/line.rs @@ -154,53 +154,53 @@ impl fmt::Display for Tag { impl FromStr for Tag { type Err = Error; - fn from_str(s: &str) -> Result { - if s.starts_with(tags::ExtM3u::PREFIX) { - s.parse().map(Tag::ExtM3u) - } else if s.starts_with(tags::ExtXVersion::PREFIX) { - s.parse().map(Tag::ExtXVersion) - } else if s.starts_with(tags::ExtInf::PREFIX) { - s.parse().map(Tag::ExtInf) - } else if s.starts_with(tags::ExtXByteRange::PREFIX) { - s.parse().map(Tag::ExtXByteRange) - } else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) { - s.parse().map(Tag::ExtXDiscontinuity) - } else if s.starts_with(tags::ExtXKey::PREFIX) { - s.parse().map(Tag::ExtXKey) - } else if s.starts_with(tags::ExtXMap::PREFIX) { - s.parse().map(Tag::ExtXMap) - } else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) { - s.parse().map(Tag::ExtXProgramDateTime) - } else if s.starts_with(tags::ExtXTargetDuration::PREFIX) { - s.parse().map(Tag::ExtXTargetDuration) - } else if s.starts_with(tags::ExtXDateRange::PREFIX) { - s.parse().map(Tag::ExtXDateRange) - } else if s.starts_with(tags::ExtXMediaSequence::PREFIX) { - s.parse().map(Tag::ExtXMediaSequence) - } else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { - s.parse().map(Tag::ExtXDiscontinuitySequence) - } else if s.starts_with(tags::ExtXEndList::PREFIX) { - s.parse().map(Tag::ExtXEndList) - } else if s.starts_with(tags::ExtXPlaylistType::PREFIX) { - s.parse().map(Tag::ExtXPlaylistType) - } else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) { - s.parse().map(Tag::ExtXIFramesOnly) - } else if s.starts_with(tags::ExtXMedia::PREFIX) { - s.parse().map(Tag::ExtXMedia) - } else if s.starts_with(tags::ExtXStreamInf::PREFIX) { - s.parse().map(Tag::ExtXStreamInf) - } else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) { - s.parse().map(Tag::ExtXIFrameStreamInf) - } else if s.starts_with(tags::ExtXSessionData::PREFIX) { - s.parse().map(Tag::ExtXSessionData) - } else if s.starts_with(tags::ExtXSessionKey::PREFIX) { - s.parse().map(Tag::ExtXSessionKey) - } else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) { - s.parse().map(Tag::ExtXIndependentSegments) - } else if s.starts_with(tags::ExtXStart::PREFIX) { - s.parse().map(Tag::ExtXStart) + fn from_str(input: &str) -> Result { + if input.starts_with(tags::ExtM3u::PREFIX) { + input.parse().map(Tag::ExtM3u) + } else if input.starts_with(tags::ExtXVersion::PREFIX) { + input.parse().map(Tag::ExtXVersion) + } else if input.starts_with(tags::ExtInf::PREFIX) { + input.parse().map(Tag::ExtInf) + } else if input.starts_with(tags::ExtXByteRange::PREFIX) { + input.parse().map(Tag::ExtXByteRange) + } else if input.starts_with(tags::ExtXDiscontinuity::PREFIX) { + input.parse().map(Tag::ExtXDiscontinuity) + } else if input.starts_with(tags::ExtXKey::PREFIX) { + input.parse().map(Tag::ExtXKey) + } else if input.starts_with(tags::ExtXMap::PREFIX) { + input.parse().map(Tag::ExtXMap) + } else if input.starts_with(tags::ExtXProgramDateTime::PREFIX) { + input.parse().map(Tag::ExtXProgramDateTime) + } else if input.starts_with(tags::ExtXTargetDuration::PREFIX) { + input.parse().map(Tag::ExtXTargetDuration) + } else if input.starts_with(tags::ExtXDateRange::PREFIX) { + input.parse().map(Tag::ExtXDateRange) + } else if input.starts_with(tags::ExtXMediaSequence::PREFIX) { + input.parse().map(Tag::ExtXMediaSequence) + } else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { + input.parse().map(Tag::ExtXDiscontinuitySequence) + } else if input.starts_with(tags::ExtXEndList::PREFIX) { + input.parse().map(Tag::ExtXEndList) + } else if input.starts_with(tags::ExtXPlaylistType::PREFIX) { + input.parse().map(Tag::ExtXPlaylistType) + } else if input.starts_with(tags::ExtXIFramesOnly::PREFIX) { + input.parse().map(Tag::ExtXIFramesOnly) + } else if input.starts_with(tags::ExtXMedia::PREFIX) { + input.parse().map(Tag::ExtXMedia).map_err(Error::custom) + } else if input.starts_with(tags::ExtXStreamInf::PREFIX) { + input.parse().map(Tag::ExtXStreamInf) + } else if input.starts_with(tags::ExtXIFrameStreamInf::PREFIX) { + input.parse().map(Tag::ExtXIFrameStreamInf) + } else if input.starts_with(tags::ExtXSessionData::PREFIX) { + input.parse().map(Tag::ExtXSessionData) + } else if input.starts_with(tags::ExtXSessionKey::PREFIX) { + input.parse().map(Tag::ExtXSessionKey) + } else if input.starts_with(tags::ExtXIndependentSegments::PREFIX) { + input.parse().map(Tag::ExtXIndependentSegments) + } else if input.starts_with(tags::ExtXStart::PREFIX) { + input.parse().map(Tag::ExtXStart) } else { - Ok(Tag::Unknown(s.to_string())) + Ok(Tag::Unknown(input.to_string())) } } } diff --git a/src/tags/basic/m3u.rs b/src/tags/basic/m3u.rs index ac870b1..2298865 100644 --- a/src/tags/basic/m3u.rs +++ b/src/tags/basic/m3u.rs @@ -5,17 +5,35 @@ use crate::types::{ProtocolVersion, RequiredVersion}; use crate::utils::tag; use crate::Error; -/// # [4.3.1.1. EXTM3U] -/// The [ExtM3u] tag indicates that the file is an Extended [M3U] +/// # [4.4.1.1. EXTM3U] +/// 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`]. /// -/// Its format is: -/// ```text -/// #EXTM3U +/// # Examples +/// Parsing from a [`str`]: +/// ``` +/// # use failure::Error; +/// # use hls_m3u8::tags::ExtM3u; +/// # +/// # fn main() -> Result<(), Error> { +/// assert_eq!("#EXTM3U".parse::()?, ExtM3u); +/// # +/// # Ok(()) +/// # } +/// ``` +/// Converting to a [`str`]: +/// ``` +/// # use hls_m3u8::tags::ExtM3u; +/// # +/// assert_eq!("#EXTM3U".to_string(), ExtM3u.to_string()); /// ``` /// -/// [M3U]: https://en.wikipedia.org/wiki/M3U -/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1 +/// [`Media Playlist`]: crate::MediaPlaylist +/// [`Master Playlist`]: crate::MasterPlaylist +/// [`M3U`]: https://en.wikipedia.org/wiki/M3U +/// [4.4.1.1. EXTM3U]: +/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.1 #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ExtM3u; @@ -23,6 +41,7 @@ impl ExtM3u { pub(crate) const PREFIX: &'static str = "#EXTM3U"; } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtM3u { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 @@ -31,7 +50,7 @@ impl RequiredVersion for ExtM3u { impl fmt::Display for ExtM3u { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Self::PREFIX.fmt(f) + write!(f, "{}", Self::PREFIX) } } @@ -40,7 +59,7 @@ impl FromStr for ExtM3u { fn from_str(input: &str) -> Result { tag(input, Self::PREFIX)?; - Ok(ExtM3u) + Ok(Self) } } diff --git a/src/tags/basic/version.rs b/src/tags/basic/version.rs index 874d156..452b460 100644 --- a/src/tags/basic/version.rs +++ b/src/tags/basic/version.rs @@ -5,40 +5,64 @@ use crate::types::{ProtocolVersion, RequiredVersion}; use crate::utils::tag; use crate::Error; -/// # [4.3.1.2. EXT-X-VERSION] -/// The [ExtXVersion] tag indicates the compatibility version of the -/// Playlist file, its associated media, and its server. +/// # [4.4.1.2. EXT-X-VERSION] +/// The [`ExtXVersion`] tag indicates the compatibility version of the +/// [`Master Playlist`] or [`Media Playlist`] file. +/// It applies to the entire Playlist. /// -/// The [ExtXVersion] tag applies to the entire Playlist file. Its -/// format is: -/// -/// ```text -/// #EXT-X-VERSION: +/// # Examples +/// Parsing from a [`str`]: /// ``` -/// where `n` is an integer indicating the protocol compatibility version -/// number. +/// # use failure::Error; +/// # use hls_m3u8::tags::ExtXVersion; +/// # +/// # fn main() -> Result<(), Error> { +/// use hls_m3u8::types::ProtocolVersion; /// -/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2 +/// assert_eq!( +/// "#EXT-X-VERSION:5".parse::()?, +/// ExtXVersion::new(ProtocolVersion::V5) +/// ); +/// # +/// # Ok(()) +/// # } +/// ``` +/// Converting to a [`str`]: +/// ``` +/// # use hls_m3u8::tags::ExtXVersion; +/// # +/// use hls_m3u8::types::ProtocolVersion; +/// +/// assert_eq!( +/// "#EXT-X-VERSION:5".to_string(), +/// ExtXVersion::new(ProtocolVersion::V5).to_string() +/// ); +/// ``` +/// +/// [`Media Playlist`]: crate::MediaPlaylist +/// [`Master Playlist`]: crate::MasterPlaylist +/// [4.4.1.2. EXT-X-VERSION]: +/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.2 #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct ExtXVersion(ProtocolVersion); impl ExtXVersion { pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:"; - /// Makes a new [ExtXVersion] tag. + /// Makes a new [`ExtXVersion`] tag. /// /// # Example /// ``` /// # use hls_m3u8::tags::ExtXVersion; /// use hls_m3u8::types::ProtocolVersion; /// - /// let version_tag = ExtXVersion::new(ProtocolVersion::V2); + /// let version = ExtXVersion::new(ProtocolVersion::V2); /// ``` pub const fn new(version: ProtocolVersion) -> Self { Self(version) } - /// Returns the protocol compatibility version of the playlist, containing this tag. + /// Returns the [`ProtocolVersion`] of the playlist, containing this tag. /// /// # Example /// ``` @@ -55,6 +79,7 @@ impl ExtXVersion { } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXVersion { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 @@ -84,7 +109,7 @@ impl FromStr for ExtXVersion { fn from_str(input: &str) -> Result { let version = tag(input, Self::PREFIX)?.parse()?; - Ok(ExtXVersion::new(version)) + Ok(Self::new(version)) } } @@ -115,4 +140,12 @@ mod test { ProtocolVersion::V1 ); } + + #[test] + fn test_default_and_from() { + assert_eq!( + ExtXVersion::default(), + ExtXVersion::from(ProtocolVersion::V1) + ); + } } diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 076478d..4a87b85 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -7,20 +7,17 @@ use crate::types::{ProtocolVersion, RequiredVersion, StreamInf}; use crate::utils::{quote, tag, unquote}; use crate::Error; -/// # [4.3.4.3. EXT-X-I-FRAME-STREAM-INF] -/// The [ExtXIFrameStreamInf] tag identifies a [Media Playlist] file -/// containing the I-frames of a multimedia presentation. It stands -/// alone, in that it does not apply to a particular `URI` in the [Master Playlist]. +/// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF] +/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file, +/// containing the I-frames of a multimedia presentation. /// -/// Its format is: +/// I-frames are encoded video frames, whose decoding +/// does not depend on any other frame. /// -/// ```text -/// #EXT-X-I-FRAME-STREAM-INF: -/// ``` -/// -/// [Master Playlist]: crate::MasterPlaylist -/// [Media Playlist]: crate::MediaPlaylist -/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3 +/// [`Master Playlist`]: crate::MasterPlaylist +/// [`Media Playlist`]: crate::MediaPlaylist +/// [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]: +/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.3 #[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXIFrameStreamInf { uri: String, @@ -30,7 +27,7 @@ pub struct ExtXIFrameStreamInf { impl ExtXIFrameStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:"; - /// Makes a new [ExtXIFrameStreamInf] tag. + /// Makes a new [`ExtXIFrameStreamInf`] tag. /// /// # Example /// ``` @@ -44,7 +41,7 @@ impl ExtXIFrameStreamInf { } } - /// Returns the `URI`, that identifies the associated media playlist. + /// Returns the `URI`, that identifies the associated [`media playlist`]. /// /// # Example /// ``` @@ -52,11 +49,13 @@ impl ExtXIFrameStreamInf { /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); /// assert_eq!(stream.uri(), &"https://www.example.com".to_string()); /// ``` + /// + /// [`media playlist`]: crate::MediaPlaylist pub const fn uri(&self) -> &String { &self.uri } - /// Sets the `URI`, that identifies the associated media playlist. + /// Sets the `URI`, that identifies the associated [`media playlist`]. /// /// # Example /// ``` @@ -67,12 +66,15 @@ impl ExtXIFrameStreamInf { /// stream.set_uri("../new/uri"); /// assert_eq!(stream.uri(), &"../new/uri".to_string()); /// ``` + /// + /// [`media playlist`]: crate::MediaPlaylist pub fn set_uri(&mut self, value: T) -> &mut Self { self.uri = value.to_string(); self } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXIFrameStreamInf { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 9437fbd..a7f3365 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -4,83 +4,120 @@ use std::str::FromStr; use derive_builder::Builder; use crate::attribute::AttributePairs; -use crate::types::{InStreamId, MediaType, ProtocolVersion, RequiredVersion}; +use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion, RequiredVersion}; use crate::utils::{parse_yes_or_no, quote, tag, unquote}; use crate::Error; -/// # [4.4.4.1. EXT-X-MEDIA] -/// The [ExtXMedia] tag is used to relate [Media Playlist]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 -/// of the same presentation. Or, two [ExtXMedia] tags can be used to -/// identify video-only [Media Playlist]s that show two different camera +/// # [4.4.5.1. EXT-X-MEDIA] +/// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]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 +/// of the same presentation. Or, two [`ExtXMedia`] tags can be used to +/// identify video-only [`Media Playlist`]s that show two different camera /// angles. /// -/// Its format is: -/// ```text -/// #EXT-X-MEDIA: -/// ``` -/// -/// [Media Playlist]: crate::MediaPlaylist -/// [4.4.4.1. EXT-X-MEDIA]: -/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.4.1 +/// [`Media Playlist`]: 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(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[builder(setter(into))] #[builder(build_fn(validate = "Self::validate"))] pub struct ExtXMedia { - /// Sets the media type of the rendition. + /// Sets the [`MediaType`] of the rendition. + /// + /// # Note + /// This attribute is **required**. media_type: MediaType, #[builder(setter(strip_option, into), default)] - /// Sets the URI that identifies the media playlist. + /// Sets the `URI` that identifies the [`Media Playlist`]. + /// + /// # Note + /// - This attribute is **required**, if the [`MediaType`] is [`MediaType::Subtitles`]. + /// - This attribute is **not allowed**, if the [`MediaType`] is + /// [`MediaType::ClosedCaptions`]. + /// + /// [`Media Playlist`]: crate::MediaPlaylist uri: Option, - /// Sets the identifier that specifies the group to which the rendition belongs. + /// Sets the identifier, that specifies the group to which the rendition belongs. + /// + /// # Note + /// This attribute is **required**. group_id: String, + #[builder(setter(strip_option, into), default)] /// Sets the name of the primary language used in the rendition. - #[builder(setter(strip_option, into), default)] + /// The value has to conform to [`RFC5646`]. + /// + /// # Note + /// This attribute is **optional**. + /// + /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646 language: Option, - /// Sets the name of a language associated with the rendition. #[builder(setter(strip_option, into), default)] + /// Sets the name of a language associated with the rendition. + /// + /// # Note + /// This attribute is **optional**. + /// + /// [`language`]: #method.language assoc_language: Option, /// Sets a human-readable description of the rendition. + /// + /// # Note + /// This attribute is **required**. + /// + /// If the [`language`] attribute is present, this attribute should be in that language. + /// + /// [`language`]: #method.language name: String, + #[builder(default)] /// Sets the value of the `default` flag. - #[builder(default)] + /// + /// # Note + /// This attribute is **optional**, its absence indicates an implicit value of `false`. is_default: bool, + #[builder(default)] /// Sets the value of the `autoselect` flag. - #[builder(default)] + /// + /// # Note + /// This attribute is **optional**, its absence indicates an implicit value of `false`. is_autoselect: bool, - /// Sets the value of the `forced` flag. #[builder(default)] + /// Sets the value of the `forced` flag. is_forced: bool, + #[builder(setter(strip_option, into), default)] /// Sets the identifier that specifies a rendition within the segments in the media playlist. - #[builder(setter(strip_option, into), default)] instream_id: Option, + #[builder(setter(strip_option, into), default)] /// Sets the string that represents uniform type identifiers (UTI). - #[builder(setter(strip_option, into), default)] characteristics: Option, - /// Sets the string that represents the parameters of the rendition. #[builder(setter(strip_option, into), default)] - channels: Option, + /// Sets the parameters of the rendition. + channels: Option, } impl ExtXMediaBuilder { fn validate(&self) -> Result<(), String> { + // A MediaType is always required! let media_type = self .media_type .ok_or_else(|| Error::missing_attribute("MEDIA-TYPE").to_string())?; - if MediaType::ClosedCaptions == media_type { + if media_type == MediaType::Subtitles && self.uri.is_none() { + return Err(Error::missing_attribute("URI").to_string()); + } + + if media_type == MediaType::ClosedCaptions { if self.uri.is_some() { - return Err(Error::custom( - "Unexpected attribute: \"URL\" for MediaType::ClosedCaptions!", - ) - .to_string()); + return Err(Error::unexpected_attribute("URI").to_string()); + } + if self.instream_id.is_none() { + return Err(Error::missing_attribute("INSTREAM-ID").to_string()); } - self.instream_id - .ok_or_else(|| Error::missing_attribute("INSTREAM-ID").to_string())?; } else if self.instream_id.is_some() { - return Err(Error::custom("Unexpected attribute: \"INSTREAM-ID\"!").to_string()); + return Err(Error::unexpected_attribute("INSTREAM-ID").to_string()); } if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) { @@ -89,7 +126,7 @@ impl ExtXMediaBuilder { ); } - if MediaType::Subtitles != media_type && self.is_forced.is_some() { + if media_type != MediaType::Subtitles && self.is_forced.is_some() { return Err(Error::invalid_input().to_string()); } @@ -100,7 +137,7 @@ impl ExtXMediaBuilder { impl ExtXMedia { pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:"; - /// Makes a new [ExtXMedia] tag. + /// Makes a new [`ExtXMedia`] tag. pub fn new(media_type: MediaType, group_id: T, name: T) -> Self { Self { media_type, @@ -118,7 +155,7 @@ impl ExtXMedia { } } - /// Returns a builder for [ExtXMedia]. + /// Returns a builder for [`ExtXMedia`]. pub fn builder() -> ExtXMediaBuilder { ExtXMediaBuilder::default() } @@ -215,6 +252,9 @@ impl ExtXMedia { /// Sets a human-readable description of the rendition. /// + /// # Note + /// If the [`language`] attribute is present, this attribute should be in that language. + /// /// # Example /// ``` /// # use hls_m3u8::tags::ExtXMedia; @@ -229,12 +269,14 @@ impl ExtXMedia { /// &"new_name".to_string() /// ); /// ``` + /// + /// [`language`]: #method.language pub fn set_name>(&mut self, value: T) -> &mut Self { self.name = value.into(); self } - /// Returns the `URI`, that identifies the [MediaPlaylist]. + /// Returns the `URI`, that identifies the [`Media Playlist`]. /// /// # Example /// ``` @@ -251,11 +293,18 @@ impl ExtXMedia { /// &Some("https://www.example.com/".into()) /// ); /// ``` + /// + /// [`Media Playlist`]: crate::MediaPlaylist pub const fn uri(&self) -> &Option { &self.uri } - /// Sets the `URI`, that identifies the [MediaPlaylist]. + /// Sets the `URI`, that identifies the [`Media Playlist`]. + /// + /// # Note + /// This attribute is **required**, if the [`MediaType`] is [`MediaType::Subtitles`]. + /// This attribute is **not allowed**, if the [`MediaType`] is + /// [`MediaType::ClosedCaptions`]. /// /// # Example /// ``` @@ -272,6 +321,8 @@ impl ExtXMedia { /// &Some("https://www.example.com/".into()) /// ); /// ``` + /// + /// [`Media Playlist`]: crate::MediaPlaylist pub fn set_uri>(&mut self, value: Option) -> &mut Self { self.uri = value.map(|v| v.into()); self @@ -299,6 +350,7 @@ impl ExtXMedia { } /// Sets the name of the primary language used in the rendition. + /// The value has to conform to [`RFC5646`]. /// /// # Example /// ``` @@ -315,6 +367,8 @@ impl ExtXMedia { /// &Some("english".into()) /// ); /// ``` + /// + /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646 pub fn set_language>(&mut self, value: Option) -> &mut Self { self.language = value.map(|v| v.into()); self @@ -342,6 +396,9 @@ impl ExtXMedia { } /// Sets the name of a language associated with the rendition. + /// An associated language is often used in a different role, than the + /// language specified by the [`language`] attribute (e.g., written versus + /// spoken, or a fallback dialect). /// /// # Example /// ``` @@ -358,12 +415,14 @@ impl ExtXMedia { /// &Some("spanish".into()) /// ); /// ``` + /// + /// [`language`]: #method.language pub fn set_assoc_language>(&mut self, value: Option) -> &mut Self { self.assoc_language = value.map(|v| v.into()); self } - /// Returns whether this is the default rendition. + /// Returns whether this is the `default` rendition. /// /// # Example /// ``` @@ -385,6 +444,9 @@ impl ExtXMedia { } /// Sets the `default` flag. + /// A value of `true` indicates, that the client should play + /// this rendition of the content in the absence of information + /// from the user indicating a different choice. /// /// # Example /// ``` @@ -479,7 +541,7 @@ impl ExtXMedia { } /// Returns the identifier that specifies a rendition within the segments in the - /// [MediaPlaylist]. + /// [`Media Playlist`]. /// /// # Example /// ``` @@ -493,11 +555,14 @@ impl ExtXMedia { /// /// assert_eq!(media.instream_id(), Some(InStreamId::Cc1)); /// ``` + /// + /// [`Media Playlist`]: crate::MediaPlaylist pub const fn instream_id(&self) -> Option { self.instream_id } - /// Sets the [InStreamId]. + /// Sets the [`InStreamId`], that specifies a rendition within the + /// segments in the [`Media Playlist`]. /// /// # Example /// ``` @@ -536,7 +601,20 @@ impl ExtXMedia { &self.characteristics } - /// Sets the characteristics. + /// Sets the characteristics attribute, containing one or more Uniform Type + /// Identifiers separated by comma. + /// Each [`UTI`] indicates an individual characteristic of the Rendition. + /// + /// A [`subtitles`] Rendition may include the following characteristics: + /// "public.accessibility.transcribes-spoken-dialog", + /// "public.accessibility.describes-music-and-sound", and + /// "public.easy-to-read" (which indicates that the subtitles have + /// been edited for ease of reading). + /// + /// An AUDIO Rendition MAY include the following characteristic: + /// "public.accessibility.describes-video". + /// + /// The characteristics attribute may include private UTIs. /// /// # Example /// ``` @@ -550,26 +628,29 @@ impl ExtXMedia { /// /// assert_eq!(media.characteristics(), &Some("characteristic".into())); /// ``` + /// + /// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI + /// [`subtitles`]: crate::types::MediaType::Subtitles pub fn set_characteristics>(&mut self, value: Option) -> &mut Self { self.characteristics = value.map(|v| v.into()); self } - /// Returns a [String] that represents the parameters of the rendition. + /// Returns the channels. /// /// # Example /// ``` /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; + /// use hls_m3u8::types::{Channels, MediaType}; /// /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); /// # assert_eq!(media.channels(), &None); /// - /// media.set_channels(Some("channel")); + /// media.set_channels(Some(Channels::new(6))); /// - /// assert_eq!(media.channels(), &Some("channel".into())); + /// assert_eq!(media.channels(), &Some(Channels::new(6))); /// ``` - pub const fn channels(&self) -> &Option { + pub const fn channels(&self) -> &Option { &self.channels } @@ -578,16 +659,16 @@ impl ExtXMedia { /// # Example /// ``` /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; + /// use hls_m3u8::types::{Channels, MediaType}; /// /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.characteristics(), &None); + /// # assert_eq!(media.channels(), &None); /// - /// media.set_characteristics(Some("characteristic")); + /// media.set_channels(Some(Channels::new(6))); /// - /// assert_eq!(media.characteristics(), &Some("characteristic".into())); + /// assert_eq!(media.channels(), &Some(Channels::new(6))); /// ``` - pub fn set_channels>(&mut self, value: Option) -> &mut Self { + pub fn set_channels>(&mut self, value: Option) -> &mut Self { self.channels = value.map(|v| v.into()); self } @@ -687,7 +768,7 @@ impl FromStr for ExtXMedia { builder.characteristics(unquote(value)); } "CHANNELS" => { - builder.channels(unquote(value)); + builder.channels(unquote(value).parse::()?); } _ => { // [6.3.1. General Client Responsibilities] @@ -695,6 +776,7 @@ impl FromStr for ExtXMedia { } } } + builder.build().map_err(Error::builder_error) } } @@ -915,7 +997,7 @@ mod test { .name("English") .is_autoselect(true) .is_default(true) - .channels("2") + .channels(Channels::new(2)) .build() .unwrap() .to_string(), @@ -1196,7 +1278,7 @@ mod test { .name("English") .is_autoselect(true) .is_default(true) - .channels("2") + .channels(Channels::new(2)) .build() .unwrap(), "#EXT-X-MEDIA:\ @@ -1262,7 +1344,30 @@ mod test { "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"foo\",NAME=\"bar\"" .parse() .unwrap() - ) + ); + } + + #[test] + fn test_parser_error() { + assert!("".parse::().is_err()); + assert!("garbage".parse::().is_err()); + + assert!( + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,URI=\"http://www.example.com\"" + .parse::() + .is_err() + ); + assert!("#EXT-X-MEDIA:TYPE=AUDIO,INSTREAM-ID=CC1" + .parse::() + .is_err()); + + assert!("#EXT-X-MEDIA:TYPE=AUDIO,DEFAULT=YES,AUTOSELECT=NO" + .parse::() + .is_err()); + + assert!("#EXT-X-MEDIA:TYPE=AUDIO,FORCED=YES" + .parse::() + .is_err()); } #[test] diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index f080d5f..dc7827b 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -11,37 +11,41 @@ use crate::Error; /// The data of an [ExtXSessionData] tag. #[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] pub enum SessionData { - /// A String, that contains the data identified by [data_id](ExtXSessionData::data_id). - /// If a [language](ExtXSessionData::language) is specified, the value should + /// A String, that contains the data identified by [`data_id`](ExtXSessionData::data_id). + /// If a [`language`](ExtXSessionData::language) is specified, the value should /// contain a human-readable string written in the specified language. Value(String), - /// An [uri], which points to a [json]. + /// An [`uri`], which points to a [`json`]. /// - /// [json]: https://tools.ietf.org/html/rfc8259 - /// [uri]: https://tools.ietf.org/html/rfc3986 + /// [`json`]: https://tools.ietf.org/html/rfc8259 + /// [`uri`]: https://tools.ietf.org/html/rfc3986 Uri(String), } /// # [4.3.4.4. EXT-X-SESSION-DATA] /// -/// The [ExtXSessionData] tag allows arbitrary session data to be -/// carried in a [Master Playlist]. +/// The [`ExtXSessionData`] tag allows arbitrary session data to be +/// carried in a [`Master Playlist`]. /// -/// [Master Playlist]: crate::MasterPlaylist +/// [`Master Playlist`]: crate::MasterPlaylist /// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 #[derive(Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] #[builder(setter(into))] pub struct ExtXSessionData { - /// The identifier of the data. For more information look [here](ExtXSessionData::set_data_id). + /// The identifier of the data. + /// For more information look [`here`](ExtXSessionData::set_data_id). + /// /// # Note /// This field is required. data_id: String, - /// The data associated with the [data_id](ExtXSessionDataBuilder::data_id). - /// For more information look [here](SessionData). + /// The data associated with the + /// [`data_id`](ExtXSessionDataBuilder::data_id). + /// For more information look [`here`](SessionData). + /// /// # Note /// This field is required. data: SessionData, - /// The language of the [data](ExtXSessionDataBuilder::data). + /// The language of the [`data`](ExtXSessionDataBuilder::data). #[builder(setter(into, strip_option), default)] language: Option, } @@ -49,7 +53,7 @@ pub struct ExtXSessionData { impl ExtXSessionData { pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:"; - /// Makes a new [ExtXSessionData] tag. + /// Makes a new [`ExtXSessionData`] tag. /// /// # Example /// ``` @@ -68,7 +72,7 @@ impl ExtXSessionData { } } - /// Returns a new Builder for [ExtXSessionData]. + /// Returns a new Builder for [`ExtXSessionData`]. /// /// # Example /// ``` @@ -94,7 +98,7 @@ impl ExtXSessionData { ExtXSessionDataBuilder::default() } - /// Makes a new [ExtXSessionData] tag, with the given language. + /// Makes a new [`ExtXSessionData`] tag, with the given language. /// /// # Example /// ``` @@ -154,7 +158,7 @@ impl ExtXSessionData { &self.data } - /// Returns the `language` tag, that identifies the language of [SessionData]. + /// Returns the `language` tag, that identifies the language of [`SessionData`]. /// /// # Example /// ``` @@ -175,7 +179,7 @@ impl ExtXSessionData { &self.language } - /// Sets the `language` attribute, that identifies the language of [SessionData]. + /// Sets the `language` attribute, that identifies the language of [`SessionData`]. /// See [rfc5646](https://tools.ietf.org/html/rfc5646). /// /// # Example @@ -224,7 +228,7 @@ impl ExtXSessionData { self } - /// Sets the [data](ExtXSessionData::data) of this tag. + /// Sets the [`data`](ExtXSessionData::data) of this tag. /// /// # Example /// ``` diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index 797474f..2b745cd 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -7,9 +7,9 @@ use crate::utils::tag; use crate::Error; /// # [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 [`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 /// first. /// /// Its format is: @@ -17,8 +17,8 @@ use crate::Error; /// #EXT-X-SESSION-KEY: /// ``` /// -/// [Media Playlist]: crate::MediaPlaylist -/// [Master Playlist]: crate::MasterPlaylist +/// [`Media Playlist`]: crate::MediaPlaylist +/// [`Master Playlist`]: crate::MasterPlaylist /// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5 #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXSessionKey(DecryptionKey); @@ -26,11 +26,13 @@ pub struct ExtXSessionKey(DecryptionKey); impl ExtXSessionKey { pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:"; - /// Makes a new [ExtXSessionKey] tag. + /// Makes a new [`ExtXSessionKey`] tag. /// /// # Panic - /// An [ExtXSessionKey] should only be used, if the segments of the stream are encrypted. - /// Therefore this function will panic, if the `method` is [EncryptionMethod::None]. + /// An [`ExtXSessionKey`] should only be used, + /// if the segments of the stream are encrypted. + /// Therefore this function will panic, + /// if the `method` is [`EncryptionMethod::None`]. /// /// # Example /// ``` diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index a025346..328b432 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -25,7 +25,7 @@ pub struct ExtXStreamInf { impl ExtXStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; - /// Creates a new [ExtXStreamInf] tag. + /// Creates a new [`ExtXStreamInf`] tag. /// /// # Example /// ``` @@ -164,7 +164,7 @@ impl ExtXStreamInf { self } - /// Returns the value of [ClosedCaptions] attribute. + /// Returns the value of [`ClosedCaptions`] attribute. /// /// # Example /// ``` @@ -181,7 +181,7 @@ impl ExtXStreamInf { &self.closed_captions } - /// Returns the value of [ClosedCaptions] attribute. + /// Returns the value of [`ClosedCaptions`] attribute. /// /// # Example /// ``` diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index fa90b8b..1a73654 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -6,9 +6,9 @@ use crate::utils::tag; /// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// -/// The [ExtXDiscontinuitySequence] tag allows synchronization between +/// 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. +/// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s. /// /// Its format is: /// ```text @@ -16,8 +16,8 @@ use crate::utils::tag; /// ``` /// where `number` is a [u64]. /// -/// [ExtXDiscontinuity]: crate::tags::ExtXDiscontinuity -/// [Media Playlist]: crate::MediaPlaylist +/// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity +/// [`Media Playlist`]: 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(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 2865b4c..174f726 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.rs @@ -6,16 +6,16 @@ use crate::utils::tag; use crate::Error; /// # [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 [`Media Segment`]s will be +/// added to the [`Media Playlist`] file. /// /// Its format is: /// ```text /// #EXT-X-ENDLIST /// ``` /// -/// [Media Segment]: crate::MediaSegment -/// [Media Playlist]: crate::MediaPlaylist +/// [`Media Segment`]: crate::MediaSegment +/// [`Media Playlist`]: 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)] diff --git a/src/tags/media_playlist/i_frames_only.rs b/src/tags/media_playlist/i_frames_only.rs index c0291ca..5e7390a 100644 --- a/src/tags/media_playlist/i_frames_only.rs +++ b/src/tags/media_playlist/i_frames_only.rs @@ -6,7 +6,7 @@ use crate::utils::tag; use crate::Error; /// # [4.4.3.6. EXT-X-I-FRAMES-ONLY] -/// The [ExtXIFramesOnly] tag indicates that each [Media Segment] in the +/// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the /// Playlist describes a single I-frame. I-frames are encoded video /// frames, whose decoding does not depend on any other frame. I-frame /// Playlists can be used for trick play, such as fast forward, rapid @@ -17,7 +17,7 @@ use crate::Error; /// #EXT-X-I-FRAMES-ONLY /// ``` /// -/// [Media Segment]: crate::MediaSegment +/// [`Media Segment`]: crate::MediaSegment /// [4.4.3.6. EXT-X-I-FRAMES-ONLY]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index 7387577..e27061a 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -6,14 +6,14 @@ use crate::utils::tag; use crate::Error; /// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE] -/// The [ExtXMediaSequence] tag indicates the Media Sequence Number of -/// the first [Media Segment] that appears in a Playlist file. +/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of +/// the first [`Media Segment`] that appears in a Playlist file. /// /// Its format is: /// ```text /// #EXT-X-MEDIA-SEQUENCE: /// ``` -/// where `number` is a [u64]. +/// where `number` is a [`u64`]. /// /// [Media Segment]: crate::MediaSegment /// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]: @@ -24,7 +24,7 @@ pub struct ExtXMediaSequence(u64); impl ExtXMediaSequence { pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:"; - /// Makes a new [ExtXMediaSequence] tag. + /// Makes a new [`ExtXMediaSequence`] tag. /// /// # Example /// ``` diff --git a/src/tags/media_playlist/playlist_type.rs b/src/tags/media_playlist/playlist_type.rs index b6ded13..59409fa 100644 --- a/src/tags/media_playlist/playlist_type.rs +++ b/src/tags/media_playlist/playlist_type.rs @@ -7,8 +7,8 @@ use crate::Error; /// # [4.4.3.5. EXT-X-PLAYLIST-TYPE] /// -/// The [ExtXPlaylistType] tag provides mutability information about the -/// [Media Playlist]. It applies to the entire [Media Playlist]. +/// The [`ExtXPlaylistType`] tag provides mutability information about the +/// [`Media Playlist`]. It applies to the entire [`Media Playlist`]. /// /// Its format is: /// ```text @@ -20,10 +20,10 @@ use crate::Error; /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ExtXPlaylistType { - /// If the [ExtXPlaylistType] is Event, Media Segments can only be added to - /// the end of the Media Playlist. + /// If the [`ExtXPlaylistType`] is Event, Media Segments + /// can only be added to the end of the Media Playlist. Event, - /// If the [ExtXPlaylistType] is Video On Demand (Vod), + /// If the [`ExtXPlaylistType`] is Video On Demand (Vod), /// the Media Playlist cannot change. Vod, } diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index d7fe890..1c9dff9 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -7,39 +7,52 @@ use crate::utils::tag; use crate::Error; /// # [4.4.3.1. EXT-X-TARGETDURATION] -/// The [ExtXTargetDuration] tag specifies the maximum [Media Segment] +/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`] /// duration. /// -/// Its format is: -/// ```text -/// #EXT-X-TARGETDURATION: -/// ``` -/// where `s` is the target [Duration] in seconds. -/// -/// [Media Segment]: crate::MediaSegment +/// [`Media Segment`]: crate::MediaSegment /// [4.4.3.1. EXT-X-TARGETDURATION]: -/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.1 +/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct ExtXTargetDuration(Duration); impl ExtXTargetDuration { pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:"; - /// Makes a new [ExtXTargetduration] tag. + /// Makes a new [`ExtXTargetDuration`] tag. + /// + /// # Example + /// ``` + /// # use hls_m3u8::tags::ExtXTargetDuration; + /// use std::time::Duration; + /// + /// let target_duration = ExtXTargetDuration::new(Duration::from_secs(20)); + /// ``` /// /// # Note - /// The nanoseconds part of the [Duration] will be discarded. + /// The nanoseconds part of the [`Duration`] will be discarded. pub const fn new(duration: Duration) -> Self { // TOOD: round instead of discarding? Self(Duration::from_secs(duration.as_secs())) } - /// Returns the maximum media segment duration in the associated playlist. + /// Returns the maximum media segment duration. + /// + /// # Example + /// ``` + /// # use hls_m3u8::tags::ExtXTargetDuration; + /// use std::time::Duration; + /// + /// let target_duration = ExtXTargetDuration::new(Duration::from_nanos(2_000_000_000)); + /// + /// assert_eq!(target_duration.duration(), Duration::from_secs(2)); + /// ``` pub const fn duration(&self) -> Duration { self.0 } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXTargetDuration { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index caefa24..aea6350 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -8,7 +8,7 @@ use crate::Error; /// # [4.4.2.2. EXT-X-BYTERANGE] /// -/// The [ExtXByteRange] tag indicates that a [Media Segment] is a sub-range +/// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range /// of the resource identified by its `URI`. /// /// Its format is: @@ -20,7 +20,7 @@ use crate::Error; /// If present, `o` is a [usize] indicating the start of the sub-range, /// as a byte offset from the beginning of the resource. /// -/// [Media Segment]: crate::MediaSegment +/// [`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(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -29,7 +29,7 @@ pub struct ExtXByteRange(ByteRange); impl ExtXByteRange { pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:"; - /// Makes a new [ExtXByteRange] tag. + /// Makes a new [`ExtXByteRange`] tag. /// /// # Example /// ``` @@ -40,7 +40,7 @@ impl ExtXByteRange { Self(ByteRange::new(length, start)) } - /// Converts the [ExtXByteRange] to a [ByteRange]. + /// Converts the [`ExtXByteRange`] to a [`ByteRange`]. /// /// # Example /// ``` diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index fabf7af..c2ec8de 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -10,7 +10,7 @@ use crate::types::{ProtocolVersion, RequiredVersion}; use crate::utils::{quote, tag, unquote}; use crate::Error; -/// [4.3.2.7. EXT-X-DATERANGE] +/// # [4.3.2.7. EXT-X-DATERANGE] /// /// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7 /// @@ -18,7 +18,7 @@ use crate::Error; #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXDateRange { - /// A string that uniquely identifies a [ExtXDateRange] in the Playlist. + /// A string that uniquely identifies a [`ExtXDateRange`] in the Playlist. /// This attribute is required. id: String, /// A client-defined string that specifies some set of attributes and their associated value @@ -64,7 +64,7 @@ pub struct ExtXDateRange { impl ExtXDateRange { pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:"; - /// Makes a new [ExtXDateRange] tag. + /// Makes a new [`ExtXDateRange`] tag. /// /// # Example /// ``` @@ -74,7 +74,7 @@ impl ExtXDateRange { /// /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds /// - /// ExtXDateRange::new("id", FixedOffset::east(8 * HOURS_IN_SECS) + /// let date_range = ExtXDateRange::new("id", FixedOffset::east(8 * HOURS_IN_SECS) /// .ymd(2010, 2, 19) /// .and_hms_milli(14, 54, 23, 31)); /// ``` @@ -95,6 +95,22 @@ impl ExtXDateRange { } } +/// This tag requires [`ProtocolVersion::V1`]. +/// +/// # Example +/// ``` +/// # use hls_m3u8::tags::ExtXDateRange; +/// use hls_m3u8::types::{RequiredVersion, ProtocolVersion}; +/// use chrono::{DateTime, FixedOffset}; +/// use chrono::offset::TimeZone; +/// +/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds +/// +/// let date_range = ExtXDateRange::new("id", FixedOffset::east(8 * HOURS_IN_SECS) +/// .ymd(2010, 2, 19) +/// .and_hms_milli(14, 54, 23, 31)); +/// assert_eq!(date_range.required_version(), ProtocolVersion::V1); +/// ``` impl RequiredVersion for ExtXDateRange { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index a4846d9..72097ca 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -6,15 +6,15 @@ use crate::utils::tag; use crate::Error; /// # [4.4.2.3. EXT-X-DISCONTINUITY] -/// The [ExtXDiscontinuity] tag indicates a discontinuity between the -/// [Media Segment] that follows it and the one that preceded it. +/// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the +/// [`Media Segment`] that follows it and the one that preceded it. /// /// Its format is: /// ```text /// #EXT-X-DISCONTINUITY /// ``` /// -/// [Media Segment]: crate::MediaSegment +/// [`Media Segment`]: crate::MediaSegment /// [4.4.2.3. EXT-X-DISCONTINUITY]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/src/tags/media_segment/inf.rs b/src/tags/media_segment/inf.rs index 99c51a5..71d4bb3 100644 --- a/src/tags/media_segment/inf.rs +++ b/src/tags/media_segment/inf.rs @@ -8,8 +8,8 @@ use crate::Error; /// # [4.4.2.1. EXTINF] /// -/// The [ExtInf] tag specifies the duration of a [Media Segment]. It applies -/// only to the next [Media Segment]. +/// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies +/// only to the next [`Media Segment`]. /// /// Its format is: /// ```text @@ -17,7 +17,7 @@ use crate::Error; /// ``` /// The title is an optional informative title about the [Media Segment]. /// -/// [Media Segment]: crate::media_segment::MediaSegment +/// [`Media Segment`]: crate::media_segment::MediaSegment /// [4.4.2.1. EXTINF]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1 #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -29,7 +29,7 @@ pub struct ExtInf { impl ExtInf { pub(crate) const PREFIX: &'static str = "#EXTINF:"; - /// Makes a new [ExtInf] tag. + /// Makes a new [`ExtInf`] tag. /// /// # Example /// ``` @@ -45,7 +45,7 @@ impl ExtInf { } } - /// Makes a new [ExtInf] tag with the given title. + /// Makes a new [`ExtInf`] tag with the given title. /// /// # Example /// ``` diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index fd987c9..c86b759 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -7,11 +7,11 @@ use crate::utils::tag; use crate::Error; /// # [4.4.2.4. EXT-X-KEY] -/// [Media Segment]s may be encrypted. The [ExtXKey] tag specifies how to -/// decrypt them. It applies to every [Media Segment] and to every Media -/// Initialization Section declared by an [ExtXMap] tag, that appears -/// between it and the next [ExtXKey] tag in the Playlist file with the -/// same [KeyFormat] attribute (or the end of the Playlist file). +/// [`Media Segment`]s may be encrypted. The [`ExtXKey`] tag specifies how to +/// decrypt them. It applies to every [`Media Segment`] and to every Media +/// Initialization Section declared by an [`ExtXMap`] tag, that appears +/// between it and the next [`ExtXKey`] tag in the Playlist file with the +/// same [`KeyFormat`] attribute (or the end of the Playlist file). /// /// The format is: /// ```text @@ -19,11 +19,12 @@ use crate::Error; /// ``` /// /// # Note -/// In case of an empty key (`EncryptionMethod::None`), all attributes will be ignored. +/// In case of an empty key (`EncryptionMethod::None`), +/// all attributes will be ignored. /// -/// [KeyFormat]: crate::types::KeyFormat -/// [ExtXMap]: crate::tags::ExtXMap -/// [Media Segment]: crate::MediaSegment +/// [`KeyFormat`]: crate::types::KeyFormat +/// [`ExtXMap`]: crate::tags::ExtXMap +/// [`Media Segment`]: crate::MediaSegment /// [4.4.2.4. EXT-X-KEY]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4 #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -32,8 +33,9 @@ pub struct ExtXKey(DecryptionKey); impl ExtXKey { pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:"; - /// Makes a new `ExtXKey` tag. - /// # Examples + /// Makes a new [`ExtXKey`] tag. + /// + /// # Example /// ``` /// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::types::EncryptionMethod; @@ -52,8 +54,9 @@ impl ExtXKey { Self(DecryptionKey::new(method, uri)) } - /// Makes a new `ExtXKey` tag without a decryption key. - /// # Examples + /// Makes a new [`ExtXKey`] tag without a decryption key. + /// + /// # Example /// ``` /// use hls_m3u8::tags::ExtXKey; /// @@ -74,8 +77,10 @@ impl ExtXKey { }) } - /// Returns whether the [EncryptionMethod] is [None](EncryptionMethod::None). - /// # Examples + /// Returns whether the [`EncryptionMethod`] is + /// [`None`](EncryptionMethod::None). + /// + /// # Example /// ``` /// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::types::EncryptionMethod; diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index 29377ed..8fac518 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -7,7 +7,7 @@ use crate::utils::{quote, tag, unquote}; use crate::Error; /// # [4.4.2.5. EXT-X-MAP] -/// The [ExtXMap] tag specifies how to obtain the Media Initialization +/// The [`ExtXMap`] tag specifies how to obtain the Media Initialization /// Section, required to parse the applicable [Media Segment]s. /// /// Its format is: @@ -27,7 +27,7 @@ pub struct ExtXMap { impl ExtXMap { pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:"; - /// Makes a new `ExtXMap` tag. + /// Makes a new [`ExtXMap`] tag. pub fn new(uri: T) -> Self { ExtXMap { uri: uri.to_string(), @@ -35,7 +35,7 @@ impl ExtXMap { } } - /// Makes a new `ExtXMap` tag with the given range. + /// Makes a new [`ExtXMap`] tag with the given range. pub fn with_range(uri: T, range: ByteRange) -> Self { ExtXMap { uri: uri.to_string(), @@ -43,7 +43,8 @@ impl ExtXMap { } } - /// Returns the URI that identifies a resource that contains the media initialization section. + /// Returns the `URI` that identifies a resource, + /// that contains the media initialization section. pub const fn uri(&self) -> &String { &self.uri } diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index d6a68ce..7f3a5e1 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -9,10 +9,10 @@ use crate::utils::tag; use crate::Error; /// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] -/// The [ExtXProgramDateTime] tag associates the first sample of a -/// [Media Segment] with an absolute date and/or time. +/// The [`ExtXProgramDateTime`] tag associates the first sample of a +/// [`Media Segment`] with an absolute date and/or time. /// -/// [Media Segment]: crate::MediaSegment +/// [`Media Segment`]: crate::MediaSegment /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ExtXProgramDateTime(DateTime); @@ -20,7 +20,7 @@ pub struct ExtXProgramDateTime(DateTime); impl ExtXProgramDateTime { pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:"; - /// Makes a new `ExtXProgramDateTime` tag. + /// Makes a new [`ExtXProgramDateTime`] tag. /// /// # Example /// ``` diff --git a/src/tags/shared/start.rs b/src/tags/shared/start.rs index ed5f573..8de2b10 100644 --- a/src/tags/shared/start.rs +++ b/src/tags/shared/start.rs @@ -18,7 +18,7 @@ pub struct ExtXStart { impl ExtXStart { pub(crate) const PREFIX: &'static str = "#EXT-X-START:"; - /// Makes a new [ExtXStart] tag. + /// Makes a new [`ExtXStart`] tag. /// /// # Panic /// Panics if the time_offset value is infinite. @@ -35,7 +35,7 @@ impl ExtXStart { } } - /// Makes a new [ExtXStart] tag with the given `precise` flag. + /// Makes a new [`ExtXStart`] tag with the given `precise` flag. /// /// # Panic /// Panics if the time_offset value is infinite. diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs index a87fc4e..6c4556b 100644 --- a/src/types/byte_range.rs +++ b/src/types/byte_range.rs @@ -15,7 +15,7 @@ pub struct ByteRange { } impl ByteRange { - /// Creates a new [ByteRange]. + /// Creates a new [`ByteRange`]. /// /// # Example /// ``` diff --git a/src/types/channels.rs b/src/types/channels.rs new file mode 100644 index 0000000..bdd557a --- /dev/null +++ b/src/types/channels.rs @@ -0,0 +1,188 @@ +use core::fmt; +use core::str::FromStr; + +use crate::Error; + +/// Specifies a list of parameters. +/// +/// # `MediaType::Audio` +/// The first parameter is a count of audio channels expressed as a [`u64`], +/// indicating the maximum number of independent, simultaneous audio channels +/// present in any [`MediaSegment`] in the rendition. For example, an +/// `AC-3 5.1` rendition would have a `CHANNELS="6"` attribute. +/// +/// The second parameter identifies the encoding of object-based audio used by the +/// rendition. This parameter is a comma-separated list of Audio +/// Object Coding Identifiers. It is optional. An Audio Object +/// Coding Identifier is a string containing characters from the set +/// `[A..Z]`, `[0..9]`, and `'-'`. They are codec-specific. A parameter +/// value of consisting solely of the dash character (`'-'`) indicates +/// that the audio is not object-based. +/// +/// # Example +/// Creating a `CHANNELS="6"` attribute +/// ``` +/// # use hls_m3u8::types::Channels; +/// let mut channels = Channels::new(6); +/// +/// assert_eq!(format!("CHANNELS=\"{}\"", channels), "CHANNELS=\"6\"".to_string()); +/// ``` +/// +/// # Note +/// Currently there are no example playlists in the documentation, +/// or in popular m3u8 libraries, showing a usage for the second parameter +/// of [`Channels`], so if you have one please open an issue on github! +/// +/// [`MediaSegment`]: crate::MediaSegment +#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Channels { + first_parameter: u64, + second_parameter: Option>, +} + +impl Channels { + /// Makes a new [`Channels`] struct. + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::Channels; + /// let mut channels = Channels::new(6); + /// ``` + pub const fn new(value: u64) -> Self { + Self { + first_parameter: value, + second_parameter: None, + } + } + + /// Returns the first parameter. + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::Channels; + /// let mut channels = Channels::new(6); + /// + /// assert_eq!(channels.first_parameter(), 6); + /// ``` + pub const fn first_parameter(&self) -> u64 { + self.first_parameter + } + + /// Sets the first parameter. + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::Channels; + /// let mut channels = Channels::new(3); + /// + /// channels.set_first_parameter(6); + /// assert_eq!(channels.first_parameter(), 6) + /// ``` + pub fn set_first_parameter(&mut self, value: u64) -> &mut Self { + self.first_parameter = value; + self + } + + /// Returns the second parameter, if there is any! + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::Channels; + /// let mut channels = Channels::new(3); + /// # assert_eq!(channels.second_parameter(), &None); + /// + /// channels.set_second_parameter(Some(vec!["AAC","MP3"])); + /// assert_eq!(channels.second_parameter(), &Some(vec!["AAC".to_string(),"MP3".to_string()])) + /// ``` + /// + /// # Note + /// Currently there is no use for this parameter. + pub const fn second_parameter(&self) -> &Option> { + &self.second_parameter + } + + /// Sets the second parameter. + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::Channels; + /// let mut channels = Channels::new(3); + /// # assert_eq!(channels.second_parameter(), &None); + /// + /// channels.set_second_parameter(Some(vec!["AAC","MP3"])); + /// assert_eq!(channels.second_parameter(), &Some(vec!["AAC".to_string(),"MP3".to_string()])) + /// ``` + /// + /// # Note + /// Currently there is no use for this parameter. + pub fn set_second_parameter(&mut self, value: Option>) -> &mut Self { + self.second_parameter = value.map(|v| v.into_iter().map(|s| s.to_string()).collect()); + self + } +} + +impl FromStr for Channels { + type Err = Error; + + fn from_str(input: &str) -> Result { + let parameters = input.split('/').collect::>(); + let first_parameter = parameters + .first() + .ok_or_else(|| Error::missing_attribute("First parameter of channels!"))? + .parse()?; + + let second_parameter = parameters + .get(1) + .map(|v| v.split(',').map(|v| v.to_string()).collect()); + + Ok(Self { + first_parameter, + second_parameter, + }) + } +} + +impl fmt::Display for Channels { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.first_parameter)?; + + if let Some(second) = &self.second_parameter { + write!(f, "/{}", second.join(","))?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_display() { + let mut channels = Channels::new(6); + assert_eq!(channels.to_string(), "6".to_string()); + + channels.set_first_parameter(7); + assert_eq!(channels.to_string(), "7".to_string()); + + assert_eq!( + "6/P,K,J".to_string(), + Channels::new(6) + .set_second_parameter(Some(vec!["P", "K", "J"])) + .to_string() + ); + } + + #[test] + fn test_parser() { + assert_eq!("6".parse::().unwrap(), Channels::new(6)); + let mut result = Channels::new(6); + result.set_second_parameter(Some(vec!["P", "K", "J"])); + + assert_eq!("6/P,K,J".parse::().unwrap(), result); + + assert!("garbage".parse::().is_err()); + assert!("".parse::().is_err()); + } +} diff --git a/src/types/decimal_floating_point.rs b/src/types/decimal_floating_point.rs index ceee6f7..055154a 100644 --- a/src/types/decimal_floating_point.rs +++ b/src/types/decimal_floating_point.rs @@ -15,7 +15,7 @@ use crate::Error; pub(crate) struct DecimalFloatingPoint(f64); impl DecimalFloatingPoint { - /// Makes a new [DecimalFloatingPoint] instance. + /// Makes a new [`DecimalFloatingPoint`] instance. /// /// # Errors /// @@ -32,7 +32,7 @@ impl DecimalFloatingPoint { Self(value) } - /// Converts [DecimalFloatingPoint] to [f64]. + /// Converts [`DecimalFloatingPoint`] to [`f64`]. pub const fn as_f64(self) -> f64 { self.0 } @@ -40,7 +40,7 @@ impl DecimalFloatingPoint { impl Eq for DecimalFloatingPoint {} -// this trait is implemented manually, so it doesn't construct a [DecimalFloatingPoint], +// this trait is implemented manually, so it doesn't construct a [`DecimalFloatingPoint`], // with a negative value. impl FromStr for DecimalFloatingPoint { type Err = Error; diff --git a/src/types/decimal_resolution.rs b/src/types/decimal_resolution.rs index 81fde55..5dd1e0f 100644 --- a/src/types/decimal_resolution.rs +++ b/src/types/decimal_resolution.rs @@ -17,7 +17,7 @@ pub(crate) struct DecimalResolution { } impl DecimalResolution { - /// Creates a new DecimalResolution. + /// Creates a new [`DecimalResolution`]. pub const fn new(width: usize, height: usize) -> Self { Self { width, height } } @@ -45,7 +45,7 @@ impl DecimalResolution { } } -/// [DecimalResolution] can be constructed from a tuple; `(width, height)`. +/// [`DecimalResolution`] can be constructed from a tuple; `(width, height)`. impl From<(usize, usize)> for DecimalResolution { fn from(value: (usize, usize)) -> Self { DecimalResolution::new(value.0, value.1) diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index e821f57..3aa9ff9 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -13,10 +13,10 @@ use crate::Error; #[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[builder(setter(into), build_fn(validate = "Self::validate"))] -/// [DecryptionKey] contains data, that is shared between [ExtXSessionKey] and [ExtXKey]. +/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] and [`ExtXKey`]. /// -/// [ExtXSessionKey]: crate::tags::ExtXSessionKey -/// [ExtXKey]: crate::tags::ExtXKey +/// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey +/// [`ExtXKey`]: crate::tags::ExtXKey pub struct DecryptionKey { /// The [EncryptionMethod]. pub(crate) method: EncryptionMethod, diff --git a/src/types/key_format.rs b/src/types/key_format.rs index c42f9a9..d51687b 100644 --- a/src/types/key_format.rs +++ b/src/types/key_format.rs @@ -6,7 +6,8 @@ use crate::utils::{quote, tag, unquote}; use crate::Error; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -/// KeyFormat specifies, how the key is represented in the resource identified by the URI +/// [`KeyFormat`] specifies, how the key is represented in the +/// resource identified by the `URI`. pub enum KeyFormat { /// The key is a single packed array of 16 octets in binary format. Identity, diff --git a/src/types/key_format_versions.rs b/src/types/key_format_versions.rs index 5ee66dd..40543eb 100644 --- a/src/types/key_format_versions.rs +++ b/src/types/key_format_versions.rs @@ -8,9 +8,9 @@ use crate::Error; /// A list of [usize], that can be used to indicate which version(s) /// this instance complies with, if more than one version of a particular -/// [KeyFormat] is defined. +/// [`KeyFormat`] is defined. /// -/// [KeyFormat]: crate::types::KeyFormat +/// [`KeyFormat`]: crate::types::KeyFormat #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct KeyFormatVersions(Vec); @@ -21,12 +21,12 @@ impl Default for KeyFormatVersions { } impl KeyFormatVersions { - /// Makes a new [KeyFormatVersions]. + /// Makes a new [`KeyFormatVersions`]. pub fn new() -> Self { Self::default() } - /// Add a value to the [KeyFormatVersions]. + /// Add a value to the [`KeyFormatVersions`]. pub fn push(&mut self, value: usize) { if self.is_default() { self.0 = vec![value]; @@ -35,7 +35,7 @@ impl KeyFormatVersions { } } - /// Returns `true`, if [KeyFormatVersions] has the default value of `vec![1]`. + /// Returns `true`, if [`KeyFormatVersions`] has the default value of `vec![1]`. pub fn is_default(&self) -> bool { self.0 == vec![1] && self.0.len() == 1 || self.0.is_empty() } diff --git a/src/types/mod.rs b/src/types/mod.rs index 70c891f..64aa441 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ //! Miscellaneous types. mod byte_range; +mod channels; mod closed_captions; mod decimal_floating_point; mod decimal_resolution; @@ -16,6 +17,7 @@ mod signed_decimal_floating_point; mod stream_inf; pub use byte_range::*; +pub use channels::*; pub use closed_captions::*; pub(crate) use decimal_floating_point::*; pub(crate) use decimal_resolution::*; diff --git a/src/types/protocol_version.rs b/src/types/protocol_version.rs index 9a3e42f..a0b6e78 100644 --- a/src/types/protocol_version.rs +++ b/src/types/protocol_version.rs @@ -28,7 +28,7 @@ pub trait RequiredVersion { } /// # [7. Protocol Version Compatibility] -/// The [ProtocolVersion] specifies, which m3u8 revision is required, to parse +/// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse /// a certain tag correctly. /// /// [7. Protocol Version Compatibility]: @@ -46,7 +46,8 @@ pub enum ProtocolVersion { } impl ProtocolVersion { - /// Returns the newest [ProtocolVersion], that is supported by this library. + /// Returns the newest [`ProtocolVersion`], that is supported by + /// this library. /// /// # Example /// ```