mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 00:20:59 +00:00
remove _tag
suffix from MediaSegment fields
This commit is contained in:
parent
8cced1ac53
commit
b2c997d04d
5 changed files with 92 additions and 93 deletions
|
@ -124,7 +124,7 @@ impl MediaPlaylistBuilder {
|
||||||
if let Some(segments) = &self.segments {
|
if let Some(segments) = &self.segments {
|
||||||
for s in segments {
|
for s in segments {
|
||||||
// CHECK: `#EXT-X-TARGETDURATION`
|
// CHECK: `#EXT-X-TARGETDURATION`
|
||||||
let segment_duration = s.inf_tag().duration();
|
let segment_duration = s.inf().duration();
|
||||||
let rounded_segment_duration = {
|
let rounded_segment_duration = {
|
||||||
if segment_duration.subsec_nanos() < 500_000_000 {
|
if segment_duration.subsec_nanos() < 500_000_000 {
|
||||||
Duration::from_secs(segment_duration.as_secs())
|
Duration::from_secs(segment_duration.as_secs())
|
||||||
|
@ -152,7 +152,7 @@ impl MediaPlaylistBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: `#EXT-X-BYTE-RANGE`
|
// CHECK: `#EXT-X-BYTE-RANGE`
|
||||||
if let Some(tag) = s.byte_range_tag() {
|
if let Some(tag) = s.byte_range() {
|
||||||
if tag.to_range().start().is_none() {
|
if tag.to_range().start().is_none() {
|
||||||
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
|
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
|
||||||
if last_uri != s.uri() {
|
if last_uri != s.uri() {
|
||||||
|
@ -286,7 +286,7 @@ fn parse_media_playlist(
|
||||||
let mut has_discontinuity_tag = false;
|
let mut has_discontinuity_tag = false;
|
||||||
let mut unknown_tags = vec![];
|
let mut unknown_tags = vec![];
|
||||||
|
|
||||||
let mut available_key_tags: Vec<crate::tags::ExtXKey> = vec![];
|
let mut available_keys: Vec<crate::tags::ExtXKey> = vec![];
|
||||||
|
|
||||||
for line in Lines::from(input) {
|
for line in Lines::from(input) {
|
||||||
match line? {
|
match line? {
|
||||||
|
@ -294,25 +294,25 @@ fn parse_media_playlist(
|
||||||
match tag {
|
match tag {
|
||||||
Tag::ExtInf(t) => {
|
Tag::ExtInf(t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
segment.inf_tag(t);
|
segment.inf(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXByteRange(t) => {
|
Tag::ExtXByteRange(t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
segment.byte_range_tag(t);
|
segment.byte_range(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXDiscontinuity(t) => {
|
Tag::ExtXDiscontinuity(t) => {
|
||||||
has_discontinuity_tag = true;
|
has_discontinuity_tag = true;
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
segment.discontinuity_tag(t);
|
segment.discontinuity(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXKey(t) => {
|
Tag::ExtXKey(t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
if available_key_tags.is_empty() {
|
if available_keys.is_empty() {
|
||||||
// An ExtXKey applies to every MediaSegment and to every Media
|
// An ExtXKey applies to every MediaSegment and to every Media
|
||||||
// Initialization Section declared by an EXT-X-MAP tag, that appears
|
// Initialization Section declared by an EXT-X-MAP tag, that appears
|
||||||
// between it and the next EXT-X-KEY tag in the Playlist file with the
|
// between it and the next EXT-X-KEY tag in the Playlist file with the
|
||||||
// same KEYFORMAT attribute (or the end of the Playlist file).
|
// same KEYFORMAT attribute (or the end of the Playlist file).
|
||||||
available_key_tags = available_key_tags
|
available_keys = available_keys
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|k| {
|
.map(|k| {
|
||||||
if t.key_format() == k.key_format() {
|
if t.key_format() == k.key_format() {
|
||||||
|
@ -323,22 +323,22 @@ fn parse_media_playlist(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
} else {
|
} else {
|
||||||
available_key_tags.push(t);
|
available_keys.push(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tag::ExtXMap(mut t) => {
|
Tag::ExtXMap(mut t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
|
|
||||||
t.set_keys(available_key_tags.clone());
|
t.set_keys(available_keys.clone());
|
||||||
segment.map_tag(t);
|
segment.map(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXProgramDateTime(t) => {
|
Tag::ExtXProgramDateTime(t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
segment.program_date_time_tag(t);
|
segment.program_date_time(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXDateRange(t) => {
|
Tag::ExtXDateRange(t) => {
|
||||||
has_partial_segment = true;
|
has_partial_segment = true;
|
||||||
segment.date_range_tag(t);
|
segment.date_range(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXTargetDuration(t) => {
|
Tag::ExtXTargetDuration(t) => {
|
||||||
builder.target_duration(t);
|
builder.target_duration(t);
|
||||||
|
@ -350,9 +350,11 @@ fn parse_media_playlist(
|
||||||
if segments.is_empty() {
|
if segments.is_empty() {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_discontinuity_tag {
|
if has_discontinuity_tag {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.discontinuity_sequence(t);
|
builder.discontinuity_sequence(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXEndList(t) => {
|
Tag::ExtXEndList(t) => {
|
||||||
|
@ -386,7 +388,7 @@ fn parse_media_playlist(
|
||||||
}
|
}
|
||||||
Line::Uri(uri) => {
|
Line::Uri(uri) => {
|
||||||
segment.uri(uri);
|
segment.uri(uri);
|
||||||
segment.keys(available_key_tags.clone());
|
segment.keys(available_keys.clone());
|
||||||
segments.push(segment.build().map_err(Error::builder)?);
|
segments.push(segment.build().map_err(Error::builder)?);
|
||||||
segment = MediaSegment::builder();
|
segment = MediaSegment::builder();
|
||||||
has_partial_segment = false;
|
has_partial_segment = false;
|
||||||
|
|
|
@ -19,22 +19,22 @@ pub struct MediaSegment {
|
||||||
keys: Vec<ExtXKey>,
|
keys: Vec<ExtXKey>,
|
||||||
/// The [`ExtXMap`] tag associated with the media segment.
|
/// The [`ExtXMap`] tag associated with the media segment.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
map_tag: Option<ExtXMap>,
|
map: Option<ExtXMap>,
|
||||||
/// The [`ExtXByteRange`] tag associated with the [`MediaSegment`].
|
/// The [`ExtXByteRange`] tag associated with the [`MediaSegment`].
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
byte_range_tag: Option<ExtXByteRange>,
|
byte_range: Option<ExtXByteRange>,
|
||||||
/// The [`ExtXDateRange`] tag associated with the media segment.
|
/// The [`ExtXDateRange`] tag associated with the media segment.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
date_range_tag: Option<ExtXDateRange>,
|
date_range: Option<ExtXDateRange>,
|
||||||
/// The [`ExtXDiscontinuity`] tag associated with the media segment.
|
/// The [`ExtXDiscontinuity`] tag associated with the media segment.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
discontinuity_tag: Option<ExtXDiscontinuity>,
|
discontinuity: Option<ExtXDiscontinuity>,
|
||||||
/// The [`ExtXProgramDateTime`] tag associated with the media
|
/// The [`ExtXProgramDateTime`] tag associated with the media
|
||||||
/// segment.
|
/// segment.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
program_date_time_tag: Option<ExtXProgramDateTime>,
|
program_date_time: Option<ExtXProgramDateTime>,
|
||||||
/// The [`ExtInf`] tag associated with the [`MediaSegment`].
|
/// The [`ExtInf`] tag associated with the [`MediaSegment`].
|
||||||
inf_tag: ExtInf,
|
inf: ExtInf,
|
||||||
/// The `URI` of the [`MediaSegment`].
|
/// The `URI` of the [`MediaSegment`].
|
||||||
#[shorthand(enable(into))]
|
#[shorthand(enable(into))]
|
||||||
uri: String,
|
uri: String,
|
||||||
|
@ -47,12 +47,13 @@ impl MediaSegment {
|
||||||
|
|
||||||
impl MediaSegmentBuilder {
|
impl MediaSegmentBuilder {
|
||||||
/// Pushes an [`ExtXKey`] tag.
|
/// Pushes an [`ExtXKey`] tag.
|
||||||
pub fn push_key_tag<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
|
pub fn push_key<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
|
||||||
if let Some(key_tags) = &mut self.keys {
|
if let Some(keys) = &mut self.keys {
|
||||||
key_tags.push(value.into());
|
keys.push(value.into());
|
||||||
} else {
|
} else {
|
||||||
self.keys = Some(vec![value.into()]);
|
self.keys = Some(vec![value.into()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,27 +64,27 @@ impl fmt::Display for MediaSegment {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.map_tag {
|
if let Some(value) = &self.map {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.byte_range_tag {
|
if let Some(value) = &self.byte_range {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.date_range_tag {
|
if let Some(value) = &self.date_range {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.discontinuity_tag {
|
if let Some(value) = &self.discontinuity {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.program_date_time_tag {
|
if let Some(value) = &self.program_date_time {
|
||||||
writeln!(f, "{}", value)?;
|
writeln!(f, "{}", value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", self.inf_tag)?; // TODO: there might be a `,` missing
|
writeln!(f, "{}", self.inf)?; // TODO: there might be a `,` missing
|
||||||
writeln!(f, "{}", self.uri)?;
|
writeln!(f, "{}", self.uri)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -93,12 +94,12 @@ impl RequiredVersion for MediaSegment {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion {
|
||||||
required_version![
|
required_version![
|
||||||
self.keys,
|
self.keys,
|
||||||
self.map_tag,
|
self.map,
|
||||||
self.byte_range_tag,
|
self.byte_range,
|
||||||
self.date_range_tag,
|
self.date_range,
|
||||||
self.discontinuity_tag,
|
self.discontinuity,
|
||||||
self.program_date_time_tag,
|
self.program_date_time,
|
||||||
self.inf_tag
|
self.inf
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +121,11 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MediaSegment::builder()
|
MediaSegment::builder()
|
||||||
.keys(vec![ExtXKey::empty()])
|
.keys(vec![ExtXKey::empty()])
|
||||||
.map_tag(ExtXMap::new("https://www.example.com/"))
|
.map(ExtXMap::new("https://www.example.com/"))
|
||||||
.byte_range_tag(ExtXByteRange::new(20, Some(5)))
|
.byte_range(ExtXByteRange::new(20, Some(5)))
|
||||||
//.date_range_tag() // TODO!
|
//.date_range() // TODO!
|
||||||
.discontinuity_tag(ExtXDiscontinuity)
|
.discontinuity(ExtXDiscontinuity)
|
||||||
.inf_tag(ExtInf::new(Duration::from_secs(4)))
|
.inf(ExtInf::new(Duration::from_secs(4)))
|
||||||
.uri("http://www.uri.com/")
|
.uri("http://www.uri.com/")
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -75,6 +75,10 @@ impl FromStr for ExtXTargetDuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Duration> for ExtXTargetDuration {
|
||||||
|
fn from(value: Duration) -> Self { Self::new(value) }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -6,50 +6,48 @@ use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_media_playlist_with_byterange() {
|
fn test_media_playlist_with_byterange() {
|
||||||
let media_playlist = concat!(
|
|
||||||
"#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\n"
|
|
||||||
)
|
|
||||||
.parse::<MediaPlaylist>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MediaPlaylist::builder()
|
MediaPlaylist::builder()
|
||||||
.target_duration(ExtXTargetDuration::new(Duration::from_secs(10)))
|
.target_duration(ExtXTargetDuration::new(Duration::from_secs(10)))
|
||||||
.media_sequence(ExtXMediaSequence::new(0))
|
.media_sequence(ExtXMediaSequence::new(0))
|
||||||
.segments(vec![
|
.segments(vec![
|
||||||
MediaSegment::builder()
|
MediaSegment::builder()
|
||||||
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
.byte_range_tag(ExtXByteRange::new(75232, Some(0)))
|
.byte_range(ExtXByteRange::new(75232, Some(0)))
|
||||||
.uri("video.ts")
|
.uri("video.ts")
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
MediaSegment::builder()
|
MediaSegment::builder()
|
||||||
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
.byte_range_tag(ExtXByteRange::new(82112, Some(752321)))
|
.byte_range(ExtXByteRange::new(82112, Some(752321)))
|
||||||
.uri("video.ts")
|
.uri("video.ts")
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
MediaSegment::builder()
|
MediaSegment::builder()
|
||||||
.inf_tag(ExtInf::new(Duration::from_secs_f64(10.0)))
|
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
|
||||||
.byte_range_tag(ExtXByteRange::new(69864, None))
|
.byte_range(ExtXByteRange::new(69864, None))
|
||||||
.uri("video.ts")
|
.uri("video.ts")
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
])
|
])
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
media_playlist
|
concat!(
|
||||||
|
"#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\n"
|
||||||
|
)
|
||||||
|
.parse::<MediaPlaylist>()
|
||||||
|
.unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
//! Credits go to
|
//! Credits go to
|
||||||
//! - https://github.com/globocom/m3u8/blob/master/tests/playlists.py
|
//! - https://github.com/globocom/m3u8/blob/master/tests/playlists.py
|
||||||
use hls_m3u8::tags::*;
|
|
||||||
use hls_m3u8::MediaPlaylist;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use hls_m3u8::tags::{ExtInf, ExtXEndList};
|
||||||
|
use hls_m3u8::{MediaPlaylist, MediaSegment};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_playlist() {
|
fn test_simple_playlist() {
|
||||||
let playlist = concat!(
|
let playlist = concat!(
|
||||||
|
@ -17,31 +18,24 @@ fn test_simple_playlist() {
|
||||||
"#EXT-X-ENDLIST\n"
|
"#EXT-X-ENDLIST\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
let media_playlist = playlist.parse::<MediaPlaylist>().unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
media_playlist.target_duration(),
|
MediaPlaylist::builder()
|
||||||
ExtXTargetDuration::new(Duration::from_secs(5220))
|
.target_duration(Duration::from_secs(5220))
|
||||||
);
|
.segments(vec![
|
||||||
|
MediaSegment::builder()
|
||||||
assert_eq!(media_playlist.segments().len(), 2);
|
.inf(ExtInf::new(Duration::from_secs(0)))
|
||||||
|
.uri("http://media.example.com/entire1.ts")
|
||||||
assert_eq!(
|
.build()
|
||||||
media_playlist.segments()[0].inf_tag(),
|
.unwrap(),
|
||||||
&ExtInf::new(Duration::from_secs(0))
|
MediaSegment::builder()
|
||||||
);
|
.inf(ExtInf::new(Duration::from_secs(5220)))
|
||||||
|
.uri("http://media.example.com/entire2.ts")
|
||||||
assert_eq!(
|
.build()
|
||||||
media_playlist.segments()[1].inf_tag(),
|
.unwrap(),
|
||||||
&ExtInf::new(Duration::from_secs(5220))
|
])
|
||||||
);
|
.end_list(ExtXEndList)
|
||||||
|
.build()
|
||||||
assert_eq!(
|
.unwrap(),
|
||||||
media_playlist.segments()[0].uri(),
|
playlist.parse::<MediaPlaylist>().unwrap(),
|
||||||
&"http://media.example.com/entire1.ts".to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
media_playlist.segments()[1].uri(),
|
|
||||||
&"http://media.example.com/entire2.ts".to_string()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue