1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-22 15:21:01 +00:00

Refactor types module

This commit is contained in:
Takeru Ohta 2018-02-14 23:11:25 +09:00
parent 80537c3e43
commit 9f6d4e7ed7
5 changed files with 169 additions and 136 deletions

View file

@ -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<QuotedString>,
assoc_language: Option<QuotedString>,
name: QuotedString,
default: YesOrNo,
autoselect: YesOrNo,
forced: YesOrNo,
default: bool,
autoselect: bool,
forced: bool,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
@ -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<DecimalInteger>,
codecs: Option<QuotedString>,
@ -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);

View file

@ -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,
})
}
}

View file

@ -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<M3u8String>,
title: Option<SingleLineString>,
}
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<QuotedString>,
pub scte35_out: Option<QuotedString>,
pub scte35_in: Option<QuotedString>,
pub end_on_next: Option<Yes>,
pub end_on_next: bool,
pub client_attributes: BTreeMap<String, String>,
}
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);

View file

@ -229,3 +229,11 @@ impl FromStr for Tag {
}
}
}
fn parse_yes_or_no(s: &str) -> Result<bool> {
match s {
"YES" => Ok(true),
"NO" => Ok(false),
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
}
}

View file

@ -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<T: Into<String>>(s: T) -> Result<Self> {
// TODO: validate
Ok(M3u8String(s.into()))
}
pub unsafe fn new_unchecked<T: Into<String>>(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<str> for M3u8String {
impl AsRef<str> 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<Self> {
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<HexadecimalSequence>,
pub iv: Option<HexadecimalSequence>, // TODO: iv
pub key_format: Option<QuotedString>,
pub key_format_versions: Option<QuotedString>,
}
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<Self> {
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());
}
}