use core::convert::TryFrom; use core::fmt; use std::borrow::Cow; use derive_builder::Builder; use shorthand::ShortHand; use crate::attribute::AttributePairs; use crate::types::{Codecs, HdcpLevel, ProtocolVersion, Resolution}; use crate::utils::{quote, unquote}; use crate::{Error, RequiredVersion}; /// The [`StreamData`] struct contains the data that is shared between both /// variants of the [`VariantStream`]. /// /// [`VariantStream`]: crate::tags::VariantStream #[derive(ShortHand, Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)] #[builder(setter(strip_option))] #[builder(derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash))] #[shorthand(enable(must_use, into))] pub struct StreamData<'a> { /// The peak segment bitrate of the [`VariantStream`] in bits per second. /// /// If all the [`MediaSegment`]s in a [`VariantStream`] have already been /// created, the bandwidth value must be the largest sum of peak segment /// bitrates that is produced by any playable combination of renditions. /// /// (For a [`VariantStream`] with a single [`MediaPlaylist`], this is just /// the peak segment bit rate of that [`MediaPlaylist`].) /// /// An inaccurate value can cause playback stalls or prevent clients from /// playing the variant. If the [`MasterPlaylist`] is to be made available /// before all [`MediaSegment`]s in the presentation have been encoded, the /// bandwidth value should be the bandwidth value of a representative /// period of similar content, encoded using the same settings. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// # /// let mut stream = StreamData::new(20); /// /// stream.set_bandwidth(5); /// assert_eq!(stream.bandwidth(), 5); /// ``` /// /// # Note /// /// This field is required. /// /// [`VariantStream`]: crate::tags::VariantStream /// [`MediaSegment`]: crate::MediaSegment /// [`MasterPlaylist`]: crate::MasterPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist #[shorthand(disable(into))] bandwidth: u64, /// The average bandwidth of the stream in bits per second. /// /// It represents the average segment bitrate of the [`VariantStream`]. If /// all the [`MediaSegment`]s in a [`VariantStream`] have already been /// created, the average bandwidth must be the largest sum of average /// segment bitrates that is produced by any playable combination of /// renditions. /// /// (For a [`VariantStream`] with a single [`MediaPlaylist`], this is just /// the average segment bitrate of that [`MediaPlaylist`].) /// /// An inaccurate value can cause playback stalls or prevent clients from /// playing the variant. If the [`MasterPlaylist`] is to be made available /// before all [`MediaSegment`]s in the presentation have been encoded, the /// average bandwidth should be the average bandwidth of a representative /// period of similar content, encoded using the same settings. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// # /// let mut stream = StreamData::new(20); /// /// stream.set_average_bandwidth(Some(300)); /// assert_eq!(stream.average_bandwidth(), Some(300)); /// ``` /// /// # Note /// /// This field is optional. /// /// [`MediaSegment`]: crate::MediaSegment /// [`MasterPlaylist`]: crate::MasterPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist /// [`VariantStream`]: crate::tags::VariantStream #[builder(default)] #[shorthand(enable(copy), disable(into, option_as_ref))] average_bandwidth: Option, /// A list of formats, where each format specifies a media sample type that /// is present in one or more renditions specified by the [`VariantStream`]. /// /// Valid format identifiers are those in the ISO Base Media File Format /// Name Space defined by "The 'Codecs' and 'Profiles' Parameters for /// "Bucket" Media Types" ([RFC6381]). /// /// For example, a stream containing AAC low complexity (AAC-LC) audio and /// H.264 Main Profile Level 3.0 video would be /// /// ``` /// # use hls_m3u8::types::Codecs; /// let codecs = Codecs::from(&["mp4a.40.2", "avc1.4d401e"]); /// ``` /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// use hls_m3u8::types::Codecs; /// /// let mut stream = StreamData::new(20); /// /// stream.set_codecs(Some(&["mp4a.40.2", "avc1.4d401e"])); /// assert_eq!( /// stream.codecs(), /// Some(&Codecs::from(&["mp4a.40.2", "avc1.4d401e"])) /// ); /// ``` /// /// # Note /// /// This field is optional, but every instance of /// [`VariantStream::ExtXStreamInf`] should include a codecs attribute. /// /// [`VariantStream`]: crate::tags::VariantStream /// [`VariantStream::ExtXStreamInf`]: /// crate::tags::VariantStream::ExtXStreamInf /// [RFC6381]: https://tools.ietf.org/html/rfc6381 #[builder(default, setter(into))] codecs: Option>, /// The resolution of the stream. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// use hls_m3u8::types::Resolution; /// /// let mut stream = StreamData::new(20); /// /// stream.set_resolution(Some((1920, 1080))); /// assert_eq!(stream.resolution(), Some(Resolution::new(1920, 1080))); /// # stream.set_resolution(Some((1280, 10))); /// # assert_eq!(stream.resolution(), Some(Resolution::new(1280, 10))); /// ``` /// /// # Note /// /// This field is optional, but it is recommended if the [`VariantStream`] /// includes video. /// /// [`VariantStream`]: crate::tags::VariantStream #[builder(default, setter(into))] #[shorthand(enable(copy))] resolution: Option, /// High-bandwidth Digital Content Protection level of the /// [`VariantStream`]. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// use hls_m3u8::types::HdcpLevel; /// # /// let mut stream = StreamData::new(20); /// /// stream.set_hdcp_level(Some(HdcpLevel::None)); /// assert_eq!(stream.hdcp_level(), Some(HdcpLevel::None)); /// ``` /// /// # Note /// /// This field is optional. /// /// [`VariantStream`]: crate::tags::VariantStream #[builder(default)] #[shorthand(enable(copy), disable(into))] hdcp_level: Option, /// It indicates the set of video renditions, that should be used when /// playing the presentation. /// /// It must match the value of the [`ExtXMedia::group_id`] attribute /// [`ExtXMedia`] tag elsewhere in the [`MasterPlaylist`] whose /// [`ExtXMedia::media_type`] attribute is video. It indicates the set of /// video renditions that should be used when playing the presentation. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// # /// let mut stream = StreamData::new(20); /// /// stream.set_video(Some("video_01")); /// assert_eq!(stream.video(), Some(&"video_01".into())); /// ``` /// /// # Note /// /// This field is optional. /// /// [`ExtXMedia::group_id`]: crate::tags::ExtXMedia::group_id /// [`ExtXMedia`]: crate::tags::ExtXMedia /// [`MasterPlaylist`]: crate::MasterPlaylist /// [`ExtXMedia::media_type`]: crate::tags::ExtXMedia::media_type #[builder(default, setter(into))] video: Option>, } impl<'a> StreamData<'a> { /// Creates a new [`StreamData`]. /// /// # Example /// /// ``` /// # use hls_m3u8::types::StreamData; /// # /// let stream = StreamData::new(20); /// ``` #[must_use] pub const fn new(bandwidth: u64) -> Self { Self { bandwidth, average_bandwidth: None, codecs: None, resolution: None, hdcp_level: None, video: None, } } /// Returns a builder for [`StreamData`]. /// /// # Example /// /// ``` /// use hls_m3u8::types::{HdcpLevel, StreamData}; /// /// StreamData::builder() /// .bandwidth(200) /// .average_bandwidth(15) /// .codecs(&["mp4a.40.2", "avc1.4d401e"]) /// .resolution((1920, 1080)) /// .hdcp_level(HdcpLevel::Type0) /// .video("video_01") /// .build()?; /// # Ok::<(), Box>(()) /// ``` #[must_use] pub fn builder() -> StreamDataBuilder<'a> { StreamDataBuilder::default() } /// Makes the struct independent of its lifetime, by taking ownership of all /// internal [`Cow`]s. /// /// # Note /// /// This is a relatively expensive operation. #[must_use] pub fn into_owned(self) -> StreamData<'static> { StreamData { bandwidth: self.bandwidth, average_bandwidth: self.average_bandwidth, codecs: self.codecs.map(|v| v.into_owned()), resolution: self.resolution, hdcp_level: self.hdcp_level, video: self.video.map(|v| Cow::Owned(v.into_owned())), } } } impl<'a> fmt::Display for StreamData<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "BANDWIDTH={}", self.bandwidth)?; if let Some(value) = &self.average_bandwidth { write!(f, ",AVERAGE-BANDWIDTH={}", value)?; } if let Some(value) = &self.codecs { write!(f, ",CODECS={}", quote(value))?; } if let Some(value) = &self.resolution { write!(f, ",RESOLUTION={}", value)?; } if let Some(value) = &self.hdcp_level { write!(f, ",HDCP-LEVEL={}", value)?; } if let Some(value) = &self.video { write!(f, ",VIDEO={}", quote(value))?; } Ok(()) } } impl<'a> TryFrom<&'a str> for StreamData<'a> { type Error = Error; fn try_from(input: &'a str) -> Result { let mut bandwidth = None; let mut average_bandwidth = None; let mut codecs = None; let mut resolution = None; let mut hdcp_level = None; let mut video = None; for (key, value) in AttributePairs::new(input) { match key { "BANDWIDTH" => { bandwidth = Some( value .parse::() .map_err(|e| Error::parse_int(value, e))?, ); } "AVERAGE-BANDWIDTH" => { average_bandwidth = Some( value .parse::() .map_err(|e| Error::parse_int(value, e))?, ) } "CODECS" => codecs = Some(TryFrom::try_from(unquote(value))?), "RESOLUTION" => resolution = Some(value.parse()?), "HDCP-LEVEL" => { hdcp_level = Some(value.parse::().map_err(Error::strum)?) } "VIDEO" => video = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized // AttributeName. } } } let bandwidth = bandwidth.ok_or_else(|| Error::missing_value("BANDWIDTH"))?; Ok(Self { bandwidth, average_bandwidth, codecs, resolution, hdcp_level, video, }) } } /// This struct requires [`ProtocolVersion::V1`]. impl<'a> RequiredVersion for StreamData<'a> { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } fn introduced_version(&self) -> ProtocolVersion { if self.video.is_some() { ProtocolVersion::V4 } else { ProtocolVersion::V1 } } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn test_display() { let mut stream_data = StreamData::new(200); stream_data.set_average_bandwidth(Some(15)); stream_data.set_codecs(Some(&["mp4a.40.2", "avc1.4d401e"])); stream_data.set_resolution(Some((1920, 1080))); stream_data.set_hdcp_level(Some(HdcpLevel::Type0)); stream_data.set_video(Some("video")); assert_eq!( stream_data.to_string(), concat!( "BANDWIDTH=200,", "AVERAGE-BANDWIDTH=15,", "CODECS=\"mp4a.40.2,avc1.4d401e\",", "RESOLUTION=1920x1080,", "HDCP-LEVEL=TYPE-0,", "VIDEO=\"video\"" ) .to_string() ); } #[test] fn test_parser() { let mut stream_data = StreamData::new(200); stream_data.set_average_bandwidth(Some(15)); stream_data.set_codecs(Some(&["mp4a.40.2", "avc1.4d401e"])); stream_data.set_resolution(Some((1920, 1080))); stream_data.set_hdcp_level(Some(HdcpLevel::Type0)); stream_data.set_video(Some("video")); assert_eq!( stream_data, StreamData::try_from(concat!( "BANDWIDTH=200,", "AVERAGE-BANDWIDTH=15,", "CODECS=\"mp4a.40.2,avc1.4d401e\",", "RESOLUTION=1920x1080,", "HDCP-LEVEL=TYPE-0,", "VIDEO=\"video\"" )) .unwrap() ); assert!(StreamData::try_from("garbage").is_err()); } }