mirror of
https://github.com/sile/hls_m3u8.git
synced 2025-01-11 04:35:24 +00:00
Add MediaPlaylistOptions
This commit is contained in:
parent
24a6ff9851
commit
ab82edf119
2 changed files with 101 additions and 12 deletions
|
@ -27,7 +27,7 @@ extern crate trackable;
|
||||||
|
|
||||||
pub use error::{Error, ErrorKind};
|
pub use error::{Error, ErrorKind};
|
||||||
pub use master_playlist::{MasterPlaylist, MasterPlaylistBuilder};
|
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 use media_segment::{MediaSegment, MediaSegmentBuilder};
|
||||||
|
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub struct MediaPlaylistBuilder {
|
||||||
start_tag: Option<ExtXStart>,
|
start_tag: Option<ExtXStart>,
|
||||||
end_list_tag: Option<ExtXEndList>,
|
end_list_tag: Option<ExtXEndList>,
|
||||||
segments: Vec<MediaSegment>,
|
segments: Vec<MediaSegment>,
|
||||||
|
options: MediaPlaylistOptions,
|
||||||
}
|
}
|
||||||
impl MediaPlaylistBuilder {
|
impl MediaPlaylistBuilder {
|
||||||
/// Makes a new `MediaPlaylistBuilder` instance.
|
/// Makes a new `MediaPlaylistBuilder` instance.
|
||||||
|
@ -41,6 +42,7 @@ impl MediaPlaylistBuilder {
|
||||||
start_tag: None,
|
start_tag: None,
|
||||||
end_list_tag: None,
|
end_list_tag: None,
|
||||||
segments: Vec::new(),
|
segments: Vec::new(),
|
||||||
|
options: MediaPlaylistOptions::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +80,14 @@ impl MediaPlaylistBuilder {
|
||||||
self
|
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.
|
/// Builds a `MediaPlaylist` instance.
|
||||||
pub fn finish(self) -> Result<MediaPlaylist> {
|
pub fn finish(self) -> Result<MediaPlaylist> {
|
||||||
let required_version = self.required_version();
|
let required_version = self.required_version();
|
||||||
|
@ -109,23 +119,23 @@ impl MediaPlaylistBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_media_segments(&self, target_duration: Duration) -> Result<()> {
|
fn validate_media_segments(&self, target_duration: Duration) -> Result<()> {
|
||||||
let target_duration_seconds = target_duration.as_secs();
|
|
||||||
|
|
||||||
let mut last_range_uri = None;
|
let mut last_range_uri = None;
|
||||||
for s in &self.segments {
|
for s in &self.segments {
|
||||||
// CHECK: `#EXT-X-TARGETDURATION`
|
// CHECK: `#EXT-X-TARGETDURATION`
|
||||||
let segment_duration = s.inf_tag().duration();
|
let mut segment_duration = s.inf_tag().duration();
|
||||||
let segment_duration_seconds = if segment_duration.subsec_nanos() < 500_000_000 {
|
let rounded_segment_duration = if segment_duration.subsec_nanos() < 500_000_000 {
|
||||||
segment_duration.as_secs()
|
Duration::from_secs(segment_duration.as_secs())
|
||||||
} else {
|
} 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!(
|
track_assert!(
|
||||||
segment_duration_seconds <= target_duration_seconds,
|
rounded_segment_duration <= max_segment_duration,
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
"Too large segment duration: segment_duration={}, target_duration={}, uri={:?}",
|
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
||||||
segment_duration_seconds,
|
segment_duration,
|
||||||
target_duration_seconds,
|
max_segment_duration,
|
||||||
|
target_duration,
|
||||||
s.uri()
|
s.uri()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -276,11 +286,47 @@ impl fmt::Display for MediaPlaylist {
|
||||||
impl FromStr for MediaPlaylist {
|
impl FromStr for MediaPlaylist {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
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<MediaPlaylist> {
|
||||||
let mut builder = MediaPlaylistBuilder::new();
|
let mut builder = MediaPlaylistBuilder::new();
|
||||||
|
builder.options(self.clone());
|
||||||
|
|
||||||
let mut segment = MediaSegmentBuilder::new();
|
let mut segment = MediaSegmentBuilder::new();
|
||||||
let mut has_partial_segment = false;
|
let mut has_partial_segment = false;
|
||||||
let mut has_discontinuity_tag = 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)? {
|
match track!(line)? {
|
||||||
Line::Blank | Line::Comment(_) => {}
|
Line::Blank | Line::Comment(_) => {}
|
||||||
Line::Tag(tag) => {
|
Line::Tag(tag) => {
|
||||||
|
@ -402,3 +448,46 @@ impl FromStr for MediaPlaylist {
|
||||||
track!(builder.finish())
|
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::<MediaPlaylist>().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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue