mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2025-01-03 03:38:40 +00:00
Merge pull request #24 from thaytan/20-multiple-session-data-keys
Fixes for session data and keys handling
This commit is contained in:
commit
5a72e1e875
3 changed files with 64 additions and 36 deletions
|
@ -319,8 +319,9 @@ named!(pub alternative_media_tag<AlternativeMedia>,
|
||||||
);
|
);
|
||||||
|
|
||||||
named!(pub session_data_tag<SessionData>,
|
named!(pub session_data_tag<SessionData>,
|
||||||
do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >>
|
do_parse!( tag!("#EXT-X-SESSION-DATA:") >>
|
||||||
( SessionData::from_hashmap(attributes)))
|
session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >>
|
||||||
|
( session_data))
|
||||||
);
|
);
|
||||||
|
|
||||||
named!(pub session_key_tag<SessionKey>,
|
named!(pub session_key_tag<SessionKey>,
|
||||||
|
|
|
@ -65,8 +65,8 @@ impl Playlist {
|
||||||
pub struct MasterPlaylist {
|
pub struct MasterPlaylist {
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
pub variants: Vec<VariantStream>,
|
pub variants: Vec<VariantStream>,
|
||||||
pub session_data: Option<SessionData>,
|
pub session_data: Vec<SessionData>,
|
||||||
pub session_key: Option<SessionKey>,
|
pub session_key: Vec<SessionKey>,
|
||||||
pub start: Option<Start>,
|
pub start: Option<Start>,
|
||||||
pub independent_segments: bool,
|
pub independent_segments: bool,
|
||||||
pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags
|
pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags
|
||||||
|
@ -74,7 +74,6 @@ pub struct MasterPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPlaylist {
|
impl MasterPlaylist {
|
||||||
|
|
||||||
pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
||||||
let mut master_playlist = MasterPlaylist::default();
|
let mut master_playlist = MasterPlaylist::default();
|
||||||
|
|
||||||
|
@ -95,10 +94,10 @@ impl MasterPlaylist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::SessionData(data) => {
|
MasterPlaylistTag::SessionData(data) => {
|
||||||
master_playlist.session_data = Some(data);
|
master_playlist.session_data.push(data);
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::SessionKey(key) => {
|
MasterPlaylistTag::SessionKey(key) => {
|
||||||
master_playlist.session_key = Some(key);
|
master_playlist.session_key.push(key);
|
||||||
}
|
}
|
||||||
MasterPlaylistTag::Start(s) => {
|
MasterPlaylistTag::Start(s) => {
|
||||||
master_playlist.start = Some(s);
|
master_playlist.start = Some(s);
|
||||||
|
@ -131,10 +130,10 @@ impl MasterPlaylist {
|
||||||
for variant in &self.variants {
|
for variant in &self.variants {
|
||||||
variant.write_to(w)?;
|
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)?;
|
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)?;
|
session_key.write_to(w)?;
|
||||||
}
|
}
|
||||||
if let Some(ref start) = self.start {
|
if let Some(ref start) = self.start {
|
||||||
|
@ -343,6 +342,9 @@ impl fmt::Display for AlternativeMediaType {
|
||||||
|
|
||||||
/// [`#EXT-X-SESSION-KEY:<attribute-list>`]
|
/// [`#EXT-X-SESSION-KEY:<attribute-list>`]
|
||||||
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5)
|
/// (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)]
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
pub struct SessionKey(pub Key);
|
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:<attribute-list>`]
|
/// [`#EXT-X-SESSION-DATA:<attribute-list>`]
|
||||||
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4)
|
/// (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
|
/// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried
|
||||||
/// to be specified in a Master Playlist. This allows the client to
|
/// in a Master Playlist.
|
||||||
/// preload these keys without having to read the Media Playlist(s) first.
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
#[derive(Debug, Default, PartialEq, Clone)]
|
|
||||||
pub struct SessionData {
|
pub struct SessionData {
|
||||||
pub data_id: String,
|
pub data_id: String,
|
||||||
pub value: String,
|
pub field: SessionDataField,
|
||||||
pub uri: String,
|
|
||||||
pub language: Option<String>,
|
pub language: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionData {
|
impl SessionData {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> SessionData {
|
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> {
|
||||||
SessionData {
|
let data_id = match attrs.remove("DATA-ID") {
|
||||||
data_id: attrs.remove("DATA-ID").unwrap_or_else(String::new),
|
Some(data_id) => data_id,
|
||||||
value: attrs.remove("VALUE").unwrap_or_else(String::new),
|
None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string())
|
||||||
uri: attrs.remove("URI").unwrap_or_else(String::new),
|
};
|
||||||
|
|
||||||
|
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"),
|
language: attrs.remove("LANGUAGE"),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
|
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
|
||||||
write!(w, "#EXT-X-SESSION-DATA:")?;
|
write!(w, "#EXT-X-SESSION-DATA:")?;
|
||||||
write!(w, "DATA-ID=\"{}\"", self.data_id)?;
|
write!(w, "DATA-ID=\"{}\"", self.data_id)?;
|
||||||
write!(w, ",VALUE=\"{}\"", self.value)?;
|
match &self.field {
|
||||||
write!(w, ",URI=\"{}\"", self.uri)?;
|
SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?,
|
||||||
|
SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?,
|
||||||
|
};
|
||||||
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
|
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
|
||||||
write!(w, "\n")
|
write!(w, "\n")
|
||||||
}
|
}
|
||||||
|
|
29
tests/lib.rs
29
tests/lib.rs
|
@ -333,19 +333,22 @@ fn create_and_parse_master_playlist_full() {
|
||||||
closed_captions: Some("closed_captions".into()),
|
closed_captions: Some("closed_captions".into()),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
session_data: Some(SessionData {
|
session_data: vec![
|
||||||
data_id: "****".into(),
|
SessionData {
|
||||||
value: "%%%%".into(),
|
data_id: "****".into(),
|
||||||
uri: "++++".into(),
|
field: SessionDataField::Value("%%%%".to_string()),
|
||||||
language: Some("SessionDataLanguage".into()),
|
language: Some("SessionDataLanguage".into()),
|
||||||
}),
|
}
|
||||||
session_key: Some(SessionKey(Key {
|
],
|
||||||
method: "AES-128".into(),
|
session_key: vec![
|
||||||
uri: Some("https://secure.domain.com".into()),
|
SessionKey(Key {
|
||||||
iv: Some("0xb059217aa2649ce170b734".into()),
|
method: "AES-128".into(),
|
||||||
keyformat: Some("xXkeyformatXx".into()),
|
uri: Some("https://secure.domain.com".into()),
|
||||||
keyformatversions: Some("xXFormatVers".into()),
|
iv: Some("0xb059217aa2649ce170b734".into()),
|
||||||
})),
|
keyformat: Some("xXkeyformatXx".into()),
|
||||||
|
keyformatversions: Some("xXFormatVers".into()),
|
||||||
|
})
|
||||||
|
],
|
||||||
start: Some(Start {
|
start: Some(Start {
|
||||||
time_offset: "123123123".into(),
|
time_offset: "123123123".into(),
|
||||||
precise: Some("YES".into()),
|
precise: Some("YES".into()),
|
||||||
|
|
Loading…
Reference in a new issue