2019-09-06 10:55:00 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::time::Duration;
|
2019-09-13 14:06:52 +00:00
|
|
|
|
2019-10-04 09:02:21 +00:00
|
|
|
use crate::types::ProtocolVersion;
|
2019-09-13 14:06:52 +00:00
|
|
|
use crate::utils::tag;
|
2019-10-04 09:02:21 +00:00
|
|
|
use crate::{Error, RequiredVersion};
|
2019-09-06 10:55:00 +00:00
|
|
|
|
2019-10-05 10:49:08 +00:00
|
|
|
/// # [4.3.2.1. EXTINF]
|
2019-09-06 10:55:00 +00:00
|
|
|
///
|
2019-10-03 14:23:27 +00:00
|
|
|
/// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies
|
|
|
|
/// only to the next [`Media Segment`].
|
2019-09-15 08:40:45 +00:00
|
|
|
///
|
2019-10-03 14:23:27 +00:00
|
|
|
/// [`Media Segment`]: crate::media_segment::MediaSegment
|
2019-10-05 10:49:08 +00:00
|
|
|
/// [4.3.2.1. EXTINF]: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
|
2019-09-15 08:40:45 +00:00
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
2019-09-06 10:55:00 +00:00
|
|
|
pub struct ExtInf {
|
|
|
|
duration: Duration,
|
2019-09-15 09:05:22 +00:00
|
|
|
title: Option<String>,
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ExtInf {
|
|
|
|
pub(crate) const PREFIX: &'static str = "#EXTINF:";
|
|
|
|
|
2019-10-03 14:23:27 +00:00
|
|
|
/// Makes a new [`ExtInf`] tag.
|
2019-09-22 16:00:38 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
|
|
|
/// ```
|
2019-09-08 10:23:33 +00:00
|
|
|
pub const fn new(duration: Duration) -> Self {
|
2019-09-22 18:33:40 +00:00
|
|
|
Self {
|
2019-09-06 10:55:00 +00:00
|
|
|
duration,
|
|
|
|
title: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 14:23:27 +00:00
|
|
|
/// Makes a new [`ExtInf`] tag with the given title.
|
2019-09-22 16:00:38 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
|
|
|
/// ```
|
2019-09-15 09:05:22 +00:00
|
|
|
pub fn with_title<T: ToString>(duration: Duration, title: T) -> Self {
|
2019-09-22 18:33:40 +00:00
|
|
|
Self {
|
2019-09-06 10:55:00 +00:00
|
|
|
duration,
|
2019-09-15 09:05:22 +00:00
|
|
|
title: Some(title.to_string()),
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the duration of the associated media segment.
|
2019-09-22 16:00:38 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
|
|
|
///
|
2019-10-03 15:01:15 +00:00
|
|
|
/// assert_eq!(ext_inf.duration(), Duration::from_secs(5));
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn duration(&self) -> Duration { self.duration }
|
2019-09-06 10:55:00 +00:00
|
|
|
|
2019-09-22 16:00:38 +00:00
|
|
|
/// Sets the duration of the associated media segment.
|
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let mut ext_inf = ExtInf::new(Duration::from_secs(5));
|
|
|
|
///
|
|
|
|
/// ext_inf.set_duration(Duration::from_secs(10));
|
|
|
|
///
|
2019-10-03 15:01:15 +00:00
|
|
|
/// assert_eq!(ext_inf.duration(), Duration::from_secs(10));
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
pub fn set_duration(&mut self, value: Duration) -> &mut Self {
|
|
|
|
self.duration = value;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-09-06 10:55:00 +00:00
|
|
|
/// Returns the title of the associated media segment.
|
2019-09-22 16:00:38 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
|
|
|
///
|
2019-10-03 15:01:15 +00:00
|
|
|
/// assert_eq!(ext_inf.title(), &Some("title".to_string()));
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
2019-10-03 15:01:15 +00:00
|
|
|
pub const fn title(&self) -> &Option<String> { &self.title }
|
2019-09-22 16:00:38 +00:00
|
|
|
|
|
|
|
/// Sets the title of the associated media segment.
|
|
|
|
///
|
|
|
|
/// # Example
|
2020-02-02 12:38:11 +00:00
|
|
|
///
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
/// # use hls_m3u8::tags::ExtInf;
|
|
|
|
/// use std::time::Duration;
|
|
|
|
///
|
|
|
|
/// let mut ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
|
|
|
///
|
|
|
|
/// ext_inf.set_title(Some("better title"));
|
|
|
|
///
|
2019-10-03 15:01:15 +00:00
|
|
|
/// assert_eq!(ext_inf.title(), &Some("better title".to_string()));
|
2019-09-22 16:00:38 +00:00
|
|
|
/// ```
|
|
|
|
pub fn set_title<T: ToString>(&mut self, value: Option<T>) -> &mut Self {
|
|
|
|
self.title = value.map(|v| v.to_string());
|
|
|
|
self
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|
2019-09-22 08:57:28 +00:00
|
|
|
}
|
2019-09-06 10:55:00 +00:00
|
|
|
|
2020-02-02 12:38:11 +00:00
|
|
|
/// This tag requires [`ProtocolVersion::V1`], if the duration does not have
|
|
|
|
/// nanoseconds, otherwise it requires [`ProtocolVersion::V3`].
|
2019-09-22 08:57:28 +00:00
|
|
|
impl RequiredVersion for ExtInf {
|
|
|
|
fn required_version(&self) -> ProtocolVersion {
|
2019-09-06 10:55:00 +00:00
|
|
|
if self.duration.subsec_nanos() == 0 {
|
|
|
|
ProtocolVersion::V1
|
|
|
|
} else {
|
|
|
|
ProtocolVersion::V3
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for ExtInf {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{}", Self::PREFIX)?;
|
2019-10-05 14:08:03 +00:00
|
|
|
write!(f, "{},", self.duration.as_secs_f64())?;
|
2019-09-06 10:55:00 +00:00
|
|
|
|
2019-09-14 19:42:06 +00:00
|
|
|
if let Some(value) = &self.title {
|
2019-09-15 08:40:45 +00:00
|
|
|
write!(f, "{}", value)?;
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for ExtInf {
|
|
|
|
type Err = Error;
|
|
|
|
|
2019-09-13 14:06:52 +00:00
|
|
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
|
|
let input = tag(input, Self::PREFIX)?;
|
2019-09-15 08:40:45 +00:00
|
|
|
let tokens = input.splitn(2, ',').collect::<Vec<_>>();
|
|
|
|
|
2019-09-22 18:33:40 +00:00
|
|
|
if tokens.is_empty() {
|
2019-09-15 08:40:45 +00:00
|
|
|
return Err(Error::custom(format!(
|
|
|
|
"failed to parse #EXTINF tag, couldn't split input: {:?}",
|
|
|
|
input
|
|
|
|
)));
|
|
|
|
}
|
2019-09-13 14:06:52 +00:00
|
|
|
|
2019-09-22 18:33:40 +00:00
|
|
|
let duration = Duration::from_secs_f64(tokens[0].parse()?);
|
2019-09-06 10:55:00 +00:00
|
|
|
|
2019-09-13 14:06:52 +00:00
|
|
|
let title = {
|
2019-09-15 08:40:45 +00:00
|
|
|
if tokens.len() >= 2 {
|
|
|
|
if tokens[1].trim().is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
2019-09-15 09:05:22 +00:00
|
|
|
Some(tokens[1].to_string())
|
2019-09-15 08:40:45 +00:00
|
|
|
}
|
2019-09-13 14:06:52 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2019-09-06 10:55:00 +00:00
|
|
|
};
|
2019-09-15 08:40:45 +00:00
|
|
|
|
2019-09-22 18:33:40 +00:00
|
|
|
Ok(Self { duration, title })
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-15 08:40:45 +00:00
|
|
|
impl From<Duration> for ExtInf {
|
2019-10-03 15:01:15 +00:00
|
|
|
fn from(value: Duration) -> Self { Self::new(value) }
|
2019-09-15 08:40:45 +00:00
|
|
|
}
|
|
|
|
|
2019-09-06 10:55:00 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2019-10-08 13:42:33 +00:00
|
|
|
use pretty_assertions::assert_eq;
|
2019-09-06 10:55:00 +00:00
|
|
|
|
|
|
|
#[test]
|
2019-09-15 08:40:45 +00:00
|
|
|
fn test_display() {
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5,".to_string(),
|
|
|
|
ExtInf::new(Duration::from_secs(5)).to_string()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5.5,".to_string(),
|
|
|
|
ExtInf::new(Duration::from_millis(5500)).to_string()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5.5,title".to_string(),
|
2019-09-15 09:05:22 +00:00
|
|
|
ExtInf::with_title(Duration::from_millis(5500), "title").to_string()
|
2019-09-15 08:40:45 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5,title".to_string(),
|
2019-09-15 09:05:22 +00:00
|
|
|
ExtInf::with_title(Duration::from_secs(5), "title").to_string()
|
2019-09-15 08:40:45 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parser() {
|
|
|
|
// #EXTINF:<duration>,[<title>]
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5".parse::<ExtInf>().unwrap(),
|
|
|
|
ExtInf::new(Duration::from_secs(5))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5,".parse::<ExtInf>().unwrap(),
|
|
|
|
ExtInf::new(Duration::from_secs(5))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5.5".parse::<ExtInf>().unwrap(),
|
|
|
|
ExtInf::new(Duration::from_millis(5500))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5.5,".parse::<ExtInf>().unwrap(),
|
|
|
|
ExtInf::new(Duration::from_millis(5500))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5.5,title".parse::<ExtInf>().unwrap(),
|
2019-09-15 09:05:22 +00:00
|
|
|
ExtInf::with_title(Duration::from_millis(5500), "title")
|
2019-09-15 08:40:45 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"#EXTINF:5,title".parse::<ExtInf>().unwrap(),
|
2019-09-15 09:05:22 +00:00
|
|
|
ExtInf::with_title(Duration::from_secs(5), "title")
|
2019-09-15 08:40:45 +00:00
|
|
|
);
|
2019-10-06 14:39:18 +00:00
|
|
|
|
|
|
|
assert!("#EXTINF:".parse::<ExtInf>().is_err());
|
|
|
|
assert!("#EXTINF:garbage".parse::<ExtInf>().is_err());
|
2019-09-15 08:40:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_title() {
|
2019-09-22 16:00:38 +00:00
|
|
|
assert_eq!(ExtInf::new(Duration::from_secs(5)).title(), &None);
|
2019-09-15 08:40:45 +00:00
|
|
|
assert_eq!(
|
2019-09-15 09:05:22 +00:00
|
|
|
ExtInf::with_title(Duration::from_secs(5), "title").title(),
|
2019-09-22 16:00:38 +00:00
|
|
|
&Some("title".to_string())
|
2019-09-15 08:40:45 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-09-22 08:57:28 +00:00
|
|
|
fn test_required_version() {
|
2019-09-15 08:40:45 +00:00
|
|
|
assert_eq!(
|
2019-09-22 08:57:28 +00:00
|
|
|
ExtInf::new(Duration::from_secs(4)).required_version(),
|
2019-09-15 08:40:45 +00:00
|
|
|
ProtocolVersion::V1
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-09-22 08:57:28 +00:00
|
|
|
ExtInf::new(Duration::from_millis(4400)).required_version(),
|
2019-09-15 08:40:45 +00:00
|
|
|
ProtocolVersion::V3
|
2019-09-06 10:55:00 +00:00
|
|
|
);
|
|
|
|
}
|
2019-10-06 14:39:18 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_from() {
|
|
|
|
assert_eq!(
|
|
|
|
ExtInf::from(Duration::from_secs(1)),
|
|
|
|
ExtInf::new(Duration::from_secs(1))
|
|
|
|
);
|
|
|
|
}
|
2019-09-06 10:55:00 +00:00
|
|
|
}
|