1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-26 00:51:00 +00:00

Refactor media_playlist module

This commit is contained in:
Takeru Ohta 2018-02-15 04:18:02 +09:00
parent bddd211045
commit c2b1dd6a45
4 changed files with 322 additions and 84 deletions

View file

@ -12,7 +12,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; pub use media_playlist::{MediaPlaylist, MediaPlaylistBuilder};
pub mod segment { pub mod segment {
//! Media segment. //! Media segment.

View file

@ -1,74 +1,274 @@
use std::fmt; use std::fmt;
use std::iter;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration;
use {Error, ErrorKind, Result}; use {Error, ErrorKind, Result};
use line::{Line, Lines, Tag}; use line::{Line, Lines, Tag};
use media_segment::{MediaSegment, MediaSegmentBuilder}; use media_segment::{MediaSegment, MediaSegmentBuilder};
use tag::{ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, use tag::{ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly,
ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart,
ExtXTargetDuration, ExtXVersion}; ExtXTargetDuration, ExtXVersion, MediaPlaylistTag};
use types::ProtocolVersion; use types::ProtocolVersion;
// TODO: There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist. /// Media playlist builder.
// TODO: A Media Playlist tag MUST NOT appear in a Master Playlist. #[derive(Debug, Clone)]
pub struct MediaPlaylistBuilder {
version: Option<ProtocolVersion>,
target_duration_tag: Option<ExtXTargetDuration>,
media_sequence_tag: Option<ExtXMediaSequence>,
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
playlist_type_tag: Option<ExtXPlaylistType>,
i_frames_only_tag: Option<ExtXIFramesOnly>,
independent_segments_tag: Option<ExtXIndependentSegments>,
start_tag: Option<ExtXStart>,
end_list_tag: Option<ExtXEndList>,
segments: Vec<MediaSegment>,
}
impl MediaPlaylistBuilder {
/// Makes a new `MediaPlaylistBuilder` instance.
pub fn new() -> Self {
MediaPlaylistBuilder {
version: None,
target_duration_tag: None,
media_sequence_tag: None,
discontinuity_sequence_tag: None,
playlist_type_tag: None,
i_frames_only_tag: None,
independent_segments_tag: None,
start_tag: None,
end_list_tag: None,
segments: Vec::new(),
}
}
/// Sets the protocol compatibility version of the resulting playlist.
///
/// If the resulting playlist has tags which requires a compatibility version greater than `version`,
/// `finish()` method will fail with an `ErrorKind::InvalidInput` error.
///
/// The default is the maximum version among the tags in the playlist.
pub fn version(&mut self, version: ProtocolVersion) -> &mut Self {
self.version = Some(version);
self
}
/// Sets the given tag to the resulting playlist.
pub fn tag<T: Into<MediaPlaylistTag>>(&mut self, tag: T) -> &mut Self {
match tag.into() {
MediaPlaylistTag::ExtXTargetDuration(t) => self.target_duration_tag = Some(t),
MediaPlaylistTag::ExtXMediaSequence(t) => self.media_sequence_tag = Some(t),
MediaPlaylistTag::ExtXDiscontinuitySequence(t) => {
self.discontinuity_sequence_tag = Some(t)
}
MediaPlaylistTag::ExtXPlaylistType(t) => self.playlist_type_tag = Some(t),
MediaPlaylistTag::ExtXIFramesOnly(t) => self.i_frames_only_tag = Some(t),
MediaPlaylistTag::ExtXIndependentSegments(t) => self.independent_segments_tag = Some(t),
MediaPlaylistTag::ExtXStart(t) => self.start_tag = Some(t),
MediaPlaylistTag::ExtXEndList(t) => self.end_list_tag = Some(t),
}
self
}
/// Adds a media segment to the resulting playlist.
pub fn segment(&mut self, segment: MediaSegment) -> &mut Self {
self.segments.push(segment);
self
}
/// Builds a `MediaPlaylist` instance.
pub fn finish(self) -> Result<MediaPlaylist> {
let required_version = self.required_version();
let specified_version = self.version.unwrap_or(required_version);
track_assert!(
required_version <= specified_version,
ErrorKind::InvalidInput,
"required_version:{}, specified_version:{}",
required_version,
specified_version,
);
let target_duration_tag =
track_assert_some!(self.target_duration_tag, ErrorKind::InvalidInput);
track!(self.validate_media_segments(target_duration_tag.duration()))?;
Ok(MediaPlaylist {
version_tag: ExtXVersion::new(specified_version),
target_duration_tag,
media_sequence_tag: self.media_sequence_tag,
discontinuity_sequence_tag: self.discontinuity_sequence_tag,
playlist_type_tag: self.playlist_type_tag,
i_frames_only_tag: self.i_frames_only_tag,
independent_segments_tag: self.independent_segments_tag,
start_tag: self.start_tag,
end_list_tag: self.end_list_tag,
segments: self.segments,
})
}
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().duration();
let segment_duration_seconds = if segment_duration.subsec_nanos() < 500_000_000 {
segment_duration.as_secs()
} else {
segment_duration.as_secs() + 1
};
track_assert!(
segment_duration_seconds <= target_duration_seconds,
ErrorKind::InvalidInput,
"Too large segment duration: segment_duration={}, target_duration={}, uri={:?}",
segment_duration_seconds,
target_duration_seconds,
s.uri()
);
// CHECK: `#EXT-X-BYTE-RANGE`
if let Some(tag) = s.byte_range_tag() {
if tag.range().start.is_none() {
let last_uri = track_assert_some!(last_range_uri, ErrorKind::InvalidInput);
track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput);
} else {
last_range_uri = Some(s.uri());
}
} else {
last_range_uri = None;
}
}
Ok(())
}
fn required_version(&self) -> ProtocolVersion {
iter::empty()
.chain(
self.target_duration_tag
.iter()
.map(|t| t.requires_version()),
)
.chain(self.media_sequence_tag.iter().map(|t| t.requires_version()))
.chain(
self.discontinuity_sequence_tag
.iter()
.map(|t| t.requires_version()),
)
.chain(self.playlist_type_tag.iter().map(|t| t.requires_version()))
.chain(self.i_frames_only_tag.iter().map(|t| t.requires_version()))
.chain(
self.independent_segments_tag
.iter()
.map(|t| t.requires_version()),
)
.chain(self.start_tag.iter().map(|t| t.requires_version()))
.chain(self.end_list_tag.iter().map(|t| t.requires_version()))
.chain(self.segments.iter().map(|s| s.requires_version()))
.max()
.expect("Never fails")
}
}
impl Default for MediaPlaylistBuilder {
fn default() -> Self {
Self::new()
}
}
/// Media playlist.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MediaPlaylist { pub struct MediaPlaylist {
pub version: ExtXVersion, version_tag: ExtXVersion,
target_duration_tag: ExtXTargetDuration,
media_sequence_tag: Option<ExtXMediaSequence>,
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
playlist_type_tag: Option<ExtXPlaylistType>,
i_frames_only_tag: Option<ExtXIFramesOnly>,
independent_segments_tag: Option<ExtXIndependentSegments>,
start_tag: Option<ExtXStart>,
end_list_tag: Option<ExtXEndList>,
segments: Vec<MediaSegment>,
}
impl MediaPlaylist {
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
pub fn version_tag(&self) -> ExtXVersion {
self.version_tag
}
// TODO: The EXTINF duration of each Media Segment in the Playlist /// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
// file, when rounded to the nearest integer, MUST be less than or equal pub fn target_duration_tag(&self) -> ExtXTargetDuration {
// to the target duration self.target_duration_tag
pub target_duration: ExtXTargetDuration, }
// TODO: The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media /// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
// Segment in the Playlist. pub fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> {
pub media_sequence: Option<ExtXMediaSequence>, self.media_sequence_tag
}
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first /// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the playlist.
// Media Segment in the Playlist. pub fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
// self.discontinuity_sequence_tag
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT- }
// X-DISCONTINUITY tag.
pub discontinuity_sequence: Option<ExtXDiscontinuitySequence>,
pub playlist_type: Option<ExtXPlaylistType>, /// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
pub i_frames_only: Option<ExtXIFramesOnly>, pub fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> {
pub independent_segments: Option<ExtXIndependentSegments>, self.playlist_type_tag
pub start: Option<ExtXStart>, }
pub segments: Vec<MediaSegment>, /// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist.
pub fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> {
self.i_frames_only_tag
}
pub end_list: Option<ExtXEndList>, /// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
pub fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
self.independent_segments_tag
}
/// Returns the `EXT-X-START` tag contained in the playlist.
pub fn start_tag(&self) -> Option<ExtXStart> {
self.start_tag
}
/// Returns the `EXT-X-ENDLIST` tag contained in the playlist.
pub fn end_list_tag(&self) -> Option<ExtXEndList> {
self.end_list_tag
}
/// Returns the media segments contained in the playlist.
pub fn segments(&self) -> &[MediaSegment] {
&self.segments
}
} }
impl fmt::Display for MediaPlaylist { impl fmt::Display for MediaPlaylist {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", ExtM3u)?; writeln!(f, "{}", ExtM3u)?;
if self.version.version() != ProtocolVersion::V1 { if self.version_tag.version() != ProtocolVersion::V1 {
writeln!(f, "{}", self.version)?; writeln!(f, "{}", self.version_tag)?;
} }
writeln!(f, "{}", self.target_duration)?; writeln!(f, "{}", self.target_duration_tag)?;
if let Some(ref t) = self.media_sequence { if let Some(ref t) = self.media_sequence_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
if let Some(ref t) = self.discontinuity_sequence { if let Some(ref t) = self.discontinuity_sequence_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
if let Some(ref t) = self.playlist_type { if let Some(ref t) = self.playlist_type_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
if let Some(ref t) = self.i_frames_only { if let Some(ref t) = self.i_frames_only_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
if let Some(ref t) = self.independent_segments { if let Some(ref t) = self.independent_segments_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
if let Some(ref t) = self.start { if let Some(ref t) = self.start_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
for segment in &self.segments { for segment in &self.segments {
writeln!(f, "{}", segment)?; writeln!(f, "{}", segment)?;
} }
if let Some(ref t) = self.end_list { if let Some(ref t) = self.end_list_tag {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
} }
Ok(()) Ok(())
@ -77,18 +277,11 @@ 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> {
let mut version = None; let mut builder = MediaPlaylistBuilder::new();
let mut target_duration = None;
let mut media_sequence = None;
let mut discontinuity_sequence = None;
let mut playlist_type = None;
let mut i_frames_only = None;
let mut independent_segments = None;
let mut start = None;
let mut end_list = None;
let mut segment = MediaSegmentBuilder::new(); let mut segment = MediaSegmentBuilder::new();
let mut segments = Vec::new(); let mut segments = Vec::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(s).enumerate() {
match track!(line)? { match track!(line)? {
Line::Blank | Line::Comment(_) => {} Line::Blank | Line::Comment(_) => {}
@ -98,59 +291,81 @@ impl FromStr for MediaPlaylist {
continue; continue;
} }
match tag { match tag {
Tag::ExtM3u(_) => unreachable!(), Tag::ExtM3u(_) => track_panic!(ErrorKind::InvalidInput),
Tag::ExtXVersion(t) => { Tag::ExtXVersion(t) => {
track_assert_eq!(version, None, ErrorKind::InvalidInput); track_assert_eq!(builder.version, None, ErrorKind::InvalidInput);
version = Some(t); builder.version(t.version());
} }
Tag::ExtInf(t) => { Tag::ExtInf(t) => {
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXByteRange(t) => { Tag::ExtXByteRange(t) => {
// TODO: If o is not present, a previous Media Segment MUST appear in the has_partial_segment = true;
// Playlist file and MUST be a sub-range of the same media resource, or
// the Media Segment is undefined and the client MUST fail to parse the
// Playlist.
segment.tag(t); segment.tag(t);
} }
Tag::ExtXDiscontinuity(t) => { Tag::ExtXDiscontinuity(t) => {
has_discontinuity_tag = true;
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXKey(t) => { Tag::ExtXKey(t) => {
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXMap(t) => { Tag::ExtXMap(t) => {
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXProgramDateTime(t) => { Tag::ExtXProgramDateTime(t) => {
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXDateRange(t) => { Tag::ExtXDateRange(t) => {
has_partial_segment = true;
segment.tag(t); segment.tag(t);
} }
Tag::ExtXTargetDuration(t) => { Tag::ExtXTargetDuration(t) => {
track_assert_eq!(target_duration, None, ErrorKind::InvalidInput); track_assert_eq!(
target_duration = Some(t); builder.target_duration_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
} }
Tag::ExtXMediaSequence(t) => { Tag::ExtXMediaSequence(t) => {
track_assert_eq!(media_sequence, None, ErrorKind::InvalidInput); track_assert_eq!(
media_sequence = Some(t); builder.media_sequence_tag,
None,
ErrorKind::InvalidInput
);
track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
builder.tag(t);
} }
Tag::ExtXDiscontinuitySequence(t) => { Tag::ExtXDiscontinuitySequence(t) => {
track_assert_eq!(discontinuity_sequence, None, ErrorKind::InvalidInput); track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
discontinuity_sequence = Some(t); track_assert!(!has_discontinuity_tag, ErrorKind::InvalidInput);
builder.tag(t);
} }
Tag::ExtXEndList(t) => { Tag::ExtXEndList(t) => {
track_assert_eq!(end_list, None, ErrorKind::InvalidInput); track_assert_eq!(builder.end_list_tag, None, ErrorKind::InvalidInput);
end_list = Some(t); builder.tag(t);
} }
Tag::ExtXPlaylistType(t) => { Tag::ExtXPlaylistType(t) => {
track_assert_eq!(playlist_type, None, ErrorKind::InvalidInput); track_assert_eq!(
playlist_type = Some(t); builder.playlist_type_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
} }
Tag::ExtXIFramesOnly(t) => { Tag::ExtXIFramesOnly(t) => {
track_assert_eq!(i_frames_only, None, ErrorKind::InvalidInput); track_assert_eq!(
i_frames_only = Some(t); builder.i_frames_only_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
} }
Tag::ExtXMedia(_) Tag::ExtXMedia(_)
| Tag::ExtXStreamInf(_) | Tag::ExtXStreamInf(_)
@ -160,12 +375,16 @@ impl FromStr for MediaPlaylist {
track_panic!(ErrorKind::InvalidInput, "{}", tag) track_panic!(ErrorKind::InvalidInput, "{}", tag)
} }
Tag::ExtXIndependentSegments(t) => { Tag::ExtXIndependentSegments(t) => {
track_assert_eq!(independent_segments, None, ErrorKind::InvalidInput); track_assert_eq!(
independent_segments = Some(t); builder.independent_segments_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
} }
Tag::ExtXStart(t) => { Tag::ExtXStart(t) => {
track_assert_eq!(start, None, ErrorKind::InvalidInput); track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput);
start = Some(t); builder.tag(t);
} }
Tag::Unknown(_) => { Tag::Unknown(_) => {
// [6.3.1. General Client Responsibilities] // [6.3.1. General Client Responsibilities]
@ -177,23 +396,11 @@ impl FromStr for MediaPlaylist {
segment.uri(uri); segment.uri(uri);
segments.push(track!(segment.finish())?); segments.push(track!(segment.finish())?);
segment = MediaSegmentBuilder::new(); segment = MediaSegmentBuilder::new();
has_partial_segment = false;
} }
} }
} }
track_assert!(!has_partial_segment, ErrorKind::InvalidInput);
let target_duration = track_assert_some!(target_duration, ErrorKind::InvalidInput); track!(builder.finish())
// TODO: check compatibility
Ok(MediaPlaylist {
version: version.unwrap_or_else(|| ExtXVersion::new(ProtocolVersion::V1)),
target_duration,
media_sequence,
discontinuity_sequence,
playlist_type,
i_frames_only,
independent_segments,
start,
segments,
end_list,
})
} }
} }

View file

@ -4,7 +4,7 @@ use std::iter;
use {ErrorKind, Result}; use {ErrorKind, Result};
use tag::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, use tag::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap,
ExtXProgramDateTime, MediaSegmentTag}; ExtXProgramDateTime, MediaSegmentTag};
use types::SingleLineString; use types::{ProtocolVersion, SingleLineString};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MediaSegmentBuilder { pub struct MediaSegmentBuilder {
@ -78,13 +78,17 @@ impl fmt::Display for MediaSegment {
} }
} }
impl MediaSegment { impl MediaSegment {
pub fn requires_version(&self) -> ProtocolVersion {
// TODO:
ProtocolVersion::V1
}
pub fn uri(&self) -> &str { pub fn uri(&self) -> &str {
&self.uri &self.uri
} }
pub fn inf(&self) -> &ExtInf { pub fn inf(&self) -> &ExtInf {
&self.ext_inf &self.ext_inf
} }
pub fn byte_range(&self) -> Option<&ExtXByteRange> { pub fn byte_range_tag(&self) -> Option<&ExtXByteRange> {
self.tags.iter().filter_map(|t| t.as_byte_range()).nth(0) self.tags.iter().filter_map(|t| t.as_byte_range()).nth(0)
} }
pub fn date_range(&self) -> Option<&ExtXDateRange> { pub fn date_range(&self) -> Option<&ExtXDateRange> {

View file

@ -62,6 +62,33 @@ impl_from!(MasterPlaylistTag, ExtXSessionKey);
impl_from!(MasterPlaylistTag, ExtXIndependentSegments); impl_from!(MasterPlaylistTag, ExtXIndependentSegments);
impl_from!(MasterPlaylistTag, ExtXStart); impl_from!(MasterPlaylistTag, ExtXStart);
/// [4.3.3. Media Playlist Tags]
///
/// See also [4.3.5. Media or Master Playlist Tags]
///
/// [4.3.3. Media Playlist Tags] https://tools.ietf.org/html/rfc8216#section-4.3.3
/// [4.3.5. Media or Master Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3.5
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MediaPlaylistTag {
ExtXTargetDuration(ExtXTargetDuration),
ExtXMediaSequence(ExtXMediaSequence),
ExtXDiscontinuitySequence(ExtXDiscontinuitySequence),
ExtXEndList(ExtXEndList),
ExtXPlaylistType(ExtXPlaylistType),
ExtXIFramesOnly(ExtXIFramesOnly),
ExtXIndependentSegments(ExtXIndependentSegments),
ExtXStart(ExtXStart),
}
impl_from!(MediaPlaylistTag, ExtXTargetDuration);
impl_from!(MediaPlaylistTag, ExtXMediaSequence);
impl_from!(MediaPlaylistTag, ExtXDiscontinuitySequence);
impl_from!(MediaPlaylistTag, ExtXEndList);
impl_from!(MediaPlaylistTag, ExtXPlaylistType);
impl_from!(MediaPlaylistTag, ExtXIFramesOnly);
impl_from!(MediaPlaylistTag, ExtXIndependentSegments);
impl_from!(MediaPlaylistTag, ExtXStart);
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum MediaSegmentTag { pub enum MediaSegmentTag {