diff --git a/src/lib.rs b/src/lib.rs index bb7588c..cdb2765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -319,8 +319,9 @@ named!(pub alternative_media_tag, ); named!(pub session_data_tag, - do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >> - ( SessionData::from_hashmap(attributes))) + do_parse!( tag!("#EXT-X-SESSION-DATA:") >> + session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >> + ( session_data)) ); named!(pub session_key_tag, diff --git a/src/playlist.rs b/src/playlist.rs index a085e0f..af937c1 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -65,8 +65,8 @@ impl Playlist { pub struct MasterPlaylist { pub version: usize, pub variants: Vec, - pub session_data: Option, - pub session_key: Option, + pub session_data: Vec, + pub session_key: Vec, pub start: Option, pub independent_segments: bool, pub alternatives: Vec, // EXT-X-MEDIA tags @@ -74,7 +74,6 @@ pub struct MasterPlaylist { } impl MasterPlaylist { - pub fn from_tags(mut tags: Vec) -> MasterPlaylist { let mut master_playlist = MasterPlaylist::default(); @@ -95,10 +94,10 @@ impl MasterPlaylist { } } MasterPlaylistTag::SessionData(data) => { - master_playlist.session_data = Some(data); + master_playlist.session_data.push(data); } MasterPlaylistTag::SessionKey(key) => { - master_playlist.session_key = Some(key); + master_playlist.session_key.push(key); } MasterPlaylistTag::Start(s) => { master_playlist.start = Some(s); @@ -131,10 +130,10 @@ impl MasterPlaylist { for variant in &self.variants { variant.write_to(w)?; } - if let Some(ref session_data) = self.session_data { + for session_data in &self.session_data { session_data.write_to(w)?; } - if let Some(ref session_key) = self.session_key { + for session_key in &self.session_key { session_key.write_to(w)?; } if let Some(ref start) = self.start { @@ -343,6 +342,9 @@ impl fmt::Display for AlternativeMediaType { /// [`#EXT-X-SESSION-KEY:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5) +/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists +/// to be specified in a Master Playlist. This allows the client to +/// preload these keys without having to read the Media Playlist(s) first. #[derive(Debug, Default, PartialEq, Clone)] pub struct SessionKey(pub Key); @@ -354,34 +356,56 @@ impl SessionKey { } } +#[derive(Debug, PartialEq, Clone)] +pub enum SessionDataField { + Value(String), + Uri(String) +} + /// [`#EXT-X-SESSION-DATA:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4) -/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists -/// to be specified in a Master Playlist. This allows the client to -/// preload these keys without having to read the Media Playlist(s) first. -#[derive(Debug, Default, PartialEq, Clone)] +/// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried +/// in a Master Playlist. +#[derive(Debug, PartialEq, Clone)] pub struct SessionData { pub data_id: String, - pub value: String, - pub uri: String, + pub field: SessionDataField, pub language: Option, } impl SessionData { - pub fn from_hashmap(mut attrs: HashMap) -> SessionData { - SessionData { - data_id: attrs.remove("DATA-ID").unwrap_or_else(String::new), - value: attrs.remove("VALUE").unwrap_or_else(String::new), - uri: attrs.remove("URI").unwrap_or_else(String::new), + pub fn from_hashmap(mut attrs: HashMap) -> Result { + let data_id = match attrs.remove("DATA-ID") { + Some(data_id) => data_id, + None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()) + }; + + let value = attrs.remove("VALUE"); + let uri = attrs.remove("URI"); + + // SessionData must contain either a VALUE or a URI, + // but not both https://tools.ietf.org/html/rfc8216#section-4.3.4.4 + let field = match (value, uri) { + (Some(value), None) => SessionDataField::Value(value), + (None, Some(uri)) => SessionDataField::Uri(uri), + (Some(_), Some(_)) => return Err(format!["EXT-X-SESSION-DATA tag {} contains both a value and a uri", data_id]), + (None, None) => return Err(format!["EXT-X-SESSION-DATA tag {} must contain either a value or a uri", data_id]), + }; + + Ok(SessionData { + data_id, + field, language: attrs.remove("LANGUAGE"), - } + }) } pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { write!(w, "#EXT-X-SESSION-DATA:")?; write!(w, "DATA-ID=\"{}\"", self.data_id)?; - write!(w, ",VALUE=\"{}\"", self.value)?; - write!(w, ",URI=\"{}\"", self.uri)?; + match &self.field { + SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?, + SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?, + }; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write!(w, "\n") } diff --git a/tests/lib.rs b/tests/lib.rs index 41cb456..601ff43 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -333,19 +333,22 @@ fn create_and_parse_master_playlist_full() { closed_captions: Some("closed_captions".into()), } ], - session_data: Some(SessionData { - data_id: "****".into(), - value: "%%%%".into(), - uri: "++++".into(), - language: Some("SessionDataLanguage".into()), - }), - session_key: Some(SessionKey(Key { - method: "AES-128".into(), - uri: Some("https://secure.domain.com".into()), - iv: Some("0xb059217aa2649ce170b734".into()), - keyformat: Some("xXkeyformatXx".into()), - keyformatversions: Some("xXFormatVers".into()), - })), + session_data: vec![ + SessionData { + data_id: "****".into(), + field: SessionDataField::Value("%%%%".to_string()), + language: Some("SessionDataLanguage".into()), + } + ], + session_key: vec![ + SessionKey(Key { + method: "AES-128".into(), + uri: Some("https://secure.domain.com".into()), + iv: Some("0xb059217aa2649ce170b734".into()), + keyformat: Some("xXkeyformatXx".into()), + keyformatversions: Some("xXFormatVers".into()), + }) + ], start: Some(Start { time_offset: "123123123".into(), precise: Some("YES".into()),