1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-02 13:39:27 +00:00
hls_m3u8/src/media_playlist.rs

405 lines
16 KiB
Rust
Raw Normal View History

2018-02-13 15:25:33 +00:00
use std::fmt;
2018-02-14 19:18:02 +00:00
use std::iter;
2018-02-13 15:25:33 +00:00
use std::str::FromStr;
2018-02-14 19:18:02 +00:00
use std::time::Duration;
2018-02-13 15:25:33 +00:00
2018-02-14 15:50:57 +00:00
use line::{Line, Lines, Tag};
2018-02-13 15:25:33 +00:00
use media_segment::{MediaSegment, MediaSegmentBuilder};
2018-10-04 11:18:56 +00:00
use tags::{
ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXIndependentSegments,
ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXTargetDuration, ExtXVersion,
MediaPlaylistTag,
};
2018-02-14 03:16:36 +00:00
use types::ProtocolVersion;
2018-10-04 11:18:56 +00:00
use {Error, ErrorKind, Result};
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// Media playlist builder.
#[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`
2018-02-14 19:47:44 +00:00
let segment_duration = s.inf_tag().duration();
2018-02-14 19:18:02 +00:00
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()),
2018-10-04 11:18:56 +00:00
).chain(self.media_sequence_tag.iter().map(|t| t.requires_version()))
2018-02-14 19:18:02 +00:00
.chain(
self.discontinuity_sequence_tag
.iter()
.map(|t| t.requires_version()),
2018-10-04 11:18:56 +00:00
).chain(self.playlist_type_tag.iter().map(|t| t.requires_version()))
2018-02-14 19:18:02 +00:00
.chain(self.i_frames_only_tag.iter().map(|t| t.requires_version()))
.chain(
self.independent_segments_tag
.iter()
.map(|t| t.requires_version()),
2018-10-04 11:18:56 +00:00
).chain(self.start_tag.iter().map(|t| t.requires_version()))
2018-02-14 19:18:02 +00:00
.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.
2018-02-13 15:25:33 +00:00
#[derive(Debug, Clone)]
pub struct MediaPlaylist {
2018-02-14 19:18:02 +00:00
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
}
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
pub 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.
pub fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> {
self.media_sequence_tag
}
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the playlist.
pub fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
self.discontinuity_sequence_tag
}
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
pub fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> {
self.playlist_type_tag
}
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// 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
}
/// 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
}
2018-02-13 15:25:33 +00:00
2018-02-14 19:18:02 +00:00
/// Returns the media segments contained in the playlist.
pub fn segments(&self) -> &[MediaSegment] {
&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)?;
2018-02-14 19:18:02 +00:00
if self.version_tag.version() != ProtocolVersion::V1 {
writeln!(f, "{}", self.version_tag)?;
2018-02-13 15:25:33 +00:00
}
2018-02-14 19:18:02 +00:00
writeln!(f, "{}", self.target_duration_tag)?;
if let Some(ref t) = self.media_sequence_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.discontinuity_sequence_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.playlist_type_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.i_frames_only_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.independent_segments_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.start_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
for segment in &self.segments {
writeln!(f, "{}", segment)?;
}
2018-02-14 19:18:02 +00:00
if let Some(ref t) = self.end_list_tag {
2018-02-13 15:25:33 +00:00
writeln!(f, "{}", t)?;
}
Ok(())
}
}
impl FromStr for MediaPlaylist {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
2018-02-14 19:18:02 +00:00
let mut builder = MediaPlaylistBuilder::new();
2018-02-13 15:25:33 +00:00
let mut segment = MediaSegmentBuilder::new();
2018-02-14 19:18:02 +00:00
let mut has_partial_segment = false;
let mut has_discontinuity_tag = false;
2018-02-13 15:25:33 +00:00
for (i, line) in Lines::new(s).enumerate() {
match track!(line)? {
Line::Blank | Line::Comment(_) => {}
Line::Tag(tag) => {
if i == 0 {
track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput);
continue;
}
match tag {
2018-02-14 19:18:02 +00:00
Tag::ExtM3u(_) => track_panic!(ErrorKind::InvalidInput),
2018-02-13 15:25:33 +00:00
Tag::ExtXVersion(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(builder.version, None, ErrorKind::InvalidInput);
builder.version(t.version());
2018-02-13 15:25:33 +00:00
}
Tag::ExtInf(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXByteRange(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXDiscontinuity(t) => {
2018-02-14 19:18:02 +00:00
has_discontinuity_tag = true;
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXKey(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXMap(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXProgramDateTime(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXDateRange(t) => {
2018-02-14 19:18:02 +00:00
has_partial_segment = true;
2018-02-13 15:25:33 +00:00
segment.tag(t);
}
Tag::ExtXTargetDuration(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(
builder.target_duration_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXMediaSequence(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(
builder.media_sequence_tag,
None,
ErrorKind::InvalidInput
);
track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXDiscontinuitySequence(t) => {
2018-02-14 19:18:02 +00:00
track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
track_assert!(!has_discontinuity_tag, ErrorKind::InvalidInput);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXEndList(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(builder.end_list_tag, None, ErrorKind::InvalidInput);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXPlaylistType(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(
builder.playlist_type_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXIFramesOnly(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(
builder.i_frames_only_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXMedia(_)
| Tag::ExtXStreamInf(_)
| Tag::ExtXIFrameStreamInf(_)
| Tag::ExtXSessionData(_)
| Tag::ExtXSessionKey(_) => {
track_panic!(ErrorKind::InvalidInput, "{}", tag)
}
Tag::ExtXIndependentSegments(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(
builder.independent_segments_tag,
None,
ErrorKind::InvalidInput
);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
Tag::ExtXStart(t) => {
2018-02-14 19:18:02 +00:00
track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput);
builder.tag(t);
2018-02-13 15:25:33 +00:00
}
2018-02-14 15:50:57 +00:00
Tag::Unknown(_) => {
// [6.3.1. General Client Responsibilities]
// > ignore any unrecognized tags.
}
2018-02-13 15:25:33 +00:00
}
}
Line::Uri(uri) => {
2018-02-14 15:50:57 +00:00
segment.uri(uri);
2018-02-14 19:47:44 +00:00
builder.segment(track!(segment.finish())?);
2018-02-13 15:25:33 +00:00
segment = MediaSegmentBuilder::new();
2018-02-14 19:18:02 +00:00
has_partial_segment = false;
2018-02-13 15:25:33 +00:00
}
}
}
2018-02-14 19:18:02 +00:00
track_assert!(!has_partial_segment, ErrorKind::InvalidInput);
track!(builder.finish())
2018-02-13 15:25:33 +00:00
}
}