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

minor changes

This commit is contained in:
Luro02 2019-10-12 11:38:28 +02:00
parent c53e9e33f1
commit 73d9eb4f79
17 changed files with 351 additions and 65 deletions

View file

@ -14,7 +14,7 @@ use crate::types::ProtocolVersion;
use crate::{Encrypted, Error, RequiredVersion};
/// Media playlist.
#[derive(Debug, Clone, Builder)]
#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
#[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))]
pub struct MediaPlaylist {

View file

@ -8,7 +8,7 @@ use crate::tags::{
use crate::types::ProtocolVersion;
use crate::{Encrypted, RequiredVersion};
#[derive(Debug, Clone, Builder)]
#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)]
#[builder(setter(into, strip_option))]
/// Media segment.
pub struct MediaSegment {

View file

@ -128,9 +128,11 @@ impl ExtXMediaBuilder {
}
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
return Err(
Error::custom("If `DEFAULT` is true, `AUTOSELECT` has to be true too!").to_string(),
);
return Err(Error::custom(format!(
"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() {

View file

@ -18,7 +18,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.3.4. EXT-X-ENDLIST]:
/// 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;
impl ExtXEndList {

View file

@ -20,7 +20,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
/// 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;
impl ExtXIFramesOnly {

View file

@ -9,12 +9,6 @@ use crate::{Error, RequiredVersion};
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
/// 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
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
/// 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 {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
}

View file

@ -12,7 +12,7 @@ use crate::{Error, RequiredVersion};
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [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 {
/// If the [`ExtXPlaylistType`] is Event, [`Media Segment`]s
/// can only be added to the end of the [`Media Playlist`].

View file

@ -1,4 +1,5 @@
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use std::time::Duration;
@ -6,14 +7,13 @@ use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::{Error, RequiredVersion};
/// # [4.4.3.1. EXT-X-TARGETDURATION]
/// # [4.3.3.1. EXT-X-TARGETDURATION]
/// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`]
/// duration.
///
/// [`MediaSegment`]: crate::MediaSegment
/// [4.4.3.1. EXT-X-TARGETDURATION]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct ExtXTargetDuration(Duration);
impl ExtXTargetDuration {
@ -31,10 +31,7 @@ impl ExtXTargetDuration {
///
/// # Note
/// The nanoseconds part of the [`Duration`] will be discarded.
pub const fn new(duration: Duration) -> Self {
// TOOD: round instead of discarding?
Self(Duration::from_secs(duration.as_secs()))
}
pub const fn new(duration: Duration) -> Self { Self(Duration::from_secs(duration.as_secs())) }
/// Returns the maximum media segment duration.
///
@ -55,6 +52,12 @@ impl RequiredVersion for ExtXTargetDuration {
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.0.as_secs())
@ -98,4 +101,9 @@ mod test {
"#EXT-X-TARGETDURATION:5".parse().unwrap()
);
}
#[test]
fn test_deref() {
assert_eq!(ExtXTargetDuration::new(Duration::from_secs(5)).as_secs(), 5);
}
}

View file

@ -23,7 +23,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.2. EXT-X-BYTERANGE]:
/// 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);
impl ExtXByteRange {

View file

@ -12,9 +12,12 @@ use crate::utils::{quote, tag, unquote};
use crate::{Error, RequiredVersion};
/// # [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
#[derive(Builder, Debug, Clone, PartialEq)]
#[derive(Builder, Debug, Clone, PartialEq, PartialOrd)]
#[builder(setter(into))]
pub struct ExtXDateRange {
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
@ -87,7 +90,7 @@ pub struct ExtXDateRange {
/// This attribute is optional.
end_on_next: bool,
#[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.
/// Clients should use a reverse-DNS syntax when defining their own
/// attribute names to avoid collisions. An example of a client-defined
@ -513,10 +516,13 @@ impl ExtXDateRange {
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 }
/// 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 }
/// 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 }
/// This attribute indicates that the end of the range containing it is

View file

@ -17,7 +17,7 @@ use crate::{Error, RequiredVersion};
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.3. EXT-X-DISCONTINUITY]:
/// 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;
impl ExtXDiscontinuity {

View file

@ -22,7 +22,7 @@ use crate::{Error, RequiredVersion};
/// [`ExtXMap`]: crate::tags::ExtXMap
/// [`Media Segment`]: crate::MediaSegment
/// [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);
impl ExtXKey {

View file

@ -14,7 +14,7 @@ use crate::{Encrypted, Error, RequiredVersion};
///
/// [`MediaSegment`]: crate::MediaSegment
/// [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 {
uri: String,
range: Option<ByteRange>,

View file

@ -10,7 +10,7 @@ use crate::types::{
use crate::utils::{quote, unquote};
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"))]
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
/// and [`ExtXKey`].
@ -483,38 +483,6 @@ mod test {
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!(
DecryptionKey::builder()
.method(EncryptionMethod::Aes128)

View file

@ -11,7 +11,7 @@ use crate::Error;
/// # [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
#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)]
#[builder(setter(into, strip_option))]
#[builder(derive(Debug, PartialEq))]
pub struct StreamInf {

View file

@ -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 pretty_assertions::assert_eq;
#[test]
fn test_master_playlist() {
// https://tools.ietf.org/html/rfc8216#section-8.4
let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n\
http://example.com/low.m3u8\n\
@ -53,6 +55,7 @@ fn test_master_playlist() {
#[test]
fn test_master_playlist_with_i_frames() {
// https://tools.ietf.org/html/rfc8216#section-8.5
let master_playlist = "#EXTM3U\n\
#EXT-X-STREAM-INF:BANDWIDTH=1280000\n\
low/audio-video.m3u8\n\
@ -115,3 +118,254 @@ fn test_master_playlist_with_i_frames() {
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
View 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
)
}