1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-09 16:59:34 +00:00

improve ExtXMedia

This commit is contained in:
Luro02 2020-03-17 15:39:07 +01:00
parent 187174042d
commit 4e41585cbd
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
3 changed files with 282 additions and 151 deletions

View file

@ -234,9 +234,9 @@ impl MasterPlaylistBuilder {
fn check_media_group<T: AsRef<str>>(&self, media_type: MediaType, group_id: T) -> bool { fn check_media_group<T: AsRef<str>>(&self, media_type: MediaType, group_id: T) -> bool {
if let Some(value) = &self.media { if let Some(value) = &self.media {
value value.iter().any(|media| {
.iter() media.media_type == media_type && media.group_id().as_str() == group_id.as_ref()
.any(|t| t.media_type() == media_type && t.group_id().as_str() == group_id.as_ref()) })
} else { } else {
false false
} }

View file

@ -9,69 +9,113 @@ use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, tag, unquote}; use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// # [4.4.5.1. EXT-X-MEDIA] /// An [`ExtXMedia`] tag is an alternative rendition of a [`VariantStream`].
/// ///
/// The [`ExtXMedia`] tag is used to relate [`MediaPlaylist`]s, /// For example an [`ExtXMedia`] tag can be used to specify different audio
/// that contain alternative renditions of the same content. /// languages (e.g. english is the default and there also exists an
/// /// [`ExtXMedia`] stream with a german audio).
/// For example, three [`ExtXMedia`] tags can be used to identify audio-only
/// [`MediaPlaylist`]s, that contain English, French, and Spanish renditions
/// of the same presentation. Or, two [`ExtXMedia`] tags can be used to
/// identify video-only [`MediaPlaylist`]s that show two different camera
/// angles.
/// ///
/// [`MediaPlaylist`]: crate::MediaPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.4.5.1. EXT-X-MEDIA]: /// [`VariantStream`]: crate::tags::VariantStream
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.1
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash)] #[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash)]
#[shorthand(enable(must_use, into))] #[shorthand(enable(must_use, into))]
#[builder(setter(into))] #[builder(setter(into))]
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
pub struct ExtXMedia { pub struct ExtXMedia {
/// The [`MediaType`] that is associated with this tag. /// The [`MediaType`] associated with this tag.
/// ///
/// # Note /// ### Note
/// ///
/// This attribute is **required**. /// This field is required.
#[shorthand(enable(copy))] #[shorthand(enable(skip))]
media_type: MediaType, pub media_type: MediaType,
/// An `URI` to a [`MediaPlaylist`]. /// An `URI` to a [`MediaPlaylist`].
/// ///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
///
/// let mut media = ExtXMedia::new(MediaType::Audio, "ag1", "english audio channel");
/// # assert_eq!(media.uri(), None);
///
/// media.set_uri(Some("https://www.example.com/stream1.m3u8"));
///
/// assert_eq!(
/// media.uri(),
/// Some(&"https://www.example.com/stream1.m3u8".to_string())
/// );
/// ```
///
/// # Note /// # Note
/// ///
/// - This attribute is **required**, if the [`MediaType`] is /// - This field is required, if the [`ExtXMedia::media_type`] is
/// [`MediaType::Subtitles`]. /// [`MediaType::Subtitles`].
/// - This attribute is **not allowed**, if the [`MediaType`] is /// - This field is not allowed, if the [`ExtXMedia::media_type`] is
/// [`MediaType::ClosedCaptions`]. /// [`MediaType::ClosedCaptions`].
/// ///
/// An absent value indicates that the media data for this rendition is
/// included in the [`MediaPlaylist`] of any
/// [`VariantStream::ExtXStreamInf`] tag with the same `group_id` of
/// this [`ExtXMedia`] instance.
///
/// [`MediaPlaylist`]: crate::MediaPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`VariantStream::ExtXStreamInf`]:
/// crate::tags::VariantStream::ExtXStreamInf
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
uri: Option<String>, uri: Option<String>,
/// The identifier that specifies the group to which the rendition /// The identifier that specifies the group to which the rendition
/// belongs. /// belongs.
/// ///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
///
/// let mut media = ExtXMedia::new(MediaType::Audio, "ag1", "english audio channel");
///
/// media.set_group_id("ag2");
///
/// assert_eq!(media.group_id(), &"ag2".to_string());
/// ```
///
/// # Note /// # Note
/// ///
/// This attribute is **required**. /// This field is required.
group_id: String, group_id: String,
/// The name of the primary language used in the rendition. /// The name of the primary language used in the rendition.
/// The value has to conform to [`RFC5646`]. /// The value has to conform to [`RFC5646`].
/// ///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
///
/// let mut media = ExtXMedia::new(MediaType::Audio, "ag1", "english audio channel");
///
/// media.set_language(Some("en"));
///
/// assert_eq!(media.language(), Some(&"en".to_string()));
/// ```
///
/// # Note /// # Note
/// ///
/// This attribute is **optional**. /// This field is optional.
/// ///
/// [`RFC5646`]: https://tools.ietf.org/html/rfc5646 /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
language: Option<String>, language: Option<String>,
/// The name of a language associated with the rendition. /// The name of a language associated with the rendition.
/// An associated language is often used in a different role, than the /// An associated language is often used in a different role, than the
/// language specified by the [`language`] attribute (e.g., written versus /// language specified by the [`language`] field (e.g., written versus
/// spoken, or a fallback dialect). /// spoken, or a fallback dialect).
/// ///
/// # Note /// # Note
/// ///
/// This attribute is **optional**. /// This field is optional.
/// ///
/// [`language`]: #method.language /// [`language`]: #method.language
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
@ -80,9 +124,9 @@ pub struct ExtXMedia {
/// ///
/// # Note /// # Note
/// ///
/// This attribute is **required**. /// This field is required.
/// ///
/// If the [`language`] attribute is present, this attribute should be in /// If the [`language`] field is present, this field should be in
/// that language. /// that language.
/// ///
/// [`language`]: #method.language /// [`language`]: #method.language
@ -92,61 +136,82 @@ pub struct ExtXMedia {
/// this rendition of the content in the absence of information /// this rendition of the content in the absence of information
/// from the user indicating a different choice. /// from the user indicating a different choice.
/// ///
/// # Note /// ### Note
/// ///
/// This attribute is **optional**, its absence indicates an implicit value /// This field is optional, its absence indicates an implicit value
/// of `false`. /// of `false`.
#[builder(default)] #[builder(default)]
is_default: bool, #[shorthand(enable(skip))]
pub is_default: bool,
/// Whether the client may choose to play this rendition in the absence of /// Whether the client may choose to play this rendition in the absence of
/// explicit user preference. /// explicit user preference.
/// ///
/// # Note /// ### Note
/// ///
/// This attribute is **optional**, its absence indicates an implicit value /// This field is optional, its absence indicates an implicit value
/// of `false`. /// of `false`.
#[builder(default)] #[builder(default)]
is_autoselect: bool, #[shorthand(enable(skip))]
pub is_autoselect: bool,
/// Whether the rendition contains content that is considered /// Whether the rendition contains content that is considered
/// essential to play. /// essential to play.
#[builder(default)] #[builder(default)]
is_forced: bool, #[shorthand(enable(skip))]
pub is_forced: bool,
/// An [`InStreamId`] identifies a rendition within the /// An [`InStreamId`] identifies a rendition within the
/// [`MediaSegment`]s in a [`MediaPlaylist`]. /// [`MediaSegment`]s in a [`MediaPlaylist`].
/// ///
/// # Note /// ### Note
/// ///
/// This attribute is required, if the [`ExtXMedia::media_type`] is /// This field is required, if the media type is
/// [`MediaType::ClosedCaptions`]. For all other [`ExtXMedia::media_type`] /// [`MediaType::ClosedCaptions`]. For all other media types the
/// the [`InStreamId`] must not be specified! /// [`InStreamId`] must not be specified!
/// ///
/// [`MediaPlaylist`]: crate::MediaPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist
/// [`MediaSegment`]: crate::MediaSegment /// [`MediaSegment`]: crate::MediaSegment
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
#[shorthand(enable(copy))] #[shorthand(enable(skip))]
instream_id: Option<InStreamId>, pub instream_id: Option<InStreamId>,
/// The characteristics attribute, containing one or more Uniform Type /// The characteristics field contains one or more Uniform Type
/// Identifiers (UTI) separated by comma. /// Identifiers ([`UTI`]) separated by a comma.
/// Each [`UTI`] indicates an individual characteristic of the Rendition. /// Each [`UTI`] indicates an individual characteristic of the Rendition.
/// ///
/// A [`subtitles`] rendition may include the following characteristics: /// An `ExtXMedia` instance with [`MediaType::Subtitles`] may include the
/// "public.accessibility.transcribes-spoken-dialog", /// following characteristics:
/// "public.accessibility.describes-music-and-sound", and /// - `"public.accessibility.transcribes-spoken-dialog"`,
/// "public.easy-to-read" (which indicates that the subtitles have /// - `"public.accessibility.describes-music-and-sound"`, and
/// - `"public.easy-to-read"` (which indicates that the subtitles have
/// been edited for ease of reading). /// been edited for ease of reading).
/// ///
/// An AUDIO Rendition MAY include the following characteristic: /// An `ExtXMedia` instance with [`MediaType::Audio`] may include the
/// "public.accessibility.describes-video". /// following characteristic:
/// - `"public.accessibility.describes-video"`
/// ///
/// The characteristics attribute may include private UTIs. /// The characteristics field may include private UTIs.
///
/// # Note
///
/// This field is optional.
/// ///
/// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI /// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI
/// [`subtitles`]: crate::types::MediaType::Subtitles
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
characteristics: Option<String>, characteristics: Option<String>,
/// The [`Channels`]. /// A count of audio channels indicating the maximum number of independent,
/// simultaneous audio channels present in any [`MediaSegment`] in the
/// rendition.
///
/// ### Note
///
/// This field is optional, but every instance of [`ExtXMedia`] with
/// [`MediaType::Audio`] should have this field. If the [`MasterPlaylist`]
/// contains two renditions with the same codec, but a different number of
/// channels, then the channels field is required.
///
/// [`MediaSegment`]: crate::MediaSegment
/// [`MasterPlaylist`]: crate::MasterPlaylist
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
channels: Option<Channels>, #[shorthand(enable(skip))]
pub channels: Option<Channels>,
} }
impl ExtXMediaBuilder { impl ExtXMediaBuilder {
@ -168,7 +233,9 @@ impl ExtXMediaBuilder {
return Err(Error::missing_attribute("INSTREAM-ID").to_string()); return Err(Error::missing_attribute("INSTREAM-ID").to_string());
} }
} else if self.instream_id.is_some() { } else if self.instream_id.is_some() {
return Err(Error::unexpected_attribute("INSTREAM-ID").to_string()); return Err(Error::custom(
"InStreamId should only be specified for an ExtXMedia tag with `MediaType::ClosedCaptions`"
).to_string());
} }
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) { if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
@ -190,7 +257,21 @@ impl ExtXMediaBuilder {
impl ExtXMedia { impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:"; pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new [`ExtXMedia`] tag. /// Makes a new [`ExtXMedia`] tag with the associated [`MediaType`], the
/// identifier that specifies the group to which the rendition belongs
/// (group id) and a human-readable description of the rendition. If the
/// [`language`] is specified it should be in that language.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
///
/// let media = ExtXMedia::new(MediaType::Video, "vg1", "1080p video stream");
/// ```
///
/// [`language`]: #method.language
#[must_use] #[must_use]
pub fn new<T, K>(media_type: MediaType, group_id: T, name: K) -> Self pub fn new<T, K>(media_type: MediaType, group_id: T, name: K) -> Self
where where
@ -214,10 +295,36 @@ impl ExtXMedia {
} }
/// Returns a builder for [`ExtXMedia`]. /// Returns a builder for [`ExtXMedia`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
///
/// let media = ExtXMedia::builder()
/// .media_type(MediaType::Subtitles)
/// .uri("french/ed.ttml")
/// .group_id("subs")
/// .language("fra")
/// .assoc_language("fra")
/// .name("French")
/// .is_autoselect(true)
/// .is_forced(true)
/// // concat! joins multiple `&'static str`
/// .characteristics(concat!(
/// "public.accessibility.transcribes-spoken-dialog,",
/// "public.accessibility.describes-music-and-sound"
/// ))
/// .build()?;
/// # Ok::<(), Box<dyn ::std::error::Error>>(())
/// ```
#[must_use] #[must_use]
pub fn builder() -> ExtXMediaBuilder { ExtXMediaBuilder::default() } pub fn builder() -> ExtXMediaBuilder { ExtXMediaBuilder::default() }
} }
/// This tag requires either `ProtocolVersion::V1` or if there is an
/// `instream_id` it requires it's version.
impl RequiredVersion for ExtXMedia { impl RequiredVersion for ExtXMedia {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
self.instream_id self.instream_id
@ -338,10 +445,8 @@ mod test {
#[test] #[test]
fn test_display_and_parse() { fn test_display_and_parse() {
// TODO: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/adding_alternate_media_to_a_playlist
macro_rules! generate_tests { macro_rules! generate_tests {
( $( { $media:expr, $string:tt } ),* $(,)* ) => { ( $( { $media:expr, $string:expr } ),* $(,)* ) => {
$( $(
assert_eq!( assert_eq!(
$media.to_string(), $media.to_string(),
@ -368,14 +473,16 @@ mod test {
.uri("eng/prog_index.m3u8") .uri("eng/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"eng/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio\",\ "URI=\"eng/prog_index.m3u8\",",
LANGUAGE=\"eng\",\ "GROUP-ID=\"audio\",",
NAME=\"English\",\ "LANGUAGE=\"eng\",",
DEFAULT=YES,\ "NAME=\"English\",",
AUTOSELECT=YES" "DEFAULT=YES,",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -388,13 +495,15 @@ mod test {
.is_autoselect(true) .is_autoselect(true)
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"fre/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio\",\ "URI=\"fre/prog_index.m3u8\",",
LANGUAGE=\"fre\",\ "GROUP-ID=\"audio\",",
NAME=\"Français\",\ "LANGUAGE=\"fre\",",
AUTOSELECT=YES" "NAME=\"Français\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -407,13 +516,15 @@ mod test {
.uri("sp/prog_index.m3u8") .uri("sp/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"sp/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio\",\ "URI=\"sp/prog_index.m3u8\",",
LANGUAGE=\"sp\",\ "GROUP-ID=\"audio\",",
NAME=\"Espanol\",\ "LANGUAGE=\"sp\",",
AUTOSELECT=YES" "NAME=\"Espanol\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -426,14 +537,16 @@ mod test {
.uri("englo/prog_index.m3u8") .uri("englo/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"englo/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-lo\",\ "URI=\"englo/prog_index.m3u8\",",
LANGUAGE=\"eng\",\ "GROUP-ID=\"audio-lo\",",
NAME=\"English\",\ "LANGUAGE=\"eng\",",
DEFAULT=YES,\ "NAME=\"English\",",
AUTOSELECT=YES" "DEFAULT=YES,",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -446,13 +559,15 @@ mod test {
.uri("frelo/prog_index.m3u8") .uri("frelo/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"frelo/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-lo\",\ "URI=\"frelo/prog_index.m3u8\",",
LANGUAGE=\"fre\",\ "GROUP-ID=\"audio-lo\",",
NAME=\"Français\",\ "LANGUAGE=\"fre\",",
AUTOSELECT=YES" "NAME=\"Français\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -465,13 +580,15 @@ mod test {
.uri("splo/prog_index.m3u8") .uri("splo/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"splo/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-lo\",\ "URI=\"splo/prog_index.m3u8\",",
LANGUAGE=\"es\",\ "GROUP-ID=\"audio-lo\",",
NAME=\"Espanol\",\ "LANGUAGE=\"es\",",
AUTOSELECT=YES" "NAME=\"Espanol\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -484,14 +601,16 @@ mod test {
.uri("eng/prog_index.m3u8") .uri("eng/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"eng/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-hi\",\ "URI=\"eng/prog_index.m3u8\",",
LANGUAGE=\"eng\",\ "GROUP-ID=\"audio-hi\",",
NAME=\"English\",\ "LANGUAGE=\"eng\",",
DEFAULT=YES,\ "NAME=\"English\",",
AUTOSELECT=YES" "DEFAULT=YES,",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -504,13 +623,15 @@ mod test {
.uri("fre/prog_index.m3u8") .uri("fre/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"fre/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-hi\",\ "URI=\"fre/prog_index.m3u8\",",
LANGUAGE=\"fre\",\ "GROUP-ID=\"audio-hi\",",
NAME=\"Français\",\ "LANGUAGE=\"fre\",",
AUTOSELECT=YES" "NAME=\"Français\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -523,13 +644,15 @@ mod test {
.uri("sp/prog_index.m3u8") .uri("sp/prog_index.m3u8")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
URI=\"sp/prog_index.m3u8\",\ "TYPE=AUDIO,",
GROUP-ID=\"audio-hi\",\ "URI=\"sp/prog_index.m3u8\",",
LANGUAGE=\"es\",\ "GROUP-ID=\"audio-hi\",",
NAME=\"Espanol\",\ "LANGUAGE=\"es\",",
AUTOSELECT=YES" "NAME=\"Espanol\",",
"AUTOSELECT=YES"
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -542,14 +665,16 @@ mod test {
.channels(Channels::new(2)) .channels(Channels::new(2))
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=AUDIO,\ "#EXT-X-MEDIA:",
GROUP-ID=\"audio-aacl-312\",\ "TYPE=AUDIO,",
LANGUAGE=\"en\",\ "GROUP-ID=\"audio-aacl-312\",",
NAME=\"English\",\ "LANGUAGE=\"en\",",
DEFAULT=YES,\ "NAME=\"English\",",
AUTOSELECT=YES,\ "DEFAULT=YES,",
CHANNELS=\"2\"" "AUTOSELECT=YES,",
"CHANNELS=\"2\""
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -565,17 +690,21 @@ mod test {
-dialog,public.accessibility.describes-music-and-sound") -dialog,public.accessibility.describes-music-and-sound")
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=SUBTITLES,\ "#EXT-X-MEDIA:",
URI=\"french/ed.ttml\",\ "TYPE=SUBTITLES,",
GROUP-ID=\"subs\",\ "URI=\"french/ed.ttml\",",
LANGUAGE=\"fra\",\ "GROUP-ID=\"subs\",",
ASSOC-LANGUAGE=\"fra\",\ "LANGUAGE=\"fra\",",
NAME=\"French\",\ "ASSOC-LANGUAGE=\"fra\",",
AUTOSELECT=YES,\ "NAME=\"French\",",
FORCED=YES,\ "AUTOSELECT=YES,",
CHARACTERISTICS=\"public.accessibility.\ "FORCED=YES,",
transcribes-spoken-dialog,public.accessibility.describes-music-and-sound\"" "CHARACTERISTICS=\"",
"public.accessibility.transcribes-spoken-dialog,",
"public.accessibility.describes-music-and-sound",
"\""
)
}, },
{ {
ExtXMedia::builder() ExtXMedia::builder()
@ -587,13 +716,15 @@ mod test {
.is_autoselect(true) .is_autoselect(true)
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ concat!(
TYPE=CLOSED-CAPTIONS,\ "#EXT-X-MEDIA:",
GROUP-ID=\"cc\",\ "TYPE=CLOSED-CAPTIONS,",
LANGUAGE=\"sp\",\ "GROUP-ID=\"cc\",",
NAME=\"CC2\",\ "LANGUAGE=\"sp\",",
AUTOSELECT=YES,\ "NAME=\"CC2\",",
INSTREAM-ID=\"CC2\"" "AUTOSELECT=YES,",
"INSTREAM-ID=\"CC2\""
)
}, },
{ {
ExtXMedia::new(MediaType::Audio, "foo", "bar"), ExtXMedia::new(MediaType::Audio, "foo", "bar"),

View file

@ -205,7 +205,7 @@ impl VariantStream {
pub fn is_associated(&self, media: &ExtXMedia) -> bool { pub fn is_associated(&self, media: &ExtXMedia) -> bool {
match &self { match &self {
Self::ExtXIFrame { stream_data, .. } => { Self::ExtXIFrame { stream_data, .. } => {
if let MediaType::Video = media.media_type() { if let MediaType::Video = media.media_type {
if let Some(value) = stream_data.video() { if let Some(value) = stream_data.video() {
return value == media.group_id(); return value == media.group_id();
} }
@ -220,7 +220,7 @@ impl VariantStream {
stream_data, stream_data,
.. ..
} => { } => {
match media.media_type() { match media.media_type {
MediaType::Audio => audio.as_ref().map_or(false, |v| v == media.group_id()), MediaType::Audio => audio.as_ref().map_or(false, |v| v == media.group_id()),
MediaType::Video => { MediaType::Video => {
stream_data.video().map_or(false, |v| v == media.group_id()) stream_data.video().map_or(false, |v| v == media.group_id())