mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 00:20:59 +00:00
added more tests
This commit is contained in:
parent
b954ae1134
commit
3acf67df6a
10 changed files with 365 additions and 138 deletions
|
@ -7,7 +7,7 @@ use failure::{Backtrace, Context, Fail};
|
|||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// The ErrorKind.
|
||||
#[derive(Debug, Fail, Clone)]
|
||||
#[derive(Debug, Fail, Clone, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
#[fail(display = "UnknownError: {}", _0)]
|
||||
/// An unknown error occured.
|
||||
|
|
|
@ -43,6 +43,7 @@ impl FromStr for Lines {
|
|||
} else if line.starts_with("#") {
|
||||
continue; // ignore comments
|
||||
} else {
|
||||
// stream inf line needs special treatment
|
||||
if stream_inf {
|
||||
stream_inf = false;
|
||||
if let Some(first_line) = stream_inf_line {
|
||||
|
|
|
@ -319,6 +319,7 @@ fn parse_media_playlist(
|
|||
|
||||
let mut has_partial_segment = false;
|
||||
let mut has_discontinuity_tag = false;
|
||||
let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1
|
||||
|
||||
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
|
||||
match line {
|
||||
|
@ -333,6 +334,7 @@ fn parse_media_playlist(
|
|||
Tag::ExtM3u(_) => return Err(Error::invalid_input()),
|
||||
Tag::ExtXVersion(t) => {
|
||||
builder.version(t.version());
|
||||
has_version = true;
|
||||
}
|
||||
Tag::ExtInf(t) => {
|
||||
has_partial_segment = true;
|
||||
|
@ -417,6 +419,9 @@ fn parse_media_playlist(
|
|||
if has_partial_segment {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
if !has_version {
|
||||
builder.version(ProtocolVersion::V1);
|
||||
}
|
||||
|
||||
builder.segments(segments);
|
||||
builder.build().map_err(Error::builder_error)
|
||||
|
@ -436,36 +441,37 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn too_large_segment_duration_test() {
|
||||
let m3u8 = "#EXTM3U\n\
|
||||
#EXT-X-TARGETDURATION:8\n\
|
||||
#EXT-X-VERSION:3\n\
|
||||
#EXTINF:9.009,\n\
|
||||
http://media.example.com/first.ts\n\
|
||||
#EXTINF:9.509,\n\
|
||||
http://media.example.com/second.ts\n\
|
||||
#EXTINF:3.003,\n\
|
||||
http://media.example.com/third.ts\n\
|
||||
#EXT-X-ENDLIST";
|
||||
let playlist = r#"
|
||||
#EXTM3U
|
||||
#EXT-X-TARGETDURATION:8
|
||||
#EXT-X-VERSION:3
|
||||
#EXTINF:9.009,
|
||||
http://media.example.com/first.ts
|
||||
#EXTINF:9.509,
|
||||
http://media.example.com/second.ts
|
||||
#EXTINF:3.003,
|
||||
http://media.example.com/third.ts
|
||||
#EXT-X-ENDLIST"#;
|
||||
|
||||
// Error (allowable segment duration = target duration = 8)
|
||||
assert!(m3u8.parse::<MediaPlaylist>().is_err());
|
||||
assert!(playlist.parse::<MediaPlaylist>().is_err());
|
||||
|
||||
// Error (allowable segment duration = 9)
|
||||
assert!(MediaPlaylist::builder()
|
||||
.allowable_excess_duration(Duration::from_secs(1))
|
||||
.parse(m3u8)
|
||||
.parse(playlist)
|
||||
.is_err());
|
||||
|
||||
// Ok (allowable segment duration = 10)
|
||||
MediaPlaylist::builder()
|
||||
.allowable_excess_duration(Duration::from_secs(2))
|
||||
.parse(m3u8)
|
||||
.parse(playlist)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let m3u8 = "";
|
||||
assert!(m3u8.parse::<MediaPlaylist>().is_err());
|
||||
fn test_empty_playlist() {
|
||||
let playlist = "";
|
||||
assert!(playlist.parse::<MediaPlaylist>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,49 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{PlaylistType, ProtocolVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
|
||||
/// [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)
|
||||
///
|
||||
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
|
||||
/// The EXT-X-PLAYLIST-TYPE tag provides mutability information about the
|
||||
/// Media Playlist. It applies to the entire Media Playlist.
|
||||
/// It is OPTIONAL. Its format is:
|
||||
///
|
||||
/// ```text
|
||||
/// #EXT-X-PLAYLIST-TYPE:<type-enum>
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// If the EXT-X-PLAYLIST-TYPE tag is omitted from a Media Playlist, the
|
||||
/// Playlist can be updated according to the rules in Section 6.2.1 with
|
||||
/// no additional restrictions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXPlaylistType {
|
||||
playlist_type: PlaylistType,
|
||||
pub enum ExtXPlaylistType {
|
||||
/// If the ExtXPlaylistType is Event, Media Segments can only be added to
|
||||
/// the end of the Media Playlist.
|
||||
Event,
|
||||
/// If the ExtXPlaylistType is Video On Demand (Vod),
|
||||
/// the Media Playlist cannot change.
|
||||
Vod,
|
||||
}
|
||||
|
||||
impl ExtXPlaylistType {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:";
|
||||
|
||||
/// Makes a new `ExtXPlaylistType` tag.
|
||||
pub const fn new(playlist_type: PlaylistType) -> Self {
|
||||
ExtXPlaylistType { playlist_type }
|
||||
}
|
||||
|
||||
/// Returns the type of the associated media playlist.
|
||||
pub const fn playlist_type(self) -> PlaylistType {
|
||||
self.playlist_type
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub const fn requires_version(self) -> ProtocolVersion {
|
||||
pub const 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)
|
||||
match &self {
|
||||
Self::Event => write!(f, "{}EVENT", Self::PREFIX),
|
||||
Self::Vod => write!(f, "{}VOD", Self::PREFIX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,9 +51,12 @@ impl FromStr for ExtXPlaylistType {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?.parse()?;
|
||||
|
||||
Ok(ExtXPlaylistType::new(input))
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
match input {
|
||||
"EVENT" => Ok(Self::Event),
|
||||
"VOD" => Ok(Self::Vod),
|
||||
_ => Err(Error::custom(format!("Unknown playlist type: {:?}", input))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,11 +65,48 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
#[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);
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
"#EXT-X-PLAYLIST-TYPE:VOD"
|
||||
.parse::<ExtXPlaylistType>()
|
||||
.unwrap(),
|
||||
ExtXPlaylistType::Vod,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"#EXT-X-PLAYLIST-TYPE:EVENT"
|
||||
.parse::<ExtXPlaylistType>()
|
||||
.unwrap(),
|
||||
ExtXPlaylistType::Event,
|
||||
);
|
||||
|
||||
assert!("#EXT-X-PLAYLIST-TYPE:H"
|
||||
.parse::<ExtXPlaylistType>()
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
"#EXT-X-PLAYLIST-TYPE:VOD".to_string(),
|
||||
ExtXPlaylistType::Vod.to_string(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"#EXT-X-PLAYLIST-TYPE:EVENT".to_string(),
|
||||
ExtXPlaylistType::Event.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requires_version() {
|
||||
assert_eq!(
|
||||
ExtXPlaylistType::Vod.requires_version(),
|
||||
ProtocolVersion::V1
|
||||
);
|
||||
assert_eq!(
|
||||
ExtXPlaylistType::Event.requires_version(),
|
||||
ProtocolVersion::V1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,46 @@ use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString};
|
|||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
|
||||
/// [4.3.2.1. EXTINF]
|
||||
/// [4.3.2.1. EXTINF](https://tools.ietf.org/html/rfc8216#section-4.3.2.1)
|
||||
///
|
||||
/// [4.3.2.1. EXTINF]: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// The [ExtInf] tag specifies the duration of a [Media Segment]. It applies
|
||||
/// only to the next [Media Segment]. This tag is REQUIRED for each [Media Segment].
|
||||
///
|
||||
/// Its format is:
|
||||
/// ```text
|
||||
/// #EXTINF:<duration>,[<title>]
|
||||
/// ```
|
||||
/// The title is an optional informative title about the [Media Segment].
|
||||
///
|
||||
/// [Media Segment]: crate::media_segment::MediaSegment
|
||||
///
|
||||
/// # Examples
|
||||
/// Parsing from a String:
|
||||
/// ```
|
||||
/// use std::time::Duration;
|
||||
/// use hls_m3u8::tags::ExtInf;
|
||||
///
|
||||
/// let ext_inf = "#EXTINF:8,".parse::<ExtInf>().expect("Failed to parse tag!");
|
||||
///
|
||||
/// assert_eq!(ext_inf.duration(), Duration::from_secs(8));
|
||||
/// assert_eq!(ext_inf.title(), None);
|
||||
/// ```
|
||||
///
|
||||
/// Converting to a String:
|
||||
/// ```
|
||||
/// use std::time::Duration;
|
||||
/// use hls_m3u8::tags::ExtInf;
|
||||
/// use hls_m3u8::types::SingleLineString;
|
||||
///
|
||||
/// let ext_inf = ExtInf::with_title(
|
||||
/// Duration::from_millis(88),
|
||||
/// SingleLineString::new("title").unwrap()
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(ext_inf.duration(), Duration::from_millis(88));
|
||||
/// assert_eq!(ext_inf.to_string(), "#EXTINF:0.088,title".to_string());
|
||||
/// ```
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ExtInf {
|
||||
duration: Duration,
|
||||
title: Option<SingleLineString>,
|
||||
|
@ -60,10 +96,10 @@ impl fmt::Display for ExtInf {
|
|||
|
||||
let duration = (self.duration.as_secs() as f64)
|
||||
+ (f64::from(self.duration.subsec_nanos()) / 1_000_000_000.0);
|
||||
write!(f, "{}", duration)?;
|
||||
write!(f, "{},", duration)?;
|
||||
|
||||
if let Some(value) = &self.title {
|
||||
write!(f, ",{}", value)?;
|
||||
write!(f, "{}", value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -74,44 +110,129 @@ impl FromStr for ExtInf {
|
|||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
let mut tokens = input.splitn(2, ',');
|
||||
dbg!(&input);
|
||||
let tokens = input.splitn(2, ',').collect::<Vec<_>>();
|
||||
|
||||
let seconds: DecimalFloatingPoint = tokens.next().expect("Never fails").parse()?;
|
||||
let duration = seconds.to_duration();
|
||||
if tokens.len() == 0 {
|
||||
return Err(Error::custom(format!(
|
||||
"failed to parse #EXTINF tag, couldn't split input: {:?}",
|
||||
input
|
||||
)));
|
||||
}
|
||||
|
||||
let duration = tokens[0].parse::<DecimalFloatingPoint>()?.to_duration();
|
||||
|
||||
let title = {
|
||||
if let Some(title) = tokens.next() {
|
||||
Some((SingleLineString::new(title))?)
|
||||
if tokens.len() >= 2 {
|
||||
if tokens[1].trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SingleLineString::new(tokens[1])?)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExtInf { duration, title })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for ExtInf {
|
||||
fn from(value: Duration) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extinf() {
|
||||
let tag = ExtInf::new(Duration::from_secs(5));
|
||||
assert_eq!("#EXTINF:5".parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), "#EXTINF:5");
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
|
||||
let tag = ExtInf::with_title(
|
||||
Duration::from_secs(5),
|
||||
SingleLineString::new("foo").unwrap(),
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
"#EXTINF:5,".to_string(),
|
||||
ExtInf::new(Duration::from_secs(5)).to_string()
|
||||
);
|
||||
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);
|
||||
assert_eq!(
|
||||
"#EXTINF:5.5,".to_string(),
|
||||
ExtInf::new(Duration::from_millis(5500)).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5.5,title".to_string(),
|
||||
ExtInf::with_title(
|
||||
Duration::from_millis(5500),
|
||||
SingleLineString::new("title").unwrap()
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5,title".to_string(),
|
||||
ExtInf::with_title(
|
||||
Duration::from_secs(5),
|
||||
SingleLineString::new("title").unwrap()
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let tag = ExtInf::new(Duration::from_millis(1234));
|
||||
assert_eq!("#EXTINF:1.234".parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), "#EXTINF:1.234");
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V3);
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
// #EXTINF:<duration>,[<title>]
|
||||
assert_eq!(
|
||||
"#EXTINF:5".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::new(Duration::from_secs(5))
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5,".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::new(Duration::from_secs(5))
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5.5".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::new(Duration::from_millis(5500))
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5.5,".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::new(Duration::from_millis(5500))
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5.5,title".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::with_title(
|
||||
Duration::from_millis(5500),
|
||||
SingleLineString::new("title").unwrap()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"#EXTINF:5,title".parse::<ExtInf>().unwrap(),
|
||||
ExtInf::with_title(
|
||||
Duration::from_secs(5),
|
||||
SingleLineString::new("title").unwrap()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_title() {
|
||||
assert_eq!(ExtInf::new(Duration::from_secs(5)).title(), None);
|
||||
assert_eq!(
|
||||
ExtInf::with_title(
|
||||
Duration::from_secs(5),
|
||||
SingleLineString::new("title").unwrap()
|
||||
)
|
||||
.title(),
|
||||
Some(&SingleLineString::new("title").unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requires_version() {
|
||||
assert_eq!(
|
||||
ExtInf::new(Duration::from_secs(4)).requires_version(),
|
||||
ProtocolVersion::V1
|
||||
);
|
||||
assert_eq!(
|
||||
ExtInf::new(Duration::from_millis(4400)).requires_version(),
|
||||
ProtocolVersion::V3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::fmt;
|
||||
use std::str::{self, FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
|
@ -10,11 +10,37 @@ use crate::Error;
|
|||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DecimalResolution {
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl DecimalResolution {
|
||||
/// Creates a new DecimalResolution.
|
||||
pub const fn new(width: usize, height: usize) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
/// Horizontal pixel dimension.
|
||||
pub width: usize,
|
||||
pub const fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
/// Sets Horizontal pixel dimension.
|
||||
pub fn set_width(&mut self, value: usize) -> &mut Self {
|
||||
self.width = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Vertical pixel dimension.
|
||||
pub height: usize,
|
||||
pub const fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// Sets Vertical pixel dimension.
|
||||
pub fn set_height(&mut self, value: usize) -> &mut Self {
|
||||
self.height = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DecimalResolution {
|
||||
|
@ -27,13 +53,21 @@ impl FromStr for DecimalResolution {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut tokens = input.splitn(2, 'x');
|
||||
let width = tokens.next().ok_or(Error::missing_value("width"))?;
|
||||
let height = tokens.next().ok_or(Error::missing_value("height"))?;
|
||||
let tokens = input.splitn(2, 'x').collect::<Vec<_>>();
|
||||
|
||||
if tokens.len() != 2 {
|
||||
return Err(Error::custom(format!(
|
||||
"InvalidInput: Expected input format: [width]x[height] (ex. 1920x1080), got {:?}",
|
||||
input,
|
||||
)));
|
||||
}
|
||||
|
||||
let width = tokens[0];
|
||||
let height = tokens[1];
|
||||
|
||||
Ok(DecimalResolution {
|
||||
width: width.parse().map_err(|e| Error::custom(e))?,
|
||||
height: height.parse().map_err(|e| Error::custom(e))?,
|
||||
width: width.parse()?,
|
||||
height: height.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -44,37 +78,44 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let decimal_resolution = DecimalResolution {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
};
|
||||
assert_eq!(decimal_resolution.to_string(), "1920x1080".to_string());
|
||||
assert_eq!(
|
||||
DecimalResolution::new(1920, 1080).to_string(),
|
||||
"1920x1080".to_string()
|
||||
);
|
||||
|
||||
let decimal_resolution = DecimalResolution {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
};
|
||||
assert_eq!(decimal_resolution.to_string(), "1280x720".to_string());
|
||||
assert_eq!(
|
||||
DecimalResolution::new(1280, 720).to_string(),
|
||||
"1280x720".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let decimal_resolution = DecimalResolution {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
};
|
||||
assert_eq!(
|
||||
decimal_resolution,
|
||||
DecimalResolution::new(1920, 1080),
|
||||
"1920x1080".parse::<DecimalResolution>().unwrap()
|
||||
);
|
||||
|
||||
let decimal_resolution = DecimalResolution {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
};
|
||||
assert_eq!(
|
||||
decimal_resolution,
|
||||
DecimalResolution::new(1280, 720),
|
||||
"1280x720".parse::<DecimalResolution>().unwrap()
|
||||
);
|
||||
|
||||
assert!("1280".parse::<DecimalResolution>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width() {
|
||||
assert_eq!(DecimalResolution::new(1920, 1080).width(), 1920);
|
||||
assert_eq!(DecimalResolution::new(1920, 1080).set_width(12).width(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_height() {
|
||||
assert_eq!(DecimalResolution::new(1920, 1080).height(), 1080);
|
||||
assert_eq!(
|
||||
DecimalResolution::new(1920, 1080).set_height(12).height(),
|
||||
12
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ mod hexadecimal_sequence;
|
|||
mod in_stream_id;
|
||||
mod initialization_vector;
|
||||
mod media_type;
|
||||
mod playlist_type;
|
||||
mod protocol_version;
|
||||
mod session_data;
|
||||
mod signed_decimal_floating_point;
|
||||
|
@ -27,7 +26,6 @@ pub use hexadecimal_sequence::*;
|
|||
pub use in_stream_id::*;
|
||||
pub use initialization_vector::*;
|
||||
pub use media_type::*;
|
||||
pub use playlist_type::*;
|
||||
pub use protocol_version::*;
|
||||
pub use session_data::*;
|
||||
pub use signed_decimal_floating_point::*;
|
||||
|
|
|
@ -1,37 +1 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// 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,
|
||||
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(input: &str) -> Result<Self, Self::Err> {
|
||||
match input {
|
||||
"EVENT" => Ok(PlaylistType::Event),
|
||||
"VOD" => Ok(PlaylistType::Vod),
|
||||
_ => Err(Error::custom(format!("Unknown playlist type: {:?}", input))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Error, Result};
|
||||
use crate::Error;
|
||||
|
||||
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
|
||||
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
||||
match s.as_ref() {
|
||||
"YES" => Ok(true),
|
||||
"NO" => Ok(false),
|
||||
|
@ -8,7 +8,7 @@ pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> Result<u64> {
|
||||
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> crate::Result<u64> {
|
||||
let n = s.as_ref().parse().map_err(Error::unknown)?; // TODO: Error::number
|
||||
Ok(n)
|
||||
}
|
||||
|
|
47
tests/playlist.rs
Normal file
47
tests/playlist.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
//! Credits go to
|
||||
//! - https://github.com/globocom/m3u8/blob/master/tests/playlists.py
|
||||
use hls_m3u8::tags::*;
|
||||
use hls_m3u8::types::*;
|
||||
use hls_m3u8::MediaPlaylist;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_simple_playlist() {
|
||||
let playlist = r#"
|
||||
#EXTM3U
|
||||
#EXT-X-TARGETDURATION:5220
|
||||
#EXTINF:0,
|
||||
http://media.example.com/entire1.ts
|
||||
#EXTINF:5220,
|
||||
http://media.example.com/entire2.ts
|
||||
#EXT-X-ENDLIST"#;
|
||||
|
||||
let media_playlist = playlist.parse::<MediaPlaylist>().unwrap();
|
||||
assert_eq!(
|
||||
media_playlist.target_duration_tag(),
|
||||
ExtXTargetDuration::new(Duration::from_secs(5220))
|
||||
);
|
||||
|
||||
assert_eq!(media_playlist.segments().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
media_playlist.segments()[0].inf_tag(),
|
||||
&ExtInf::new(Duration::from_secs(0))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
media_playlist.segments()[1].inf_tag(),
|
||||
&ExtInf::new(Duration::from_secs(5220))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
media_playlist.segments()[0].uri(),
|
||||
&SingleLineString::new("http://media.example.com/entire1.ts").unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
media_playlist.segments()[1].uri(),
|
||||
&SingleLineString::new("http://media.example.com/entire2.ts").unwrap()
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue