diff --git a/src/attribute.rs b/src/attribute.rs index 2debe9a..91091c5 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -195,7 +195,7 @@ impl FromStr for DecimalFloatingPoint { } #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] -pub struct SignedDecimalFloatingPoint(f64); +pub struct SignedDecimalFloatingPoint(pub f64); impl Eq for SignedDecimalFloatingPoint {} impl fmt::Display for SignedDecimalFloatingPoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/tag/media_or_master_playlist.rs b/src/tag/media_or_master_playlist.rs new file mode 100644 index 0000000..7c173bb --- /dev/null +++ b/src/tag/media_or_master_playlist.rs @@ -0,0 +1,142 @@ +use std::fmt; +use std::str::FromStr; + +use {Error, ErrorKind, Result}; +use attribute::{AttributePairs, SignedDecimalFloatingPoint}; +use types::{ProtocolVersion, YesOrNo}; + +/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] +/// +/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXIndependentSegments; +impl ExtXIndependentSegments { + pub(crate) const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS"; + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXIndependentSegments { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Self::PREFIX.fmt(f) + } +} +impl FromStr for ExtXIndependentSegments { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); + Ok(ExtXIndependentSegments) + } +} + +/// [4.3.5.2. EXT-X-START] +/// +/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ExtXStart { + time_offset: SignedDecimalFloatingPoint, + precise: YesOrNo, +} +impl ExtXStart { + pub(crate) const PREFIX: &'static str = "#EXT-X-START:"; + + /// Makes a new `ExtXStart` tag. + pub fn new(time_offset: SignedDecimalFloatingPoint) -> Self { + ExtXStart { + time_offset, + precise: YesOrNo::No, + } + } + + /// Makes a new `ExtXStart` tag with the given `precise` flag. + pub fn with_precise(time_offset: SignedDecimalFloatingPoint, precise: YesOrNo) -> Self { + ExtXStart { + time_offset, + precise, + } + } + + /// Returns the time offset of the media segments in the playlist. + pub fn time_offset(&self) -> SignedDecimalFloatingPoint { + self.time_offset + } + + /// Returns whether clients should not render media stream whose presentation times are + /// prior to the specified time offset. + pub fn precise(&self) -> YesOrNo { + self.precise + } + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXStart { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", Self::PREFIX)?; + write!(f, "TIME-OFFSET={}", self.time_offset)?; + if self.precise == YesOrNo::Yes { + write!(f, ",PRECISE={}", self.precise)?; + } + Ok(()) + } +} +impl FromStr for ExtXStart { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + + let mut time_offset = None; + let mut precise = None; + let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); + for attr in attrs { + let (key, value) = track!(attr)?; + match key { + "TIME-OFFSET" => time_offset = Some(track!(value.parse())?), + "PRECISE" => precise = Some(track!(value.parse())?), + _ => { + // [6.3.1. General Client Responsibilities] + // > ignore any attribute/value pair with an unrecognized AttributeName. + } + } + } + + let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput); + Ok(ExtXStart { + time_offset, + precise: precise.unwrap_or(YesOrNo::No), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn ext_x_independent_segments() { + let tag = ExtXIndependentSegments; + let text = "#EXT-X-INDEPENDENT-SEGMENTS"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_x_start() { + let tag = ExtXStart::new(SignedDecimalFloatingPoint(-1.23)); + let text = "#EXT-X-START:TIME-OFFSET=-1.23"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + + let tag = ExtXStart::with_precise(SignedDecimalFloatingPoint(1.23), YesOrNo::Yes); + let text = "#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } +} diff --git a/src/tag/mod.rs b/src/tag/mod.rs index 415945c..e879e0e 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -5,8 +5,6 @@ use std::fmt; use std::str::FromStr; use {Error, ErrorKind, Result}; -use attribute::{AttributePairs, SignedDecimalFloatingPoint}; -use types::YesOrNo; macro_rules! may_invalid { ($expr:expr) => { @@ -27,6 +25,7 @@ macro_rules! impl_from { pub use self::basic::{ExtM3u, ExtXVersion}; pub use self::master_playlist::{ExtXIFrameStreamInf, ExtXMedia, ExtXSessionData, ExtXSessionKey, ExtXStreamInf}; +pub use self::media_or_master_playlist::{ExtXIndependentSegments, ExtXStart}; pub use self::media_playlist::{ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXMediaSequence, ExtXPlaylistType, ExtXTargetDuration}; pub use self::media_segment::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, @@ -34,6 +33,7 @@ pub use self::media_segment::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDisconti mod basic; mod master_playlist; +mod media_or_master_playlist; mod media_playlist; mod media_segment; @@ -229,70 +229,3 @@ impl FromStr for Tag { } } } - -// 4.3.5. Media or Master Playlist Tags -// TODO: A tag that appears in both MUST have the same value; otherwise, clients SHOULD ignore the value in the Media Playlist(s). -// TODO: These tags MUST NOT appear more than once in a Playlist. - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXIndependentSegments; -impl ExtXIndependentSegments { - const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS"; -} -impl fmt::Display for ExtXIndependentSegments { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Self::PREFIX.fmt(f) - } -} -impl FromStr for ExtXIndependentSegments { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); - Ok(ExtXIndependentSegments) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXStart { - pub time_offset: SignedDecimalFloatingPoint, - pub precise: YesOrNo, -} -impl ExtXStart { - const PREFIX: &'static str = "#EXT-X-START:"; -} -impl fmt::Display for ExtXStart { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", Self::PREFIX)?; - write!(f, "TIME-OFFSET={}", self.time_offset)?; - if self.precise == YesOrNo::Yes { - write!(f, ",PRECISE={}", self.precise)?; - } - Ok(()) - } -} -impl FromStr for ExtXStart { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - - let mut time_offset = None; - let mut precise = None; - let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); - for attr in attrs { - let (key, value) = track!(attr)?; - match key { - "TIME-OFFSET" => time_offset = Some(track!(value.parse())?), - "PRECISE" => precise = Some(track!(value.parse())?), - _ => { - // [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName. - } - } - } - - let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput); - Ok(ExtXStart { - time_offset, - precise: precise.unwrap_or(YesOrNo::No), - }) - } -}