diff --git a/src/error.rs b/src/error.rs index 1e30f59..7ed5be1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -176,6 +176,10 @@ impl Error { }) } + pub(crate) fn unexpected_data(value: &str) -> Self { + Self::custom(format!("Unexpected data in the line: {:?}", value)) + } + // third party crates: #[cfg(feature = "chrono")] pub(crate) fn chrono(source: chrono::format::ParseError) -> Self { diff --git a/src/line.rs b/src/line.rs index 6713c28..3603933 100644 --- a/src/line.rs +++ b/src/line.rs @@ -92,6 +92,8 @@ impl<'a> TryFrom<&'a str> for Tag<'a> { TryFrom::try_from(input).map(Self::ExtInf) } else if input.starts_with(tags::ExtXByteRange::PREFIX) { TryFrom::try_from(input).map(Self::ExtXByteRange) + } else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { + TryFrom::try_from(input).map(Self::ExtXDiscontinuitySequence) } else if input.starts_with(tags::ExtXDiscontinuity::PREFIX) { TryFrom::try_from(input).map(Self::ExtXDiscontinuity) } else if input.starts_with(tags::ExtXKey::PREFIX) { @@ -106,8 +108,6 @@ impl<'a> TryFrom<&'a str> for Tag<'a> { TryFrom::try_from(input).map(Self::ExtXDateRange) } else if input.starts_with(tags::ExtXMediaSequence::PREFIX) { TryFrom::try_from(input).map(Self::ExtXMediaSequence) - } else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { - TryFrom::try_from(input).map(Self::ExtXDiscontinuitySequence) } else if input.starts_with(tags::ExtXEndList::PREFIX) { TryFrom::try_from(input).map(Self::ExtXEndList) } else if input.starts_with(PlaylistType::PREFIX) { diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 20a8164..5dc5e2a 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -659,12 +659,16 @@ fn parse_media_playlist<'a>( builder.media_sequence(t.0); } Tag::ExtXDiscontinuitySequence(t) => { - if segments.is_empty() { - return Err(Error::invalid_input()); + // this tag must appear before the first MediaSegment in the playlist + // https://tools.ietf.org/html/rfc8216#section-4.3.3.3 + if !segments.is_empty() { + return Err(Error::custom("discontinuity sequence tag must appear before the first media segment in the playlist")); } + // this tag must appear before any ExtXDiscontinuity tag + // https://tools.ietf.org/html/rfc8216#section-4.3.3.3 if has_discontinuity_tag { - return Err(Error::invalid_input()); + return Err(Error::custom("discontinuity sequence tag must appear before any `ExtXDiscontinuity` tag")); } builder.discontinuity_sequence(t.0); diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index 4274fe3..df40643 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -2,7 +2,6 @@ use std::convert::TryFrom; use std::fmt; use crate::types::ProtocolVersion; -use crate::utils::tag; use crate::{Error, RequiredVersion}; /// The `ExtXDiscontinuity` tag indicates a discontinuity between the @@ -27,8 +26,13 @@ impl TryFrom<&str> for ExtXDiscontinuity { type Error = Error; fn try_from(input: &str) -> Result { - tag(input, Self::PREFIX)?; - Ok(Self) + // the parser assumes that only a single line is passed as input, + // which should be "#EXT-X-DISCONTINUITY" + if input == Self::PREFIX { + Ok(Self) + } else { + Err(Error::unexpected_data(input)) + } } } @@ -50,7 +54,9 @@ mod test { assert_eq!( ExtXDiscontinuity, ExtXDiscontinuity::try_from("#EXT-X-DISCONTINUITY").unwrap() - ) + ); + + assert!(ExtXDiscontinuity::try_from("#EXT-X-DISCONTINUITY:0").is_err()); } #[test] diff --git a/tests/issues/issue_00059.rs b/tests/issues/issue_00059.rs new file mode 100644 index 0000000..a48a0b2 --- /dev/null +++ b/tests/issues/issue_00059.rs @@ -0,0 +1,27 @@ +// The relevant issue: +// https://github.com/sile/hls_m3u8/issues/59 +use std::convert::TryFrom; + +use hls_m3u8::MediaPlaylist; + +use pretty_assertions::assert_eq; + +#[test] +fn parse() { + let playlist = concat!( + "#EXTM3U\n", + "#EXT-X-DISCONTINUITY-SEQUENCE:1\n", + "#EXT-X-TARGETDURATION:10\n", + "#EXT-X-VERSION:3\n", + "#EXTINF:9.009,\n", + "http://media.example.com/first.ts\n", + "#EXTINF:9.009,\n", + "http://media.example.com/second.ts\n", + "#EXTINF:3.003,\n", + "http://media.example.com/third.ts\n", + "#EXT-X-ENDLIST" + ); + + let playlist = MediaPlaylist::try_from(playlist).unwrap(); + assert_eq!(playlist.discontinuity_sequence, 1); +}