diff --git a/src/tag/media_playlist.rs b/src/tag/media_playlist.rs new file mode 100644 index 0000000..899e3a3 --- /dev/null +++ b/src/tag/media_playlist.rs @@ -0,0 +1,280 @@ +use std::fmt; +use std::str::FromStr; +use std::time::Duration; +use trackable::error::ErrorKindExt; + +use {Error, ErrorKind, Result}; +use types::{PlaylistType, ProtocolVersion}; + +/// [4.3.3.1. EXT-X-TARGETDURATION] +/// +/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXTargetDuration { + duration: Duration, +} +impl ExtXTargetDuration { + pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:"; + + /// Makes a new `ExtXTargetduration` tag. + /// + /// Note that the nanoseconds part of the `duration` will be discarded. + pub fn new(duration: Duration) -> Self { + let duration = Duration::from_secs(duration.as_secs()); + ExtXTargetDuration { duration } + } + + /// Returns the maximum media segment duration in the associated playlist. + pub fn duration(&self) -> Duration { + self.duration + } + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXTargetDuration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", Self::PREFIX, self.duration.as_secs()) + } +} +impl FromStr for ExtXTargetDuration { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + let duration = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; + Ok(ExtXTargetDuration { + duration: Duration::from_secs(duration), + }) + } +} + +/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE] +/// +/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.2 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXMediaSequence { + seq_num: u64, +} +impl ExtXMediaSequence { + pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:"; + + /// Makes a new `ExtXMediaSequence` tag. + pub fn new(seq_num: u64) -> Self { + ExtXMediaSequence { seq_num } + } + + /// Returns the sequence number of the first media segment that appears in the associated playlist. + pub fn seq_num(&self) -> u64 { + self.seq_num + } + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXMediaSequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", Self::PREFIX, self.seq_num) + } +} +impl FromStr for ExtXMediaSequence { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; + Ok(ExtXMediaSequence { seq_num }) + } +} + +/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE] +/// +/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.3 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXDiscontinuitySequence { + seq_num: u64, +} +impl ExtXDiscontinuitySequence { + pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY-SEQUENCE:"; + + /// Makes a new `ExtXDiscontinuitySequence` tag. + pub fn new(seq_num: u64) -> Self { + ExtXDiscontinuitySequence { seq_num } + } + + /// Returns the discontinuity sequence number of + /// the first media segment that appears in the associated playlist. + pub fn seq_num(&self) -> u64 { + self.seq_num + } + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXDiscontinuitySequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", Self::PREFIX, self.seq_num) + } +} +impl FromStr for ExtXDiscontinuitySequence { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; + Ok(ExtXDiscontinuitySequence { seq_num }) + } +} + +/// [4.3.3.4. EXT-X-ENDLIST] +/// +/// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXEndList; +impl ExtXEndList { + pub(crate) const PREFIX: &'static str = "#EXT-X-ENDLIST"; + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXEndList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Self::PREFIX.fmt(f) + } +} +impl FromStr for ExtXEndList { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); + Ok(ExtXEndList) + } +} + +/// [4.3.3.5. EXT-X-PLAYLIST-TYPE] +/// +/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXPlaylistType { + playlist_type: PlaylistType, +} +impl ExtXPlaylistType { + pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:"; + + /// Makes a new `ExtXPlaylistType` tag. + pub fn new(playlist_type: PlaylistType) -> Self { + ExtXPlaylistType { playlist_type } + } + + /// Returns the type of the associated media playlist. + pub fn playlist_type(&self) -> PlaylistType { + self.playlist_type + } + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V1 + } +} +impl fmt::Display for ExtXPlaylistType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", Self::PREFIX, self.playlist_type) + } +} +impl FromStr for ExtXPlaylistType { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + let playlist_type = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; + Ok(ExtXPlaylistType { playlist_type }) + } +} + +/// [4.3.3.6. EXT-X-I-FRAMES-ONLY] +/// +/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExtXIFramesOnly; +impl ExtXIFramesOnly { + pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY"; + + /// Returns the protocol compatibility version that this tag requires. + pub fn requires_version(&self) -> ProtocolVersion { + ProtocolVersion::V4 + } +} +impl fmt::Display for ExtXIFramesOnly { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Self::PREFIX.fmt(f) + } +} +impl FromStr for ExtXIFramesOnly { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); + Ok(ExtXIFramesOnly) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn ext_x_targetduration() { + let tag = ExtXTargetDuration::new(Duration::from_secs(5)); + let text = "#EXT-X-TARGETDURATION:5"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_x_media_sequence() { + let tag = ExtXMediaSequence::new(123); + let text = "#EXT-X-MEDIA-SEQUENCE:123"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_x_discontinuity_sequence() { + let tag = ExtXDiscontinuitySequence::new(123); + let text = "#EXT-X-DISCONTINUITY-SEQUENCE:123"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_x_endlist() { + let tag = ExtXEndList; + let text = "#EXT-X-ENDLIST"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_x_playlist_type() { + let tag = ExtXPlaylistType::new(PlaylistType::Vod); + let text = "#EXT-X-PLAYLIST-TYPE:VOD"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V1); + } + + #[test] + fn ext_i_frames_only() { + let tag = ExtXIFramesOnly; + let text = "#EXT-X-I-FRAMES-ONLY"; + assert_eq!(text.parse().ok(), Some(tag)); + assert_eq!(tag.to_string(), text); + assert_eq!(tag.requires_version(), ProtocolVersion::V4); + } +} diff --git a/src/tag/mod.rs b/src/tag/mod.rs index 57f0028..a80643d 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -3,8 +3,6 @@ //! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3 use std::fmt; use std::str::FromStr; -use std::time::Duration; -use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution, @@ -27,10 +25,13 @@ macro_rules! impl_from { } pub use self::basic::{ExtM3u, ExtXVersion}; +pub use self::media_playlist::{ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, + ExtXMediaSequence, ExtXPlaylistType, ExtXTargetDuration}; pub use self::media_segment::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime}; mod basic; +mod media_playlist; mod media_segment; #[allow(missing_docs)] @@ -118,6 +119,7 @@ impl_from!(MediaSegmentTag, ExtXKey); impl_from!(MediaSegmentTag, ExtXMap); impl_from!(MediaSegmentTag, ExtXProgramDateTime); +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Tag { ExtM3u(ExtM3u), @@ -225,158 +227,6 @@ impl FromStr for Tag { } } -// TODO: he EXT-X-TARGETDURATION tag is REQUIRED. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXTargetDuration { - pub duration: Duration, -} -impl ExtXTargetDuration { - const PREFIX: &'static str = "#EXT-X-TARGETDURATION:"; -} -impl fmt::Display for ExtXTargetDuration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.duration.as_secs()) - } -} -impl FromStr for ExtXTargetDuration { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let duration = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXTargetDuration { - duration: Duration::from_secs(duration), - }) - } -} - -// TODO: The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media Segment in the Playlist. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXMediaSequence { - pub seq_num: u64, -} -impl ExtXMediaSequence { - const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:"; -} -impl fmt::Display for ExtXMediaSequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.seq_num) - } -} -impl FromStr for ExtXMediaSequence { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXMediaSequence { seq_num }) - } -} - -// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first Media Segment in the Playlist. -// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-X-DISCONTINUITY tag. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXDiscontinuitySequence { - pub seq_num: u64, -} -impl ExtXDiscontinuitySequence { - const PREFIX: &'static str = "#EXT-X-DISCONTINUITY-SEQUENCE:"; -} -impl fmt::Display for ExtXDiscontinuitySequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.seq_num) - } -} -impl FromStr for ExtXDiscontinuitySequence { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXDiscontinuitySequence { seq_num }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXEndList; -impl ExtXEndList { - const PREFIX: &'static str = "#EXT-X-ENDLIST"; -} -impl fmt::Display for ExtXEndList { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Self::PREFIX.fmt(f) - } -} -impl FromStr for ExtXEndList { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); - Ok(ExtXEndList) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXPlaylistType { - pub playlist_type: PlaylistType, -} -impl ExtXPlaylistType { - const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:"; -} -impl fmt::Display for ExtXPlaylistType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.playlist_type) - } -} -impl FromStr for ExtXPlaylistType { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let playlist_type = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXPlaylistType { playlist_type }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PlaylistType { - Event, - Vod, -} -impl fmt::Display for PlaylistType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - PlaylistType::Event => write!(f, "EVENT"), - PlaylistType::Vod => write!(f, "VOD"), - } - } -} -impl FromStr for PlaylistType { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "EVENT" => Ok(PlaylistType::Event), - "VOD" => Ok(PlaylistType::Vod), - _ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s), - } - } -} - -// TODO: Media resources containing I-frame segments MUST begin with ... -// TODO: Use of the EXT-X-I-FRAMES-ONLY REQUIRES a compatibility version number of 4 or greater. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtXIFramesOnly; -impl ExtXIFramesOnly { - const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY"; -} -impl fmt::Display for ExtXIFramesOnly { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Self::PREFIX.fmt(f) - } -} -impl FromStr for ExtXIFramesOnly { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); - Ok(ExtXIFramesOnly) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MediaType { Audio, diff --git a/src/types.rs b/src/types.rs index 08b4ac3..70f05bd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -237,3 +237,27 @@ impl FromStr for EncryptionMethod { } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PlaylistType { + Event, + Vod, +} +impl fmt::Display for PlaylistType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + PlaylistType::Event => write!(f, "EVENT"), + PlaylistType::Vod => write!(f, "VOD"), + } + } +} +impl FromStr for PlaylistType { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "EVENT" => Ok(PlaylistType::Event), + "VOD" => Ok(PlaylistType::Vod), + _ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s), + } + } +}