mirror of
https://github.com/sile/hls_m3u8.git
synced 2025-01-10 20:25:25 +00:00
fix #20
This commit is contained in:
parent
ebddb7a0e2
commit
4b4cffc248
31 changed files with 354 additions and 207 deletions
|
@ -76,6 +76,10 @@ pub enum ErrorKind {
|
|||
/// An unexpected value.
|
||||
UnexpectedAttribute(String),
|
||||
|
||||
#[fail(display = "Unexpected Tag: {:?}", _0)]
|
||||
/// An unexpected tag.
|
||||
UnexpectedTag(String),
|
||||
|
||||
/// Hints that destructuring should not be exhaustive.
|
||||
///
|
||||
/// This enum may grow additional variants, so this makes sure clients
|
||||
|
@ -119,6 +123,10 @@ impl Error {
|
|||
Self::from(ErrorKind::UnexpectedAttribute(value.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn unexpected_tag<T: ToString>(value: T) -> Self {
|
||||
Self::from(ErrorKind::UnexpectedTag(value.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) }
|
||||
|
||||
pub(crate) fn parse_int_error<T: ToString>(value: T) -> Self {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![forbid(unsafe_code)]
|
||||
#![feature(option_flattening)]
|
||||
#![warn(
|
||||
//clippy::pedantic,
|
||||
clippy::nursery,
|
||||
|
@ -49,6 +50,8 @@ mod line;
|
|||
mod master_playlist;
|
||||
mod media_playlist;
|
||||
mod media_segment;
|
||||
mod traits;
|
||||
mod utils;
|
||||
|
||||
pub use error::Result;
|
||||
pub use traits::*;
|
||||
|
|
|
@ -10,8 +10,8 @@ use crate::tags::{
|
|||
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
||||
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion,
|
||||
};
|
||||
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, RequiredVersion};
|
||||
use crate::Error;
|
||||
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// Master playlist.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
|
|
|
@ -11,8 +11,8 @@ use crate::tags::{
|
|||
ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXIndependentSegments,
|
||||
ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXTargetDuration, ExtXVersion,
|
||||
};
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::Error;
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::{Encrypted, Error, RequiredVersion};
|
||||
|
||||
/// Media playlist.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
|
@ -28,30 +28,30 @@ pub struct MediaPlaylist {
|
|||
/// The default is the maximum version among the tags in the playlist.
|
||||
#[builder(setter(name = "version"))]
|
||||
version_tag: ExtXVersion,
|
||||
/// Sets the [ExtXTargetDuration] tag.
|
||||
/// Sets the [`ExtXTargetDuration`] tag.
|
||||
target_duration_tag: ExtXTargetDuration,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXMediaSequence] tag.
|
||||
/// Sets the [`ExtXMediaSequence`] tag.
|
||||
media_sequence_tag: Option<ExtXMediaSequence>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXDiscontinuitySequence] tag.
|
||||
/// Sets the [`ExtXDiscontinuitySequence`] tag.
|
||||
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXPlaylistType] tag.
|
||||
/// Sets the [`ExtXPlaylistType`] tag.
|
||||
playlist_type_tag: Option<ExtXPlaylistType>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXIFramesOnly] tag.
|
||||
/// Sets the [`ExtXIFramesOnly`] tag.
|
||||
i_frames_only_tag: Option<ExtXIFramesOnly>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXIndependentSegments] tag.
|
||||
/// Sets the [`ExtXIndependentSegments`] tag.
|
||||
independent_segments_tag: Option<ExtXIndependentSegments>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXStart] tag.
|
||||
/// Sets the [`ExtXStart`] tag.
|
||||
start_tag: Option<ExtXStart>,
|
||||
#[builder(default)]
|
||||
/// Sets the [ExtXEndList] tag.
|
||||
/// Sets the [`ExtXEndList`] tag.
|
||||
end_list_tag: Option<ExtXEndList>,
|
||||
/// Sets all [MediaSegment]s.
|
||||
/// Sets all [`MediaSegment`]s.
|
||||
segments: Vec<MediaSegment>,
|
||||
/// Sets the allowable excess duration of each media segment in the
|
||||
/// associated playlist.
|
||||
|
@ -69,10 +69,7 @@ pub struct MediaPlaylist {
|
|||
impl MediaPlaylistBuilder {
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
let required_version = self.required_version();
|
||||
let specified_version = self
|
||||
.version_tag
|
||||
.unwrap_or_else(|| required_version.into())
|
||||
.version();
|
||||
let specified_version = self.version_tag.map_or(required_version, |p| p.version());
|
||||
|
||||
if required_version > specified_version {
|
||||
return Err(Error::custom(format!(
|
||||
|
@ -138,72 +135,6 @@ impl MediaPlaylistBuilder {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn required_version(&self) -> ProtocolVersion {
|
||||
iter::empty()
|
||||
.chain(
|
||||
self.target_duration_tag
|
||||
.iter()
|
||||
.map(|t| t.required_version()),
|
||||
)
|
||||
.chain(self.media_sequence_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.discontinuity_sequence_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.playlist_type_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.i_frames_only_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.independent_segments_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.start_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.end_list_tag.iter().map(|t| {
|
||||
if let Some(p) = t {
|
||||
p.required_version()
|
||||
} else {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}))
|
||||
.chain(self.segments.iter().map(|t| {
|
||||
t.iter()
|
||||
.map(|s| s.required_version())
|
||||
.max()
|
||||
.unwrap_or(ProtocolVersion::V1)
|
||||
}))
|
||||
.max()
|
||||
.unwrap_or_else(ProtocolVersion::latest)
|
||||
}
|
||||
|
||||
/// Adds a media segment to the resulting playlist.
|
||||
pub fn push_segment<VALUE: Into<MediaSegment>>(&mut self, value: VALUE) -> &mut Self {
|
||||
if let Some(segments) = &mut self.segments {
|
||||
|
@ -214,50 +145,111 @@ impl MediaPlaylistBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Parse the rest of the [MediaPlaylist] from an m3u8 file.
|
||||
/// Parse the rest of the [`MediaPlaylist`] from an m3u8 file.
|
||||
pub fn parse(&mut self, input: &str) -> crate::Result<MediaPlaylist> {
|
||||
parse_media_playlist(input, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RequiredVersion for MediaPlaylistBuilder {
|
||||
fn required_version(&self) -> ProtocolVersion {
|
||||
iter::empty()
|
||||
.chain(
|
||||
self.target_duration_tag
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.media_sequence_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.discontinuity_sequence_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.playlist_type_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.i_frames_only_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.independent_segments_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.start_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(
|
||||
self.end_list_tag
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|p| p.required_version()),
|
||||
)
|
||||
.chain(self.segments.iter().map(|t| {
|
||||
t.iter()
|
||||
.map(|p| p.required_version())
|
||||
.max()
|
||||
.unwrap_or(ProtocolVersion::V1)
|
||||
}))
|
||||
.max()
|
||||
.unwrap_or_else(ProtocolVersion::latest)
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaPlaylist {
|
||||
/// Creates a [MediaPlaylistBuilder].
|
||||
/// Returns a builder for [`MediaPlaylist`].
|
||||
pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() }
|
||||
|
||||
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
||||
/// Returns the [`ExtXVersion`] tag contained in the playlist.
|
||||
pub const fn version_tag(&self) -> ExtXVersion { self.version_tag }
|
||||
|
||||
/// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
|
||||
/// Returns the [`ExtXTargetDuration`] tag contained in the playlist.
|
||||
pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag }
|
||||
|
||||
/// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
|
||||
pub const fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> { self.media_sequence_tag }
|
||||
|
||||
/// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the
|
||||
/// Returns the [`ExtXDiscontinuitySequence`] tag contained in the
|
||||
/// playlist.
|
||||
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
|
||||
self.discontinuity_sequence_tag
|
||||
}
|
||||
|
||||
/// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
|
||||
/// Returns the [`ExtXPlaylistType`] tag contained in the playlist.
|
||||
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> { self.playlist_type_tag }
|
||||
|
||||
/// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist.
|
||||
/// Returns the [`ExtXIFramesOnly`] tag contained in the playlist.
|
||||
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> { self.i_frames_only_tag }
|
||||
|
||||
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
|
||||
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
|
||||
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
||||
self.independent_segments_tag
|
||||
}
|
||||
|
||||
/// Returns the `EXT-X-START` tag contained in the playlist.
|
||||
/// Returns the [`ExtXStart`] tag contained in the playlist.
|
||||
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
|
||||
|
||||
/// Returns the `EXT-X-ENDLIST` tag contained in the playlist.
|
||||
/// Returns the [`ExtXEndList`] tag contained in the playlist.
|
||||
pub const fn end_list_tag(&self) -> Option<ExtXEndList> { self.end_list_tag }
|
||||
|
||||
/// Returns the media segments contained in the playlist.
|
||||
pub fn segments(&self) -> &[MediaSegment] { &self.segments }
|
||||
/// Returns the [`MediaSegment`]s contained in the playlist.
|
||||
pub const fn segments(&self) -> &Vec<MediaSegment> { &self.segments }
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaPlaylist {
|
||||
|
@ -306,6 +298,8 @@ fn parse_media_playlist(
|
|||
let mut has_discontinuity_tag = false;
|
||||
let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1
|
||||
|
||||
let mut available_key_tags = vec![];
|
||||
|
||||
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
|
||||
match line {
|
||||
Line::Tag(tag) => {
|
||||
|
@ -336,10 +330,29 @@ fn parse_media_playlist(
|
|||
}
|
||||
Tag::ExtXKey(t) => {
|
||||
has_partial_segment = true;
|
||||
segment.push_key_tag(t);
|
||||
if !available_key_tags.is_empty() {
|
||||
available_key_tags.push(t);
|
||||
} else {
|
||||
// An ExtXKey applies to every MediaSegment and to every Media
|
||||
// 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
|
||||
// same KEYFORMAT attribute (or the end of the Playlist file).
|
||||
available_key_tags = available_key_tags
|
||||
.into_iter()
|
||||
.map(|k| {
|
||||
if t.key_format() == k.key_format() {
|
||||
t.clone()
|
||||
} else {
|
||||
k
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
Tag::ExtXMap(t) => {
|
||||
Tag::ExtXMap(mut t) => {
|
||||
has_partial_segment = true;
|
||||
|
||||
t.set_keys(available_key_tags.clone());
|
||||
segment.map_tag(t);
|
||||
}
|
||||
Tag::ExtXProgramDateTime(t) => {
|
||||
|
@ -379,7 +392,7 @@ fn parse_media_playlist(
|
|||
| Tag::ExtXIFrameStreamInf(_)
|
||||
| Tag::ExtXSessionData(_)
|
||||
| Tag::ExtXSessionKey(_) => {
|
||||
return Err(Error::custom(tag));
|
||||
return Err(Error::unexpected_tag(tag));
|
||||
}
|
||||
Tag::ExtXIndependentSegments(t) => {
|
||||
builder.independent_segments_tag(t);
|
||||
|
@ -395,15 +408,18 @@ fn parse_media_playlist(
|
|||
}
|
||||
Line::Uri(uri) => {
|
||||
segment.uri(uri);
|
||||
segment.keys(available_key_tags.clone());
|
||||
segments.push(segment.build().map_err(Error::builder_error)?);
|
||||
segment = MediaSegment::builder();
|
||||
has_partial_segment = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if has_partial_segment {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
|
||||
if !has_version {
|
||||
builder.version(ProtocolVersion::V1);
|
||||
}
|
||||
|
|
|
@ -6,43 +6,44 @@ use derive_builder::Builder;
|
|||
use crate::tags::{
|
||||
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
|
||||
};
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::{Encrypted, RequiredVersion};
|
||||
|
||||
/// Media segment.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[builder(setter(into, strip_option))]
|
||||
/// Media segment.
|
||||
pub struct MediaSegment {
|
||||
#[builder(default)]
|
||||
/// Sets all [ExtXKey] tags.
|
||||
key_tags: Vec<ExtXKey>,
|
||||
/// Sets all [`ExtXKey`] tags.
|
||||
keys: Vec<ExtXKey>,
|
||||
#[builder(default)]
|
||||
/// Sets an [ExtXMap] tag.
|
||||
/// Sets an [`ExtXMap`] tag.
|
||||
map_tag: Option<ExtXMap>,
|
||||
#[builder(default)]
|
||||
/// Sets an [ExtXByteRange] tag.
|
||||
/// Sets an [`ExtXByteRange`] tag.
|
||||
byte_range_tag: Option<ExtXByteRange>,
|
||||
#[builder(default)]
|
||||
/// Sets an [ExtXDateRange] tag.
|
||||
/// Sets an [`ExtXDateRange`] tag.
|
||||
date_range_tag: Option<ExtXDateRange>,
|
||||
#[builder(default)]
|
||||
/// Sets an [ExtXDiscontinuity] tag.
|
||||
/// Sets an [`ExtXDiscontinuity`] tag.
|
||||
discontinuity_tag: Option<ExtXDiscontinuity>,
|
||||
#[builder(default)]
|
||||
/// Sets an [ExtXProgramDateTime] tag.
|
||||
/// Sets an [`ExtXProgramDateTime`] tag.
|
||||
program_date_time_tag: Option<ExtXProgramDateTime>,
|
||||
/// Sets an [ExtInf] tag.
|
||||
/// Sets an [`ExtInf`] tag.
|
||||
inf_tag: ExtInf,
|
||||
/// Sets an Uri.
|
||||
/// Sets an `URI`.
|
||||
uri: String,
|
||||
}
|
||||
|
||||
impl MediaSegmentBuilder {
|
||||
/// Pushes an [ExtXKey] tag.
|
||||
/// Pushes an [`ExtXKey`] tag.
|
||||
pub fn push_key_tag<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
|
||||
if let Some(key_tags) = &mut self.key_tags {
|
||||
if let Some(key_tags) = &mut self.keys {
|
||||
key_tags.push(value.into());
|
||||
} else {
|
||||
self.key_tags = Some(vec![value.into()]);
|
||||
self.keys = Some(vec![value.into()]);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -50,7 +51,7 @@ impl MediaSegmentBuilder {
|
|||
|
||||
impl fmt::Display for MediaSegment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for value in &self.key_tags {
|
||||
for value in &self.keys {
|
||||
writeln!(f, "{}", value)?;
|
||||
}
|
||||
if let Some(value) = &self.map_tag {
|
||||
|
@ -75,41 +76,38 @@ impl fmt::Display for MediaSegment {
|
|||
}
|
||||
|
||||
impl MediaSegment {
|
||||
/// Creates a [MediaSegmentBuilder].
|
||||
/// Creates a [`MediaSegmentBuilder`].
|
||||
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
|
||||
|
||||
/// Returns the URI of the media segment.
|
||||
/// Returns the `URI` of the media segment.
|
||||
pub const fn uri(&self) -> &String { &self.uri }
|
||||
|
||||
/// Returns the `EXT-X-INF` tag associated with the media segment.
|
||||
/// Returns the [`ExtInf`] tag associated with the media segment.
|
||||
pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag }
|
||||
|
||||
/// Returns the `EXT-X-BYTERANGE` tag associated with the media segment.
|
||||
/// Returns the [`ExtXByteRange`] tag associated with the media segment.
|
||||
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> { self.byte_range_tag }
|
||||
|
||||
/// Returns the `EXT-X-DATERANGE` tag associated with the media segment.
|
||||
pub fn date_range_tag(&self) -> Option<&ExtXDateRange> { self.date_range_tag.as_ref() }
|
||||
/// Returns the [`ExtXDateRange`] tag associated with the media segment.
|
||||
pub const fn date_range_tag(&self) -> &Option<ExtXDateRange> { &self.date_range_tag }
|
||||
|
||||
/// Returns the `EXT-X-DISCONTINUITY` tag associated with the media segment.
|
||||
/// Returns the [`ExtXDiscontinuity`] tag associated with the media segment.
|
||||
pub const fn discontinuity_tag(&self) -> Option<ExtXDiscontinuity> { self.discontinuity_tag }
|
||||
|
||||
/// Returns the `EXT-X-PROGRAM-DATE-TIME` tag associated with the media
|
||||
/// Returns the [`ExtXProgramDateTime`] tag associated with the media
|
||||
/// segment.
|
||||
pub fn program_date_time_tag(&self) -> Option<&ExtXProgramDateTime> {
|
||||
self.program_date_time_tag.as_ref()
|
||||
pub const fn program_date_time_tag(&self) -> Option<ExtXProgramDateTime> {
|
||||
self.program_date_time_tag
|
||||
}
|
||||
|
||||
/// Returns the `EXT-X-MAP` tag associated with the media segment.
|
||||
pub fn map_tag(&self) -> Option<&ExtXMap> { self.map_tag.as_ref() }
|
||||
|
||||
/// Returns the `EXT-X-KEY` tags associated with the media segment.
|
||||
pub fn key_tags(&self) -> &[ExtXKey] { &self.key_tags }
|
||||
/// Returns the [`ExtXMap`] tag associated with the media segment.
|
||||
pub const fn map_tag(&self) -> &Option<ExtXMap> { &self.map_tag }
|
||||
}
|
||||
|
||||
impl RequiredVersion for MediaSegment {
|
||||
fn required_version(&self) -> ProtocolVersion {
|
||||
iter::empty()
|
||||
.chain(self.key_tags.iter().map(|t| t.required_version()))
|
||||
.chain(self.keys.iter().map(|t| t.required_version()))
|
||||
.chain(self.map_tag.iter().map(|t| t.required_version()))
|
||||
.chain(self.byte_range_tag.iter().map(|t| t.required_version()))
|
||||
.chain(self.date_range_tag.iter().map(|t| t.required_version()))
|
||||
|
@ -121,6 +119,12 @@ impl RequiredVersion for MediaSegment {
|
|||
)
|
||||
.chain(iter::once(self.inf_tag.required_version()))
|
||||
.max()
|
||||
.unwrap_or(ProtocolVersion::V7)
|
||||
.unwrap_or_else(ProtocolVersion::latest)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypted for MediaSegment {
|
||||
fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
|
||||
|
||||
fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.1.1. EXTM3U]
|
||||
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.1.2. EXT-X-VERSION]
|
||||
/// The [`ExtXVersion`] tag indicates the compatibility version of the
|
||||
|
|
|
@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, RequiredVersion, StreamInf};
|
||||
use crate::types::{ProtocolVersion, StreamInf};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]
|
||||
/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,
|
||||
|
|
|
@ -4,9 +4,9 @@ use std::str::FromStr;
|
|||
use derive_builder::Builder;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion, RequiredVersion};
|
||||
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion};
|
||||
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.5.1. EXT-X-MEDIA]
|
||||
/// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s,
|
||||
|
|
|
@ -4,9 +4,9 @@ use std::str::FromStr;
|
|||
use derive_builder::Builder;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// The data of an [ExtXSessionData] tag.
|
||||
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion, RequiredVersion};
|
||||
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion};
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.3.4.5. EXT-X-SESSION-KEY]
|
||||
/// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s
|
||||
|
|
|
@ -3,11 +3,9 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{
|
||||
ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, RequiredVersion, StreamInf,
|
||||
};
|
||||
use crate::types::{ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, StreamInf};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||
///
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::RequiredVersion;
|
||||
|
||||
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
|
||||
///
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.3.4. EXT-X-ENDLIST]
|
||||
/// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY]
|
||||
/// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE]
|
||||
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
|
||||
///
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.3.1. EXT-X-TARGETDURATION]
|
||||
/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ByteRange, ProtocolVersion, RequiredVersion};
|
||||
use crate::types::{ByteRange, ProtocolVersion};
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.2.2. EXT-X-BYTERANGE]
|
||||
///
|
||||
|
|
|
@ -6,9 +6,9 @@ use std::time::Duration;
|
|||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.3.2.7. EXT-X-DATERANGE]
|
||||
///
|
||||
|
@ -106,7 +106,8 @@ impl ExtXDateRange {
|
|||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use hls_m3u8::types::{ProtocolVersion, RequiredVersion};
|
||||
/// use hls_m3u8::types::ProtocolVersion;
|
||||
/// use hls_m3u8::RequiredVersion;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.2.3. EXT-X-DISCONTINUITY]
|
||||
/// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.2.1. EXTINF]
|
||||
///
|
||||
|
|
|
@ -2,9 +2,10 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ByteRange, ProtocolVersion, RequiredVersion};
|
||||
use crate::tags::ExtXKey;
|
||||
use crate::types::{ByteRange, ProtocolVersion};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Encrypted, Error, RequiredVersion};
|
||||
|
||||
/// # [4.4.2.5. EXT-X-MAP]
|
||||
/// The [`ExtXMap`] tag specifies how to obtain the Media Initialization
|
||||
|
@ -22,6 +23,7 @@ use crate::Error;
|
|||
pub struct ExtXMap {
|
||||
uri: String,
|
||||
range: Option<ByteRange>,
|
||||
keys: Vec<ExtXKey>,
|
||||
}
|
||||
|
||||
impl ExtXMap {
|
||||
|
@ -29,17 +31,19 @@ impl ExtXMap {
|
|||
|
||||
/// Makes a new [`ExtXMap`] tag.
|
||||
pub fn new<T: ToString>(uri: T) -> Self {
|
||||
ExtXMap {
|
||||
Self {
|
||||
uri: uri.to_string(),
|
||||
range: None,
|
||||
keys: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`ExtXMap`] tag with the given range.
|
||||
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
|
||||
ExtXMap {
|
||||
Self {
|
||||
uri: uri.to_string(),
|
||||
range: Some(range),
|
||||
keys: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,6 +55,13 @@ impl ExtXMap {
|
|||
pub const fn range(&self) -> Option<ByteRange> { self.range }
|
||||
}
|
||||
|
||||
impl Encrypted for ExtXMap {
|
||||
fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
|
||||
|
||||
fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
|
||||
}
|
||||
|
||||
/// This tag requires [`ProtocolVersion::V6`].
|
||||
impl RequiredVersion for ExtXMap {
|
||||
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V6 }
|
||||
}
|
||||
|
@ -59,9 +70,11 @@ impl fmt::Display for ExtXMap {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "URI={}", quote(&self.uri))?;
|
||||
|
||||
if let Some(value) = &self.range {
|
||||
write!(f, ",BYTERANGE={}", quote(value))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +103,11 @@ impl FromStr for ExtXMap {
|
|||
}
|
||||
|
||||
let uri = uri.ok_or_else(|| Error::missing_value("EXT-X-URI"))?;
|
||||
Ok(ExtXMap { uri, range })
|
||||
Ok(Self {
|
||||
uri,
|
||||
range,
|
||||
keys: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ use std::str::FromStr;
|
|||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
|
||||
/// The [`ExtXProgramDateTime`] tag associates the first sample of a
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||
///
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, RequiredVersion, SignedDecimalFloatingPoint};
|
||||
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
||||
use crate::utils::{parse_yes_or_no, tag};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// [4.3.5.2. EXT-X-START]
|
||||
///
|
||||
|
|
124
src/traits.rs
Normal file
124
src/traits.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use crate::tags::ExtXKey;
|
||||
use crate::types::{EncryptionMethod, ProtocolVersion};
|
||||
|
||||
/// A trait, that is implemented on all tags, that could be encrypted.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use hls_m3u8::tags::ExtXKey;
|
||||
/// use hls_m3u8::types::EncryptionMethod;
|
||||
/// use hls_m3u8::Encrypted;
|
||||
///
|
||||
/// struct ExampleTag {
|
||||
/// keys: Vec<ExtXKey>,
|
||||
/// }
|
||||
///
|
||||
/// // Implementing the trait is very simple:
|
||||
/// // Simply expose the internal buffer, that contains all the keys.
|
||||
/// impl Encrypted for ExampleTag {
|
||||
/// fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
|
||||
///
|
||||
/// fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
|
||||
/// }
|
||||
///
|
||||
/// let mut example_tag = ExampleTag { keys: vec![] };
|
||||
///
|
||||
/// // adding new keys:
|
||||
/// example_tag.set_keys(vec![ExtXKey::empty()]);
|
||||
/// example_tag.push_key(ExtXKey::new(
|
||||
/// EncryptionMethod::Aes128,
|
||||
/// "http://www.example.com/data.bin",
|
||||
/// ));
|
||||
///
|
||||
/// // getting the keys:
|
||||
/// assert_eq!(
|
||||
/// example_tag.keys(),
|
||||
/// &vec![
|
||||
/// ExtXKey::empty(),
|
||||
/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",)
|
||||
/// ]
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// example_tag.keys_mut(),
|
||||
/// &mut vec![
|
||||
/// ExtXKey::empty(),
|
||||
/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",)
|
||||
/// ]
|
||||
/// );
|
||||
///
|
||||
/// assert!(example_tag.is_encrypted());
|
||||
/// assert!(!example_tag.is_not_encrypted());
|
||||
/// ```
|
||||
pub trait Encrypted {
|
||||
/// Returns a shared reference to all keys, that can be used to decrypt this
|
||||
/// tag.
|
||||
fn keys(&self) -> &Vec<ExtXKey>;
|
||||
|
||||
/// Returns an exclusive reference to all keys, that can be used to decrypt
|
||||
/// this tag.
|
||||
fn keys_mut(&mut self) -> &mut Vec<ExtXKey>;
|
||||
|
||||
/// Sets all keys, that can be used to decrypt this tag.
|
||||
fn set_keys(&mut self, value: Vec<ExtXKey>) -> &mut Self {
|
||||
let keys = self.keys_mut();
|
||||
*keys = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a single key to the list of keys, that can be used to decrypt this
|
||||
/// tag.
|
||||
fn push_key(&mut self, value: ExtXKey) -> &mut Self {
|
||||
self.keys_mut().push(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true`, if the tag is encrypted.
|
||||
///
|
||||
/// # Note
|
||||
/// This will return `true`, if any of the keys satisfies
|
||||
/// ```text
|
||||
/// key.method() != EncryptionMethod::None
|
||||
/// ```
|
||||
fn is_encrypted(&self) -> bool {
|
||||
if self.keys().is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.keys()
|
||||
.iter()
|
||||
.any(|k| k.method() != EncryptionMethod::None)
|
||||
}
|
||||
|
||||
/// Returns `false`, if the tag is not encrypted.
|
||||
///
|
||||
/// # Note
|
||||
/// This is the inverse of [`is_encrypted`].
|
||||
///
|
||||
/// [`is_encrypted`]: #method.is_encrypted
|
||||
fn is_not_encrypted(&self) -> bool { !self.is_encrypted() }
|
||||
}
|
||||
|
||||
/// # Example
|
||||
/// Implementing it:
|
||||
/// ```
|
||||
/// # use hls_m3u8::RequiredVersion;
|
||||
/// use hls_m3u8::types::ProtocolVersion;
|
||||
///
|
||||
/// struct ExampleTag(u64);
|
||||
///
|
||||
/// impl RequiredVersion for ExampleTag {
|
||||
/// fn required_version(&self) -> ProtocolVersion {
|
||||
/// if self.0 == 5 {
|
||||
/// ProtocolVersion::V4
|
||||
/// } else {
|
||||
/// ProtocolVersion::V1
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// assert_eq!(ExampleTag(5).required_version(), ProtocolVersion::V4);
|
||||
/// assert_eq!(ExampleTag(2).required_version(), ProtocolVersion::V1);
|
||||
/// ```
|
||||
pub trait RequiredVersion {
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
fn required_version(&self) -> ProtocolVersion;
|
||||
}
|
|
@ -6,10 +6,9 @@ use derive_builder::Builder;
|
|||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{
|
||||
EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion,
|
||||
RequiredVersion,
|
||||
};
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[builder(setter(into), build_fn(validate = "Self::validate"))]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
/// [`KeyFormat`] specifies, how the key is represented in the
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::fmt;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::Error;
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// A list of [usize], that can be used to indicate which version(s)
|
||||
/// this instance complies with, if more than one version of a particular
|
||||
|
|
|
@ -3,30 +3,6 @@ use std::str::FromStr;
|
|||
|
||||
use crate::Error;
|
||||
|
||||
/// # Example
|
||||
/// Implementing it:
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::{ProtocolVersion, RequiredVersion};
|
||||
/// #
|
||||
/// struct NewTag(u64);
|
||||
///
|
||||
/// impl RequiredVersion for NewTag {
|
||||
/// fn required_version(&self) -> ProtocolVersion {
|
||||
/// if self.0 == 5 {
|
||||
/// ProtocolVersion::V4
|
||||
/// } else {
|
||||
/// ProtocolVersion::V1
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// assert_eq!(NewTag(5).required_version(), ProtocolVersion::V4);
|
||||
/// assert_eq!(NewTag(2).required_version(), ProtocolVersion::V1);
|
||||
/// ```
|
||||
pub trait RequiredVersion {
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
fn required_version(&self) -> ProtocolVersion;
|
||||
}
|
||||
|
||||
/// # [7. Protocol Version Compatibility]
|
||||
/// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse
|
||||
/// a certain tag correctly.
|
||||
|
|
Loading…
Reference in a new issue