From 9f6d4e7ed7df45d21af7e0dca62980a0c8da3f7c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 14 Feb 2018 23:11:25 +0900 Subject: [PATCH] Refactor `types` module --- src/tag/master_playlist.rs | 57 ++++----- src/tag/media_or_master_playlist.rs | 21 ++-- src/tag/media_segment.rs | 42 ++++--- src/tag/mod.rs | 8 ++ src/types.rs | 177 +++++++++++++++------------- 5 files changed, 169 insertions(+), 136 deletions(-) diff --git a/src/tag/master_playlist.rs b/src/tag/master_playlist.rs index f22cd76..4f55e29 100644 --- a/src/tag/master_playlist.rs +++ b/src/tag/master_playlist.rs @@ -4,8 +4,9 @@ use std::str::FromStr; use {Error, ErrorKind, Result}; use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution, QuotedString}; -use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, M3u8String, MediaType, - ProtocolVersion, SessionData, YesOrNo}; +use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, MediaType, ProtocolVersion, + SessionData, SingleLineString}; +use super::parse_yes_or_no; /// [4.3.4.1. EXT-X-MEDIA] /// @@ -20,9 +21,9 @@ pub struct ExtXMedia { language: Option, assoc_language: Option, name: QuotedString, - default: YesOrNo, - autoselect: YesOrNo, - forced: YesOrNo, + default: bool, + autoselect: bool, + forced: bool, instream_id: Option, characteristics: Option, channels: Option, @@ -39,9 +40,9 @@ impl ExtXMedia { language: None, assoc_language: None, name, - default: YesOrNo::No, - autoselect: YesOrNo::No, - forced: YesOrNo::No, + default: false, + autoselect: false, + forced: false, instream_id: None, characteristics: None, channels: None, @@ -79,18 +80,18 @@ impl ExtXMedia { } /// Returns whether this is the default rendition. - pub fn default(&self) -> YesOrNo { + pub fn default(&self) -> bool { self.default } /// Returns whether the client may choose to /// play this rendition in the absence of explicit user preference. - pub fn autoselect(&self) -> YesOrNo { + pub fn autoselect(&self) -> bool { self.autoselect } /// Returns whether the rendition contains content that is considered essential to play. - pub fn forced(&self) -> YesOrNo { + pub fn forced(&self) -> bool { self.forced } @@ -138,13 +139,13 @@ impl fmt::Display for ExtXMedia { write!(f, ",ASSOC-LANGUAGE={}", x)?; } write!(f, ",NAME={}", self.name)?; - if YesOrNo::Yes == self.default { + if self.default { write!(f, ",DEFAULT=YES")?; } - if YesOrNo::Yes == self.autoselect { + if self.autoselect { write!(f, ",AUTOSELECT=YES")?; } - if YesOrNo::Yes == self.forced { + if self.forced { write!(f, ",FORCED=YES")?; } if let Some(ref x) = self.instream_id { @@ -170,7 +171,7 @@ impl FromStr for ExtXMedia { let mut language = None; let mut assoc_language = None; let mut name = None; - let mut default = None; + let mut default = false; let mut autoselect = None; let mut forced = None; let mut instream_id = None; @@ -186,9 +187,9 @@ impl FromStr for ExtXMedia { "LANGUAGE" => language = Some(track!(value.parse())?), "ASSOC-LANGUAGE" => assoc_language = Some(track!(value.parse())?), "NAME" => name = Some(track!(value.parse())?), - "DEFAULT" => default = Some(track!(value.parse())?), - "AUTOSELECT" => autoselect = Some(track!(value.parse())?), - "FORCED" => forced = Some(track!(value.parse())?), + "DEFAULT" => default = track!(parse_yes_or_no(value))?, + "AUTOSELECT" => autoselect = Some(track!(parse_yes_or_no(value))?), + "FORCED" => forced = Some(track!(parse_yes_or_no(value))?), "INSTREAM-ID" => { let s: QuotedString = track!(value.parse())?; instream_id = Some(track!(s.as_str().parse())?); @@ -210,8 +211,8 @@ impl FromStr for ExtXMedia { } else { track_assert!(instream_id.is_none(), ErrorKind::InvalidInput); } - if default == Some(YesOrNo::Yes) && autoselect.is_some() { - track_assert_eq!(autoselect, Some(YesOrNo::Yes), ErrorKind::InvalidInput); + if default && autoselect.is_some() { + track_assert_eq!(autoselect, Some(true), ErrorKind::InvalidInput); } if MediaType::Subtitles != media_type { track_assert_eq!(forced, None, ErrorKind::InvalidInput); @@ -223,9 +224,9 @@ impl FromStr for ExtXMedia { language, assoc_language, name, - default: default.unwrap_or(YesOrNo::No), - autoselect: autoselect.unwrap_or(YesOrNo::No), - forced: forced.unwrap_or(YesOrNo::No), + default, + autoselect: autoselect.unwrap_or(false), + forced: forced.unwrap_or(false), instream_id, characteristics, channels, @@ -238,7 +239,7 @@ impl FromStr for ExtXMedia { /// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExtXStreamInf { - uri: M3u8String, + uri: SingleLineString, bandwidth: DecimalInteger, average_bandwidth: Option, codecs: Option, @@ -254,7 +255,7 @@ impl ExtXStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; /// Makes a new `ExtXStreamInf` tag. - pub fn new(uri: M3u8String, bandwidth: DecimalInteger) -> Self { + pub fn new(uri: SingleLineString, bandwidth: DecimalInteger) -> Self { ExtXStreamInf { uri, bandwidth, @@ -271,7 +272,7 @@ impl ExtXStreamInf { } /// Returns the URI that identifies the associated media playlist. - pub fn uri(&self) -> &M3u8String { + pub fn uri(&self) -> &SingleLineString { &self.uri } @@ -376,7 +377,7 @@ impl FromStr for ExtXStreamInf { first_line.starts_with(Self::PREFIX), ErrorKind::InvalidInput ); - let uri = track!(M3u8String::new(second_line))?; + let uri = track!(SingleLineString::new(second_line))?; let mut bandwidth = None; let mut average_bandwidth = None; let mut codecs = None; @@ -722,7 +723,7 @@ mod test { #[test] fn ext_x_stream_inf() { - let tag = ExtXStreamInf::new(M3u8String::new("foo").unwrap(), DecimalInteger(1000)); + let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), DecimalInteger(1000)); let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo"; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); diff --git a/src/tag/media_or_master_playlist.rs b/src/tag/media_or_master_playlist.rs index 7c173bb..d3b1be6 100644 --- a/src/tag/media_or_master_playlist.rs +++ b/src/tag/media_or_master_playlist.rs @@ -3,7 +3,8 @@ use std::str::FromStr; use {Error, ErrorKind, Result}; use attribute::{AttributePairs, SignedDecimalFloatingPoint}; -use types::{ProtocolVersion, YesOrNo}; +use types::ProtocolVersion; +use super::parse_yes_or_no; /// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] /// @@ -37,7 +38,7 @@ impl FromStr for ExtXIndependentSegments { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExtXStart { time_offset: SignedDecimalFloatingPoint, - precise: YesOrNo, + precise: bool, } impl ExtXStart { pub(crate) const PREFIX: &'static str = "#EXT-X-START:"; @@ -46,12 +47,12 @@ impl ExtXStart { pub fn new(time_offset: SignedDecimalFloatingPoint) -> Self { ExtXStart { time_offset, - precise: YesOrNo::No, + precise: false, } } /// Makes a new `ExtXStart` tag with the given `precise` flag. - pub fn with_precise(time_offset: SignedDecimalFloatingPoint, precise: YesOrNo) -> Self { + pub fn with_precise(time_offset: SignedDecimalFloatingPoint, precise: bool) -> Self { ExtXStart { time_offset, precise, @@ -65,7 +66,7 @@ impl ExtXStart { /// Returns whether clients should not render media stream whose presentation times are /// prior to the specified time offset. - pub fn precise(&self) -> YesOrNo { + pub fn precise(&self) -> bool { self.precise } @@ -78,8 +79,8 @@ 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)?; + if self.precise { + write!(f, ",PRECISE=YES")?; } Ok(()) } @@ -90,13 +91,13 @@ impl FromStr for ExtXStart { track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let mut time_offset = None; - let mut precise = None; + let mut precise = false; 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())?), + "PRECISE" => precise = track!(parse_yes_or_no(value))?, _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. @@ -107,7 +108,7 @@ impl FromStr for ExtXStart { let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput); Ok(ExtXStart { time_offset, - precise: precise.unwrap_or(YesOrNo::No), + precise, }) } } diff --git a/src/tag/media_segment.rs b/src/tag/media_segment.rs index 44070cb..8799d1c 100644 --- a/src/tag/media_segment.rs +++ b/src/tag/media_segment.rs @@ -7,7 +7,7 @@ use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString}; -use types::{ByteRange, DecryptionKey, M3u8String, ProtocolVersion, Yes}; +use types::{ByteRange, DecryptionKey, ProtocolVersion, SingleLineString}; /// [4.3.2.1. EXTINF] /// @@ -15,7 +15,7 @@ use types::{ByteRange, DecryptionKey, M3u8String, ProtocolVersion, Yes}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtInf { duration: Duration, - title: Option, + title: Option, } impl ExtInf { pub(crate) const PREFIX: &'static str = "#EXTINF:"; @@ -29,7 +29,7 @@ impl ExtInf { } /// Makes a new `ExtInf` tag with the given title. - pub fn with_title(duration: Duration, title: M3u8String) -> Self { + pub fn with_title(duration: Duration, title: SingleLineString) -> Self { ExtInf { duration, title: Some(title), @@ -42,7 +42,7 @@ impl ExtInf { } /// Returns the title of the associated media segment. - pub fn title(&self) -> Option<&M3u8String> { + pub fn title(&self) -> Option<&SingleLineString> { self.title.as_ref() } @@ -80,7 +80,7 @@ impl FromStr for ExtInf { let duration = seconds.to_duration(); let title = if let Some(title) = tokens.next() { - Some(track!(M3u8String::new(title))?) + Some(track!(SingleLineString::new(title))?) } else { None }; @@ -355,7 +355,7 @@ pub struct ExtXDateRange { pub scte35_cmd: Option, pub scte35_out: Option, pub scte35_in: Option, - pub end_on_next: Option, + pub end_on_next: bool, pub client_attributes: BTreeMap, } impl ExtXDateRange { @@ -375,11 +375,11 @@ impl fmt::Display for ExtXDateRange { } write!( f, - ",START_DATE={:?}", + ",START-DATE={:?}", self.start_date.format("%Y-%m-%d").to_string() )?; if let Some(ref x) = self.end_date { - write!(f, ",END_DATE={:?}", x.format("%Y-%m-%d").to_string())?; + write!(f, ",END-DATE={:?}", x.format("%Y-%m-%d").to_string())?; } if let Some(x) = self.duration { write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?; @@ -387,21 +387,21 @@ impl fmt::Display for ExtXDateRange { if let Some(x) = self.planned_duration { write!( f, - ",PLANNED_DURATION={}", + ",PLANNED-DURATION={}", DecimalFloatingPoint::from_duration(x) )?; } if let Some(ref x) = self.scte35_cmd { - write!(f, ",SCTE35_CMD={}", x)?; + write!(f, ",SCTE35-CMD={}", x)?; } if let Some(ref x) = self.scte35_out { - write!(f, ",SCTE35_OUT={}", x)?; + write!(f, ",SCTE35-OUT={}", x)?; } if let Some(ref x) = self.scte35_in { - write!(f, ",SCTE35_IN={}", x)?; + write!(f, ",SCTE35-IN={}", x)?; } - if let Some(ref x) = self.end_on_next { - write!(f, ",END_ON_NEXT={}", x)?; + if self.end_on_next { + write!(f, ",END-ON-NEXT=YES",)?; } for (k, v) in &self.client_attributes { write!(f, ",{}={}", k, v)?; @@ -423,7 +423,7 @@ impl FromStr for ExtXDateRange { let mut scte35_cmd = None; let mut scte35_out = None; let mut scte35_in = None; - let mut end_on_next = None; + let mut end_on_next = false; let mut client_attributes = BTreeMap::new(); let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); for attr in attrs { @@ -456,7 +456,10 @@ impl FromStr for ExtXDateRange { "SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?), "SCTE35-OUT" => scte35_out = Some(track!(value.parse())?), "SCTE35-IN" => scte35_in = Some(track!(value.parse())?), - "END-ON-NEXT" => end_on_next = Some(track!(value.parse())?), + "END-ON-NEXT" => { + track_assert_eq!(value, "YES", ErrorKind::InvalidInput); + end_on_next = true; + } _ => { if key.starts_with("X-") { client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned()); @@ -470,7 +473,7 @@ impl FromStr for ExtXDateRange { let id = track_assert_some!(id, ErrorKind::InvalidInput); let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput); - if end_on_next.is_some() { + if end_on_next { track_assert!(class.is_some(), ErrorKind::InvalidInput); } Ok(ExtXDateRange { @@ -504,7 +507,10 @@ mod test { assert_eq!(tag.to_string(), "#EXTINF:5"); assert_eq!(tag.requires_version(), ProtocolVersion::V1); - let tag = ExtInf::with_title(Duration::from_secs(5), M3u8String::new("foo").unwrap()); + let tag = ExtInf::with_title( + Duration::from_secs(5), + SingleLineString::new("foo").unwrap(), + ); assert_eq!("#EXTINF:5,foo".parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), "#EXTINF:5,foo"); assert_eq!(tag.requires_version(), ProtocolVersion::V1); diff --git a/src/tag/mod.rs b/src/tag/mod.rs index e879e0e..4425066 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -229,3 +229,11 @@ impl FromStr for Tag { } } } + +fn parse_yes_or_no(s: &str) -> Result { + match s { + "YES" => Ok(true), + "NO" => Ok(false), + _ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s), + } +} diff --git a/src/types.rs b/src/types.rs index 69ef857..76c4b22 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,51 +7,47 @@ use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; use attribute::{AttributePairs, HexadecimalSequence, QuotedString}; -// TODO: rename +/// String that represents a single line in a playlist file. +/// +/// See: [4.1. Definition of a Playlist] +/// +/// [4.1. Definition of a Playlist]: https://tools.ietf.org/html/rfc8216#section-4.1 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct M3u8String(String); -impl M3u8String { +pub struct SingleLineString(String); +impl SingleLineString { + /// Makes a new `SingleLineString` instance. + /// + /// # Errors + /// + /// If the given string contains any control characters, + /// this function will return an error which has the kind `ErrorKind::InvalidInput`. pub fn new>(s: T) -> Result { - // TODO: validate - Ok(M3u8String(s.into())) - } - pub unsafe fn new_unchecked>(s: T) -> Self { - M3u8String(s.into()) + let s = s.into(); + track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput); + Ok(SingleLineString(s)) } } -impl Deref for M3u8String { +impl Deref for SingleLineString { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } -impl AsRef for M3u8String { +impl AsRef for SingleLineString { fn as_ref(&self) -> &str { &self.0 } } -impl fmt::Display for M3u8String { +impl fmt::Display for SingleLineString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Yes; -impl fmt::Display for Yes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - "YES".fmt(f) - } -} -impl FromStr for Yes { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, "YES", ErrorKind::InvalidInput); - Ok(Yes) - } -} - -// https://tools.ietf.org/html/rfc8216#section-7 +/// [7. Protocol Version Compatibility] +/// +/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ProtocolVersion { V1, @@ -92,6 +88,12 @@ impl FromStr for ProtocolVersion { } } +/// Byte range. +/// +/// See: [4.3.2.2. EXT-X-BYTERANGE] +/// +/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ByteRange { pub length: usize, @@ -125,16 +127,22 @@ impl FromStr for ByteRange { } } +/// Decryption key. +/// +/// See: [4.3.2.4. EXT-X-KEY] +/// +/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DecryptionKey { pub method: EncryptionMethod, pub uri: QuotedString, - pub iv: Option, + pub iv: Option, // TODO: iv pub key_format: Option, pub key_format_versions: Option, } impl DecryptionKey { - pub fn requires_version(&self) -> ProtocolVersion { + pub(crate) fn requires_version(&self) -> ProtocolVersion { if self.key_format.is_some() | self.key_format_versions.is_some() { ProtocolVersion::V5 } else if self.iv.is_some() { @@ -172,29 +180,14 @@ impl FromStr for DecryptionKey { for attr in attrs { let (key, value) = track!(attr)?; match key { - "METHOD" => { - track_assert_eq!(method, None, ErrorKind::InvalidInput); - method = Some(track!(value.parse())?); - } - "URI" => { - track_assert_eq!(uri, None, ErrorKind::InvalidInput); - uri = Some(track!(value.parse())?); - } - "IV" => { - // TODO: validate length(128-bit) - track_assert_eq!(iv, None, ErrorKind::InvalidInput); - iv = Some(track!(value.parse())?); - } - "KEYFORMAT" => { - track_assert_eq!(key_format, None, ErrorKind::InvalidInput); - key_format = Some(track!(value.parse())?); - } - "KEYFORMATVERSIONS" => { - track_assert_eq!(key_format_versions, None, ErrorKind::InvalidInput); - key_format_versions = Some(track!(value.parse())?); - } + "METHOD" => method = Some(track!(value.parse())?), + "URI" => uri = Some(track!(value.parse())?), + "IV" => iv = Some(track!(value.parse())?), + "KEYFORMAT" => key_format = Some(track!(value.parse())?), + "KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?), _ => { - // [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName. + // [6.3.1. General Client Responsibilities] + // > ignore any attribute/value pair with an unrecognized AttributeName. } } } @@ -210,6 +203,12 @@ impl FromStr for DecryptionKey { } } +/// Encryption method. +/// +/// See: [4.3.2.4. EXT-X-KEY] +/// +/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EncryptionMethod { Aes128, @@ -238,6 +237,12 @@ impl FromStr for EncryptionMethod { } } +/// Playlist type. +/// +/// See: [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 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PlaylistType { Event, @@ -262,6 +267,12 @@ impl FromStr for PlaylistType { } } +/// Media type. +/// +/// See: [4.3.4.1. EXT-X-MEDIA] +/// +/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MediaType { Audio, @@ -292,35 +303,12 @@ impl FromStr for MediaType { } } -// TODO: remove -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum YesOrNo { - Yes, - No, -} -impl fmt::Display for YesOrNo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - YesOrNo::Yes => "YES".fmt(f), - YesOrNo::No => "NO".fmt(f), - } - } -} -impl FromStr for YesOrNo { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "YES" => Ok(YesOrNo::Yes), - "NO" => Ok(YesOrNo::No), - _ => track_panic!( - ErrorKind::InvalidInput, - "Unexpected enumerated-string: {:?}", - s - ), - } - } -} - +/// Identifier of a rendition within the segments in a media playlist. +/// +/// See: [4.3.4.1. EXT-X-MEDIA] +/// +/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum InStreamId { Cc1, @@ -472,6 +460,12 @@ impl FromStr for InStreamId { } } +/// HDCP level. +/// +/// See: [4.3.4.2. EXT-X-STREAM-INF] +/// +/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum HdcpLevel { Type0, @@ -496,6 +490,12 @@ impl FromStr for HdcpLevel { } } +/// The identifier of a closed captions group or its absence. +/// +/// See: [4.3.4.2. EXT-X-STREAM-INF] +/// +/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ClosedCaptions { GroupId(QuotedString), @@ -520,8 +520,25 @@ impl FromStr for ClosedCaptions { } } +/// Session data. +/// +/// See: [4.3.4.4. EXT-X-SESSION-DATA] +/// +/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SessionData { Value(QuotedString), Uri(QuotedString), } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn single_line_string() { + assert!(SingleLineString::new("foo").is_ok()); + assert!(SingleLineString::new("b\rar").is_err()); + } +}