2019-09-13 14:06:52 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
use derive_builder::Builder;
|
|
|
|
|
2019-03-31 09:58:11 +00:00
|
|
|
use crate::line::{Line, Lines, Tag};
|
2019-09-14 19:21:44 +00:00
|
|
|
use crate::media_segment::MediaSegment;
|
2019-03-31 09:58:11 +00:00
|
|
|
use crate::tags::{
|
2018-10-04 11:18:56 +00:00
|
|
|
ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXIndependentSegments,
|
|
|
|
ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXTargetDuration, ExtXVersion,
|
|
|
|
};
|
2019-10-04 09:02:21 +00:00
|
|
|
use crate::types::ProtocolVersion;
|
|
|
|
use crate::{Encrypted, Error, RequiredVersion};
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
/// Media playlist.
|
2019-10-12 09:38:28 +00:00
|
|
|
#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(build_fn(validate = "Self::validate"))]
|
|
|
|
#[builder(setter(into, strip_option))]
|
|
|
|
pub struct MediaPlaylist {
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXTargetDuration`] tag.
|
2019-09-14 19:08:35 +00:00
|
|
|
target_duration_tag: ExtXTargetDuration,
|
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXMediaSequence`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
media_sequence_tag: Option<ExtXMediaSequence>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXDiscontinuitySequence`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXPlaylistType`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
playlist_type_tag: Option<ExtXPlaylistType>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXIFramesOnly`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
i_frames_only_tag: Option<ExtXIFramesOnly>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXIndependentSegments`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
independent_segments_tag: Option<ExtXIndependentSegments>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXStart`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
start_tag: Option<ExtXStart>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets the [`ExtXEndList`] tag.
|
2018-02-14 19:18:02 +00:00
|
|
|
end_list_tag: Option<ExtXEndList>,
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Sets all [`MediaSegment`]s.
|
2018-02-14 19:18:02 +00:00
|
|
|
segments: Vec<MediaSegment>,
|
2019-10-03 15:01:15 +00:00
|
|
|
/// Sets the allowable excess duration of each media segment in the
|
|
|
|
/// associated playlist.
|
2018-02-14 19:18:02 +00:00
|
|
|
///
|
2019-09-14 19:08:35 +00:00
|
|
|
/// # Error
|
|
|
|
/// If there is a media segment of which duration exceeds
|
|
|
|
/// `#EXT-X-TARGETDURATION + allowable_excess_duration`,
|
|
|
|
/// the invocation of `MediaPlaylistBuilder::build()` method will fail.
|
2018-10-04 13:59:23 +00:00
|
|
|
///
|
2019-09-14 19:08:35 +00:00
|
|
|
/// The default value is `Duration::from_secs(0)`.
|
|
|
|
#[builder(default = "Duration::from_secs(0)")]
|
|
|
|
allowable_excess_duration: Duration,
|
|
|
|
}
|
2018-10-04 13:59:23 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
impl MediaPlaylistBuilder {
|
|
|
|
fn validate(&self) -> Result<(), String> {
|
|
|
|
if let Some(target_duration) = &self.target_duration_tag {
|
|
|
|
self.validate_media_segments(target_duration.duration())
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2018-02-14 19:18:02 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 14:06:52 +00:00
|
|
|
fn validate_media_segments(&self, target_duration: Duration) -> crate::Result<()> {
|
2018-02-14 19:18:02 +00:00
|
|
|
let mut last_range_uri = None;
|
2019-09-14 19:08:35 +00:00
|
|
|
if let Some(segments) = &self.segments {
|
|
|
|
for s in segments {
|
|
|
|
// CHECK: `#EXT-X-TARGETDURATION`
|
|
|
|
let segment_duration = s.inf_tag().duration();
|
2019-10-05 14:24:48 +00:00
|
|
|
let rounded_segment_duration = {
|
|
|
|
if segment_duration.subsec_nanos() < 500_000_000 {
|
|
|
|
Duration::from_secs(segment_duration.as_secs())
|
|
|
|
} else {
|
|
|
|
Duration::from_secs(segment_duration.as_secs() + 1)
|
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let max_segment_duration = {
|
|
|
|
if let Some(value) = &self.allowable_excess_duration {
|
|
|
|
target_duration + *value
|
|
|
|
} else {
|
|
|
|
target_duration
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-09-22 18:33:40 +00:00
|
|
|
if rounded_segment_duration > max_segment_duration {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::custom(format!(
|
|
|
|
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
|
|
|
segment_duration,
|
|
|
|
max_segment_duration,
|
|
|
|
target_duration,
|
|
|
|
s.uri()
|
|
|
|
)));
|
|
|
|
}
|
2018-02-14 19:18:02 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
// CHECK: `#EXT-X-BYTE-RANGE`
|
|
|
|
if let Some(tag) = s.byte_range_tag() {
|
|
|
|
if tag.to_range().start().is_none() {
|
2019-09-22 18:33:40 +00:00
|
|
|
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
|
2019-09-14 19:08:35 +00:00
|
|
|
if last_uri != s.uri() {
|
|
|
|
return Err(Error::invalid_input());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
last_range_uri = Some(s.uri());
|
2019-09-13 14:06:52 +00:00
|
|
|
}
|
2018-02-14 19:18:02 +00:00
|
|
|
} else {
|
2019-09-14 19:08:35 +00:00
|
|
|
last_range_uri = None;
|
2018-02-14 19:18:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Adds a media segment to the resulting playlist.
|
|
|
|
pub fn push_segment<VALUE: Into<MediaSegment>>(&mut self, value: VALUE) -> &mut Self {
|
|
|
|
if let Some(segments) = &mut self.segments {
|
|
|
|
segments.push(value.into());
|
|
|
|
} else {
|
|
|
|
self.segments = Some(vec![value.into()]);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse the rest of the [`MediaPlaylist`] from an m3u8 file.
|
|
|
|
pub fn parse(&mut self, input: &str) -> crate::Result<MediaPlaylist> {
|
|
|
|
parse_media_playlist(input, self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RequiredVersion for MediaPlaylistBuilder {
|
2018-02-14 19:18:02 +00:00
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
2019-10-05 14:24:48 +00:00
|
|
|
required_version![
|
|
|
|
self.target_duration_tag,
|
|
|
|
self.media_sequence_tag,
|
|
|
|
self.discontinuity_sequence_tag,
|
|
|
|
self.playlist_type_tag,
|
|
|
|
self.i_frames_only_tag,
|
|
|
|
self.independent_segments_tag,
|
|
|
|
self.start_tag,
|
|
|
|
self.end_list_tag,
|
|
|
|
self.segments
|
|
|
|
]
|
2018-02-14 19:18:02 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2018-02-14 19:18:02 +00:00
|
|
|
impl MediaPlaylist {
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns a builder for [`MediaPlaylist`].
|
2019-10-03 15:01:15 +00:00
|
|
|
pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() }
|
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXTargetDuration`] tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag }
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2018-02-14 19:18:02 +00:00
|
|
|
/// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> { self.media_sequence_tag }
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXDiscontinuitySequence`] tag contained in the
|
2019-10-03 15:01:15 +00:00
|
|
|
/// playlist.
|
2019-09-08 10:23:33 +00:00
|
|
|
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
|
2018-02-14 19:18:02 +00:00
|
|
|
self.discontinuity_sequence_tag
|
|
|
|
}
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXPlaylistType`] tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> { self.playlist_type_tag }
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXIFramesOnly`] tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> { self.i_frames_only_tag }
|
2018-02-14 19:18:02 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
|
2019-09-08 10:23:33 +00:00
|
|
|
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
2018-02-14 19:18:02 +00:00
|
|
|
self.independent_segments_tag
|
|
|
|
}
|
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXStart`] tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
|
2018-02-14 19:18:02 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`ExtXEndList`] tag contained in the playlist.
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn end_list_tag(&self) -> Option<ExtXEndList> { self.end_list_tag }
|
2018-02-13 15:25:33 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
/// Returns the [`MediaSegment`]s contained in the playlist.
|
|
|
|
pub const fn segments(&self) -> &Vec<MediaSegment> { &self.segments }
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2019-10-05 14:24:48 +00:00
|
|
|
impl RequiredVersion for MediaPlaylist {
|
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
|
|
|
required_version![
|
|
|
|
self.target_duration_tag,
|
|
|
|
self.media_sequence_tag,
|
|
|
|
self.discontinuity_sequence_tag,
|
|
|
|
self.playlist_type_tag,
|
|
|
|
self.i_frames_only_tag,
|
|
|
|
self.independent_segments_tag,
|
|
|
|
self.start_tag,
|
|
|
|
self.end_list_tag,
|
|
|
|
self.segments
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 15:25:33 +00:00
|
|
|
impl fmt::Display for MediaPlaylist {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f, "{}", ExtM3u)?;
|
2019-10-05 14:24:48 +00:00
|
|
|
if self.required_version() != ProtocolVersion::V1 {
|
|
|
|
writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2018-02-14 19:18:02 +00:00
|
|
|
writeln!(f, "{}", self.target_duration_tag)?;
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.media_sequence_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.discontinuity_sequence_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.playlist_type_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.i_frames_only_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.independent_segments_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.start_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
|
|
|
for segment in &self.segments {
|
2019-05-20 07:01:01 +00:00
|
|
|
write!(f, "{}", segment)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.end_list_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
fn parse_media_playlist(
|
|
|
|
input: &str,
|
|
|
|
builder: &mut MediaPlaylistBuilder,
|
|
|
|
) -> crate::Result<MediaPlaylist> {
|
2019-09-14 19:21:44 +00:00
|
|
|
let mut segment = MediaSegment::builder();
|
2019-09-14 19:08:35 +00:00
|
|
|
let mut segments = vec![];
|
|
|
|
|
|
|
|
let mut has_partial_segment = false;
|
|
|
|
let mut has_discontinuity_tag = false;
|
|
|
|
|
2019-10-05 14:24:48 +00:00
|
|
|
let mut available_key_tags: Vec<crate::tags::ExtXKey> = vec![];
|
2019-10-04 09:02:21 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
|
|
|
|
match line {
|
|
|
|
Line::Tag(tag) => {
|
|
|
|
if i == 0 {
|
|
|
|
if tag != Tag::ExtM3u(ExtM3u) {
|
|
|
|
return Err(Error::custom("m3u8 doesn't start with #EXTM3U"));
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match tag {
|
|
|
|
Tag::ExtM3u(_) => return Err(Error::invalid_input()),
|
|
|
|
Tag::ExtInf(t) => {
|
|
|
|
has_partial_segment = true;
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.inf_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXByteRange(t) => {
|
|
|
|
has_partial_segment = true;
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.byte_range_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXDiscontinuity(t) => {
|
|
|
|
has_discontinuity_tag = true;
|
|
|
|
has_partial_segment = true;
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.discontinuity_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXKey(t) => {
|
|
|
|
has_partial_segment = true;
|
2019-10-05 14:24:48 +00:00
|
|
|
if available_key_tags.is_empty() {
|
2019-10-04 09:02:21 +00:00
|
|
|
// 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
|
|
|
|
.into_iter()
|
|
|
|
.map(|k| {
|
|
|
|
if t.key_format() == k.key_format() {
|
|
|
|
t.clone()
|
|
|
|
} else {
|
|
|
|
k
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2019-10-05 14:24:48 +00:00
|
|
|
} else {
|
|
|
|
available_key_tags.push(t);
|
2019-10-04 09:02:21 +00:00
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
2019-10-04 09:02:21 +00:00
|
|
|
Tag::ExtXMap(mut t) => {
|
2019-09-14 19:08:35 +00:00
|
|
|
has_partial_segment = true;
|
2019-10-04 09:02:21 +00:00
|
|
|
|
|
|
|
t.set_keys(available_key_tags.clone());
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.map_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXProgramDateTime(t) => {
|
|
|
|
has_partial_segment = true;
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.program_date_time_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXDateRange(t) => {
|
|
|
|
has_partial_segment = true;
|
2019-09-14 19:21:44 +00:00
|
|
|
segment.date_range_tag(t);
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXTargetDuration(t) => {
|
|
|
|
builder.target_duration_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXMediaSequence(t) => {
|
|
|
|
builder.media_sequence_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXDiscontinuitySequence(t) => {
|
|
|
|
if segments.is_empty() {
|
|
|
|
return Err(Error::invalid_input());
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
if has_discontinuity_tag {
|
|
|
|
return Err(Error::invalid_input());
|
2018-02-14 15:50:57 +00:00
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
builder.discontinuity_sequence_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXEndList(t) => {
|
|
|
|
builder.end_list_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXPlaylistType(t) => {
|
|
|
|
builder.playlist_type_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXIFramesOnly(t) => {
|
|
|
|
builder.i_frames_only_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXMedia(_)
|
|
|
|
| Tag::ExtXStreamInf(_)
|
|
|
|
| Tag::ExtXIFrameStreamInf(_)
|
|
|
|
| Tag::ExtXSessionData(_)
|
|
|
|
| Tag::ExtXSessionKey(_) => {
|
2019-10-04 09:02:21 +00:00
|
|
|
return Err(Error::unexpected_tag(tag));
|
2019-09-14 19:08:35 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXIndependentSegments(t) => {
|
|
|
|
builder.independent_segments_tag(t);
|
|
|
|
}
|
|
|
|
Tag::ExtXStart(t) => {
|
|
|
|
builder.start_tag(t);
|
|
|
|
}
|
2019-10-05 14:24:48 +00:00
|
|
|
Tag::Unknown(_) | Tag::ExtXVersion(_) => {
|
2019-09-14 19:08:35 +00:00
|
|
|
// [6.3.1. General Client Responsibilities]
|
|
|
|
// > ignore any unrecognized tags.
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
Line::Uri(uri) => {
|
|
|
|
segment.uri(uri);
|
2019-10-04 09:02:21 +00:00
|
|
|
segment.keys(available_key_tags.clone());
|
2020-01-23 18:13:26 +00:00
|
|
|
segments.push(segment.build().map_err(Error::builder)?);
|
2019-09-14 19:21:44 +00:00
|
|
|
segment = MediaSegment::builder();
|
2019-09-14 19:08:35 +00:00
|
|
|
has_partial_segment = false;
|
|
|
|
}
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-04 09:02:21 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
if has_partial_segment {
|
|
|
|
return Err(Error::invalid_input());
|
|
|
|
}
|
2019-10-04 09:02:21 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
builder.segments(segments);
|
2020-01-23 18:13:26 +00:00
|
|
|
builder.build().map_err(Error::builder)
|
2018-02-13 15:25:33 +00:00
|
|
|
}
|
2019-09-08 10:23:33 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
impl FromStr for MediaPlaylist {
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
|
|
parse_media_playlist(input, &mut Self::builder())
|
2018-10-04 13:59:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-10-08 13:42:33 +00:00
|
|
|
use pretty_assertions::assert_eq;
|
2018-10-04 13:59:23 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn too_large_segment_duration_test() {
|
2019-09-15 08:40:45 +00:00
|
|
|
let playlist = r#"
|
|
|
|
#EXTM3U
|
|
|
|
#EXT-X-TARGETDURATION:8
|
|
|
|
#EXT-X-VERSION:3
|
|
|
|
#EXTINF:9.009,
|
|
|
|
http://media.example.com/first.ts
|
|
|
|
#EXTINF:9.509,
|
|
|
|
http://media.example.com/second.ts
|
|
|
|
#EXTINF:3.003,
|
|
|
|
http://media.example.com/third.ts
|
|
|
|
#EXT-X-ENDLIST"#;
|
2018-10-04 13:59:23 +00:00
|
|
|
|
|
|
|
// Error (allowable segment duration = target duration = 8)
|
2019-09-15 08:40:45 +00:00
|
|
|
assert!(playlist.parse::<MediaPlaylist>().is_err());
|
2018-10-04 13:59:23 +00:00
|
|
|
|
|
|
|
// Error (allowable segment duration = 9)
|
2019-09-14 19:08:35 +00:00
|
|
|
assert!(MediaPlaylist::builder()
|
|
|
|
.allowable_excess_duration(Duration::from_secs(1))
|
2019-09-15 08:40:45 +00:00
|
|
|
.parse(playlist)
|
2019-03-31 09:54:21 +00:00
|
|
|
.is_err());
|
2018-10-04 13:59:23 +00:00
|
|
|
|
|
|
|
// Ok (allowable segment duration = 10)
|
2019-09-14 19:08:35 +00:00
|
|
|
MediaPlaylist::builder()
|
|
|
|
.allowable_excess_duration(Duration::from_secs(2))
|
2019-09-15 08:40:45 +00:00
|
|
|
.parse(playlist)
|
2019-09-14 19:08:35 +00:00
|
|
|
.unwrap();
|
2018-10-04 13:59:23 +00:00
|
|
|
}
|
2018-10-10 15:35:24 +00:00
|
|
|
|
|
|
|
#[test]
|
2019-09-15 08:40:45 +00:00
|
|
|
fn test_empty_playlist() {
|
|
|
|
let playlist = "";
|
|
|
|
assert!(playlist.parse::<MediaPlaylist>().is_err());
|
2018-10-10 15:35:24 +00:00
|
|
|
}
|
2018-10-04 13:59:23 +00:00
|
|
|
}
|