mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2024-06-09 16:19:21 +00:00
Move alternatives to MasterPlaylist
This commit is contained in:
parent
cd9402051e
commit
57d60ba438
35
sample-playlists/master-with-alternatives-2.m3u8
Normal file
35
sample-playlists/master-with-alternatives-2.m3u8
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 1",AUTOSELECT=YES,DEFAULT=YES
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 2",AUTOSELECT=NO,DEFAULT=NO,URI="alternate_audio_aac_sinewave/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English (Forced)",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES,LANGUAGE="en",URI="subtitles/eng_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/fra/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français (Forced)",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES,LANGUAGE="fr",URI="subtitles/fra_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/spa/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español (Forced)",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES,LANGUAGE="es",URI="subtitles/spa_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="ja",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/jpn/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語 (Forced)",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES,LANGUAGE="ja",URI="subtitles/jpn_forced/prog_index.m3u8"
|
||||||
|
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear1/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=28451,CODECS="avc1.4d400d",URI="gear1/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=577610,CODECS="mp4a.40.2, avc1.4d401e",RESOLUTION=640x360,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear2/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=181534,CODECS="avc1.4d401e",URI="gear2/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=915905,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=960x540,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear3/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=297056,CODECS="avc1.4d401f",URI="gear3/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1030138,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1280x720,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear4/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=339492,CODECS="avc1.4d401f",URI="gear4/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1924009,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1920x1080,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear5/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=669554,CODECS="avc1.4d401f",URI="gear5/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS="mp4a.40.2",AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear0/prog_index.m3u8
|
|
@ -69,13 +69,13 @@ pub struct MasterPlaylist {
|
||||||
pub session_key: Option<SessionKey>,
|
pub session_key: Option<SessionKey>,
|
||||||
pub start: Option<Start>,
|
pub start: Option<Start>,
|
||||||
pub independent_segments: bool,
|
pub independent_segments: bool,
|
||||||
|
pub alternatives: Vec<AlternativeMedia> // EXT-X-MEDIA tags
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPlaylist {
|
impl MasterPlaylist {
|
||||||
|
|
||||||
pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
||||||
let mut master_playlist = MasterPlaylist::default();
|
let mut master_playlist = MasterPlaylist::default();
|
||||||
let mut alternatives = vec![];
|
|
||||||
|
|
||||||
while let Some(tag) = tags.pop() {
|
while let Some(tag) = tags.pop() {
|
||||||
match tag {
|
match tag {
|
||||||
|
@ -83,11 +83,9 @@ impl MasterPlaylist {
|
||||||
master_playlist.version = v;
|
master_playlist.version = v;
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::AlternativeMedia(v) => {
|
MasterPlaylistTag::AlternativeMedia(v) => {
|
||||||
alternatives.push(v);
|
master_playlist.alternatives.push(v);
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::VariantStream(mut stream) => {
|
MasterPlaylistTag::VariantStream(stream) => {
|
||||||
stream.alternatives = alternatives;
|
|
||||||
alternatives = vec![];
|
|
||||||
master_playlist.variants.push(stream);
|
master_playlist.variants.push(stream);
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::Uri(uri) => {
|
MasterPlaylistTag::Uri(uri) => {
|
||||||
|
@ -125,6 +123,10 @@ impl MasterPlaylist {
|
||||||
writeln!(w, "{}" ,"#EXTM3U")?;
|
writeln!(w, "{}" ,"#EXTM3U")?;
|
||||||
writeln!(w, "#EXT-X-VERSION:{}", self.version)?;
|
writeln!(w, "#EXT-X-VERSION:{}", self.version)?;
|
||||||
|
|
||||||
|
for alternative in &self.alternatives {
|
||||||
|
alternative.write_to(w)?;
|
||||||
|
}
|
||||||
|
|
||||||
for variant in &self.variants {
|
for variant in &self.variants {
|
||||||
variant.write_to(w)?;
|
variant.write_to(w)?;
|
||||||
}
|
}
|
||||||
|
@ -178,7 +180,6 @@ pub struct VariantStream {
|
||||||
pub subtitles: Option<String>,
|
pub subtitles: Option<String>,
|
||||||
pub closed_captions: Option<String>,
|
pub closed_captions: Option<String>,
|
||||||
// PROGRAM-ID tag was removed in protocol version 6
|
// PROGRAM-ID tag was removed in protocol version 6
|
||||||
pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VariantStream {
|
impl VariantStream {
|
||||||
|
@ -196,15 +197,11 @@ impl VariantStream {
|
||||||
video: attrs.remove("VIDEO"),
|
video: attrs.remove("VIDEO"),
|
||||||
subtitles: attrs.remove("SUBTITLES"),
|
subtitles: attrs.remove("SUBTITLES"),
|
||||||
closed_captions: attrs.remove("CLOSED-CAPTIONS"),
|
closed_captions: attrs.remove("CLOSED-CAPTIONS"),
|
||||||
alternatives: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
|
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
|
||||||
|
|
||||||
for alternative in &self.alternatives {
|
|
||||||
alternative.write_to(w)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_i_frame {
|
if self.is_i_frame {
|
||||||
write!(w, "#EXT-X-I-FRAME-STREAM-INF:")?;
|
write!(w, "#EXT-X-I-FRAME-STREAM-INF:")?;
|
||||||
|
|
38
tests/lib.rs
38
tests/lib.rs
|
@ -56,6 +56,13 @@ fn playlist_master_with_alternatives() {
|
||||||
assert!(print_parse_playlist_test("master-with-alternatives.m3u8"));
|
assert!(print_parse_playlist_test("master-with-alternatives.m3u8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn playlist_master_with_alternatives_2_3() {
|
||||||
|
assert!(print_parse_playlist_test("master-with-alternatives-2.m3u8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn playlist_master_with_i_frame_stream_inf() {
|
fn playlist_master_with_i_frame_stream_inf() {
|
||||||
assert!(print_parse_playlist_test("master-with-i-frame-stream-inf.m3u8"));
|
assert!(print_parse_playlist_test("master-with-i-frame-stream-inf.m3u8"));
|
||||||
|
@ -290,6 +297,21 @@ fn create_and_parse_master_playlist_full() {
|
||||||
|
|
||||||
let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist {
|
let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist {
|
||||||
version: 6,
|
version: 6,
|
||||||
|
alternatives: vec! [
|
||||||
|
AlternativeMedia {
|
||||||
|
media_type: AlternativeMediaType::Audio,
|
||||||
|
uri: Some("alt-media-uri".into()),
|
||||||
|
group_id: "group-id".into(),
|
||||||
|
language: Some("language".into()),
|
||||||
|
assoc_language: Some("assoc-language".into()),
|
||||||
|
name: "Xmedia".into(),
|
||||||
|
default: true, // Its absence indicates an implicit value of NO
|
||||||
|
autoselect: true, // Its absence indicates an implicit value of NO
|
||||||
|
forced: true, // Its absence indicates an implicit value of NO
|
||||||
|
instream_id: Some("instream_id".into()),
|
||||||
|
characteristics: Some("characteristics".into()),
|
||||||
|
}
|
||||||
|
],
|
||||||
variants: vec![
|
variants: vec![
|
||||||
VariantStream {
|
VariantStream {
|
||||||
is_i_frame: false,
|
is_i_frame: false,
|
||||||
|
@ -303,21 +325,7 @@ fn create_and_parse_master_playlist_full() {
|
||||||
video: Some("video".into()),
|
video: Some("video".into()),
|
||||||
subtitles: Some("subtitles".into()),
|
subtitles: Some("subtitles".into()),
|
||||||
closed_captions: Some("closed_captions".into()),
|
closed_captions: Some("closed_captions".into()),
|
||||||
alternatives: vec! [
|
|
||||||
AlternativeMedia {
|
|
||||||
media_type: AlternativeMediaType::Audio,
|
|
||||||
uri: Some("alt-media-uri".into()),
|
|
||||||
group_id: "group-id".into(),
|
|
||||||
language: Some("language".into()),
|
|
||||||
assoc_language: Some("assoc-language".into()),
|
|
||||||
name: "Xmedia".into(),
|
|
||||||
default: true, // Its absence indicates an implicit value of NO
|
|
||||||
autoselect: true, // Its absence indicates an implicit value of NO
|
|
||||||
forced: true, // Its absence indicates an implicit value of NO
|
|
||||||
instream_id: Some("instream_id".into()),
|
|
||||||
characteristics: Some("characteristics".into()),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
session_data: Some(SessionData {
|
session_data: Some(SessionData {
|
||||||
|
|
Loading…
Reference in a new issue