From ab82edf11921ea7c183068ed0fc0b1145183a7fd Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 4 Oct 2018 22:59:23 +0900 Subject: [PATCH] Add `MediaPlaylistOptions` --- src/lib.rs | 2 +- src/media_playlist.rs | 111 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6ddc7a8..531b847 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ extern crate trackable; pub use error::{Error, ErrorKind}; pub use master_playlist::{MasterPlaylist, MasterPlaylistBuilder}; -pub use media_playlist::{MediaPlaylist, MediaPlaylistBuilder}; +pub use media_playlist::{MediaPlaylist, MediaPlaylistBuilder, MediaPlaylistOptions}; pub use media_segment::{MediaSegment, MediaSegmentBuilder}; pub mod tags; diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 9ecd778..65ba84e 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -26,6 +26,7 @@ pub struct MediaPlaylistBuilder { start_tag: Option, end_list_tag: Option, segments: Vec, + options: MediaPlaylistOptions, } impl MediaPlaylistBuilder { /// Makes a new `MediaPlaylistBuilder` instance. @@ -41,6 +42,7 @@ impl MediaPlaylistBuilder { start_tag: None, end_list_tag: None, segments: Vec::new(), + options: MediaPlaylistOptions::new(), } } @@ -78,6 +80,14 @@ impl MediaPlaylistBuilder { self } + /// Sets the options that will be associated to the resulting playlist. + /// + /// The default value is `MediaPlaylistOptions::default()`. + pub fn options(&mut self, options: MediaPlaylistOptions) -> &mut Self { + self.options = options; + self + } + /// Builds a `MediaPlaylist` instance. pub fn finish(self) -> Result { let required_version = self.required_version(); @@ -109,23 +119,23 @@ impl MediaPlaylistBuilder { } fn validate_media_segments(&self, target_duration: Duration) -> Result<()> { - let target_duration_seconds = target_duration.as_secs(); - let mut last_range_uri = None; for s in &self.segments { // CHECK: `#EXT-X-TARGETDURATION` - let segment_duration = s.inf_tag().duration(); - let segment_duration_seconds = if segment_duration.subsec_nanos() < 500_000_000 { - segment_duration.as_secs() + let mut segment_duration = s.inf_tag().duration(); + let rounded_segment_duration = if segment_duration.subsec_nanos() < 500_000_000 { + Duration::from_secs(segment_duration.as_secs()) } else { - segment_duration.as_secs() + 1 + Duration::from_secs(segment_duration.as_secs() + 1) }; + let max_segment_duration = target_duration + self.options.allowable_excess_duration; track_assert!( - segment_duration_seconds <= target_duration_seconds, + rounded_segment_duration <= max_segment_duration, ErrorKind::InvalidInput, - "Too large segment duration: segment_duration={}, target_duration={}, uri={:?}", - segment_duration_seconds, - target_duration_seconds, + "Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}", + segment_duration, + max_segment_duration, + target_duration, s.uri() ); @@ -276,11 +286,47 @@ impl fmt::Display for MediaPlaylist { impl FromStr for MediaPlaylist { type Err = Error; fn from_str(s: &str) -> Result { + track!(MediaPlaylistOptions::new().parse(s)) + } +} + +/// Media playlist options. +#[derive(Debug, Clone)] +pub struct MediaPlaylistOptions { + allowable_excess_duration: Duration, +} +impl MediaPlaylistOptions { + /// Makes a new `MediaPlaylistOptions` with the default settings. + pub fn new() -> Self { + MediaPlaylistOptions { + allowable_excess_duration: Duration::from_secs(0), + } + } + + /// Sets the allowable excess duration of each media segment in the associated playlist. + /// + /// If there is a media segment of which duration exceeds + /// `#EXT-X-TARGETDURATION + allowable_excess_duration`, + /// the invocation of `MediaPlaylistBuilder::finish()` method will fail. + /// + /// The default value is `Duration::from_secs(0)`. + pub fn allowable_excess_segment_duration( + &mut self, + allowable_excess_duration: Duration, + ) -> &mut Self { + self.allowable_excess_duration = allowable_excess_duration; + self + } + + /// Parses the given M3U8 text with the specified settings. + pub fn parse(&self, m3u8: &str) -> Result { let mut builder = MediaPlaylistBuilder::new(); + builder.options(self.clone()); + let mut segment = MediaSegmentBuilder::new(); let mut has_partial_segment = false; let mut has_discontinuity_tag = false; - for (i, line) in Lines::new(s).enumerate() { + for (i, line) in Lines::new(m3u8).enumerate() { match track!(line)? { Line::Blank | Line::Comment(_) => {} Line::Tag(tag) => { @@ -402,3 +448,46 @@ impl FromStr for MediaPlaylist { track!(builder.finish()) } } +impl Default for MediaPlaylistOptions { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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"; + + // Error (allowable segment duration = target duration = 8) + assert!(m3u8.parse::().is_err()); + + // Error (allowable segment duration = 9) + assert!( + MediaPlaylistOptions::new() + .allowable_excess_segment_duration(Duration::from_secs(1)) + .parse(m3u8) + .is_err() + ); + + // Ok (allowable segment duration = 10) + assert!( + MediaPlaylistOptions::new() + .allowable_excess_segment_duration(Duration::from_secs(2)) + .parse(m3u8) + .is_ok() + ); + } +}