use super::UtcTime;
use super::*;
use std::fmt;
#[derive(Debug, Clone)]
pub struct MediaProperties(Vec<MediaProperty>);
#[derive(Debug, Clone, PartialEq)]
pub enum MediaProperty {
RandomAccess(Option<f64>),
BeginningOnly,
NoSeeking,
Immutable,
Dynamic,
TimeProgressing,
Unlimited,
TimeLimited(UtcTime),
TimeDuration(f64),
Scales(Vec<ScaleRange>),
Extension(String, Option<String>),
}
impl fmt::Display for MediaProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use fmt::Write;
match self {
MediaProperty::RandomAccess(Some(dur)) => write!(f, "Random-Access={}", dur),
MediaProperty::RandomAccess(None) => f.write_str("Random-Access"),
MediaProperty::BeginningOnly => f.write_str("Beginning-Only"),
MediaProperty::NoSeeking => f.write_str("No-Seeking"),
MediaProperty::Immutable => f.write_str("Immutable"),
MediaProperty::Dynamic => f.write_str("Dynamic"),
MediaProperty::TimeProgressing => f.write_str("Time-Progressing"),
MediaProperty::Unlimited => f.write_str("Unlimited"),
MediaProperty::TimeLimited(time) => write!(f, "Time-Limited={}", time),
MediaProperty::TimeDuration(dur) => write!(f, "Time-Duration={}", dur),
MediaProperty::Scales(scales) => {
let mut s = String::new();
for scale in scales {
if !s.is_empty() {
s.push_str(", ");
}
write!(&mut s, "{}", scale).unwrap();
}
write!(f, "Scales=\"{}\"", s)
}
MediaProperty::Extension(key, Some(value)) => write!(f, "{}={}", key, value),
MediaProperty::Extension(key, None) => f.write_str(key),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ScaleRange {
Scale(f64),
Range(f64, f64),
}
impl fmt::Display for ScaleRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScaleRange::Scale(scale) => write!(f, "{}", scale),
ScaleRange::Range(a, b) => write!(f, "{}:{}", a, b),
}
}
}
impl std::ops::Deref for MediaProperties {
type Target = Vec<MediaProperty>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for MediaProperties {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl AsRef<Vec<MediaProperty>> for MediaProperties {
fn as_ref(&self) -> &Vec<MediaProperty> {
&self.0
}
}
impl AsMut<Vec<MediaProperty>> for MediaProperties {
fn as_mut(&mut self) -> &mut Vec<MediaProperty> {
&mut self.0
}
}
impl From<Vec<MediaProperty>> for MediaProperties {
fn from(v: Vec<MediaProperty>) -> Self {
MediaProperties(v)
}
}
impl<'a> From<&'a [MediaProperty]> for MediaProperties {
fn from(v: &'a [MediaProperty]) -> Self {
MediaProperties(v.to_vec())
}
}
impl MediaProperties {
pub fn builder() -> MediaPropertiesBuilder {
MediaPropertiesBuilder(Vec::new())
}
}
#[derive(Debug, Clone)]
pub struct MediaPropertiesBuilder(Vec<MediaProperty>);
impl MediaPropertiesBuilder {
pub fn property(mut self, property: MediaProperty) -> Self {
self.0.push(property);
self
}
pub fn build(self) -> MediaProperties {
MediaProperties(self.0)
}
}
pub(super) mod parser {
use super::*;
use super::parser_helpers::{
cond_parser, quoted_string, rtsp_unreserved, split_once, token, trim,
};
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::{all_consuming, map_res};
use nom::multi::separated_list1;
use nom::sequence::tuple;
use nom::{Err, IResult};
use std::str;
fn param(input: &[u8]) -> IResult<&[u8], (&str, Option<&str>)> {
if input.is_empty() {
return Err(Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Eof,
)));
}
tuple((
trim(map_res(token, str::from_utf8)),
cond_parser(
tag(b"="),
trim(map_res(
alt((quoted_string, rtsp_unreserved)),
str::from_utf8,
)),
),
))(input)
}
fn media_property(input: &[u8]) -> IResult<&[u8], MediaProperty> {
map_res(param, |p| -> Result<_, HeaderParseError> {
dbg!(&p);
match p {
("Random-Access", None) => Ok(MediaProperty::RandomAccess(None)),
("Random-Access", Some(dur)) => {
let dur = dur.parse().map_err(|_| HeaderParseError)?;
Ok(MediaProperty::RandomAccess(Some(dur)))
}
("Beginning-Only", None) => Ok(MediaProperty::BeginningOnly),
("No-Seeking", None) => Ok(MediaProperty::NoSeeking),
("Immutable", None) => Ok(MediaProperty::Immutable),
("Dynamic", None) => Ok(MediaProperty::Dynamic),
("Time-Progressing", None) => Ok(MediaProperty::TimeProgressing),
("Unlimited", None) => Ok(MediaProperty::Unlimited),
("Time-Limited", Some(time)) => {
let time = time.parse().map_err(|_| HeaderParseError)?;
Ok(MediaProperty::TimeLimited(time))
}
("Time-Duration", Some(dur)) => {
let dur = dur.parse().map_err(|_| HeaderParseError)?;
Ok(MediaProperty::TimeDuration(dur))
}
("Scales", Some(scales)) => {
if !scales.starts_with('"') || !scales.ends_with('"') {
return Err(HeaderParseError);
}
let mut s = Vec::new();
for scale in scales[1..(scales.len() - 1)].split(',') {
let scale = scale.trim();
if let Some((a, b)) = split_once(scale, ':') {
let a = a.parse().map_err(|_| HeaderParseError)?;
let b = b.parse().map_err(|_| HeaderParseError)?;
s.push(ScaleRange::Range(a, b));
} else {
let a = scale.parse().map_err(|_| HeaderParseError)?;
s.push(ScaleRange::Scale(a));
}
}
Ok(MediaProperty::Scales(s))
}
(key, value) => Ok(MediaProperty::Extension(
key.into(),
value.map(String::from),
)),
}
})(input)
}
pub(crate) fn media_properties(input: &[u8]) -> IResult<&[u8], Vec<MediaProperty>> {
all_consuming(separated_list1(tag(","), media_property))(input)
}
}
impl super::TypedHeader for MediaProperties {
fn from_headers(headers: impl AsRef<Headers>) -> Result<Option<Self>, HeaderParseError> {
let headers = headers.as_ref();
let header = match headers.get(&MEDIA_PROPERTIES) {
None => return Ok(None),
Some(header) => header,
};
let (_rem, properties) =
parser::media_properties(header.as_str().as_bytes()).map_err(|_| HeaderParseError)?;
Ok(Some(properties.into()))
}
fn insert_into(&self, mut headers: impl AsMut<Headers>) {
let headers = headers.as_mut();
let mut properties = String::new();
for property in &self.0 {
if !properties.is_empty() {
properties.push_str(", ");
}
properties.push_str(&property.to_string());
}
headers.insert(MEDIA_PROPERTIES, properties);
}
}
impl super::TypedAppendableHeader for MediaProperties {
fn append_to(&self, mut headers: impl AsMut<Headers>) {
let headers = headers.as_mut();
let mut properties = String::new();
for property in &self.0 {
if !properties.is_empty() {
properties.push_str(", ");
}
properties.push_str(&property.to_string());
}
headers.append(MEDIA_PROPERTIES, properties);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_media_properties() {
let header = "Random-Access=2.5, Unlimited, Immutable, Scales=\"-20, -10, -4, 0.5:1.5, 4, 8, 10, 15, 20\"";
let response = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
.header(crate::headers::MEDIA_PROPERTIES, header)
.empty();
let props = response
.typed_header::<super::MediaProperties>()
.unwrap()
.unwrap();
assert_eq!(
&*props,
&[
MediaProperty::RandomAccess(Some(2.5)),
MediaProperty::Unlimited,
MediaProperty::Immutable,
MediaProperty::Scales(vec![
ScaleRange::Scale(-20.0),
ScaleRange::Scale(-10.0),
ScaleRange::Scale(-4.0),
ScaleRange::Range(0.5, 1.5),
ScaleRange::Scale(4.0),
ScaleRange::Scale(8.0),
ScaleRange::Scale(10.0),
ScaleRange::Scale(15.0),
ScaleRange::Scale(20.0),
]),
]
);
let response2 = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
.typed_header(&props)
.empty();
assert_eq!(response, response2);
}
}