From 73d9eb4f7978953cfddc320417e538edcb36619f Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Sat, 12 Oct 2019 11:38:28 +0200 Subject: [PATCH] minor changes --- src/media_playlist.rs | 2 +- src/media_segment.rs | 2 +- src/tags/master_playlist/media.rs | 8 +- src/tags/media_playlist/end_list.rs | 2 +- src/tags/media_playlist/i_frames_only.rs | 2 +- src/tags/media_playlist/media_sequence.rs | 7 +- src/tags/media_playlist/playlist_type.rs | 2 +- src/tags/media_playlist/target_duration.rs | 28 ++- src/tags/media_segment/byte_range.rs | 2 +- src/tags/media_segment/date_range.rs | 10 +- src/tags/media_segment/discontinuity.rs | 2 +- src/tags/media_segment/key.rs | 2 +- src/tags/media_segment/map.rs | 2 +- src/types/decryption_key.rs | 34 +-- src/types/stream_inf.rs | 2 +- tests/master_playlist.rs | 256 ++++++++++++++++++++- tests/media_playlist.rs | 53 +++++ 17 files changed, 351 insertions(+), 65 deletions(-) create mode 100644 tests/media_playlist.rs diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 7d66cb4..a334650 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -14,7 +14,7 @@ use crate::types::ProtocolVersion; use crate::{Encrypted, Error, RequiredVersion}; /// Media playlist. -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)] #[builder(build_fn(validate = "Self::validate"))] #[builder(setter(into, strip_option))] pub struct MediaPlaylist { diff --git a/src/media_segment.rs b/src/media_segment.rs index 65394d0..ea4f160 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -8,7 +8,7 @@ use crate::tags::{ use crate::types::ProtocolVersion; use crate::{Encrypted, RequiredVersion}; -#[derive(Debug, Clone, Builder)] +#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)] #[builder(setter(into, strip_option))] /// Media segment. pub struct MediaSegment { diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 794e131..f43e73f 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -128,9 +128,11 @@ impl ExtXMediaBuilder { } if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) { - return Err( - Error::custom("If `DEFAULT` is true, `AUTOSELECT` has to be true too!").to_string(), - ); + return Err(Error::custom(format!( + "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() { diff --git a/src/tags/media_playlist/end_list.rs b/src/tags/media_playlist/end_list.rs index 430354a..5463f5e 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.rs @@ -18,7 +18,7 @@ use crate::{Error, RequiredVersion}; /// [`Media Playlist`]: crate::MediaPlaylist /// [4.4.3.4. EXT-X-ENDLIST]: /// 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; impl ExtXEndList { diff --git a/src/tags/media_playlist/i_frames_only.rs b/src/tags/media_playlist/i_frames_only.rs index c28d830..c9c4dc9 100644 --- a/src/tags/media_playlist/i_frames_only.rs +++ b/src/tags/media_playlist/i_frames_only.rs @@ -20,7 +20,7 @@ use crate::{Error, RequiredVersion}; /// [`Media Segment`]: crate::MediaSegment /// [4.4.3.6. EXT-X-I-FRAMES-ONLY]: /// 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; impl ExtXIFramesOnly { diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index ed2babf..5e26eb7 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -9,12 +9,6 @@ use crate::{Error, RequiredVersion}; /// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of /// the first [`Media Segment`] that appears in a Playlist file. /// -/// Its format is: -/// ```text -/// #EXT-X-MEDIA-SEQUENCE: -/// ``` -/// where `number` is a [`u64`]. -/// /// [Media Segment]: crate::MediaSegment /// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]: /// 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 { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/media_playlist/playlist_type.rs b/src/tags/media_playlist/playlist_type.rs index 9311191..69c2e52 100644 --- a/src/tags/media_playlist/playlist_type.rs +++ b/src/tags/media_playlist/playlist_type.rs @@ -12,7 +12,7 @@ use crate::{Error, RequiredVersion}; /// /// [`Media Playlist`]: crate::MediaPlaylist /// [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 { /// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s /// can only be added to the end of the [`Media Playlist`]. diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index 046ccc5..652afb4 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::ops::Deref; use std::str::FromStr; use std::time::Duration; @@ -6,14 +7,13 @@ use crate::types::ProtocolVersion; use crate::utils::tag; use crate::{Error, RequiredVersion}; -/// # [4.4.3.1. EXT-X-TARGETDURATION] -/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`] +/// # [4.3.3.1. EXT-X-TARGETDURATION] +/// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`] /// duration. /// -/// [`Media Segment`]: crate::MediaSegment -/// [4.4.3.1. EXT-X-TARGETDURATION]: -/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +/// [`MediaSegment`]: crate::MediaSegment +/// [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, Default, PartialOrd, Ord)] pub struct ExtXTargetDuration(Duration); impl ExtXTargetDuration { @@ -31,10 +31,7 @@ impl ExtXTargetDuration { /// /// # Note /// The nanoseconds part of the [`Duration`] will be discarded. - pub const fn new(duration: Duration) -> Self { - // TOOD: round instead of discarding? - Self(Duration::from_secs(duration.as_secs())) - } + pub const fn new(duration: Duration) -> Self { Self(Duration::from_secs(duration.as_secs())) } /// Returns the maximum media segment duration. /// @@ -55,6 +52,12 @@ impl RequiredVersion for ExtXTargetDuration { 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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0.as_secs()) @@ -98,4 +101,9 @@ mod test { "#EXT-X-TARGETDURATION:5".parse().unwrap() ); } + + #[test] + fn test_deref() { + assert_eq!(ExtXTargetDuration::new(Duration::from_secs(5)).as_secs(), 5); + } } diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index 936189a..f2aafb4 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -23,7 +23,7 @@ use crate::{Error, RequiredVersion}; /// [`Media Segment`]: crate::MediaSegment /// [4.4.2.2. EXT-X-BYTERANGE]: /// 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); impl ExtXByteRange { diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 93061a7..6586f8a 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -12,9 +12,12 @@ use crate::utils::{quote, tag, unquote}; use crate::{Error, RequiredVersion}; /// # [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 -#[derive(Builder, Debug, Clone, PartialEq)] +#[derive(Builder, Debug, Clone, PartialEq, PartialOrd)] #[builder(setter(into))] pub struct ExtXDateRange { /// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist. @@ -87,7 +90,7 @@ pub struct ExtXDateRange { /// This attribute is optional. end_on_next: bool, #[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. /// Clients should use a reverse-DNS syntax when defining their own /// attribute names to avoid collisions. An example of a client-defined @@ -513,10 +516,13 @@ impl ExtXDateRange { self } + /// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf pub const fn scte35_cmd(&self) -> &Option { &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 { &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 { &self.scte35_out } /// This attribute indicates that the end of the range containing it is diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index 7b3fad5..5a40451 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -17,7 +17,7 @@ use crate::{Error, RequiredVersion}; /// [`Media Segment`]: crate::MediaSegment /// [4.4.2.3. EXT-X-DISCONTINUITY]: /// 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; impl ExtXDiscontinuity { diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index 4efac85..4ebe50b 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -22,7 +22,7 @@ use crate::{Error, RequiredVersion}; /// [`ExtXMap`]: crate::tags::ExtXMap /// [`Media Segment`]: crate::MediaSegment /// [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); impl ExtXKey { diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index d702709..f63f8d5 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -14,7 +14,7 @@ use crate::{Encrypted, Error, RequiredVersion}; /// /// [`MediaSegment`]: crate::MediaSegment /// [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 { uri: String, range: Option, diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index f974a42..77d85b6 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -10,7 +10,7 @@ use crate::types::{ use crate::utils::{quote, unquote}; 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"))] /// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] /// and [`ExtXKey`]. @@ -483,38 +483,6 @@ mod test { 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!( DecryptionKey::builder() .method(EncryptionMethod::Aes128) diff --git a/src/types/stream_inf.rs b/src/types/stream_inf.rs index d460d56..3f5b4b1 100644 --- a/src/types/stream_inf.rs +++ b/src/types/stream_inf.rs @@ -11,7 +11,7 @@ use crate::Error; /// # [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 -#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)] #[builder(setter(into, strip_option))] #[builder(derive(Debug, PartialEq))] pub struct StreamInf { diff --git a/tests/master_playlist.rs b/tests/master_playlist.rs index 24d901f..fb2ecfc 100644 --- a/tests/master_playlist.rs +++ b/tests/master_playlist.rs @@ -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 pretty_assertions::assert_eq; #[test] fn test_master_playlist() { + // https://tools.ietf.org/html/rfc8216#section-8.4 let master_playlist = "#EXTM3U\n\ #EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\ http://example.com/low.m3u8\n\ @@ -53,6 +55,7 @@ fn test_master_playlist() { #[test] fn test_master_playlist_with_i_frames() { + // https://tools.ietf.org/html/rfc8216#section-8.5 let master_playlist = "#EXTM3U\n\ #EXT-X-STREAM-INF:BANDWIDTH=1280000\n\ low/audio-video.m3u8\n\ @@ -115,3 +118,254 @@ fn test_master_playlist_with_i_frames() { 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::() + .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::() + .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 + ); +} diff --git a/tests/media_playlist.rs b/tests/media_playlist.rs new file mode 100644 index 0000000..7f8a860 --- /dev/null +++ b/tests/media_playlist.rs @@ -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::() + .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 + ) +}