2019-09-13 14:06:52 +00:00
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::fmt;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
2019-09-14 11:26:16 +00:00
|
|
|
use derive_builder::Builder;
|
|
|
|
|
2019-03-31 09:58:11 +00:00
|
|
|
use crate::line::{Line, Lines, Tag};
|
|
|
|
use crate::tags::{
|
|
|
|
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
2019-09-14 11:26:16 +00:00
|
|
|
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion,
|
2019-03-31 09:58:11 +00:00
|
|
|
};
|
2019-10-04 09:02:21 +00:00
|
|
|
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
|
|
|
|
use crate::{Error, RequiredVersion};
|
2018-02-14 01:31:24 +00:00
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
#[derive(Debug, Clone, Builder, PartialEq)]
|
2019-09-14 11:26:16 +00:00
|
|
|
#[builder(build_fn(validate = "Self::validate"))]
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(setter(into, strip_option))]
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Master playlist.
|
2018-02-14 01:31:24 +00:00
|
|
|
pub struct MasterPlaylist {
|
2019-10-05 10:49:08 +00:00
|
|
|
#[builder(default, setter(skip))]
|
2018-02-14 17:52:56 +00:00
|
|
|
version_tag: ExtXVersion,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Sets the [`ExtXIndependentSegments`] tag.
|
2018-02-14 17:52:56 +00:00
|
|
|
independent_segments_tag: Option<ExtXIndependentSegments>,
|
2019-09-14 19:08:35 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Sets the [`ExtXStart`] tag.
|
2018-02-14 17:52:56 +00:00
|
|
|
start_tag: Option<ExtXStart>,
|
2019-10-05 07:44:23 +00:00
|
|
|
#[builder(default)]
|
|
|
|
/// Sets the [`ExtXMedia`] tag.
|
2018-02-14 17:52:56 +00:00
|
|
|
media_tags: Vec<ExtXMedia>,
|
2019-10-05 07:44:23 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Sets all [`ExtXStreamInf`] tags.
|
2018-02-14 17:52:56 +00:00
|
|
|
stream_inf_tags: Vec<ExtXStreamInf>,
|
2019-10-05 07:44:23 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Sets all [`ExtXIFrameStreamInf`] tags.
|
2018-02-14 17:52:56 +00:00
|
|
|
i_frame_stream_inf_tags: Vec<ExtXIFrameStreamInf>,
|
2019-10-05 07:44:23 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Sets all [`ExtXSessionData`] tags.
|
2018-02-14 17:52:56 +00:00
|
|
|
session_data_tags: Vec<ExtXSessionData>,
|
2019-10-05 07:44:23 +00:00
|
|
|
#[builder(default)]
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Sets all [`ExtXSessionKey`] tags.
|
2018-02-14 17:52:56 +00:00
|
|
|
session_key_tags: Vec<ExtXSessionKey>,
|
|
|
|
}
|
2019-09-08 09:30:52 +00:00
|
|
|
|
2018-02-14 17:52:56 +00:00
|
|
|
impl MasterPlaylist {
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns a Builder for a [`MasterPlaylist`].
|
2019-10-03 15:01:15 +00:00
|
|
|
pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() }
|
2019-09-14 11:26:16 +00:00
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
|
2019-10-05 10:49:08 +00:00
|
|
|
pub const fn independent_segments(&self) -> Option<ExtXIndependentSegments> {
|
2018-02-14 17:52:56 +00:00
|
|
|
self.independent_segments_tag
|
|
|
|
}
|
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Sets the [`ExtXIndependentSegments`] tag contained in the playlist.
|
|
|
|
pub fn set_independent_segments<T>(&mut self, value: Option<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXIndependentSegments>,
|
|
|
|
{
|
|
|
|
self.independent_segments_tag = value.map(|v| v.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXStart`] tag contained in the playlist.
|
2019-10-05 10:49:08 +00:00
|
|
|
pub const fn start(&self) -> Option<ExtXStart> { self.start_tag }
|
|
|
|
|
|
|
|
/// Sets the [`ExtXStart`] tag contained in the playlist.
|
|
|
|
pub fn set_start<T>(&mut self, value: Option<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXStart>,
|
|
|
|
{
|
|
|
|
self.start_tag = value.map(|v| v.into());
|
|
|
|
self
|
|
|
|
}
|
2018-02-14 17:52:56 +00:00
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXMedia`] tags contained in the playlist.
|
|
|
|
pub const fn media_tags(&self) -> &Vec<ExtXMedia> { &self.media_tags }
|
2018-02-14 17:52:56 +00:00
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Appends an [`ExtXMedia`].
|
|
|
|
pub fn push_media_tag(&mut self, value: ExtXMedia) -> &mut Self {
|
|
|
|
self.media_tags.push(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the [`ExtXMedia`] tags contained in the playlist.
|
|
|
|
pub fn set_media_tags<T>(&mut self, value: Vec<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXMedia>,
|
|
|
|
{
|
|
|
|
self.media_tags = value.into_iter().map(|v| v.into()).collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXStreamInf`] tags contained in the playlist.
|
|
|
|
pub const fn stream_inf_tags(&self) -> &Vec<ExtXStreamInf> { &self.stream_inf_tags }
|
2018-02-14 17:52:56 +00:00
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Appends an [`ExtXStreamInf`].
|
|
|
|
pub fn push_stream_inf(&mut self, value: ExtXStreamInf) -> &mut Self {
|
|
|
|
self.stream_inf_tags.push(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the [`ExtXStreamInf`] tags contained in the playlist.
|
|
|
|
pub fn set_stream_inf_tags<T>(&mut self, value: Vec<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXStreamInf>,
|
|
|
|
{
|
|
|
|
self.stream_inf_tags = value.into_iter().map(|v| v.into()).collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXIFrameStreamInf`] tags contained in the playlist.
|
|
|
|
pub const fn i_frame_stream_inf_tags(&self) -> &Vec<ExtXIFrameStreamInf> {
|
2018-02-14 17:52:56 +00:00
|
|
|
&self.i_frame_stream_inf_tags
|
|
|
|
}
|
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Appends an [`ExtXIFrameStreamInf`].
|
|
|
|
pub fn push_i_frame_stream_inf(&mut self, value: ExtXIFrameStreamInf) -> &mut Self {
|
|
|
|
self.i_frame_stream_inf_tags.push(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the [`ExtXIFrameStreamInf`] tags contained in the playlist.
|
|
|
|
pub fn set_i_frame_stream_inf_tags<T>(&mut self, value: Vec<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXIFrameStreamInf>,
|
|
|
|
{
|
|
|
|
self.i_frame_stream_inf_tags = value.into_iter().map(|v| v.into()).collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXSessionData`] tags contained in the playlist.
|
|
|
|
pub const fn session_data_tags(&self) -> &Vec<ExtXSessionData> { &self.session_data_tags }
|
2018-02-14 17:52:56 +00:00
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// Appends an [`ExtXSessionData`].
|
|
|
|
pub fn push_session_data(&mut self, value: ExtXSessionData) -> &mut Self {
|
|
|
|
self.session_data_tags.push(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the [`ExtXSessionData`] tags contained in the playlist.
|
|
|
|
pub fn set_session_data_tags<T>(&mut self, value: Vec<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXSessionData>,
|
|
|
|
{
|
|
|
|
self.session_data_tags = value.into_iter().map(|v| v.into()).collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:44:23 +00:00
|
|
|
/// Returns the [`ExtXSessionKey`] tags contained in the playlist.
|
|
|
|
pub const fn session_key_tags(&self) -> &Vec<ExtXSessionKey> { &self.session_key_tags }
|
2019-10-05 10:49:08 +00:00
|
|
|
|
|
|
|
/// Appends an [`ExtXSessionKey`].
|
|
|
|
pub fn push_session_key(&mut self, value: ExtXSessionKey) -> &mut Self {
|
|
|
|
self.session_key_tags.push(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the [`ExtXSessionKey`] tags contained in the playlist.
|
|
|
|
pub fn set_session_key_tags<T>(&mut self, value: Vec<T>) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Into<ExtXSessionKey>,
|
|
|
|
{
|
|
|
|
self.session_key_tags = value.into_iter().map(|v| v.into()).collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! required_version {
|
|
|
|
( $( $tag:expr ),* ) => {
|
|
|
|
::core::iter::empty()
|
|
|
|
$(
|
|
|
|
.chain(::core::iter::once($tag.required_version()))
|
|
|
|
)*
|
|
|
|
.max()
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
2019-09-08 09:30:52 +00:00
|
|
|
|
2019-09-22 08:57:28 +00:00
|
|
|
impl RequiredVersion for MasterPlaylist {
|
2019-10-05 10:49:08 +00:00
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
|
|
|
required_version![
|
|
|
|
self.independent_segments_tag,
|
|
|
|
self.start_tag,
|
|
|
|
self.media_tags,
|
|
|
|
self.stream_inf_tags,
|
|
|
|
self.i_frame_stream_inf_tags,
|
|
|
|
self.session_data_tags,
|
|
|
|
self.session_key_tags
|
|
|
|
]
|
|
|
|
}
|
2019-09-22 08:57:28 +00:00
|
|
|
}
|
|
|
|
|
2019-09-14 11:26:16 +00:00
|
|
|
impl MasterPlaylistBuilder {
|
2019-09-14 19:08:35 +00:00
|
|
|
fn validate(&self) -> Result<(), String> {
|
|
|
|
self.validate_stream_inf_tags().map_err(|e| e.to_string())?;
|
|
|
|
self.validate_i_frame_stream_inf_tags()
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
self.validate_session_data_tags()
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn validate_stream_inf_tags(&self) -> crate::Result<()> {
|
|
|
|
if let Some(value) = &self.stream_inf_tags {
|
2019-09-14 11:26:16 +00:00
|
|
|
let mut has_none_closed_captions = false;
|
2019-09-14 19:08:35 +00:00
|
|
|
|
|
|
|
for t in value {
|
2019-09-14 11:26:16 +00:00
|
|
|
if let Some(group_id) = t.audio() {
|
|
|
|
if !self.check_media_group(MediaType::Audio, group_id) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::unmatched_group(group_id));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(group_id) = t.video() {
|
|
|
|
if !self.check_media_group(MediaType::Video, group_id) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::unmatched_group(group_id));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(group_id) = t.subtitles() {
|
|
|
|
if !self.check_media_group(MediaType::Subtitles, group_id) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::unmatched_group(group_id));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
match t.closed_captions() {
|
2019-09-22 18:33:40 +00:00
|
|
|
&Some(ClosedCaptions::GroupId(ref group_id)) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::unmatched_group(group_id));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-22 18:33:40 +00:00
|
|
|
&Some(ClosedCaptions::None) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
has_none_closed_captions = true;
|
|
|
|
}
|
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
}
|
2019-09-22 18:33:40 +00:00
|
|
|
if has_none_closed_captions
|
|
|
|
&& !value
|
2019-09-14 11:26:16 +00:00
|
|
|
.iter()
|
2019-09-22 18:33:40 +00:00
|
|
|
.all(|t| t.closed_captions() == &Some(ClosedCaptions::None))
|
|
|
|
{
|
|
|
|
return Err(Error::invalid_input());
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2019-09-14 11:26:16 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
fn validate_i_frame_stream_inf_tags(&self) -> crate::Result<()> {
|
|
|
|
if let Some(value) = &self.i_frame_stream_inf_tags {
|
|
|
|
for t in value {
|
2019-09-14 11:26:16 +00:00
|
|
|
if let Some(group_id) = t.video() {
|
|
|
|
if !self.check_media_group(MediaType::Video, group_id) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::unmatched_group(group_id));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2019-09-14 11:26:16 +00:00
|
|
|
|
2019-09-14 19:08:35 +00:00
|
|
|
fn validate_session_data_tags(&self) -> crate::Result<()> {
|
|
|
|
let mut set = HashSet::new();
|
|
|
|
if let Some(value) = &self.session_data_tags {
|
|
|
|
for t in value {
|
2019-09-14 11:26:16 +00:00
|
|
|
if !set.insert((t.data_id(), t.language())) {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::custom(format!("Conflict: {}", t)));
|
2019-09-14 11:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2019-09-14 11:26:16 +00:00
|
|
|
|
|
|
|
fn check_media_group<T: ToString>(&self, media_type: MediaType, group_id: T) -> bool {
|
2019-09-14 19:08:35 +00:00
|
|
|
if let Some(value) = &self.media_tags {
|
|
|
|
value
|
2019-09-14 11:26:16 +00:00
|
|
|
.iter()
|
|
|
|
.any(|t| t.media_type() == media_type && t.group_id() == &group_id.to_string())
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
impl RequiredVersion for MasterPlaylistBuilder {
|
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
|
|
|
// TODO: the .flatten() can be removed as soon as `recursive traits` are
|
|
|
|
// supported. (RequiredVersion is implemented for Option<T>, but
|
|
|
|
// not for Option<Option<T>>)
|
|
|
|
// https://github.com/rust-lang/chalk/issues/12
|
|
|
|
required_version![
|
|
|
|
self.independent_segments_tag.flatten(),
|
|
|
|
self.start_tag.flatten(),
|
|
|
|
self.media_tags,
|
|
|
|
self.stream_inf_tags,
|
|
|
|
self.i_frame_stream_inf_tags,
|
|
|
|
self.session_data_tags,
|
|
|
|
self.session_key_tags
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-14 01:31:24 +00:00
|
|
|
impl fmt::Display for MasterPlaylist {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f, "{}", ExtM3u)?;
|
2019-10-05 10:49:08 +00:00
|
|
|
if self.required_version() != ProtocolVersion::V1 {
|
|
|
|
writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
for t in &self.media_tags {
|
|
|
|
writeln!(f, "{}", t)?;
|
|
|
|
}
|
2018-02-14 17:52:56 +00:00
|
|
|
for t in &self.stream_inf_tags {
|
|
|
|
writeln!(f, "{}", t)?;
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
2018-02-14 17:52:56 +00:00
|
|
|
for t in &self.i_frame_stream_inf_tags {
|
2018-02-14 01:31:24 +00:00
|
|
|
writeln!(f, "{}", t)?;
|
|
|
|
}
|
|
|
|
for t in &self.session_data_tags {
|
|
|
|
writeln!(f, "{}", t)?;
|
|
|
|
}
|
2018-02-14 17:52:56 +00:00
|
|
|
for t in &self.session_key_tags {
|
2018-02-14 01:31:24 +00:00
|
|
|
writeln!(f, "{}", t)?;
|
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.independent_segments_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.start_tag {
|
|
|
|
writeln!(f, "{}", value)?;
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 14:06:52 +00:00
|
|
|
|
2018-02-14 01:31:24 +00:00
|
|
|
impl FromStr for MasterPlaylist {
|
|
|
|
type Err = Error;
|
2019-09-14 09:57:56 +00:00
|
|
|
|
|
|
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
2019-09-14 11:26:16 +00:00
|
|
|
let mut builder = MasterPlaylist::builder();
|
|
|
|
|
|
|
|
let mut media_tags = vec![];
|
|
|
|
let mut stream_inf_tags = vec![];
|
|
|
|
let mut i_frame_stream_inf_tags = vec![];
|
|
|
|
let mut session_data_tags = vec![];
|
|
|
|
let mut session_key_tags = vec![];
|
|
|
|
|
2019-09-14 09:57:56 +00:00
|
|
|
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
|
|
|
|
match line {
|
2018-02-14 01:31:24 +00:00
|
|
|
Line::Tag(tag) => {
|
|
|
|
if i == 0 {
|
2019-09-13 14:06:52 +00:00
|
|
|
if tag != Tag::ExtM3u(ExtM3u) {
|
|
|
|
return Err(Error::invalid_input());
|
|
|
|
}
|
2018-02-14 01:31:24 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match tag {
|
2018-02-14 17:52:56 +00:00
|
|
|
Tag::ExtM3u(_) => {
|
2019-09-13 14:06:52 +00:00
|
|
|
return Err(Error::invalid_input());
|
2018-02-14 17:52:56 +00:00
|
|
|
}
|
2019-10-05 10:49:08 +00:00
|
|
|
Tag::ExtXVersion(_) => {
|
|
|
|
// This tag can be ignored, because the
|
|
|
|
// MasterPlaylist will automatically set the
|
|
|
|
// ExtXVersion tag to correct version!
|
|
|
|
|
|
|
|
// builder.version(t.version());
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtInf(_)
|
|
|
|
| Tag::ExtXByteRange(_)
|
|
|
|
| Tag::ExtXDiscontinuity(_)
|
|
|
|
| Tag::ExtXKey(_)
|
|
|
|
| Tag::ExtXMap(_)
|
|
|
|
| Tag::ExtXProgramDateTime(_)
|
|
|
|
| Tag::ExtXDateRange(_)
|
|
|
|
| Tag::ExtXTargetDuration(_)
|
|
|
|
| Tag::ExtXMediaSequence(_)
|
|
|
|
| Tag::ExtXDiscontinuitySequence(_)
|
|
|
|
| Tag::ExtXEndList(_)
|
|
|
|
| Tag::ExtXPlaylistType(_)
|
|
|
|
| Tag::ExtXIFramesOnly(_) => {
|
2019-09-14 19:08:35 +00:00
|
|
|
return Err(Error::custom(format!(
|
|
|
|
"This tag isn't allowed in a master playlist: {}",
|
|
|
|
tag
|
|
|
|
)));
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXMedia(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
media_tags.push(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXStreamInf(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
stream_inf_tags.push(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXIFrameStreamInf(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
i_frame_stream_inf_tags.push(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXSessionData(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
session_data_tags.push(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXSessionKey(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
session_key_tags.push(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXIndependentSegments(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
builder.independent_segments_tag(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
Tag::ExtXStart(t) => {
|
2019-09-14 11:26:16 +00:00
|
|
|
builder.start_tag(t);
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
2019-09-14 19:08:35 +00:00
|
|
|
_ => {
|
2018-02-14 15:50:57 +00:00
|
|
|
// [6.3.1. General Client Responsibilities]
|
|
|
|
// > ignore any unrecognized tags.
|
2019-09-13 14:06:52 +00:00
|
|
|
// TODO: collect custom tags
|
2018-02-14 15:50:57 +00:00
|
|
|
}
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Line::Uri(uri) => {
|
2019-09-13 14:06:52 +00:00
|
|
|
return Err(Error::custom(format!("Unexpected URI: {:?}", uri)));
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 11:26:16 +00:00
|
|
|
|
|
|
|
builder.media_tags(media_tags);
|
|
|
|
builder.stream_inf_tags(stream_inf_tags);
|
|
|
|
builder.i_frame_stream_inf_tags(i_frame_stream_inf_tags);
|
|
|
|
builder.session_data_tags(session_data_tags);
|
|
|
|
builder.session_key_tags(session_key_tags);
|
|
|
|
|
|
|
|
builder.build().map_err(Error::builder_error)
|
2018-02-14 01:31:24 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-14 09:57:56 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2019-09-14 10:34:34 +00:00
|
|
|
fn test_parser() {
|
2019-10-05 10:49:08 +00:00
|
|
|
"#EXTM3U\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/low/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/lo_mid/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/hi_mid/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
|
|
|
|
http://example.com/high/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
|
|
|
|
http://example.com/audio/index.m3u8\n"
|
|
|
|
.parse::<MasterPlaylist>()
|
|
|
|
.unwrap();
|
2019-09-14 10:34:34 +00:00
|
|
|
}
|
2019-09-14 11:26:16 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_display() {
|
2019-10-05 10:49:08 +00:00
|
|
|
let input = "#EXTM3U\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/low/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/lo_mid/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
|
|
|
|
http://example.com/hi_mid/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
|
|
|
|
http://example.com/high/index.m3u8\n\
|
|
|
|
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
|
|
|
|
http://example.com/audio/index.m3u8\n";
|
|
|
|
|
2019-09-14 11:26:16 +00:00
|
|
|
let playlist = input.parse::<MasterPlaylist>().unwrap();
|
|
|
|
assert_eq!(playlist.to_string(), input);
|
|
|
|
}
|
2019-09-14 09:57:56 +00:00
|
|
|
}
|