1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-16 20:10:33 +00:00

more tests #25 + better docs #31

This commit is contained in:
Luro02 2019-10-03 16:23:27 +02:00
parent 6b717f97c2
commit 93283f61f1
33 changed files with 666 additions and 266 deletions

View file

@ -90,11 +90,11 @@ fn split(value: &str, terminator: char) -> Vec<String> {
temp_string.push(c); temp_string.push(c);
} }
k if (k == terminator) => { k if (k == terminator) => {
if !inside_quotes { if inside_quotes {
temp_string.push(c);
} else {
result.push(temp_string); result.push(temp_string);
temp_string = String::new(); temp_string = String::new();
} else {
temp_string.push(c);
} }
} }
_ => { _ => {

View file

@ -5,7 +5,7 @@ use failure::{Backtrace, Context, Fail};
/// This crate specific `Result` type. /// This crate specific `Result` type.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// The ErrorKind. /// The [`ErrorKind`].
#[derive(Debug, Fail, Clone, PartialEq, Eq)] #[derive(Debug, Fail, Clone, PartialEq, Eq)]
pub enum ErrorKind { pub enum ErrorKind {
#[fail(display = "ChronoParseError: {}", _0)] #[fail(display = "ChronoParseError: {}", _0)]
@ -72,6 +72,10 @@ pub enum ErrorKind {
/// An attribute is missing. /// An attribute is missing.
MissingAttribute(String), MissingAttribute(String),
#[fail(display = "Unexpected Attribute: {:?}", _0)]
/// An unexpected value.
UnexpectedAttribute(String),
/// Hints that destructuring should not be exhaustive. /// Hints that destructuring should not be exhaustive.
/// ///
/// This enum may grow additional variants, so this makes sure clients /// This enum may grow additional variants, so this makes sure clients
@ -121,6 +125,10 @@ impl Error {
Self::from(ErrorKind::MissingValue(value.to_string())) Self::from(ErrorKind::MissingValue(value.to_string()))
} }
pub(crate) fn unexpected_attribute<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnexpectedAttribute(value.to_string()))
}
pub(crate) fn invalid_input() -> Self { pub(crate) fn invalid_input() -> Self {
Self::from(ErrorKind::InvalidInput) Self::from(ErrorKind::InvalidInput)
} }

View file

@ -154,53 +154,53 @@ impl fmt::Display for Tag {
impl FromStr for Tag { impl FromStr for Tag {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(input: &str) -> Result<Self, Self::Err> {
if s.starts_with(tags::ExtM3u::PREFIX) { if input.starts_with(tags::ExtM3u::PREFIX) {
s.parse().map(Tag::ExtM3u) input.parse().map(Tag::ExtM3u)
} else if s.starts_with(tags::ExtXVersion::PREFIX) { } else if input.starts_with(tags::ExtXVersion::PREFIX) {
s.parse().map(Tag::ExtXVersion) input.parse().map(Tag::ExtXVersion)
} else if s.starts_with(tags::ExtInf::PREFIX) { } else if input.starts_with(tags::ExtInf::PREFIX) {
s.parse().map(Tag::ExtInf) input.parse().map(Tag::ExtInf)
} else if s.starts_with(tags::ExtXByteRange::PREFIX) { } else if input.starts_with(tags::ExtXByteRange::PREFIX) {
s.parse().map(Tag::ExtXByteRange) input.parse().map(Tag::ExtXByteRange)
} else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) { } else if input.starts_with(tags::ExtXDiscontinuity::PREFIX) {
s.parse().map(Tag::ExtXDiscontinuity) input.parse().map(Tag::ExtXDiscontinuity)
} else if s.starts_with(tags::ExtXKey::PREFIX) { } else if input.starts_with(tags::ExtXKey::PREFIX) {
s.parse().map(Tag::ExtXKey) input.parse().map(Tag::ExtXKey)
} else if s.starts_with(tags::ExtXMap::PREFIX) { } else if input.starts_with(tags::ExtXMap::PREFIX) {
s.parse().map(Tag::ExtXMap) input.parse().map(Tag::ExtXMap)
} else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) { } else if input.starts_with(tags::ExtXProgramDateTime::PREFIX) {
s.parse().map(Tag::ExtXProgramDateTime) input.parse().map(Tag::ExtXProgramDateTime)
} else if s.starts_with(tags::ExtXTargetDuration::PREFIX) { } else if input.starts_with(tags::ExtXTargetDuration::PREFIX) {
s.parse().map(Tag::ExtXTargetDuration) input.parse().map(Tag::ExtXTargetDuration)
} else if s.starts_with(tags::ExtXDateRange::PREFIX) { } else if input.starts_with(tags::ExtXDateRange::PREFIX) {
s.parse().map(Tag::ExtXDateRange) input.parse().map(Tag::ExtXDateRange)
} else if s.starts_with(tags::ExtXMediaSequence::PREFIX) { } else if input.starts_with(tags::ExtXMediaSequence::PREFIX) {
s.parse().map(Tag::ExtXMediaSequence) input.parse().map(Tag::ExtXMediaSequence)
} else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { } else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
s.parse().map(Tag::ExtXDiscontinuitySequence) input.parse().map(Tag::ExtXDiscontinuitySequence)
} else if s.starts_with(tags::ExtXEndList::PREFIX) { } else if input.starts_with(tags::ExtXEndList::PREFIX) {
s.parse().map(Tag::ExtXEndList) input.parse().map(Tag::ExtXEndList)
} else if s.starts_with(tags::ExtXPlaylistType::PREFIX) { } else if input.starts_with(tags::ExtXPlaylistType::PREFIX) {
s.parse().map(Tag::ExtXPlaylistType) input.parse().map(Tag::ExtXPlaylistType)
} else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) { } else if input.starts_with(tags::ExtXIFramesOnly::PREFIX) {
s.parse().map(Tag::ExtXIFramesOnly) input.parse().map(Tag::ExtXIFramesOnly)
} else if s.starts_with(tags::ExtXMedia::PREFIX) { } else if input.starts_with(tags::ExtXMedia::PREFIX) {
s.parse().map(Tag::ExtXMedia) input.parse().map(Tag::ExtXMedia).map_err(Error::custom)
} else if s.starts_with(tags::ExtXStreamInf::PREFIX) { } else if input.starts_with(tags::ExtXStreamInf::PREFIX) {
s.parse().map(Tag::ExtXStreamInf) input.parse().map(Tag::ExtXStreamInf)
} else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) { } else if input.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
s.parse().map(Tag::ExtXIFrameStreamInf) input.parse().map(Tag::ExtXIFrameStreamInf)
} else if s.starts_with(tags::ExtXSessionData::PREFIX) { } else if input.starts_with(tags::ExtXSessionData::PREFIX) {
s.parse().map(Tag::ExtXSessionData) input.parse().map(Tag::ExtXSessionData)
} else if s.starts_with(tags::ExtXSessionKey::PREFIX) { } else if input.starts_with(tags::ExtXSessionKey::PREFIX) {
s.parse().map(Tag::ExtXSessionKey) input.parse().map(Tag::ExtXSessionKey)
} else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) { } else if input.starts_with(tags::ExtXIndependentSegments::PREFIX) {
s.parse().map(Tag::ExtXIndependentSegments) input.parse().map(Tag::ExtXIndependentSegments)
} else if s.starts_with(tags::ExtXStart::PREFIX) { } else if input.starts_with(tags::ExtXStart::PREFIX) {
s.parse().map(Tag::ExtXStart) input.parse().map(Tag::ExtXStart)
} else { } else {
Ok(Tag::Unknown(s.to_string())) Ok(Tag::Unknown(input.to_string()))
} }
} }
} }

View file

@ -5,17 +5,35 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::tag; use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.3.1.1. EXTM3U] /// # [4.4.1.1. EXTM3U]
/// The [ExtM3u] tag indicates that the file is an Extended [M3U] /// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
/// Playlist file. /// Playlist file.
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].
/// ///
/// Its format is: /// # Examples
/// ```text /// Parsing from a [`str`]:
/// #EXTM3U /// ```
/// # use failure::Error;
/// # use hls_m3u8::tags::ExtM3u;
/// #
/// # fn main() -> Result<(), Error> {
/// assert_eq!("#EXTM3U".parse::<ExtM3u>()?, 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 /// [`Media Playlist`]: crate::MediaPlaylist
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1 /// [`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)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ExtM3u; pub struct ExtM3u;
@ -23,6 +41,7 @@ impl ExtM3u {
pub(crate) const PREFIX: &'static str = "#EXTM3U"; pub(crate) const PREFIX: &'static str = "#EXTM3U";
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtM3u { impl RequiredVersion for ExtM3u {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1
@ -31,7 +50,7 @@ impl RequiredVersion for ExtM3u {
impl fmt::Display for ExtM3u { impl fmt::Display for ExtM3u {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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<Self, Self::Err> { fn from_str(input: &str) -> Result<Self, Self::Err> {
tag(input, Self::PREFIX)?; tag(input, Self::PREFIX)?;
Ok(ExtM3u) Ok(Self)
} }
} }

View file

@ -5,40 +5,64 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::tag; use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.3.1.2. EXT-X-VERSION] /// # [4.4.1.2. EXT-X-VERSION]
/// The [ExtXVersion] tag indicates the compatibility version of the /// The [`ExtXVersion`] tag indicates the compatibility version of the
/// Playlist file, its associated media, and its server. /// [`Master Playlist`] or [`Media Playlist`] file.
/// It applies to the entire Playlist.
/// ///
/// The [ExtXVersion] tag applies to the entire Playlist file. Its /// # Examples
/// format is: /// Parsing from a [`str`]:
///
/// ```text
/// #EXT-X-VERSION:<n>
/// ``` /// ```
/// where `n` is an integer indicating the protocol compatibility version /// # use failure::Error;
/// number. /// # 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>()?,
/// 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)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ExtXVersion(ProtocolVersion); pub struct ExtXVersion(ProtocolVersion);
impl ExtXVersion { impl ExtXVersion {
pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:"; pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:";
/// Makes a new [ExtXVersion] tag. /// Makes a new [`ExtXVersion`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # use hls_m3u8::tags::ExtXVersion; /// # use hls_m3u8::tags::ExtXVersion;
/// use hls_m3u8::types::ProtocolVersion; /// 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 { pub const fn new(version: ProtocolVersion) -> Self {
Self(version) Self(version)
} }
/// Returns the protocol compatibility version of the playlist, containing this tag. /// Returns the [`ProtocolVersion`] of the playlist, containing this tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -55,6 +79,7 @@ impl ExtXVersion {
} }
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXVersion { impl RequiredVersion for ExtXVersion {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1
@ -84,7 +109,7 @@ impl FromStr for ExtXVersion {
fn from_str(input: &str) -> Result<Self, Self::Err> { fn from_str(input: &str) -> Result<Self, Self::Err> {
let version = tag(input, Self::PREFIX)?.parse()?; let version = tag(input, Self::PREFIX)?.parse()?;
Ok(ExtXVersion::new(version)) Ok(Self::new(version))
} }
} }
@ -115,4 +140,12 @@ mod test {
ProtocolVersion::V1 ProtocolVersion::V1
); );
} }
#[test]
fn test_default_and_from() {
assert_eq!(
ExtXVersion::default(),
ExtXVersion::from(ProtocolVersion::V1)
);
}
} }

View file

@ -7,20 +7,17 @@ use crate::types::{ProtocolVersion, RequiredVersion, StreamInf};
use crate::utils::{quote, tag, unquote}; use crate::utils::{quote, tag, unquote};
use crate::Error; use crate::Error;
/// # [4.3.4.3. EXT-X-I-FRAME-STREAM-INF] /// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]
/// The [ExtXIFrameStreamInf] tag identifies a [Media Playlist] file /// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,
/// containing the I-frames of a multimedia presentation. It stands /// containing the I-frames of a multimedia presentation.
/// alone, in that it does not apply to a particular `URI` in the [Master Playlist].
/// ///
/// Its format is: /// I-frames are encoded video frames, whose decoding
/// does not depend on any other frame.
/// ///
/// ```text /// [`Master Playlist`]: crate::MasterPlaylist
/// #EXT-X-I-FRAME-STREAM-INF:<attribute-list> /// [`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
/// [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
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)] #[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXIFrameStreamInf { pub struct ExtXIFrameStreamInf {
uri: String, uri: String,
@ -30,7 +27,7 @@ pub struct ExtXIFrameStreamInf {
impl ExtXIFrameStreamInf { impl ExtXIFrameStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:"; pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
/// Makes a new [ExtXIFrameStreamInf] tag. /// Makes a new [`ExtXIFrameStreamInf`] tag.
/// ///
/// # Example /// # 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 /// # Example
/// ``` /// ```
@ -52,11 +49,13 @@ impl ExtXIFrameStreamInf {
/// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20);
/// assert_eq!(stream.uri(), &"https://www.example.com".to_string()); /// assert_eq!(stream.uri(), &"https://www.example.com".to_string());
/// ``` /// ```
///
/// [`media playlist`]: crate::MediaPlaylist
pub const fn uri(&self) -> &String { pub const fn uri(&self) -> &String {
&self.uri &self.uri
} }
/// Sets the `URI`, that identifies the associated media playlist. /// Sets the `URI`, that identifies the associated [`media playlist`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -67,12 +66,15 @@ impl ExtXIFrameStreamInf {
/// stream.set_uri("../new/uri"); /// stream.set_uri("../new/uri");
/// assert_eq!(stream.uri(), &"../new/uri".to_string()); /// assert_eq!(stream.uri(), &"../new/uri".to_string());
/// ``` /// ```
///
/// [`media playlist`]: crate::MediaPlaylist
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self { pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
self.uri = value.to_string(); self.uri = value.to_string();
self self
} }
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXIFrameStreamInf { impl RequiredVersion for ExtXIFrameStreamInf {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1

View file

@ -4,83 +4,120 @@ use std::str::FromStr;
use derive_builder::Builder; use derive_builder::Builder;
use crate::attribute::AttributePairs; 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::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::Error; use crate::Error;
/// # [4.4.4.1. EXT-X-MEDIA] /// # [4.4.5.1. EXT-X-MEDIA]
/// The [ExtXMedia] tag is used to relate [Media Playlist]s that contain /// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s,
/// alternative Renditions of the same content. For /// that contain alternative Renditions of the same content.
/// example, three [ExtXMedia] tags can be used to identify audio-only ///
/// [Media Playlist]s, that contain English, French, and Spanish Renditions /// For
/// of the same presentation. Or, two [ExtXMedia] tags can be used to /// example, three [`ExtXMedia`] tags can be used to identify audio-only
/// identify video-only [Media Playlist]s that show two different camera /// [`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. /// angles.
/// ///
/// Its format is: /// [`Media Playlist`]: crate::MediaPlaylist
/// ```text /// [4.4.5.1. EXT-X-MEDIA]:
/// #EXT-X-MEDIA:<attribute-list> /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.1
/// ```
///
/// [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
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
#[builder(setter(into))] #[builder(setter(into))]
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
pub struct ExtXMedia { pub struct ExtXMedia {
/// Sets the media type of the rendition. /// Sets the [`MediaType`] of the rendition.
///
/// # Note
/// This attribute is **required**.
media_type: MediaType, media_type: MediaType,
#[builder(setter(strip_option, into), default)] #[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<String>, uri: Option<String>,
/// 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, group_id: String,
#[builder(setter(strip_option, into), default)]
/// Sets the name of the primary language used in the rendition. /// 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<String>, language: Option<String>,
/// Sets the name of a language associated with the rendition.
#[builder(setter(strip_option, into), default)] #[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<String>, assoc_language: Option<String>,
/// Sets a human-readable description of the rendition. /// 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, name: String,
#[builder(default)]
/// Sets the value of the `default` flag. /// 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, is_default: bool,
#[builder(default)]
/// Sets the value of the `autoselect` flag. /// 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, is_autoselect: bool,
/// Sets the value of the `forced` flag.
#[builder(default)] #[builder(default)]
/// Sets the value of the `forced` flag.
is_forced: bool, is_forced: bool,
#[builder(setter(strip_option, into), default)]
/// Sets the identifier that specifies a rendition within the segments in the media playlist. /// Sets the identifier that specifies a rendition within the segments in the media playlist.
#[builder(setter(strip_option, into), default)]
instream_id: Option<InStreamId>, instream_id: Option<InStreamId>,
#[builder(setter(strip_option, into), default)]
/// Sets the string that represents uniform type identifiers (UTI). /// Sets the string that represents uniform type identifiers (UTI).
#[builder(setter(strip_option, into), default)]
characteristics: Option<String>, characteristics: Option<String>,
/// Sets the string that represents the parameters of the rendition.
#[builder(setter(strip_option, into), default)] #[builder(setter(strip_option, into), default)]
channels: Option<String>, /// Sets the parameters of the rendition.
channels: Option<Channels>,
} }
impl ExtXMediaBuilder { impl ExtXMediaBuilder {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
// A MediaType is always required!
let media_type = self let media_type = self
.media_type .media_type
.ok_or_else(|| Error::missing_attribute("MEDIA-TYPE").to_string())?; .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() { if self.uri.is_some() {
return Err(Error::custom( return Err(Error::unexpected_attribute("URI").to_string());
"Unexpected attribute: \"URL\" for MediaType::ClosedCaptions!", }
) if self.instream_id.is_none() {
.to_string()); 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() { } 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) { 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()); return Err(Error::invalid_input().to_string());
} }
@ -100,7 +137,7 @@ impl ExtXMediaBuilder {
impl ExtXMedia { impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:"; pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new [ExtXMedia] tag. /// Makes a new [`ExtXMedia`] tag.
pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self { pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self {
Self { Self {
media_type, media_type,
@ -118,7 +155,7 @@ impl ExtXMedia {
} }
} }
/// Returns a builder for [ExtXMedia]. /// Returns a builder for [`ExtXMedia`].
pub fn builder() -> ExtXMediaBuilder { pub fn builder() -> ExtXMediaBuilder {
ExtXMediaBuilder::default() ExtXMediaBuilder::default()
} }
@ -215,6 +252,9 @@ impl ExtXMedia {
/// Sets a human-readable description of the rendition. /// Sets a human-readable description of the rendition.
/// ///
/// # Note
/// If the [`language`] attribute is present, this attribute should be in that language.
///
/// # Example /// # Example
/// ``` /// ```
/// # use hls_m3u8::tags::ExtXMedia; /// # use hls_m3u8::tags::ExtXMedia;
@ -229,12 +269,14 @@ impl ExtXMedia {
/// &"new_name".to_string() /// &"new_name".to_string()
/// ); /// );
/// ``` /// ```
///
/// [`language`]: #method.language
pub fn set_name<T: Into<String>>(&mut self, value: T) -> &mut Self { pub fn set_name<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.name = value.into(); self.name = value.into();
self self
} }
/// Returns the `URI`, that identifies the [MediaPlaylist]. /// Returns the `URI`, that identifies the [`Media Playlist`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -251,11 +293,18 @@ impl ExtXMedia {
/// &Some("https://www.example.com/".into()) /// &Some("https://www.example.com/".into())
/// ); /// );
/// ``` /// ```
///
/// [`Media Playlist`]: crate::MediaPlaylist
pub const fn uri(&self) -> &Option<String> { pub const fn uri(&self) -> &Option<String> {
&self.uri &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 /// # Example
/// ``` /// ```
@ -272,6 +321,8 @@ impl ExtXMedia {
/// &Some("https://www.example.com/".into()) /// &Some("https://www.example.com/".into())
/// ); /// );
/// ``` /// ```
///
/// [`Media Playlist`]: crate::MediaPlaylist
pub fn set_uri<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self { pub fn set_uri<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
self.uri = value.map(|v| v.into()); self.uri = value.map(|v| v.into());
self self
@ -299,6 +350,7 @@ impl ExtXMedia {
} }
/// Sets the name of the primary language used in the rendition. /// Sets the name of the primary language used in the rendition.
/// The value has to conform to [`RFC5646`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -315,6 +367,8 @@ impl ExtXMedia {
/// &Some("english".into()) /// &Some("english".into())
/// ); /// );
/// ``` /// ```
///
/// [`RFC5646`]: https://tools.ietf.org/html/rfc5646
pub fn set_language<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self { pub fn set_language<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
self.language = value.map(|v| v.into()); self.language = value.map(|v| v.into());
self self
@ -342,6 +396,9 @@ impl ExtXMedia {
} }
/// Sets the name of a language associated with the rendition. /// 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 /// # Example
/// ``` /// ```
@ -358,12 +415,14 @@ impl ExtXMedia {
/// &Some("spanish".into()) /// &Some("spanish".into())
/// ); /// );
/// ``` /// ```
///
/// [`language`]: #method.language
pub fn set_assoc_language<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self { pub fn set_assoc_language<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
self.assoc_language = value.map(|v| v.into()); self.assoc_language = value.map(|v| v.into());
self self
} }
/// Returns whether this is the default rendition. /// Returns whether this is the `default` rendition.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -385,6 +444,9 @@ impl ExtXMedia {
} }
/// Sets the `default` flag. /// 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 /// # Example
/// ``` /// ```
@ -479,7 +541,7 @@ impl ExtXMedia {
} }
/// Returns the identifier that specifies a rendition within the segments in the /// Returns the identifier that specifies a rendition within the segments in the
/// [MediaPlaylist]. /// [`Media Playlist`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -493,11 +555,14 @@ impl ExtXMedia {
/// ///
/// assert_eq!(media.instream_id(), Some(InStreamId::Cc1)); /// assert_eq!(media.instream_id(), Some(InStreamId::Cc1));
/// ``` /// ```
///
/// [`Media Playlist`]: crate::MediaPlaylist
pub const fn instream_id(&self) -> Option<InStreamId> { pub const fn instream_id(&self) -> Option<InStreamId> {
self.instream_id self.instream_id
} }
/// Sets the [InStreamId]. /// Sets the [`InStreamId`], that specifies a rendition within the
/// segments in the [`Media Playlist`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -536,7 +601,20 @@ impl ExtXMedia {
&self.characteristics &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 /// # Example
/// ``` /// ```
@ -550,26 +628,29 @@ impl ExtXMedia {
/// ///
/// assert_eq!(media.characteristics(), &Some("characteristic".into())); /// 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<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self { pub fn set_characteristics<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
self.characteristics = value.map(|v| v.into()); self.characteristics = value.map(|v| v.into());
self self
} }
/// Returns a [String] that represents the parameters of the rendition. /// Returns the channels.
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # use hls_m3u8::tags::ExtXMedia; /// # 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"); /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name");
/// # assert_eq!(media.channels(), &None); /// # 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<String> { pub const fn channels(&self) -> &Option<Channels> {
&self.channels &self.channels
} }
@ -578,16 +659,16 @@ impl ExtXMedia {
/// # Example /// # Example
/// ``` /// ```
/// # use hls_m3u8::tags::ExtXMedia; /// # 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"); /// 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<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self { pub fn set_channels<T: Into<Channels>>(&mut self, value: Option<T>) -> &mut Self {
self.channels = value.map(|v| v.into()); self.channels = value.map(|v| v.into());
self self
} }
@ -687,7 +768,7 @@ impl FromStr for ExtXMedia {
builder.characteristics(unquote(value)); builder.characteristics(unquote(value));
} }
"CHANNELS" => { "CHANNELS" => {
builder.channels(unquote(value)); builder.channels(unquote(value).parse::<Channels>()?);
} }
_ => { _ => {
// [6.3.1. General Client Responsibilities] // [6.3.1. General Client Responsibilities]
@ -695,6 +776,7 @@ impl FromStr for ExtXMedia {
} }
} }
} }
builder.build().map_err(Error::builder_error) builder.build().map_err(Error::builder_error)
} }
} }
@ -915,7 +997,7 @@ mod test {
.name("English") .name("English")
.is_autoselect(true) .is_autoselect(true)
.is_default(true) .is_default(true)
.channels("2") .channels(Channels::new(2))
.build() .build()
.unwrap() .unwrap()
.to_string(), .to_string(),
@ -1196,7 +1278,7 @@ mod test {
.name("English") .name("English")
.is_autoselect(true) .is_autoselect(true)
.is_default(true) .is_default(true)
.channels("2") .channels(Channels::new(2))
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ "#EXT-X-MEDIA:\
@ -1262,7 +1344,30 @@ mod test {
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"foo\",NAME=\"bar\"" "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"foo\",NAME=\"bar\""
.parse() .parse()
.unwrap() .unwrap()
) );
}
#[test]
fn test_parser_error() {
assert!("".parse::<ExtXMedia>().is_err());
assert!("garbage".parse::<ExtXMedia>().is_err());
assert!(
"#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,URI=\"http://www.example.com\""
.parse::<ExtXMedia>()
.is_err()
);
assert!("#EXT-X-MEDIA:TYPE=AUDIO,INSTREAM-ID=CC1"
.parse::<ExtXMedia>()
.is_err());
assert!("#EXT-X-MEDIA:TYPE=AUDIO,DEFAULT=YES,AUTOSELECT=NO"
.parse::<ExtXMedia>()
.is_err());
assert!("#EXT-X-MEDIA:TYPE=AUDIO,FORCED=YES"
.parse::<ExtXMedia>()
.is_err());
} }
#[test] #[test]

View file

@ -11,37 +11,41 @@ use crate::Error;
/// The data of an [ExtXSessionData] tag. /// The data of an [ExtXSessionData] tag.
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] #[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
pub enum SessionData { pub enum SessionData {
/// A String, that contains the data identified by [data_id](ExtXSessionData::data_id). /// A String, that contains the data identified by [`data_id`](ExtXSessionData::data_id).
/// If a [language](ExtXSessionData::language) is specified, the value should /// If a [`language`](ExtXSessionData::language) is specified, the value should
/// contain a human-readable string written in the specified language. /// contain a human-readable string written in the specified language.
Value(String), Value(String),
/// An [uri], which points to a [json]. /// An [`uri`], which points to a [`json`].
/// ///
/// [json]: https://tools.ietf.org/html/rfc8259 /// [`json`]: https://tools.ietf.org/html/rfc8259
/// [uri]: https://tools.ietf.org/html/rfc3986 /// [`uri`]: https://tools.ietf.org/html/rfc3986
Uri(String), Uri(String),
} }
/// # [4.3.4.4. EXT-X-SESSION-DATA] /// # [4.3.4.4. EXT-X-SESSION-DATA]
/// ///
/// The [ExtXSessionData] tag allows arbitrary session data to be /// The [`ExtXSessionData`] tag allows arbitrary session data to be
/// carried in a [Master Playlist]. /// 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 /// [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)] #[derive(Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
#[builder(setter(into))] #[builder(setter(into))]
pub struct ExtXSessionData { 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 /// # Note
/// This field is required. /// This field is required.
data_id: String, data_id: String,
/// The data associated with the [data_id](ExtXSessionDataBuilder::data_id). /// The data associated with the
/// For more information look [here](SessionData). /// [`data_id`](ExtXSessionDataBuilder::data_id).
/// For more information look [`here`](SessionData).
///
/// # Note /// # Note
/// This field is required. /// This field is required.
data: SessionData, data: SessionData,
/// The language of the [data](ExtXSessionDataBuilder::data). /// The language of the [`data`](ExtXSessionDataBuilder::data).
#[builder(setter(into, strip_option), default)] #[builder(setter(into, strip_option), default)]
language: Option<String>, language: Option<String>,
} }
@ -49,7 +53,7 @@ pub struct ExtXSessionData {
impl ExtXSessionData { impl ExtXSessionData {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:"; pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
/// Makes a new [ExtXSessionData] tag. /// Makes a new [`ExtXSessionData`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -68,7 +72,7 @@ impl ExtXSessionData {
} }
} }
/// Returns a new Builder for [ExtXSessionData]. /// Returns a new Builder for [`ExtXSessionData`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -94,7 +98,7 @@ impl ExtXSessionData {
ExtXSessionDataBuilder::default() ExtXSessionDataBuilder::default()
} }
/// Makes a new [ExtXSessionData] tag, with the given language. /// Makes a new [`ExtXSessionData`] tag, with the given language.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -154,7 +158,7 @@ impl ExtXSessionData {
&self.data &self.data
} }
/// Returns the `language` tag, that identifies the language of [SessionData]. /// Returns the `language` tag, that identifies the language of [`SessionData`].
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -175,7 +179,7 @@ impl ExtXSessionData {
&self.language &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). /// See [rfc5646](https://tools.ietf.org/html/rfc5646).
/// ///
/// # Example /// # Example
@ -224,7 +228,7 @@ impl ExtXSessionData {
self self
} }
/// Sets the [data](ExtXSessionData::data) of this tag. /// Sets the [`data`](ExtXSessionData::data) of this tag.
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -7,9 +7,9 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.3.4.5. EXT-X-SESSION-KEY] /// # [4.3.4.5. EXT-X-SESSION-KEY]
/// The [ExtXSessionKey] tag allows encryption keys from [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 /// to be specified in a [`Master Playlist`]. This allows the client to
/// preload these keys without having to read the [Media Playlist]s /// preload these keys without having to read the [`Media Playlist`]s
/// first. /// first.
/// ///
/// Its format is: /// Its format is:
@ -17,8 +17,8 @@ use crate::Error;
/// #EXT-X-SESSION-KEY:<attribute-list> /// #EXT-X-SESSION-KEY:<attribute-list>
/// ``` /// ```
/// ///
/// [Media Playlist]: crate::MediaPlaylist /// [`Media Playlist`]: crate::MediaPlaylist
/// [Master Playlist]: crate::MasterPlaylist /// [`Master Playlist`]: crate::MasterPlaylist
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5 /// [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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey(DecryptionKey); pub struct ExtXSessionKey(DecryptionKey);
@ -26,11 +26,13 @@ pub struct ExtXSessionKey(DecryptionKey);
impl ExtXSessionKey { impl ExtXSessionKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:"; pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
/// Makes a new [ExtXSessionKey] tag. /// Makes a new [`ExtXSessionKey`] tag.
/// ///
/// # Panic /// # Panic
/// An [ExtXSessionKey] should only be used, if the segments of the stream are encrypted. /// An [`ExtXSessionKey`] should only be used,
/// Therefore this function will panic, if the `method` is [EncryptionMethod::None]. /// if the segments of the stream are encrypted.
/// Therefore this function will panic,
/// if the `method` is [`EncryptionMethod::None`].
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -25,7 +25,7 @@ pub struct ExtXStreamInf {
impl ExtXStreamInf { impl ExtXStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
/// Creates a new [ExtXStreamInf] tag. /// Creates a new [`ExtXStreamInf`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -164,7 +164,7 @@ impl ExtXStreamInf {
self self
} }
/// Returns the value of [ClosedCaptions] attribute. /// Returns the value of [`ClosedCaptions`] attribute.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -181,7 +181,7 @@ impl ExtXStreamInf {
&self.closed_captions &self.closed_captions
} }
/// Returns the value of [ClosedCaptions] attribute. /// Returns the value of [`ClosedCaptions`] attribute.
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -6,9 +6,9 @@ use crate::utils::tag;
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// # [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 /// 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: /// Its format is:
/// ```text /// ```text
@ -16,8 +16,8 @@ use crate::utils::tag;
/// ``` /// ```
/// where `number` is a [u64]. /// where `number` is a [u64].
/// ///
/// [ExtXDiscontinuity]: crate::tags::ExtXDiscontinuity /// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity
/// [Media Playlist]: crate::MediaPlaylist /// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: /// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.3 /// 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)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]

View file

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

View file

@ -6,7 +6,7 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY] /// # [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 /// Playlist describes a single I-frame. I-frames are encoded video
/// frames, whose decoding does not depend on any other frame. I-frame /// frames, whose decoding does not depend on any other frame. I-frame
/// Playlists can be used for trick play, such as fast forward, rapid /// Playlists can be used for trick play, such as fast forward, rapid
@ -17,7 +17,7 @@ use crate::Error;
/// #EXT-X-I-FRAMES-ONLY /// #EXT-X-I-FRAMES-ONLY
/// ``` /// ```
/// ///
/// [Media Segment]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]: /// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -6,14 +6,14 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE] /// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE]
/// The [ExtXMediaSequence] tag indicates the Media Sequence Number of /// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
/// the first [Media Segment] that appears in a Playlist file. /// the first [`Media Segment`] that appears in a Playlist file.
/// ///
/// Its format is: /// Its format is:
/// ```text /// ```text
/// #EXT-X-MEDIA-SEQUENCE:<number> /// #EXT-X-MEDIA-SEQUENCE:<number>
/// ``` /// ```
/// where `number` is a [u64]. /// where `number` is a [`u64`].
/// ///
/// [Media Segment]: crate::MediaSegment /// [Media Segment]: crate::MediaSegment
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]: /// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
@ -24,7 +24,7 @@ pub struct ExtXMediaSequence(u64);
impl ExtXMediaSequence { impl ExtXMediaSequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:"; pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
/// Makes a new [ExtXMediaSequence] tag. /// Makes a new [`ExtXMediaSequence`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -7,8 +7,8 @@ use crate::Error;
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE] /// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
/// ///
/// The [ExtXPlaylistType] tag provides mutability information about the /// The [`ExtXPlaylistType`] tag provides mutability information about the
/// [Media Playlist]. It applies to the entire [Media Playlist]. /// [`Media Playlist`]. It applies to the entire [`Media Playlist`].
/// ///
/// Its format is: /// Its format is:
/// ```text /// ```text
@ -20,10 +20,10 @@ use crate::Error;
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExtXPlaylistType { pub enum ExtXPlaylistType {
/// If the [ExtXPlaylistType] is Event, Media Segments can only be added to /// If the [`ExtXPlaylistType`] is Event, Media Segments
/// the end of the Media Playlist. /// can only be added to the end of the Media Playlist.
Event, Event,
/// If the [ExtXPlaylistType] is Video On Demand (Vod), /// If the [`ExtXPlaylistType`] is Video On Demand (Vod),
/// the Media Playlist cannot change. /// the Media Playlist cannot change.
Vod, Vod,
} }

View file

@ -7,39 +7,52 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.4.3.1. EXT-X-TARGETDURATION] /// # [4.4.3.1. EXT-X-TARGETDURATION]
/// The [ExtXTargetDuration] tag specifies the maximum [Media Segment] /// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]
/// duration. /// duration.
/// ///
/// Its format is: /// [`Media Segment`]: crate::MediaSegment
/// ```text
/// #EXT-X-TARGETDURATION:<s>
/// ```
/// where `s` is the target [Duration] in seconds.
///
/// [Media Segment]: crate::MediaSegment
/// [4.4.3.1. EXT-X-TARGETDURATION]: /// [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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ExtXTargetDuration(Duration); pub struct ExtXTargetDuration(Duration);
impl ExtXTargetDuration { impl ExtXTargetDuration {
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:"; 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 /// # 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 { pub const fn new(duration: Duration) -> Self {
// TOOD: round instead of discarding? // TOOD: round instead of discarding?
Self(Duration::from_secs(duration.as_secs())) 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 { pub const fn duration(&self) -> Duration {
self.0 self.0
} }
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXTargetDuration { impl RequiredVersion for ExtXTargetDuration {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1

View file

@ -8,7 +8,7 @@ use crate::Error;
/// # [4.4.2.2. EXT-X-BYTERANGE] /// # [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`. /// of the resource identified by its `URI`.
/// ///
/// Its format is: /// Its format is:
@ -20,7 +20,7 @@ use crate::Error;
/// If present, `o` is a [usize] indicating the start of the sub-range, /// If present, `o` is a [usize] indicating the start of the sub-range,
/// as a byte offset from the beginning of the resource. /// 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]: /// [4.4.2.2. EXT-X-BYTERANGE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -29,7 +29,7 @@ pub struct ExtXByteRange(ByteRange);
impl ExtXByteRange { impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:"; pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new [ExtXByteRange] tag. /// Makes a new [`ExtXByteRange`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -40,7 +40,7 @@ impl ExtXByteRange {
Self(ByteRange::new(length, start)) Self(ByteRange::new(length, start))
} }
/// Converts the [ExtXByteRange] to a [ByteRange]. /// Converts the [`ExtXByteRange`] to a [`ByteRange`].
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -10,7 +10,7 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::{quote, tag, unquote}; use crate::utils::{quote, tag, unquote};
use crate::Error; 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 /// [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)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXDateRange { 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. /// This attribute is required.
id: String, id: String,
/// A client-defined string that specifies some set of attributes and their associated value /// A client-defined string that specifies some set of attributes and their associated value
@ -64,7 +64,7 @@ pub struct ExtXDateRange {
impl ExtXDateRange { impl ExtXDateRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:"; pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
/// Makes a new [ExtXDateRange] tag. /// Makes a new [`ExtXDateRange`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -74,7 +74,7 @@ impl ExtXDateRange {
/// ///
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds /// 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) /// .ymd(2010, 2, 19)
/// .and_hms_milli(14, 54, 23, 31)); /// .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 { impl RequiredVersion for ExtXDateRange {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1

View file

@ -6,15 +6,15 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.4.2.3. EXT-X-DISCONTINUITY] /// # [4.4.2.3. EXT-X-DISCONTINUITY]
/// The [ExtXDiscontinuity] tag indicates a discontinuity between the /// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the
/// [Media Segment] that follows it and the one that preceded it. /// [`Media Segment`] that follows it and the one that preceded it.
/// ///
/// Its format is: /// Its format is:
/// ```text /// ```text
/// #EXT-X-DISCONTINUITY /// #EXT-X-DISCONTINUITY
/// ``` /// ```
/// ///
/// [Media Segment]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.3. EXT-X-DISCONTINUITY]: /// [4.4.2.3. EXT-X-DISCONTINUITY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -8,8 +8,8 @@ use crate::Error;
/// # [4.4.2.1. EXTINF] /// # [4.4.2.1. EXTINF]
/// ///
/// The [ExtInf] tag specifies the duration of a [Media Segment]. It applies /// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies
/// only to the next [Media Segment]. /// only to the next [`Media Segment`].
/// ///
/// Its format is: /// Its format is:
/// ```text /// ```text
@ -17,7 +17,7 @@ use crate::Error;
/// ``` /// ```
/// The title is an optional informative title about the [Media Segment]. /// 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]: /// [4.4.2.1. EXTINF]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -29,7 +29,7 @@ pub struct ExtInf {
impl ExtInf { impl ExtInf {
pub(crate) const PREFIX: &'static str = "#EXTINF:"; pub(crate) const PREFIX: &'static str = "#EXTINF:";
/// Makes a new [ExtInf] tag. /// Makes a new [`ExtInf`] tag.
/// ///
/// # Example /// # 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 /// # Example
/// ``` /// ```

View file

@ -7,11 +7,11 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.4.2.4. EXT-X-KEY] /// # [4.4.2.4. EXT-X-KEY]
/// [Media Segment]s may be encrypted. The [ExtXKey] tag specifies how to /// [`Media Segment`]s may be encrypted. The [`ExtXKey`] tag specifies how to
/// decrypt them. It applies to every [Media Segment] and to every Media /// decrypt them. It applies to every [`Media Segment`] and to every Media
/// Initialization Section declared by an [ExtXMap] tag, that appears /// Initialization Section declared by an [`ExtXMap`] tag, that appears
/// between it and the next [ExtXKey] tag in the Playlist file with the /// between it and the next [`ExtXKey`] tag in the Playlist file with the
/// same [KeyFormat] attribute (or the end of the Playlist file). /// same [`KeyFormat`] attribute (or the end of the Playlist file).
/// ///
/// The format is: /// The format is:
/// ```text /// ```text
@ -19,11 +19,12 @@ use crate::Error;
/// ``` /// ```
/// ///
/// # Note /// # 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 /// [`KeyFormat`]: crate::types::KeyFormat
/// [ExtXMap]: crate::tags::ExtXMap /// [`ExtXMap`]: crate::tags::ExtXMap
/// [Media Segment]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.4. EXT-X-KEY]: /// [4.4.2.4. EXT-X-KEY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -32,8 +33,9 @@ pub struct ExtXKey(DecryptionKey);
impl ExtXKey { impl ExtXKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:"; pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
/// Makes a new `ExtXKey` tag. /// Makes a new [`ExtXKey`] tag.
/// # Examples ///
/// # Example
/// ``` /// ```
/// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod; /// use hls_m3u8::types::EncryptionMethod;
@ -52,8 +54,9 @@ impl ExtXKey {
Self(DecryptionKey::new(method, uri)) Self(DecryptionKey::new(method, uri))
} }
/// Makes a new `ExtXKey` tag without a decryption key. /// Makes a new [`ExtXKey`] tag without a decryption key.
/// # Examples ///
/// # Example
/// ``` /// ```
/// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::tags::ExtXKey;
/// ///
@ -74,8 +77,10 @@ impl ExtXKey {
}) })
} }
/// Returns whether the [EncryptionMethod] is [None](EncryptionMethod::None). /// Returns whether the [`EncryptionMethod`] is
/// # Examples /// [`None`](EncryptionMethod::None).
///
/// # Example
/// ``` /// ```
/// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod; /// use hls_m3u8::types::EncryptionMethod;

View file

@ -7,7 +7,7 @@ use crate::utils::{quote, tag, unquote};
use crate::Error; use crate::Error;
/// # [4.4.2.5. EXT-X-MAP] /// # [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. /// Section, required to parse the applicable [Media Segment]s.
/// ///
/// Its format is: /// Its format is:
@ -27,7 +27,7 @@ pub struct ExtXMap {
impl ExtXMap { impl ExtXMap {
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:"; pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
/// Makes a new `ExtXMap` tag. /// Makes a new [`ExtXMap`] tag.
pub fn new<T: ToString>(uri: T) -> Self { pub fn new<T: ToString>(uri: T) -> Self {
ExtXMap { ExtXMap {
uri: uri.to_string(), 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<T: ToString>(uri: T, range: ByteRange) -> Self { pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
ExtXMap { ExtXMap {
uri: uri.to_string(), 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 { pub const fn uri(&self) -> &String {
&self.uri &self.uri
} }

View file

@ -9,10 +9,10 @@ use crate::utils::tag;
use crate::Error; use crate::Error;
/// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] /// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
/// The [ExtXProgramDateTime] tag associates the first sample of a /// The [`ExtXProgramDateTime`] tag associates the first sample of a
/// [Media Segment] with an absolute date and/or time. /// [`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 /// [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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXProgramDateTime(DateTime<FixedOffset>); pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
@ -20,7 +20,7 @@ pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
impl ExtXProgramDateTime { impl ExtXProgramDateTime {
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:"; pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
/// Makes a new `ExtXProgramDateTime` tag. /// Makes a new [`ExtXProgramDateTime`] tag.
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -18,7 +18,7 @@ pub struct ExtXStart {
impl ExtXStart { impl ExtXStart {
pub(crate) const PREFIX: &'static str = "#EXT-X-START:"; pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
/// Makes a new [ExtXStart] tag. /// Makes a new [`ExtXStart`] tag.
/// ///
/// # Panic /// # Panic
/// Panics if the time_offset value is infinite. /// 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 /// # Panic
/// Panics if the time_offset value is infinite. /// Panics if the time_offset value is infinite.

View file

@ -15,7 +15,7 @@ pub struct ByteRange {
} }
impl ByteRange { impl ByteRange {
/// Creates a new [ByteRange]. /// Creates a new [`ByteRange`].
/// ///
/// # Example /// # Example
/// ``` /// ```

188
src/types/channels.rs Normal file
View file

@ -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<Vec<String>>,
}
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<Vec<String>> {
&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<T: ToString>(&mut self, value: Option<Vec<T>>) -> &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<Self, Self::Err> {
let parameters = input.split('/').collect::<Vec<_>>();
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::<Channels>().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::<Channels>().unwrap(), result);
assert!("garbage".parse::<Channels>().is_err());
assert!("".parse::<Channels>().is_err());
}
}

View file

@ -15,7 +15,7 @@ use crate::Error;
pub(crate) struct DecimalFloatingPoint(f64); pub(crate) struct DecimalFloatingPoint(f64);
impl DecimalFloatingPoint { impl DecimalFloatingPoint {
/// Makes a new [DecimalFloatingPoint] instance. /// Makes a new [`DecimalFloatingPoint`] instance.
/// ///
/// # Errors /// # Errors
/// ///
@ -32,7 +32,7 @@ impl DecimalFloatingPoint {
Self(value) Self(value)
} }
/// Converts [DecimalFloatingPoint] to [f64]. /// Converts [`DecimalFloatingPoint`] to [`f64`].
pub const fn as_f64(self) -> f64 { pub const fn as_f64(self) -> f64 {
self.0 self.0
} }
@ -40,7 +40,7 @@ impl DecimalFloatingPoint {
impl Eq for 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. // with a negative value.
impl FromStr for DecimalFloatingPoint { impl FromStr for DecimalFloatingPoint {
type Err = Error; type Err = Error;

View file

@ -17,7 +17,7 @@ pub(crate) struct DecimalResolution {
} }
impl DecimalResolution { impl DecimalResolution {
/// Creates a new DecimalResolution. /// Creates a new [`DecimalResolution`].
pub const fn new(width: usize, height: usize) -> Self { pub const fn new(width: usize, height: usize) -> Self {
Self { width, height } 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 { impl From<(usize, usize)> for DecimalResolution {
fn from(value: (usize, usize)) -> Self { fn from(value: (usize, usize)) -> Self {
DecimalResolution::new(value.0, value.1) DecimalResolution::new(value.0, value.1)

View file

@ -13,10 +13,10 @@ use crate::Error;
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
#[builder(setter(into), build_fn(validate = "Self::validate"))] #[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 /// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey
/// [ExtXKey]: crate::tags::ExtXKey /// [`ExtXKey`]: crate::tags::ExtXKey
pub struct DecryptionKey { pub struct DecryptionKey {
/// The [EncryptionMethod]. /// The [EncryptionMethod].
pub(crate) method: EncryptionMethod, pub(crate) method: EncryptionMethod,

View file

@ -6,7 +6,8 @@ use crate::utils::{quote, tag, unquote};
use crate::Error; use crate::Error;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[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 { pub enum KeyFormat {
/// The key is a single packed array of 16 octets in binary format. /// The key is a single packed array of 16 octets in binary format.
Identity, Identity,

View file

@ -8,9 +8,9 @@ use crate::Error;
/// A list of [usize], that can be used to indicate which version(s) /// 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 /// 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)] #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct KeyFormatVersions(Vec<usize>); pub struct KeyFormatVersions(Vec<usize>);
@ -21,12 +21,12 @@ impl Default for KeyFormatVersions {
} }
impl KeyFormatVersions { impl KeyFormatVersions {
/// Makes a new [KeyFormatVersions]. /// Makes a new [`KeyFormatVersions`].
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Add a value to the [KeyFormatVersions]. /// Add a value to the [`KeyFormatVersions`].
pub fn push(&mut self, value: usize) { pub fn push(&mut self, value: usize) {
if self.is_default() { if self.is_default() {
self.0 = vec![value]; 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 { pub fn is_default(&self) -> bool {
self.0 == vec![1] && self.0.len() == 1 || self.0.is_empty() self.0 == vec![1] && self.0.len() == 1 || self.0.is_empty()
} }

View file

@ -1,5 +1,6 @@
//! Miscellaneous types. //! Miscellaneous types.
mod byte_range; mod byte_range;
mod channels;
mod closed_captions; mod closed_captions;
mod decimal_floating_point; mod decimal_floating_point;
mod decimal_resolution; mod decimal_resolution;
@ -16,6 +17,7 @@ mod signed_decimal_floating_point;
mod stream_inf; mod stream_inf;
pub use byte_range::*; pub use byte_range::*;
pub use channels::*;
pub use closed_captions::*; pub use closed_captions::*;
pub(crate) use decimal_floating_point::*; pub(crate) use decimal_floating_point::*;
pub(crate) use decimal_resolution::*; pub(crate) use decimal_resolution::*;

View file

@ -28,7 +28,7 @@ pub trait RequiredVersion {
} }
/// # [7. Protocol Version Compatibility] /// # [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. /// a certain tag correctly.
/// ///
/// [7. Protocol Version Compatibility]: /// [7. Protocol Version Compatibility]:
@ -46,7 +46,8 @@ pub enum ProtocolVersion {
} }
impl ProtocolVersion { impl ProtocolVersion {
/// Returns the newest [ProtocolVersion], that is supported by this library. /// Returns the newest [`ProtocolVersion`], that is supported by
/// this library.
/// ///
/// # Example /// # Example
/// ``` /// ```