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

made master playlist smarter

This commit is contained in:
Luro02 2019-10-05 12:49:08 +02:00
parent 5b44262dc8
commit f76b223482
11 changed files with 539 additions and 137 deletions

View file

@ -1,6 +1,5 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::iter;
use std::str::FromStr; use std::str::FromStr;
use derive_builder::Builder; use derive_builder::Builder;
@ -13,19 +12,13 @@ use crate::tags::{
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// Master playlist. #[derive(Debug, Clone, Builder, PartialEq)]
#[derive(Debug, Clone, Builder)]
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
/// Master playlist.
pub struct MasterPlaylist { pub struct MasterPlaylist {
#[builder(default, setter(name = "version"))] //#[builder(default, setter(name = "version"))]
/// Sets the protocol compatibility version of the resulting playlist. #[builder(default, setter(skip))]
///
/// If the resulting playlist has tags which requires a compatibility
/// version greater than `version`,
/// `build()` method will fail with an `ErrorKind::InvalidInput` error.
///
/// The default is the maximum version among the tags in the playlist.
version_tag: ExtXVersion, version_tag: ExtXVersion,
#[builder(default)] #[builder(default)]
/// Sets the [`ExtXIndependentSegments`] tag. /// Sets the [`ExtXIndependentSegments`] tag.
@ -37,16 +30,16 @@ pub struct MasterPlaylist {
/// Sets the [`ExtXMedia`] tag. /// Sets the [`ExtXMedia`] tag.
media_tags: Vec<ExtXMedia>, media_tags: Vec<ExtXMedia>,
#[builder(default)] #[builder(default)]
/// Sets all [`ExtXStreamInf`]s. /// Sets all [`ExtXStreamInf`] tags.
stream_inf_tags: Vec<ExtXStreamInf>, stream_inf_tags: Vec<ExtXStreamInf>,
#[builder(default)] #[builder(default)]
/// Sets all [`ExtXIFrameStreamInf`]s. /// Sets all [`ExtXIFrameStreamInf`] tags.
i_frame_stream_inf_tags: Vec<ExtXIFrameStreamInf>, i_frame_stream_inf_tags: Vec<ExtXIFrameStreamInf>,
#[builder(default)] #[builder(default)]
/// Sets all [`ExtXSessionData`]s. /// Sets all [`ExtXSessionData`] tags.
session_data_tags: Vec<ExtXSessionData>, session_data_tags: Vec<ExtXSessionData>,
#[builder(default)] #[builder(default)]
/// Sets all [`ExtXSessionKey`]s. /// Sets all [`ExtXSessionKey`] tags.
session_key_tags: Vec<ExtXSessionKey>, session_key_tags: Vec<ExtXSessionKey>,
} }
@ -54,48 +47,152 @@ impl MasterPlaylist {
/// Returns a Builder for a [`MasterPlaylist`]. /// Returns a Builder for a [`MasterPlaylist`].
pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() } pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() }
/// Returns the [`ExtXVersion`] tag contained in the playlist.
pub const fn version(&self) -> ExtXVersion { self.version_tag }
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist. /// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> { pub const fn independent_segments(&self) -> Option<ExtXIndependentSegments> {
self.independent_segments_tag self.independent_segments_tag
} }
/// 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
}
/// Returns the [`ExtXStart`] tag contained in the playlist. /// Returns the [`ExtXStart`] tag contained in the playlist.
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag } 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
}
/// Returns the [`ExtXMedia`] tags contained in the playlist. /// Returns the [`ExtXMedia`] tags contained in the playlist.
pub const fn media_tags(&self) -> &Vec<ExtXMedia> { &self.media_tags } pub const fn media_tags(&self) -> &Vec<ExtXMedia> { &self.media_tags }
/// 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
}
/// Returns the [`ExtXStreamInf`] tags contained in the playlist. /// Returns the [`ExtXStreamInf`] tags contained in the playlist.
pub const fn stream_inf_tags(&self) -> &Vec<ExtXStreamInf> { &self.stream_inf_tags } pub const fn stream_inf_tags(&self) -> &Vec<ExtXStreamInf> { &self.stream_inf_tags }
/// 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
}
/// Returns the [`ExtXIFrameStreamInf`] tags contained in the playlist. /// Returns the [`ExtXIFrameStreamInf`] tags contained in the playlist.
pub const fn i_frame_stream_inf_tags(&self) -> &Vec<ExtXIFrameStreamInf> { pub const fn i_frame_stream_inf_tags(&self) -> &Vec<ExtXIFrameStreamInf> {
&self.i_frame_stream_inf_tags &self.i_frame_stream_inf_tags
} }
/// 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
}
/// Returns the [`ExtXSessionData`] tags contained in the playlist. /// Returns the [`ExtXSessionData`] tags contained in the playlist.
pub const fn session_data_tags(&self) -> &Vec<ExtXSessionData> { &self.session_data_tags } pub const fn session_data_tags(&self) -> &Vec<ExtXSessionData> { &self.session_data_tags }
/// 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
}
/// Returns the [`ExtXSessionKey`] tags contained in the playlist. /// Returns the [`ExtXSessionKey`] tags contained in the playlist.
pub const fn session_key_tags(&self) -> &Vec<ExtXSessionKey> { &self.session_key_tags } pub const fn session_key_tags(&self) -> &Vec<ExtXSessionKey> { &self.session_key_tags }
/// 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()
}
} }
impl RequiredVersion for MasterPlaylist { impl RequiredVersion for MasterPlaylist {
fn required_version(&self) -> ProtocolVersion { self.version_tag.version() } 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
]
}
} }
impl MasterPlaylistBuilder { impl MasterPlaylistBuilder {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
let required_version = self.required_version();
let specified_version = self.version_tag.map_or(required_version, |p| p.version());
if required_version > specified_version {
return Err(Error::required_version(required_version, specified_version).to_string());
}
self.validate_stream_inf_tags().map_err(|e| e.to_string())?; self.validate_stream_inf_tags().map_err(|e| e.to_string())?;
self.validate_i_frame_stream_inf_tags() self.validate_i_frame_stream_inf_tags()
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
@ -105,54 +202,6 @@ impl MasterPlaylistBuilder {
Ok(()) Ok(())
} }
fn required_version(&self) -> ProtocolVersion {
iter::empty()
.chain(
self.independent_segments_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.start_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.media_tags
.iter()
.map(|t| t.iter().map(|t| t.required_version()))
.flatten(),
)
.chain(
self.stream_inf_tags
.iter()
.map(|t| t.iter().map(|t| t.required_version()))
.flatten(),
)
.chain(
self.i_frame_stream_inf_tags
.iter()
.map(|t| t.iter().map(|t| t.required_version()))
.flatten(),
)
.chain(
self.session_data_tags
.iter()
.map(|t| t.iter().map(|t| t.required_version()))
.flatten(),
)
.chain(
self.session_key_tags
.iter()
.map(|t| t.iter().map(|t| t.required_version()))
.flatten(),
)
.max()
.unwrap_or_else(ProtocolVersion::latest)
}
fn validate_stream_inf_tags(&self) -> crate::Result<()> { fn validate_stream_inf_tags(&self) -> crate::Result<()> {
if let Some(value) = &self.stream_inf_tags { if let Some(value) = &self.stream_inf_tags {
let mut has_none_closed_captions = false; let mut has_none_closed_captions = false;
@ -232,11 +281,29 @@ impl MasterPlaylistBuilder {
} }
} }
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
]
}
}
impl fmt::Display for MasterPlaylist { impl fmt::Display for MasterPlaylist {
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_tag.version() != ProtocolVersion::V1 { if self.required_version() != ProtocolVersion::V1 {
writeln!(f, "{}", self.version_tag)?; writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
} }
for t in &self.media_tags { for t in &self.media_tags {
writeln!(f, "{}", t)?; writeln!(f, "{}", t)?;
@ -288,8 +355,12 @@ impl FromStr for MasterPlaylist {
Tag::ExtM3u(_) => { Tag::ExtM3u(_) => {
return Err(Error::invalid_input()); return Err(Error::invalid_input());
} }
Tag::ExtXVersion(t) => { Tag::ExtXVersion(_) => {
builder.version(t.version()); // This tag can be ignored, because the
// MasterPlaylist will automatically set the
// ExtXVersion tag to correct version!
// builder.version(t.version());
} }
Tag::ExtInf(_) Tag::ExtInf(_)
| Tag::ExtXByteRange(_) | Tag::ExtXByteRange(_)
@ -359,36 +430,35 @@ mod tests {
#[test] #[test]
fn test_parser() { fn test_parser() {
r#"#EXTM3U "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/low/index.m3u8 http://example.com/low/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/lo_mid/index.m3u8 http://example.com/lo_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/hi_mid/index.m3u8 http://example.com/hi_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=640x360 #EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
http://example.com/high/index.m3u8 http://example.com/high/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio/index.m3u8 http://example.com/audio/index.m3u8\n"
"# .parse::<MasterPlaylist>()
.parse::<MasterPlaylist>() .unwrap();
.unwrap();
} }
#[test] #[test]
fn test_display() { fn test_display() {
let input = r#"#EXTM3U let input = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/low/index.m3u8 http://example.com/low/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/lo_mid/index.m3u8 http://example.com/lo_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 #EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\
http://example.com/hi_mid/index.m3u8 http://example.com/hi_mid/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=640x360 #EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\
http://example.com/high/index.m3u8 http://example.com/high/index.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio/index.m3u8 http://example.com/audio/index.m3u8\n";
"#;
let playlist = input.parse::<MasterPlaylist>().unwrap(); let playlist = input.parse::<MasterPlaylist>().unwrap();
assert_eq!(playlist.to_string(), input); assert_eq!(playlist.to_string(), input);
} }

View file

@ -6,6 +6,7 @@ use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.3.1.1. EXTM3U] /// # [4.3.1.1. EXTM3U]
///
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]** /// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
/// Playlist file. /// Playlist file.
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`]. /// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].

View file

@ -6,6 +6,7 @@ use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.3.1.2. EXT-X-VERSION] /// # [4.3.1.2. EXT-X-VERSION]
///
/// The [`ExtXVersion`] tag indicates the compatibility version of the /// The [`ExtXVersion`] tag indicates the compatibility version of the
/// [`Master Playlist`] or [`Media Playlist`] file. /// [`Master Playlist`] or [`Media Playlist`] file.
/// It applies to the entire Playlist. /// It applies to the entire Playlist.
@ -41,7 +42,7 @@ use crate::{Error, RequiredVersion};
/// ///
/// [`Media Playlist`]: crate::MediaPlaylist /// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist /// [`Master Playlist`]: crate::MasterPlaylist
/// [4.4.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2 /// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ExtXVersion(ProtocolVersion); pub struct ExtXVersion(ProtocolVersion);

View file

@ -3,11 +3,12 @@ use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, StreamInf}; use crate::types::{HdcpLevel, ProtocolVersion, StreamInf, StreamInfBuilder};
use crate::utils::{quote, tag, unquote}; use crate::utils::{quote, tag, unquote};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.3.5.3. EXT-X-I-FRAME-STREAM-INF] /// # [4.3.5.3. EXT-X-I-FRAME-STREAM-INF]
///
/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file, /// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,
/// containing the I-frames of a multimedia presentation. /// containing the I-frames of a multimedia presentation.
/// ///
@ -23,6 +24,72 @@ pub struct ExtXIFrameStreamInf {
stream_inf: StreamInf, stream_inf: StreamInf,
} }
#[derive(Default, Debug, Clone, PartialEq)]
/// Builder for [`ExtXIFrameStreamInf`].
pub struct ExtXIFrameStreamInfBuilder {
uri: Option<String>,
stream_inf: StreamInfBuilder,
}
impl ExtXIFrameStreamInfBuilder {
/// An `URI` to the [`MediaPlaylist`] file.
///
/// [`MediaPlaylist`]: crate::MediaPlaylist
pub fn uri<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.uri = Some(value.into());
self
}
/// The maximum bandwidth of the stream.
pub fn bandwidth(&mut self, value: u64) -> &mut Self {
self.stream_inf.bandwidth(value);
self
}
/// The average bandwidth of the stream.
pub fn average_bandwidth(&mut self, value: u64) -> &mut Self {
self.stream_inf.average_bandwidth(value);
self
}
/// Every media format in any of the renditions specified by the Variant
/// Stream.
pub fn codecs<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.stream_inf.codecs(value);
self
}
/// The resolution of the stream.
pub fn resolution(&mut self, value: (usize, usize)) -> &mut Self {
self.stream_inf.resolution(value);
self
}
/// High-bandwidth Digital Content Protection
pub fn hdcp_level(&mut self, value: HdcpLevel) -> &mut Self {
self.stream_inf.hdcp_level(value);
self
}
/// It indicates the set of video renditions, that should be used when
/// playing the presentation.
pub fn video<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.stream_inf.video(value);
self
}
/// Build an [`ExtXIFrameStreamInf`].
pub fn build(&self) -> crate::Result<ExtXIFrameStreamInf> {
Ok(ExtXIFrameStreamInf {
uri: self
.uri
.clone()
.ok_or_else(|| Error::missing_value("frame rate"))?,
stream_inf: self.stream_inf.build().map_err(Error::builder_error)?,
})
}
}
impl ExtXIFrameStreamInf { impl ExtXIFrameStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:"; pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
@ -40,6 +107,9 @@ impl ExtXIFrameStreamInf {
} }
} }
/// Returns a builder for [`ExtXIFrameStreamInf`].
pub fn builder() -> ExtXIFrameStreamInfBuilder { ExtXIFrameStreamInfBuilder::default() }
/// Returns the `URI`, that identifies the associated [`media playlist`]. /// Returns the `URI`, that identifies the associated [`media playlist`].
/// ///
/// # Example /// # Example

View file

@ -3,11 +3,22 @@ use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, StreamInf}; use crate::types::{
ClosedCaptions, DecimalFloatingPoint, HdcpLevel, ProtocolVersion, StreamInf, StreamInfBuilder,
};
use crate::utils::{quote, tag, unquote}; use crate::utils::{quote, tag, unquote};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// [4.3.4.2. EXT-X-STREAM-INF] /// # [4.3.4.2. EXT-X-STREAM-INF]
///
/// The [`ExtXStreamInf`] tag specifies a Variant Stream, which is a set
/// of Renditions that can be combined to play the presentation. The
/// attributes of the tag provide information about the Variant Stream.
///
/// The URI line that follows the [`ExtXStreamInf`] tag specifies a Media
/// Playlist that carries a rendition of the Variant Stream. The URI
/// line is REQUIRED. Clients that do not support multiple video
/// Renditions SHOULD play this Rendition.
/// ///
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 /// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq)] #[derive(PartialOrd, Debug, Clone, PartialEq, Eq)]
@ -20,6 +31,104 @@ pub struct ExtXStreamInf {
stream_inf: StreamInf, stream_inf: StreamInf,
} }
#[derive(Default, Debug, Clone)]
/// Builder for [`ExtXStreamInf`].
pub struct ExtXStreamInfBuilder {
uri: Option<String>,
frame_rate: Option<DecimalFloatingPoint>,
audio: Option<String>,
subtitles: Option<String>,
closed_captions: Option<ClosedCaptions>,
stream_inf: StreamInfBuilder,
}
impl ExtXStreamInfBuilder {
/// An `URI` to the [`MediaPlaylist`] file.
///
/// [`MediaPlaylist`]: crate::MediaPlaylist
pub fn uri<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.uri = Some(value.into());
self
}
/// Maximum frame rate for all the video in the variant stream.
pub fn frame_rate(&mut self, value: f64) -> &mut Self {
self.frame_rate = Some(value.into());
self
}
/// The group identifier for the audio in the variant stream.
pub fn audio<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.audio = Some(value.into());
self
}
/// The group identifier for the subtitles in the variant stream.
pub fn subtitles<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.subtitles = Some(value.into());
self
}
/// The value of [`ClosedCaptions`] attribute.
pub fn closed_captions<T: Into<ClosedCaptions>>(&mut self, value: T) -> &mut Self {
self.closed_captions = Some(value.into());
self
}
/// The maximum bandwidth of the stream.
pub fn bandwidth(&mut self, value: u64) -> &mut Self {
self.stream_inf.bandwidth(value);
self
}
/// The average bandwidth of the stream.
pub fn average_bandwidth(&mut self, value: u64) -> &mut Self {
self.stream_inf.average_bandwidth(value);
self
}
/// Every media format in any of the renditions specified by the Variant
/// Stream.
pub fn codecs<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.stream_inf.codecs(value);
self
}
/// The resolution of the stream.
pub fn resolution(&mut self, value: (usize, usize)) -> &mut Self {
self.stream_inf.resolution(value);
self
}
/// High-bandwidth Digital Content Protection
pub fn hdcp_level(&mut self, value: HdcpLevel) -> &mut Self {
self.stream_inf.hdcp_level(value);
self
}
/// It indicates the set of video renditions, that should be used when
/// playing the presentation.
pub fn video<T: Into<String>>(&mut self, value: T) -> &mut Self {
self.stream_inf.video(value);
self
}
/// Build an [`ExtXStreamInf`].
pub fn build(&self) -> crate::Result<ExtXStreamInf> {
Ok(ExtXStreamInf {
uri: self
.uri
.clone()
.ok_or_else(|| Error::missing_value("frame rate"))?,
frame_rate: self.frame_rate,
audio: self.audio.clone(),
subtitles: self.subtitles.clone(),
closed_captions: self.closed_captions.clone(),
stream_inf: self.stream_inf.build().map_err(Error::builder_error)?,
})
}
}
impl ExtXStreamInf { impl ExtXStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
@ -41,6 +150,9 @@ impl ExtXStreamInf {
} }
} }
/// Returns a builder for [`ExtXStreamInf`].
pub fn builder() -> ExtXStreamInfBuilder { ExtXStreamInfBuilder::default() }
/// Returns the `URI` that identifies the associated media playlist. /// Returns the `URI` that identifies the associated media playlist.
/// ///
/// # Example /// # Example
@ -169,7 +281,7 @@ impl ExtXStreamInf {
/// ``` /// ```
pub const fn closed_captions(&self) -> &Option<ClosedCaptions> { &self.closed_captions } pub const fn closed_captions(&self) -> &Option<ClosedCaptions> { &self.closed_captions }
/// Returns the value of [`ClosedCaptions`] attribute. /// Sets the value of [`ClosedCaptions`] attribute.
/// ///
/// # Example /// # Example
/// ``` /// ```

View file

@ -5,26 +5,25 @@ use crate::types::ProtocolVersion;
use crate::utils::tag; use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE] /// # [4.3.3.5. EXT-X-PLAYLIST-TYPE]
/// ///
/// The [`ExtXPlaylistType`] tag provides mutability information about the /// The [`ExtXPlaylistType`] tag provides mutability information about the
/// [`Media Playlist`]. It applies to the entire [`Media Playlist`]. /// [`Media Playlist`]. It applies to the entire [`Media Playlist`].
/// ///
/// Its format is: /// [`Media Playlist`]: crate::MediaPlaylist
/// ```text /// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
/// #EXT-X-PLAYLIST-TYPE:<type-enum>
/// ```
///
/// [Media Playlist]: crate::MediaPlaylist
/// [4.4.3.5. EXT-X-PLAYLIST-TYPE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExtXPlaylistType { pub enum ExtXPlaylistType {
/// If the [`ExtXPlaylistType`] is Event, Media Segments /// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s
/// can only be added to the end of the Media Playlist. /// can only be added to the end of the [`Media Playlist`].
///
/// [`Media Segment`]: crate::MediaSegment
/// [`Media Playlist`]: crate::MediaPlaylist
Event, Event,
/// If the [`ExtXPlaylistType`] is Video On Demand (Vod), /// If the [`ExtXPlaylistType`] is Video On Demand (Vod),
/// the Media Playlist cannot change. /// the [`Media Playlist`] cannot change.
///
/// [`Media Playlist`]: crate::MediaPlaylist
Vod, Vod,
} }
@ -32,6 +31,7 @@ impl ExtXPlaylistType {
pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:"; pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:";
} }
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXPlaylistType { impl RequiredVersion for ExtXPlaylistType {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
} }
@ -81,6 +81,8 @@ mod test {
assert!("#EXT-X-PLAYLIST-TYPE:H" assert!("#EXT-X-PLAYLIST-TYPE:H"
.parse::<ExtXPlaylistType>() .parse::<ExtXPlaylistType>()
.is_err()); .is_err());
assert!("garbage".parse::<ExtXPlaylistType>().is_err());
} }
#[test] #[test]

View file

@ -6,20 +6,13 @@ use crate::types::ProtocolVersion;
use crate::utils::tag; use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.4.2.1. EXTINF] /// # [4.3.2.1. EXTINF]
/// ///
/// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies /// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies
/// only to the next [`Media Segment`]. /// only to the next [`Media Segment`].
/// ///
/// Its format is:
/// ```text
/// #EXTINF:<duration>,[<title>]
/// ```
/// The title is an optional informative title about the [Media Segment].
///
/// [`Media Segment`]: crate::media_segment::MediaSegment /// [`Media Segment`]: crate::media_segment::MediaSegment
/// [4.4.2.1. EXTINF]: /// [4.3.2.1. EXTINF]: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtInf { pub struct ExtInf {
duration: Duration, duration: Duration,

View file

@ -122,3 +122,22 @@ pub trait RequiredVersion {
/// Returns the protocol compatibility version that this tag requires. /// Returns the protocol compatibility version that this tag requires.
fn required_version(&self) -> ProtocolVersion; fn required_version(&self) -> ProtocolVersion;
} }
impl<T: RequiredVersion> RequiredVersion for Vec<T> {
fn required_version(&self) -> ProtocolVersion {
self.iter()
.map(|v| v.required_version())
.max()
// return ProtocolVersion::V1, if the iterator is empty:
.unwrap_or_default()
}
}
impl<T: RequiredVersion> RequiredVersion for Option<T> {
fn required_version(&self) -> ProtocolVersion {
self.iter()
.map(|v| v.required_version())
.max()
.unwrap_or_default()
}
}

View file

@ -4,14 +4,15 @@ use derive_more::Display;
use crate::Error; use crate::Error;
/// Decimal resolution. /// This is a simple wrapper type for the display resolution. (1920x1080,
/// 1280x720, ...).
/// ///
/// See: [4.2. Attribute Lists] /// See: [4.2. Attribute Lists]
/// ///
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 /// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)] #[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
#[display(fmt = "{}x{}", width, height)] #[display(fmt = "{}x{}", width, height)]
pub(crate) struct DecimalResolution { pub struct DecimalResolution {
width: usize, width: usize,
height: usize, height: usize,
} }

View file

@ -1,26 +1,44 @@
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use derive_builder::Builder;
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{DecimalResolution, HdcpLevel}; use crate::types::{DecimalResolution, HdcpLevel};
use crate::utils::{quote, unquote}; use crate::utils::{quote, unquote};
use crate::Error; use crate::Error;
/// [4.3.4.2. EXT-X-STREAM-INF] /// # [4.3.4.2. EXT-X-STREAM-INF]
/// ///
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 /// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
#[builder(setter(into, strip_option))]
#[builder(derive(Debug, PartialEq))]
pub struct StreamInf { pub struct StreamInf {
/// The maximum bandwidth of the stream.
bandwidth: u64, bandwidth: u64,
#[builder(default)]
/// The average bandwidth of the stream.
average_bandwidth: Option<u64>, average_bandwidth: Option<u64>,
#[builder(default)]
/// Every media format in any of the renditions specified by the Variant
/// Stream.
codecs: Option<String>, codecs: Option<String>,
#[builder(default)]
/// The resolution of the stream.
resolution: Option<DecimalResolution>, resolution: Option<DecimalResolution>,
#[builder(default)]
/// High-bandwidth Digital Content Protection
hdcp_level: Option<HdcpLevel>, hdcp_level: Option<HdcpLevel>,
#[builder(default)]
/// It indicates the set of video renditions, that should be used when
/// playing the presentation.
video: Option<String>, video: Option<String>,
} }
impl StreamInf { impl StreamInf {
/// Creates a new [StreamInf]. /// Creates a new [`StreamInf`].
///
/// # Examples /// # Examples
/// ``` /// ```
/// # use hls_m3u8::types::StreamInf; /// # use hls_m3u8::types::StreamInf;

115
tests/master_playlist.rs Normal file
View file

@ -0,0 +1,115 @@
use hls_m3u8::tags::{ExtXIFrameStreamInf, ExtXStreamInf};
use hls_m3u8::MasterPlaylist;
#[test]
fn test_master_playlist() {
let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\
http://example.com/low.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000\n\
http://example.com/mid.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000\n\
http://example.com/hi.m3u8\n\
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio-only.m3u8"
.parse::<MasterPlaylist>()
.unwrap();
assert_eq!(
MasterPlaylist::builder()
.stream_inf_tags(vec![
ExtXStreamInf::builder()
.bandwidth(1280000)
.average_bandwidth(1000000)
.uri("http://example.com/low.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(2560000)
.average_bandwidth(2000000)
.uri("http://example.com/mid.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(7680000)
.average_bandwidth(6000000)
.uri("http://example.com/hi.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(65000)
.codecs("mp4a.40.5")
.uri("http://example.com/audio-only.m3u8")
.build()
.unwrap(),
])
.build()
.unwrap(),
master_playlist
);
}
#[test]
fn test_master_playlist_with_i_frames() {
let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000\n\
low/audio-video.m3u8\n\
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=86000,URI=\"low/iframe.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=2560000\n\
mid/audio-video.m3u8\n\
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI=\"mid/iframe.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=7680000\n\
hi/audio-video.m3u8\n\
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=550000,URI=\"hi/iframe.m3u8\"\n\
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n\
audio-only.m3u8"
.parse::<MasterPlaylist>()
.unwrap();
assert_eq!(
MasterPlaylist::builder()
.stream_inf_tags(vec![
ExtXStreamInf::builder()
.bandwidth(1280000)
.uri("low/audio-video.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(2560000)
.uri("mid/audio-video.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(7680000)
.uri("hi/audio-video.m3u8")
.build()
.unwrap(),
ExtXStreamInf::builder()
.bandwidth(65000)
.codecs("mp4a.40.5")
.uri("audio-only.m3u8")
.build()
.unwrap(),
])
.i_frame_stream_inf_tags(vec![
ExtXIFrameStreamInf::builder()
.bandwidth(86000)
.uri("low/iframe.m3u8")
.build()
.unwrap(),
ExtXIFrameStreamInf::builder()
.bandwidth(150000)
.uri("mid/iframe.m3u8")
.build()
.unwrap(),
ExtXIFrameStreamInf::builder()
.bandwidth(550000)
.uri("hi/iframe.m3u8")
.build()
.unwrap(),
])
.build()
.unwrap(),
master_playlist
);
}