1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-22 07:10:59 +00:00

minor changes

This commit is contained in:
Luro02 2019-10-12 11:38:28 +02:00
parent c53e9e33f1
commit 73d9eb4f79
17 changed files with 351 additions and 65 deletions

View file

@ -14,7 +14,7 @@ use crate::types::ProtocolVersion;
use crate::{Encrypted, Error, RequiredVersion}; use crate::{Encrypted, Error, RequiredVersion};
/// Media playlist. /// Media playlist.
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
pub struct MediaPlaylist { pub struct MediaPlaylist {

View file

@ -8,7 +8,7 @@ use crate::tags::{
use crate::types::ProtocolVersion; use crate::types::ProtocolVersion;
use crate::{Encrypted, RequiredVersion}; use crate::{Encrypted, RequiredVersion};
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
/// Media segment. /// Media segment.
pub struct MediaSegment { pub struct MediaSegment {

View file

@ -128,9 +128,11 @@ impl ExtXMediaBuilder {
} }
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) { if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
return Err( return Err(Error::custom(format!(
Error::custom("If `DEFAULT` is true, `AUTOSELECT` has to be true too!").to_string(), "If `DEFAULT` is true, `AUTOSELECT` has to be true too, Default: {:?}, Autoselect: {:?}!",
); self.is_default, self.is_autoselect
))
.to_string());
} }
if media_type != MediaType::Subtitles && self.is_forced.is_some() { if media_type != MediaType::Subtitles && self.is_forced.is_some() {

View file

@ -18,7 +18,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Playlist`]: crate::MediaPlaylist /// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.3.4. EXT-X-ENDLIST]: /// [4.4.3.4. EXT-X-ENDLIST]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXEndList; pub struct ExtXEndList;
impl ExtXEndList { impl ExtXEndList {

View file

@ -20,7 +20,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]: /// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXIFramesOnly; pub struct ExtXIFramesOnly;
impl ExtXIFramesOnly { impl ExtXIFramesOnly {

View file

@ -9,12 +9,6 @@ use crate::{Error, RequiredVersion};
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of /// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
/// the first [`Media Segment`] that appears in a Playlist file. /// the first [`Media Segment`] that appears in a Playlist file.
/// ///
/// Its format is:
/// ```text
/// #EXT-X-MEDIA-SEQUENCE:<number>
/// ```
/// where `number` is a [`u64`].
///
/// [Media Segment]: crate::MediaSegment /// [Media Segment]: crate::MediaSegment
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]: /// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.2 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.2
@ -61,6 +55,7 @@ impl ExtXMediaSequence {
} }
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXMediaSequence { impl RequiredVersion for ExtXMediaSequence {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
} }

View file

@ -12,7 +12,7 @@ use crate::{Error, RequiredVersion};
/// ///
/// [`Media Playlist`]: crate::MediaPlaylist /// [`Media Playlist`]: crate::MediaPlaylist
/// [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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ExtXPlaylistType { pub enum ExtXPlaylistType {
/// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s /// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s
/// can only be added to the end of the [`Media Playlist`]. /// can only be added to the end of the [`Media Playlist`].

View file

@ -1,4 +1,5 @@
use std::fmt; use std::fmt;
use std::ops::Deref;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
@ -6,14 +7,13 @@ use crate::types::ProtocolVersion;
use crate::utils::tag; use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.4.3.1. EXT-X-TARGETDURATION] /// # [4.3.3.1. EXT-X-TARGETDURATION]
/// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`] /// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`]
/// duration. /// duration.
/// ///
/// [`MediaSegment`]: crate::MediaSegment /// [`MediaSegment`]: crate::MediaSegment
/// [4.4.3.1. EXT-X-TARGETDURATION]: /// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ExtXTargetDuration(Duration); pub struct ExtXTargetDuration(Duration);
impl ExtXTargetDuration { impl ExtXTargetDuration {
@ -31,10 +31,7 @@ impl ExtXTargetDuration {
/// ///
/// # Note /// # Note
/// The nanoseconds part of the [`Duration`] will be discarded. /// The nanoseconds part of the [`Duration`] will be discarded.
pub const fn new(duration: Duration) -> Self { pub const fn new(duration: Duration) -> Self { Self(Duration::from_secs(duration.as_secs())) }
// TOOD: round instead of discarding?
Self(Duration::from_secs(duration.as_secs()))
}
/// Returns the maximum media segment duration. /// Returns the maximum media segment duration.
/// ///
@ -55,6 +52,12 @@ impl RequiredVersion for ExtXTargetDuration {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
} }
impl Deref for ExtXTargetDuration {
type Target = Duration;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl fmt::Display for ExtXTargetDuration { impl fmt::Display for ExtXTargetDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.0.as_secs()) write!(f, "{}{}", Self::PREFIX, self.0.as_secs())
@ -98,4 +101,9 @@ mod test {
"#EXT-X-TARGETDURATION:5".parse().unwrap() "#EXT-X-TARGETDURATION:5".parse().unwrap()
); );
} }
#[test]
fn test_deref() {
assert_eq!(ExtXTargetDuration::new(Duration::from_secs(5)).as_secs(), 5);
}
} }

View file

@ -23,7 +23,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.2. EXT-X-BYTERANGE]: /// [4.4.2.2. EXT-X-BYTERANGE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXByteRange(ByteRange); pub struct ExtXByteRange(ByteRange);
impl ExtXByteRange { impl ExtXByteRange {

View file

@ -12,9 +12,12 @@ use crate::utils::{quote, tag, unquote};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.3.2.7. EXT-X-DATERANGE] /// # [4.3.2.7. EXT-X-DATERANGE]
/// The [`ExtXDateRange`] tag associates a date range (i.e., a range of
/// time defined by a starting and ending date) with a set of attribute/
/// value pairs.
/// ///
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7 /// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
#[derive(Builder, Debug, Clone, PartialEq)] #[derive(Builder, Debug, Clone, PartialEq, PartialOrd)]
#[builder(setter(into))] #[builder(setter(into))]
pub struct ExtXDateRange { pub struct ExtXDateRange {
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist. /// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
@ -87,7 +90,7 @@ pub struct ExtXDateRange {
/// This attribute is optional. /// This attribute is optional.
end_on_next: bool, end_on_next: bool,
#[builder(default)] #[builder(default)]
/// The "X-" prefix defines a namespace reserved for client-defined /// The `"X-"` prefix defines a namespace reserved for client-defined
/// attributes. The client-attribute must be a uppercase characters. /// attributes. The client-attribute must be a uppercase characters.
/// Clients should use a reverse-DNS syntax when defining their own /// Clients should use a reverse-DNS syntax when defining their own
/// attribute names to avoid collisions. An example of a client-defined /// attribute names to avoid collisions. An example of a client-defined
@ -513,10 +516,13 @@ impl ExtXDateRange {
self self
} }
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
pub const fn scte35_cmd(&self) -> &Option<String> { &self.scte35_cmd } pub const fn scte35_cmd(&self) -> &Option<String> { &self.scte35_cmd }
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
pub const fn scte35_in(&self) -> &Option<String> { &self.scte35_in } pub const fn scte35_in(&self) -> &Option<String> { &self.scte35_in }
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
pub const fn scte35_out(&self) -> &Option<String> { &self.scte35_out } pub const fn scte35_out(&self) -> &Option<String> { &self.scte35_out }
/// This attribute indicates that the end of the range containing it is /// This attribute indicates that the end of the range containing it is

View file

@ -17,7 +17,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.3. EXT-X-DISCONTINUITY]: /// [4.4.2.3. EXT-X-DISCONTINUITY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3 /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXDiscontinuity; pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity { impl ExtXDiscontinuity {

View file

@ -22,7 +22,7 @@ use crate::{Error, RequiredVersion};
/// [`ExtXMap`]: crate::tags::ExtXMap /// [`ExtXMap`]: crate::tags::ExtXMap
/// [`Media Segment`]: crate::MediaSegment /// [`Media Segment`]: crate::MediaSegment
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 /// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXKey(DecryptionKey); pub struct ExtXKey(DecryptionKey);
impl ExtXKey { impl ExtXKey {

View file

@ -14,7 +14,7 @@ use crate::{Encrypted, Error, RequiredVersion};
/// ///
/// [`MediaSegment`]: crate::MediaSegment /// [`MediaSegment`]: crate::MediaSegment
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5 /// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXMap { pub struct ExtXMap {
uri: String, uri: String,
range: Option<ByteRange>, range: Option<ByteRange>,

View file

@ -10,7 +10,7 @@ use crate::types::{
use crate::utils::{quote, unquote}; use crate::utils::{quote, unquote};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[builder(setter(into), build_fn(validate = "Self::validate"))] #[builder(setter(into), build_fn(validate = "Self::validate"))]
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] /// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
/// and [`ExtXKey`]. /// and [`ExtXKey`].
@ -483,38 +483,6 @@ mod test {
ProtocolVersion::V1 ProtocolVersion::V1
); );
assert_eq!(
DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V1
);
assert_eq!(
DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V2
);
}
#[test]
fn test_introduced_version() {
assert_eq!(
DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
.required_version(),
ProtocolVersion::V1
);
assert_eq!( assert_eq!(
DecryptionKey::builder() DecryptionKey::builder()
.method(EncryptionMethod::Aes128) .method(EncryptionMethod::Aes128)

View file

@ -11,7 +11,7 @@ use crate::Error;
/// # [4.3.4.2. EXT-X-STREAM-INF] /// # [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 /// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
#[builder(derive(Debug, PartialEq))] #[builder(derive(Debug, PartialEq))]
pub struct StreamInf { pub struct StreamInf {

View file

@ -1,10 +1,12 @@
use hls_m3u8::tags::{ExtXIFrameStreamInf, ExtXStreamInf}; use hls_m3u8::tags::{ExtXIFrameStreamInf, ExtXMedia, ExtXStreamInf};
use hls_m3u8::types::MediaType;
use hls_m3u8::MasterPlaylist; use hls_m3u8::MasterPlaylist;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn test_master_playlist() { fn test_master_playlist() {
// https://tools.ietf.org/html/rfc8216#section-8.4
let master_playlist = "#EXTM3U\n\ let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\ #EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\
http://example.com/low.m3u8\n\ http://example.com/low.m3u8\n\
@ -53,6 +55,7 @@ fn test_master_playlist() {
#[test] #[test]
fn test_master_playlist_with_i_frames() { fn test_master_playlist_with_i_frames() {
// https://tools.ietf.org/html/rfc8216#section-8.5
let master_playlist = "#EXTM3U\n\ let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000\n\ #EXT-X-STREAM-INF:BANDWIDTH=1280000\n\
low/audio-video.m3u8\n\ low/audio-video.m3u8\n\
@ -115,3 +118,254 @@ fn test_master_playlist_with_i_frames() {
master_playlist master_playlist
); );
} }
#[test]
fn test_master_playlist_with_alternative_audio() {
// https://tools.ietf.org/html/rfc8216#section-8.6
// TODO: I think the CODECS=\"..." have to be replaced.
let master_playlist = "#EXTM3U\n\
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"English\", \
DEFAULT=YES,AUTOSELECT=YES,LANGUAGE=\"en\", \
URI=\"main/english-audio.m3u8\"\n\
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Deutsch\", \
DEFAULT=NO,AUTOSELECT=YES,LANGUAGE=\"de\", \
URI=\"main/german-audio.m3u8\"\n\
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Commentary\", \
DEFAULT=NO,AUTOSELECT=NO,LANGUAGE=\"en\", \
URI=\"commentary/audio-only.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"...\",AUDIO=\"aac\"\n\
low/video-only.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS=\"...\",AUDIO=\"aac\"\n\
mid/video-only.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"...\",AUDIO=\"aac\"\n\
hi/video-only.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\",AUDIO=\"aac\"\n\
main/english-audio.m3u8"
.parse::<MasterPlaylist>()
.unwrap();
assert_eq!(
MasterPlaylist::builder()
.media_tags(vec![
ExtXMedia::builder()
.media_type(MediaType::Audio)
.group_id("aac")
.name("English")
.is_default(true)
.is_autoselect(true)
.language("en")
.uri("main/english-audio.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Audio)
.group_id("aac")
.name("Deutsch")
.is_default(false)
.is_autoselect(true)
.language("de")
.uri("main/german-audio.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Audio)
.group_id("aac")
.name("Commentary")
.is_default(false)
.is_autoselect(false)
.language("en")
.uri("commentary/audio-only.m3u8")
.build()
.unwrap(),
])
.stream_inf_tags(vec![
ExtXStreamInf::builder()
.bandwidth(1280000)
.codecs("...")
.audio("aac")
.uri("low/video-only.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(2560000)
.codecs("...")
.audio("aac")
.uri("mid/video-only.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(7680000)
.codecs("...")
.audio("aac")
.uri("hi/video-only.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(65000)
.codecs("mp4a.40.5")
.audio("aac")
.uri("main/english-audio.m3u8")
.build()
.unwrap(),
])
.build()
.unwrap(),
master_playlist
);
}
#[test]
fn test_master_playlist_with_alternative_video() {
// https://tools.ietf.org/html/rfc8216#section-8.7
let master_playlist = "#EXTM3U\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Main\", \
AUTOSELECT=YES,DEFAULT=YES,URI=\"low/main/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Centerfield\", \
DEFAULT=NO,URI=\"low/centerfield/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Dugout\", \
DEFAULT=NO,URI=\"low/dugout/audio-video.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"...\",VIDEO=\"low\"\n\
low/main/audio-video.m3u8\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Main\", \
AUTOSELECT=YES,DEFAULT=YES,URI=\"mid/main/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Centerfield\", \
DEFAULT=NO,URI=\"mid/centerfield/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Dugout\", \
DEFAULT=NO,URI=\"mid/dugout/audio-video.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS=\"...\",VIDEO=\"mid\"\n\
mid/main/audio-video.m3u8\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Main\", \
AUTOSELECT=YES,DEFAULT=YES,URI=\"hi/main/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Centerfield\", \
DEFAULT=NO,URI=\"hi/centerfield/audio-video.m3u8\"\n\
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Dugout\", \
DEFAULT=NO,URI=\"hi/dugout/audio-video.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"...\",VIDEO=\"hi\"
hi/main/audio-video.m3u8"
.parse::<MasterPlaylist>()
.unwrap();
assert_eq!(
MasterPlaylist::builder()
.media_tags(vec![
// low
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("low")
.name("Main")
.is_default(true)
.is_autoselect(true)
.uri("low/main/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("low")
.name("Centerfield")
.is_default(false)
.uri("low/centerfield/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("low")
.name("Dugout")
.is_default(false)
.uri("low/dugout/audio-video.m3u8")
.build()
.unwrap(),
// mid
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("mid")
.name("Main")
.is_default(true)
.is_autoselect(true)
.uri("mid/main/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("mid")
.name("Centerfield")
.is_default(false)
.uri("mid/centerfield/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("mid")
.name("Dugout")
.is_default(false)
.uri("mid/dugout/audio-video.m3u8")
.build()
.unwrap(),
// hi
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("hi")
.name("Main")
.is_default(true)
.is_autoselect(true)
.uri("hi/main/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("hi")
.name("Centerfield")
.is_default(false)
.uri("hi/centerfield/audio-video.m3u8")
.build()
.unwrap(),
ExtXMedia::builder()
.media_type(MediaType::Video)
.group_id("hi")
.name("Dugout")
.is_default(false)
.uri("hi/dugout/audio-video.m3u8")
.build()
.unwrap(),
])
.stream_inf_tags(vec![
ExtXStreamInf::builder()
.bandwidth(1280000)
.codecs("...")
.video("low")
.uri("low/main/audio-video.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(2560000)
.codecs("...")
.video("mid")
.uri("mid/main/audio-video.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(7680000)
.codecs("...")
.video("hi")
.uri("hi/main/audio-video.m3u8")
.build()
.unwrap(),
])
.build()
.unwrap(),
master_playlist
);
}

53
tests/media_playlist.rs Normal file
View file

@ -0,0 +1,53 @@
use std::time::Duration;
use hls_m3u8::tags::{ExtInf, ExtXByteRange, ExtXMediaSequence, ExtXTargetDuration};
use hls_m3u8::{MediaPlaylist, MediaSegment};
use pretty_assertions::assert_eq;
#[test]
fn test_media_playlist_with_byterange() {
let media_playlist = "#EXTM3U\n\
#EXT-X-TARGETDURATION:10\n\
#EXT-X-VERSION:4\n\
#EXT-X-MEDIA-SEQUENCE:0\n\
#EXTINF:10.0,\n\
#EXT-X-BYTERANGE:75232@0\n\
video.ts\n\
#EXT-X-BYTERANGE:82112@752321\n\
#EXTINF:10.0,\n\
video.ts\n\
#EXTINF:10.0,\n\
#EXT-X-BYTERANGE:69864\n\
video.ts"
.parse::<MediaPlaylist>()
.unwrap();
assert_eq!(
MediaPlaylist::builder()
.target_duration_tag(ExtXTargetDuration::new(Duration::from_secs(10)))
.media_sequence_tag(ExtXMediaSequence::new(0))
.segments(vec![
MediaSegment::builder()
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range_tag(ExtXByteRange::new(75232, Some(0)))
.uri("video.ts")
.build()
.unwrap(),
MediaSegment::builder()
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range_tag(ExtXByteRange::new(82112, Some(752321)))
.uri("video.ts")
.build()
.unwrap(),
MediaSegment::builder()
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range_tag(ExtXByteRange::new(69864, None))
.uri("video.ts")
.build()
.unwrap(),
])
.build()
.unwrap(),
media_playlist
)
}