mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 16:41:00 +00:00
minor changes
This commit is contained in:
parent
c53e9e33f1
commit
73d9eb4f79
17 changed files with 351 additions and 65 deletions
|
@ -14,7 +14,7 @@ use crate::types::ProtocolVersion;
|
||||||
use crate::{Encrypted, Error, RequiredVersion};
|
use crate::{Encrypted, Error, RequiredVersion};
|
||||||
|
|
||||||
/// Media playlist.
|
/// Media playlist.
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
|
||||||
#[builder(build_fn(validate = "Self::validate"))]
|
#[builder(build_fn(validate = "Self::validate"))]
|
||||||
#[builder(setter(into, strip_option))]
|
#[builder(setter(into, strip_option))]
|
||||||
pub struct MediaPlaylist {
|
pub struct MediaPlaylist {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::tags::{
|
||||||
use crate::types::ProtocolVersion;
|
use crate::types::ProtocolVersion;
|
||||||
use crate::{Encrypted, RequiredVersion};
|
use crate::{Encrypted, RequiredVersion};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
|
||||||
#[builder(setter(into, strip_option))]
|
#[builder(setter(into, strip_option))]
|
||||||
/// Media segment.
|
/// Media segment.
|
||||||
pub struct MediaSegment {
|
pub struct MediaSegment {
|
||||||
|
|
|
@ -128,9 +128,11 @@ impl ExtXMediaBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
|
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
|
||||||
return Err(
|
return Err(Error::custom(format!(
|
||||||
Error::custom("If `DEFAULT` is true, `AUTOSELECT` has to be true too!").to_string(),
|
"If `DEFAULT` is true, `AUTOSELECT` has to be true too, Default: {:?}, Autoselect: {:?}!",
|
||||||
);
|
self.is_default, self.is_autoselect
|
||||||
|
))
|
||||||
|
.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if media_type != MediaType::Subtitles && self.is_forced.is_some() {
|
if media_type != MediaType::Subtitles && self.is_forced.is_some() {
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::{Error, RequiredVersion};
|
||||||
/// [`Media Playlist`]: crate::MediaPlaylist
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [4.4.3.4. EXT-X-ENDLIST]:
|
/// [4.4.3.4. EXT-X-ENDLIST]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXEndList;
|
pub struct ExtXEndList;
|
||||||
|
|
||||||
impl ExtXEndList {
|
impl ExtXEndList {
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{Error, RequiredVersion};
|
||||||
/// [`Media Segment`]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
|
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXIFramesOnly;
|
pub struct ExtXIFramesOnly;
|
||||||
|
|
||||||
impl ExtXIFramesOnly {
|
impl ExtXIFramesOnly {
|
||||||
|
|
|
@ -9,12 +9,6 @@ use crate::{Error, RequiredVersion};
|
||||||
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
|
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
|
||||||
/// the first [`Media Segment`] that appears in a Playlist file.
|
/// the first [`Media Segment`] that appears in a Playlist file.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
|
||||||
/// ```text
|
|
||||||
/// #EXT-X-MEDIA-SEQUENCE:<number>
|
|
||||||
/// ```
|
|
||||||
/// where `number` is a [`u64`].
|
|
||||||
///
|
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [Media Segment]: crate::MediaSegment
|
||||||
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
|
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.2
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.2
|
||||||
|
@ -61,6 +55,7 @@ impl ExtXMediaSequence {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
impl RequiredVersion for ExtXMediaSequence {
|
impl RequiredVersion for ExtXMediaSequence {
|
||||||
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{Error, RequiredVersion};
|
||||||
///
|
///
|
||||||
/// [`Media Playlist`]: crate::MediaPlaylist
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
|
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub enum ExtXPlaylistType {
|
pub enum ExtXPlaylistType {
|
||||||
/// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s
|
/// 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`].
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -6,14 +7,13 @@ use crate::types::ProtocolVersion;
|
||||||
use crate::utils::tag;
|
use crate::utils::tag;
|
||||||
use crate::{Error, RequiredVersion};
|
use crate::{Error, RequiredVersion};
|
||||||
|
|
||||||
/// # [4.4.3.1. EXT-X-TARGETDURATION]
|
/// # [4.3.3.1. EXT-X-TARGETDURATION]
|
||||||
/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]
|
/// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`]
|
||||||
/// duration.
|
/// duration.
|
||||||
///
|
///
|
||||||
/// [`Media Segment`]: crate::MediaSegment
|
/// [`MediaSegment`]: crate::MediaSegment
|
||||||
/// [4.4.3.1. EXT-X-TARGETDURATION]:
|
/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
|
||||||
pub struct ExtXTargetDuration(Duration);
|
pub struct ExtXTargetDuration(Duration);
|
||||||
|
|
||||||
impl ExtXTargetDuration {
|
impl ExtXTargetDuration {
|
||||||
|
@ -31,10 +31,7 @@ impl ExtXTargetDuration {
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// The nanoseconds part of the [`Duration`] will be discarded.
|
/// The nanoseconds part of the [`Duration`] will be discarded.
|
||||||
pub const fn new(duration: Duration) -> Self {
|
pub const fn new(duration: Duration) -> Self { Self(Duration::from_secs(duration.as_secs())) }
|
||||||
// TOOD: round instead of discarding?
|
|
||||||
Self(Duration::from_secs(duration.as_secs()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the maximum media segment duration.
|
/// Returns the maximum media segment duration.
|
||||||
///
|
///
|
||||||
|
@ -55,6 +52,12 @@ impl RequiredVersion for ExtXTargetDuration {
|
||||||
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for ExtXTargetDuration {
|
||||||
|
type Target = Duration;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXTargetDuration {
|
impl fmt::Display for ExtXTargetDuration {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}{}", Self::PREFIX, self.0.as_secs())
|
write!(f, "{}{}", Self::PREFIX, self.0.as_secs())
|
||||||
|
@ -98,4 +101,9 @@ mod test {
|
||||||
"#EXT-X-TARGETDURATION:5".parse().unwrap()
|
"#EXT-X-TARGETDURATION:5".parse().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
assert_eq!(ExtXTargetDuration::new(Duration::from_secs(5)).as_secs(), 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::{Error, RequiredVersion};
|
||||||
/// [`Media Segment`]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.2.2. EXT-X-BYTERANGE]:
|
/// [4.4.2.2. EXT-X-BYTERANGE]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXByteRange(ByteRange);
|
pub struct ExtXByteRange(ByteRange);
|
||||||
|
|
||||||
impl ExtXByteRange {
|
impl ExtXByteRange {
|
||||||
|
|
|
@ -12,9 +12,12 @@ use crate::utils::{quote, tag, unquote};
|
||||||
use crate::{Error, RequiredVersion};
|
use crate::{Error, RequiredVersion};
|
||||||
|
|
||||||
/// # [4.3.2.7. EXT-X-DATERANGE]
|
/// # [4.3.2.7. EXT-X-DATERANGE]
|
||||||
|
/// The [`ExtXDateRange`] tag associates a date range (i.e., a range of
|
||||||
|
/// time defined by a starting and ending date) with a set of attribute/
|
||||||
|
/// value pairs.
|
||||||
///
|
///
|
||||||
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
||||||
#[derive(Builder, Debug, Clone, PartialEq)]
|
#[derive(Builder, Debug, Clone, PartialEq, PartialOrd)]
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
pub struct ExtXDateRange {
|
pub struct ExtXDateRange {
|
||||||
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
|
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
|
||||||
|
@ -87,7 +90,7 @@ pub struct ExtXDateRange {
|
||||||
/// This attribute is optional.
|
/// This attribute is optional.
|
||||||
end_on_next: bool,
|
end_on_next: bool,
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
/// The `"X-"` prefix defines a namespace reserved for client-defined
|
||||||
/// attributes. The client-attribute must be a uppercase characters.
|
/// attributes. The client-attribute must be a uppercase characters.
|
||||||
/// Clients should use a reverse-DNS syntax when defining their own
|
/// Clients should use a reverse-DNS syntax when defining their own
|
||||||
/// attribute names to avoid collisions. An example of a client-defined
|
/// attribute names to avoid collisions. An example of a client-defined
|
||||||
|
@ -513,10 +516,13 @@ impl ExtXDateRange {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
|
||||||
pub const fn scte35_cmd(&self) -> &Option<String> { &self.scte35_cmd }
|
pub const fn scte35_cmd(&self) -> &Option<String> { &self.scte35_cmd }
|
||||||
|
|
||||||
|
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
|
||||||
pub const fn scte35_in(&self) -> &Option<String> { &self.scte35_in }
|
pub const fn scte35_in(&self) -> &Option<String> { &self.scte35_in }
|
||||||
|
|
||||||
|
/// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf
|
||||||
pub const fn scte35_out(&self) -> &Option<String> { &self.scte35_out }
|
pub const fn scte35_out(&self) -> &Option<String> { &self.scte35_out }
|
||||||
|
|
||||||
/// This attribute indicates that the end of the range containing it is
|
/// This attribute indicates that the end of the range containing it is
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::{Error, RequiredVersion};
|
||||||
/// [`Media Segment`]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.2.3. EXT-X-DISCONTINUITY]:
|
/// [4.4.2.3. EXT-X-DISCONTINUITY]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXDiscontinuity;
|
pub struct ExtXDiscontinuity;
|
||||||
|
|
||||||
impl ExtXDiscontinuity {
|
impl ExtXDiscontinuity {
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::{Error, RequiredVersion};
|
||||||
/// [`ExtXMap`]: crate::tags::ExtXMap
|
/// [`ExtXMap`]: crate::tags::ExtXMap
|
||||||
/// [`Media Segment`]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXKey(DecryptionKey);
|
pub struct ExtXKey(DecryptionKey);
|
||||||
|
|
||||||
impl ExtXKey {
|
impl ExtXKey {
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{Encrypted, Error, RequiredVersion};
|
||||||
///
|
///
|
||||||
/// [`MediaSegment`]: crate::MediaSegment
|
/// [`MediaSegment`]: crate::MediaSegment
|
||||||
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXMap {
|
pub struct ExtXMap {
|
||||||
uri: String,
|
uri: String,
|
||||||
range: Option<ByteRange>,
|
range: Option<ByteRange>,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::types::{
|
||||||
use crate::utils::{quote, unquote};
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, RequiredVersion};
|
use crate::{Error, RequiredVersion};
|
||||||
|
|
||||||
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[builder(setter(into), build_fn(validate = "Self::validate"))]
|
#[builder(setter(into), build_fn(validate = "Self::validate"))]
|
||||||
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
|
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
|
||||||
/// and [`ExtXKey`].
|
/// and [`ExtXKey`].
|
||||||
|
@ -483,38 +483,6 @@ mod test {
|
||||||
ProtocolVersion::V1
|
ProtocolVersion::V1
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
DecryptionKey::builder()
|
|
||||||
.method(EncryptionMethod::Aes128)
|
|
||||||
.uri("https://www.example.com/")
|
|
||||||
.key_format(KeyFormat::Identity)
|
|
||||||
.key_format_versions(vec![1, 2, 3])
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.required_version(),
|
|
||||||
ProtocolVersion::V1
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
DecryptionKey::builder()
|
|
||||||
.method(EncryptionMethod::Aes128)
|
|
||||||
.uri("https://www.example.com/")
|
|
||||||
.iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.required_version(),
|
|
||||||
ProtocolVersion::V2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_introduced_version() {
|
|
||||||
assert_eq!(
|
|
||||||
DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
|
|
||||||
.required_version(),
|
|
||||||
ProtocolVersion::V1
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DecryptionKey::builder()
|
DecryptionKey::builder()
|
||||||
.method(EncryptionMethod::Aes128)
|
.method(EncryptionMethod::Aes128)
|
||||||
|
|
|
@ -11,7 +11,7 @@ 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(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)]
|
||||||
#[builder(setter(into, strip_option))]
|
#[builder(setter(into, strip_option))]
|
||||||
#[builder(derive(Debug, PartialEq))]
|
#[builder(derive(Debug, PartialEq))]
|
||||||
pub struct StreamInf {
|
pub struct StreamInf {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use hls_m3u8::tags::{ExtXIFrameStreamInf, ExtXStreamInf};
|
use hls_m3u8::tags::{ExtXIFrameStreamInf, ExtXMedia, ExtXStreamInf};
|
||||||
|
use hls_m3u8::types::MediaType;
|
||||||
use hls_m3u8::MasterPlaylist;
|
use hls_m3u8::MasterPlaylist;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_master_playlist() {
|
fn test_master_playlist() {
|
||||||
|
// https://tools.ietf.org/html/rfc8216#section-8.4
|
||||||
let master_playlist = "#EXTM3U\n\
|
let master_playlist = "#EXTM3U\n\
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\
|
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\
|
||||||
http://example.com/low.m3u8\n\
|
http://example.com/low.m3u8\n\
|
||||||
|
@ -53,6 +55,7 @@ fn test_master_playlist() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_master_playlist_with_i_frames() {
|
fn test_master_playlist_with_i_frames() {
|
||||||
|
// https://tools.ietf.org/html/rfc8216#section-8.5
|
||||||
let master_playlist = "#EXTM3U\n\
|
let master_playlist = "#EXTM3U\n\
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1280000\n\
|
#EXT-X-STREAM-INF:BANDWIDTH=1280000\n\
|
||||||
low/audio-video.m3u8\n\
|
low/audio-video.m3u8\n\
|
||||||
|
@ -115,3 +118,254 @@ fn test_master_playlist_with_i_frames() {
|
||||||
master_playlist
|
master_playlist
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_master_playlist_with_alternative_audio() {
|
||||||
|
// https://tools.ietf.org/html/rfc8216#section-8.6
|
||||||
|
// TODO: I think the CODECS=\"..." have to be replaced.
|
||||||
|
let master_playlist = "#EXTM3U\n\
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"English\", \
|
||||||
|
DEFAULT=YES,AUTOSELECT=YES,LANGUAGE=\"en\", \
|
||||||
|
URI=\"main/english-audio.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Deutsch\", \
|
||||||
|
DEFAULT=NO,AUTOSELECT=YES,LANGUAGE=\"de\", \
|
||||||
|
URI=\"main/german-audio.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Commentary\", \
|
||||||
|
DEFAULT=NO,AUTOSELECT=NO,LANGUAGE=\"en\", \
|
||||||
|
URI=\"commentary/audio-only.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"...\",AUDIO=\"aac\"\n\
|
||||||
|
low/video-only.m3u8\n\
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS=\"...\",AUDIO=\"aac\"\n\
|
||||||
|
mid/video-only.m3u8\n\
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"...\",AUDIO=\"aac\"\n\
|
||||||
|
hi/video-only.m3u8\n\
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\",AUDIO=\"aac\"\n\
|
||||||
|
main/english-audio.m3u8"
|
||||||
|
.parse::<MasterPlaylist>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MasterPlaylist::builder()
|
||||||
|
.media_tags(vec![
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Audio)
|
||||||
|
.group_id("aac")
|
||||||
|
.name("English")
|
||||||
|
.is_default(true)
|
||||||
|
.is_autoselect(true)
|
||||||
|
.language("en")
|
||||||
|
.uri("main/english-audio.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Audio)
|
||||||
|
.group_id("aac")
|
||||||
|
.name("Deutsch")
|
||||||
|
.is_default(false)
|
||||||
|
.is_autoselect(true)
|
||||||
|
.language("de")
|
||||||
|
.uri("main/german-audio.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Audio)
|
||||||
|
.group_id("aac")
|
||||||
|
.name("Commentary")
|
||||||
|
.is_default(false)
|
||||||
|
.is_autoselect(false)
|
||||||
|
.language("en")
|
||||||
|
.uri("commentary/audio-only.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
])
|
||||||
|
.stream_inf_tags(vec![
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(1280000)
|
||||||
|
.codecs("...")
|
||||||
|
.audio("aac")
|
||||||
|
.uri("low/video-only.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(2560000)
|
||||||
|
.codecs("...")
|
||||||
|
.audio("aac")
|
||||||
|
.uri("mid/video-only.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(7680000)
|
||||||
|
.codecs("...")
|
||||||
|
.audio("aac")
|
||||||
|
.uri("hi/video-only.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(65000)
|
||||||
|
.codecs("mp4a.40.5")
|
||||||
|
.audio("aac")
|
||||||
|
.uri("main/english-audio.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
master_playlist
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_master_playlist_with_alternative_video() {
|
||||||
|
// https://tools.ietf.org/html/rfc8216#section-8.7
|
||||||
|
let master_playlist = "#EXTM3U\n\
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Main\", \
|
||||||
|
AUTOSELECT=YES,DEFAULT=YES,URI=\"low/main/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Centerfield\", \
|
||||||
|
DEFAULT=NO,URI=\"low/centerfield/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"low\",NAME=\"Dugout\", \
|
||||||
|
DEFAULT=NO,URI=\"low/dugout/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"...\",VIDEO=\"low\"\n\
|
||||||
|
low/main/audio-video.m3u8\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Main\", \
|
||||||
|
AUTOSELECT=YES,DEFAULT=YES,URI=\"mid/main/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Centerfield\", \
|
||||||
|
DEFAULT=NO,URI=\"mid/centerfield/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"mid\",NAME=\"Dugout\", \
|
||||||
|
DEFAULT=NO,URI=\"mid/dugout/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS=\"...\",VIDEO=\"mid\"\n\
|
||||||
|
mid/main/audio-video.m3u8\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Main\", \
|
||||||
|
AUTOSELECT=YES,DEFAULT=YES,URI=\"hi/main/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Centerfield\", \
|
||||||
|
DEFAULT=NO,URI=\"hi/centerfield/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"hi\",NAME=\"Dugout\", \
|
||||||
|
DEFAULT=NO,URI=\"hi/dugout/audio-video.m3u8\"\n\
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"...\",VIDEO=\"hi\"
|
||||||
|
hi/main/audio-video.m3u8"
|
||||||
|
.parse::<MasterPlaylist>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MasterPlaylist::builder()
|
||||||
|
.media_tags(vec![
|
||||||
|
// low
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("low")
|
||||||
|
.name("Main")
|
||||||
|
.is_default(true)
|
||||||
|
.is_autoselect(true)
|
||||||
|
.uri("low/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("low")
|
||||||
|
.name("Centerfield")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("low/centerfield/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("low")
|
||||||
|
.name("Dugout")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("low/dugout/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
// mid
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("mid")
|
||||||
|
.name("Main")
|
||||||
|
.is_default(true)
|
||||||
|
.is_autoselect(true)
|
||||||
|
.uri("mid/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("mid")
|
||||||
|
.name("Centerfield")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("mid/centerfield/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("mid")
|
||||||
|
.name("Dugout")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("mid/dugout/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
// hi
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("hi")
|
||||||
|
.name("Main")
|
||||||
|
.is_default(true)
|
||||||
|
.is_autoselect(true)
|
||||||
|
.uri("hi/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("hi")
|
||||||
|
.name("Centerfield")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("hi/centerfield/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXMedia::builder()
|
||||||
|
.media_type(MediaType::Video)
|
||||||
|
.group_id("hi")
|
||||||
|
.name("Dugout")
|
||||||
|
.is_default(false)
|
||||||
|
.uri("hi/dugout/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
])
|
||||||
|
.stream_inf_tags(vec![
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(1280000)
|
||||||
|
.codecs("...")
|
||||||
|
.video("low")
|
||||||
|
.uri("low/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(2560000)
|
||||||
|
.codecs("...")
|
||||||
|
.video("mid")
|
||||||
|
.uri("mid/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
ExtXStreamInf::builder()
|
||||||
|
.bandwidth(7680000)
|
||||||
|
.codecs("...")
|
||||||
|
.video("hi")
|
||||||
|
.uri("hi/main/audio-video.m3u8")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
master_playlist
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
53
tests/media_playlist.rs
Normal file
53
tests/media_playlist.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use hls_m3u8::tags::{ExtInf, ExtXByteRange, ExtXMediaSequence, ExtXTargetDuration};
|
||||||
|
use hls_m3u8::{MediaPlaylist, MediaSegment};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_media_playlist_with_byterange() {
|
||||||
|
let media_playlist = "#EXTM3U\n\
|
||||||
|
#EXT-X-TARGETDURATION:10\n\
|
||||||
|
#EXT-X-VERSION:4\n\
|
||||||
|
#EXT-X-MEDIA-SEQUENCE:0\n\
|
||||||
|
#EXTINF:10.0,\n\
|
||||||
|
#EXT-X-BYTERANGE:75232@0\n\
|
||||||
|
video.ts\n\
|
||||||
|
#EXT-X-BYTERANGE:82112@752321\n\
|
||||||
|
#EXTINF:10.0,\n\
|
||||||
|
video.ts\n\
|
||||||
|
#EXTINF:10.0,\n\
|
||||||
|
#EXT-X-BYTERANGE:69864\n\
|
||||||
|
video.ts"
|
||||||
|
.parse::<MediaPlaylist>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MediaPlaylist::builder()
|
||||||
|
.target_duration_tag(ExtXTargetDuration::new(Duration::from_secs(10)))
|
||||||
|
.media_sequence_tag(ExtXMediaSequence::new(0))
|
||||||
|
.segments(vec![
|
||||||
|
MediaSegment::builder()
|
||||||
|
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
|
.byte_range_tag(ExtXByteRange::new(75232, Some(0)))
|
||||||
|
.uri("video.ts")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
MediaSegment::builder()
|
||||||
|
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
|
.byte_range_tag(ExtXByteRange::new(82112, Some(752321)))
|
||||||
|
.uri("video.ts")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
MediaSegment::builder()
|
||||||
|
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
|
.byte_range_tag(ExtXByteRange::new(69864, None))
|
||||||
|
.uri("video.ts")
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
media_playlist
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue