diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 124e45f..9861b7e 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -124,7 +124,7 @@ impl MediaPlaylistBuilder { if let Some(segments) = &self.segments { for s in segments { // CHECK: `#EXT-X-TARGETDURATION` - let segment_duration = s.inf_tag().duration(); + let segment_duration = s.inf().duration(); let rounded_segment_duration = { if segment_duration.subsec_nanos() < 500_000_000 { Duration::from_secs(segment_duration.as_secs()) @@ -152,7 +152,7 @@ impl MediaPlaylistBuilder { } // CHECK: `#EXT-X-BYTE-RANGE` - if let Some(tag) = s.byte_range_tag() { + if let Some(tag) = s.byte_range() { if tag.to_range().start().is_none() { let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?; if last_uri != s.uri() { @@ -286,7 +286,7 @@ fn parse_media_playlist( let mut has_discontinuity_tag = false; let mut unknown_tags = vec![]; - let mut available_key_tags: Vec = vec![]; + let mut available_keys: Vec = vec![]; for line in Lines::from(input) { match line? { @@ -294,25 +294,25 @@ fn parse_media_playlist( match tag { Tag::ExtInf(t) => { has_partial_segment = true; - segment.inf_tag(t); + segment.inf(t); } Tag::ExtXByteRange(t) => { has_partial_segment = true; - segment.byte_range_tag(t); + segment.byte_range(t); } Tag::ExtXDiscontinuity(t) => { has_discontinuity_tag = true; has_partial_segment = true; - segment.discontinuity_tag(t); + segment.discontinuity(t); } Tag::ExtXKey(t) => { has_partial_segment = true; - if available_key_tags.is_empty() { + if available_keys.is_empty() { // An ExtXKey applies to every MediaSegment and to every Media // Initialization Section declared by an EXT-X-MAP tag, that appears // between it and the next EXT-X-KEY tag in the Playlist file with the // same KEYFORMAT attribute (or the end of the Playlist file). - available_key_tags = available_key_tags + available_keys = available_keys .into_iter() .map(|k| { if t.key_format() == k.key_format() { @@ -323,22 +323,22 @@ fn parse_media_playlist( }) .collect(); } else { - available_key_tags.push(t); + available_keys.push(t); } } Tag::ExtXMap(mut t) => { has_partial_segment = true; - t.set_keys(available_key_tags.clone()); - segment.map_tag(t); + t.set_keys(available_keys.clone()); + segment.map(t); } Tag::ExtXProgramDateTime(t) => { has_partial_segment = true; - segment.program_date_time_tag(t); + segment.program_date_time(t); } Tag::ExtXDateRange(t) => { has_partial_segment = true; - segment.date_range_tag(t); + segment.date_range(t); } Tag::ExtXTargetDuration(t) => { builder.target_duration(t); @@ -350,9 +350,11 @@ fn parse_media_playlist( if segments.is_empty() { return Err(Error::invalid_input()); } + if has_discontinuity_tag { return Err(Error::invalid_input()); } + builder.discontinuity_sequence(t); } Tag::ExtXEndList(t) => { @@ -386,7 +388,7 @@ fn parse_media_playlist( } Line::Uri(uri) => { segment.uri(uri); - segment.keys(available_key_tags.clone()); + segment.keys(available_keys.clone()); segments.push(segment.build().map_err(Error::builder)?); segment = MediaSegment::builder(); has_partial_segment = false; diff --git a/src/media_segment.rs b/src/media_segment.rs index 884cc1e..5d6bb59 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -19,22 +19,22 @@ pub struct MediaSegment { keys: Vec, /// The [`ExtXMap`] tag associated with the media segment. #[builder(default)] - map_tag: Option, + map: Option, /// The [`ExtXByteRange`] tag associated with the [`MediaSegment`]. #[builder(default)] - byte_range_tag: Option, + byte_range: Option, /// The [`ExtXDateRange`] tag associated with the media segment. #[builder(default)] - date_range_tag: Option, + date_range: Option, /// The [`ExtXDiscontinuity`] tag associated with the media segment. #[builder(default)] - discontinuity_tag: Option, + discontinuity: Option, /// The [`ExtXProgramDateTime`] tag associated with the media /// segment. #[builder(default)] - program_date_time_tag: Option, + program_date_time: Option, /// The [`ExtInf`] tag associated with the [`MediaSegment`]. - inf_tag: ExtInf, + inf: ExtInf, /// The `URI` of the [`MediaSegment`]. #[shorthand(enable(into))] uri: String, @@ -47,12 +47,13 @@ impl MediaSegment { impl MediaSegmentBuilder { /// Pushes an [`ExtXKey`] tag. - pub fn push_key_tag>(&mut self, value: VALUE) -> &mut Self { - if let Some(key_tags) = &mut self.keys { - key_tags.push(value.into()); + pub fn push_key>(&mut self, value: VALUE) -> &mut Self { + if let Some(keys) = &mut self.keys { + keys.push(value.into()); } else { self.keys = Some(vec![value.into()]); } + self } } @@ -63,27 +64,27 @@ impl fmt::Display for MediaSegment { writeln!(f, "{}", value)?; } - if let Some(value) = &self.map_tag { + if let Some(value) = &self.map { writeln!(f, "{}", value)?; } - if let Some(value) = &self.byte_range_tag { + if let Some(value) = &self.byte_range { writeln!(f, "{}", value)?; } - if let Some(value) = &self.date_range_tag { + if let Some(value) = &self.date_range { writeln!(f, "{}", value)?; } - if let Some(value) = &self.discontinuity_tag { + if let Some(value) = &self.discontinuity { writeln!(f, "{}", value)?; } - if let Some(value) = &self.program_date_time_tag { + if let Some(value) = &self.program_date_time { writeln!(f, "{}", value)?; } - writeln!(f, "{}", self.inf_tag)?; // TODO: there might be a `,` missing + writeln!(f, "{}", self.inf)?; // TODO: there might be a `,` missing writeln!(f, "{}", self.uri)?; Ok(()) } @@ -93,12 +94,12 @@ impl RequiredVersion for MediaSegment { fn required_version(&self) -> ProtocolVersion { required_version![ self.keys, - self.map_tag, - self.byte_range_tag, - self.date_range_tag, - self.discontinuity_tag, - self.program_date_time_tag, - self.inf_tag + self.map, + self.byte_range, + self.date_range, + self.discontinuity, + self.program_date_time, + self.inf ] } } @@ -120,11 +121,11 @@ mod tests { assert_eq!( MediaSegment::builder() .keys(vec![ExtXKey::empty()]) - .map_tag(ExtXMap::new("https://www.example.com/")) - .byte_range_tag(ExtXByteRange::new(20, Some(5))) - //.date_range_tag() // TODO! - .discontinuity_tag(ExtXDiscontinuity) - .inf_tag(ExtInf::new(Duration::from_secs(4))) + .map(ExtXMap::new("https://www.example.com/")) + .byte_range(ExtXByteRange::new(20, Some(5))) + //.date_range() // TODO! + .discontinuity(ExtXDiscontinuity) + .inf(ExtInf::new(Duration::from_secs(4))) .uri("http://www.uri.com/") .build() .unwrap() diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index 4c548ee..aec2ee4 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -75,6 +75,10 @@ impl FromStr for ExtXTargetDuration { } } +impl From for ExtXTargetDuration { + fn from(value: Duration) -> Self { Self::new(value) } +} + #[cfg(test)] mod test { use super::*; diff --git a/tests/media_playlist.rs b/tests/media_playlist.rs index c87bff2..e597bdc 100644 --- a/tests/media_playlist.rs +++ b/tests/media_playlist.rs @@ -6,50 +6,48 @@ use pretty_assertions::assert_eq; #[test] fn test_media_playlist_with_byterange() { - let media_playlist = concat!( - "#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\n" - ) - .parse::() - .unwrap(); - assert_eq!( MediaPlaylist::builder() .target_duration(ExtXTargetDuration::new(Duration::from_secs(10))) .media_sequence(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))) + .inf(ExtInf::new(Duration::from_secs_f64(10.0))) + .byte_range(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))) + .inf(ExtInf::new(Duration::from_secs_f64(10.0))) + .byte_range(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)) + .inf(ExtInf::new(Duration::from_secs_f64(10.0))) + .byte_range(ExtXByteRange::new(69864, None)) .uri("video.ts") .build() .unwrap(), ]) .build() .unwrap(), - media_playlist + concat!( + "#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\n" + ) + .parse::() + .unwrap() ) } diff --git a/tests/playlist.rs b/tests/playlist.rs index c272a4a..925f4bf 100644 --- a/tests/playlist.rs +++ b/tests/playlist.rs @@ -1,10 +1,11 @@ //! Credits go to //! - https://github.com/globocom/m3u8/blob/master/tests/playlists.py -use hls_m3u8::tags::*; -use hls_m3u8::MediaPlaylist; - use std::time::Duration; +use hls_m3u8::tags::{ExtInf, ExtXEndList}; +use hls_m3u8::{MediaPlaylist, MediaSegment}; +use pretty_assertions::assert_eq; + #[test] fn test_simple_playlist() { let playlist = concat!( @@ -17,31 +18,24 @@ fn test_simple_playlist() { "#EXT-X-ENDLIST\n" ); - let media_playlist = playlist.parse::().unwrap(); assert_eq!( - media_playlist.target_duration(), - 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(), - &"http://media.example.com/entire1.ts".to_string() - ); - - assert_eq!( - media_playlist.segments()[1].uri(), - &"http://media.example.com/entire2.ts".to_string() + MediaPlaylist::builder() + .target_duration(Duration::from_secs(5220)) + .segments(vec![ + MediaSegment::builder() + .inf(ExtInf::new(Duration::from_secs(0))) + .uri("http://media.example.com/entire1.ts") + .build() + .unwrap(), + MediaSegment::builder() + .inf(ExtInf::new(Duration::from_secs(5220))) + .uri("http://media.example.com/entire2.ts") + .build() + .unwrap(), + ]) + .end_list(ExtXEndList) + .build() + .unwrap(), + playlist.parse::().unwrap(), ); }