mirror of
https://github.com/sile/hls_m3u8.git
synced 2025-01-14 14:05:32 +00:00
Merge pull request #38 from Luro02/backup
more tests #25 + better docs #31
This commit is contained in:
commit
ebddb7a0e2
52 changed files with 3066 additions and 1474 deletions
.github/workflows
.gitignore.travis.ymlCargo.tomlrustfmt.tomlsrc
attribute.rserror.rslib.rsline.rsmaster_playlist.rsmedia_playlist.rsmedia_segment.rs
tags
basic
master_playlist
media_playlist
discontinuity_sequence.rsend_list.rsi_frames_only.rsmedia_sequence.rsplaylist_type.rstarget_duration.rs
media_segment
shared
types
byte_range.rschannels.rsclosed_captions.rsdecimal_floating_point.rsdecimal_resolution.rsdecryption_key.rsencryption_method.rshdcp_level.rsin_stream_id.rsinitialization_vector.rskey_format.rskey_format_versions.rsmedia_type.rsmod.rsprotocol_version.rssigned_decimal_floating_point.rsstream_inf.rs
utils.rs
59
.github/workflows/rust.yml
vendored
59
.github/workflows/rust.yml
vendored
|
@ -4,60 +4,13 @@ name: Rust
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
os:
|
|
||||||
- ubuntu-latest
|
|
||||||
# execute cargo build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
arguments: --all-features
|
|
||||||
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
os:
|
|
||||||
- ubuntu-latest
|
|
||||||
# execute cargo test
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
|
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: Rustfmt
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: nightly
|
||||||
override: true
|
|
||||||
- run: rustup component add rustfmt
|
- run: rustup component add rustfmt
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
@ -65,20 +18,14 @@ jobs:
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: stable
|
||||||
override: true
|
|
||||||
- run: rustup component add clippy
|
- run: rustup component add clippy
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: -- -D warnings
|
# args: -- -D warnings
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/target/
|
/target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
tarpaulin-report.html
|
||||||
|
|
52
.travis.yml
52
.travis.yml
|
@ -1,5 +1,16 @@
|
||||||
language: rust
|
language: rust
|
||||||
sudo: required
|
|
||||||
|
cache: cargo
|
||||||
|
|
||||||
|
before_cache: |
|
||||||
|
cargo install cargo-tarpaulin || echo "cargo-tarpaulin already installed"
|
||||||
|
cargo install cargo-update || echo "cargo-update already installed"
|
||||||
|
cargo install cargo-audit || echo "cargo-audit already installed"
|
||||||
|
cargo install-update --all
|
||||||
|
|
||||||
|
# before_cache:
|
||||||
|
# - rm -rf /home/travis/.cargo/registry
|
||||||
|
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
|
@ -8,32 +19,19 @@ matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
|
|
||||||
env:
|
script: |
|
||||||
global:
|
cargo clean
|
||||||
- RUSTFLAGS="-C link-dead-code"
|
cargo build
|
||||||
|
cargo test
|
||||||
|
|
||||||
addons:
|
# it's enough to run this once:
|
||||||
apt:
|
if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then
|
||||||
packages:
|
cargo audit
|
||||||
- libcurl4-openssl-dev
|
fi
|
||||||
- libelf-dev
|
|
||||||
- libdw-dev
|
|
||||||
- cmake
|
|
||||||
- gcc
|
|
||||||
- binutils-dev
|
|
||||||
- libiberty-dev
|
|
||||||
|
|
||||||
after_success: |
|
after_success: |
|
||||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
# this does require a -Z flag for Doctests, which is unstable!
|
||||||
tar xzf master.tar.gz &&
|
if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then
|
||||||
cd kcov-master &&
|
cargo tarpaulin --run-types Tests Doctests --out Xml
|
||||||
mkdir build &&
|
bash <(curl -s https://codecov.io/bash)
|
||||||
cd build &&
|
fi
|
||||||
cmake .. &&
|
|
||||||
make &&
|
|
||||||
make install DESTDIR=../../kcov-build &&
|
|
||||||
cd ../.. &&
|
|
||||||
rm -rf kcov-master &&
|
|
||||||
for file in target/debug/hls_m3u8-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
|
||||||
bash <(curl -s https://codecov.io/bash) &&
|
|
||||||
echo "Uploaded code coverage"
|
|
||||||
|
|
|
@ -12,13 +12,15 @@ edition = "2018"
|
||||||
categories = ["parser"]
|
categories = ["parser"]
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = {repository = "sile/hls_m3u8"}
|
travis-ci = { repository = "sile/hls_m3u8" }
|
||||||
codecov = {repository = "sile/hls_m3u8"}
|
codecov = { repository = "sile/hls_m3u8" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1.5"
|
failure = "0.1.5"
|
||||||
derive_builder = "0.7.2"
|
derive_builder = "0.7.2"
|
||||||
chrono = "0.4.9"
|
chrono = "0.4.9"
|
||||||
|
strum = { version = "0.16.0", features = ["derive"] }
|
||||||
|
derive_more = "0.15.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = "2"
|
clap = "2"
|
||||||
|
|
10
rustfmt.toml
Normal file
10
rustfmt.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
error_on_unformatted = true
|
||||||
|
edition = "2018"
|
||||||
|
fn_single_line = true
|
||||||
|
force_multiline_blocks = true
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
match_arm_blocks = true
|
||||||
|
reorder_impl_items = true
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
wrap_comments = true
|
|
@ -8,41 +8,31 @@ use crate::Error;
|
||||||
pub struct AttributePairs(HashMap<String, String>);
|
pub struct AttributePairs(HashMap<String, String>);
|
||||||
|
|
||||||
impl AttributePairs {
|
impl AttributePairs {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self { Self::default() }
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for AttributePairs {
|
impl Deref for AttributePairs {
|
||||||
type Target = HashMap<String, String>;
|
type Target = HashMap<String, String>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for AttributePairs {
|
impl DerefMut for AttributePairs {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for AttributePairs {
|
impl IntoIterator for AttributePairs {
|
||||||
type Item = (String, String);
|
|
||||||
type IntoIter = ::std::collections::hash_map::IntoIter<String, String>;
|
type IntoIter = ::std::collections::hash_map::IntoIter<String, String>;
|
||||||
|
type Item = (String, String);
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
||||||
self.0.into_iter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a AttributePairs {
|
impl<'a> IntoIterator for &'a AttributePairs {
|
||||||
type Item = (&'a String, &'a String);
|
|
||||||
type IntoIter = ::std::collections::hash_map::Iter<'a, String, String>;
|
type IntoIter = ::std::collections::hash_map::Iter<'a, String, String>;
|
||||||
|
type Item = (&'a String, &'a String);
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
|
||||||
self.0.iter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for AttributePairs {
|
impl FromStr for AttributePairs {
|
||||||
|
@ -55,11 +45,14 @@ impl FromStr for AttributePairs {
|
||||||
let pair = split(line.trim(), '=');
|
let pair = split(line.trim(), '=');
|
||||||
|
|
||||||
if pair.len() < 2 {
|
if pair.len() < 2 {
|
||||||
return Err(Error::invalid_input());
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = pair[0].to_uppercase();
|
let key = pair[0].trim().to_uppercase();
|
||||||
let value = pair[1].to_string();
|
let value = pair[1].trim().to_string();
|
||||||
|
if value.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
result.insert(key.trim().to_string(), value.trim().to_string());
|
result.insert(key.trim().to_string(), value.trim().to_string());
|
||||||
}
|
}
|
||||||
|
@ -87,11 +80,11 @@ fn split(value: &str, terminator: char) -> Vec<String> {
|
||||||
temp_string.push(c);
|
temp_string.push(c);
|
||||||
}
|
}
|
||||||
k if (k == terminator) => {
|
k if (k == terminator) => {
|
||||||
if !inside_quotes {
|
if inside_quotes {
|
||||||
|
temp_string.push(c);
|
||||||
|
} else {
|
||||||
result.push(temp_string);
|
result.push(temp_string);
|
||||||
temp_string = String::new();
|
temp_string = String::new();
|
||||||
} else {
|
|
||||||
temp_string.push(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -122,6 +115,11 @@ mod test {
|
||||||
|
|
||||||
let mut iterator = pairs.iter();
|
let mut iterator = pairs.iter();
|
||||||
assert!(iterator.any(|(k, v)| k == "ABC" && v == "12.3"));
|
assert!(iterator.any(|(k, v)| k == "ABC" && v == "12.3"));
|
||||||
|
|
||||||
|
let mut pairs = AttributePairs::new();
|
||||||
|
pairs.insert("FOO".to_string(), "BAR".to_string());
|
||||||
|
|
||||||
|
assert_eq!("FOO=BAR,VAL".parse::<AttributePairs>().unwrap(), pairs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -136,4 +134,18 @@ mod test {
|
||||||
let mut iterator = attrs.iter();
|
let mut iterator = attrs.iter();
|
||||||
assert!(iterator.any(|(k, v)| k == "key_02" && v == "value_02"));
|
assert!(iterator.any(|(k, v)| k == "key_02" && v == "value_02"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_iter() {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("k".to_string(), "v".to_string());
|
||||||
|
|
||||||
|
let mut attrs = AttributePairs::new();
|
||||||
|
attrs.insert("k".to_string(), "v".to_string());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
attrs.into_iter().collect::<Vec<_>>(),
|
||||||
|
map.into_iter().collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
64
src/error.rs
64
src/error.rs
|
@ -1,4 +1,3 @@
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use failure::{Backtrace, Context, Fail};
|
use failure::{Backtrace, Context, Fail};
|
||||||
|
@ -6,7 +5,7 @@ use failure::{Backtrace, Context, Fail};
|
||||||
/// This crate specific `Result` type.
|
/// This crate specific `Result` type.
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// The ErrorKind.
|
/// The [`ErrorKind`].
|
||||||
#[derive(Debug, Fail, Clone, PartialEq, Eq)]
|
#[derive(Debug, Fail, Clone, PartialEq, Eq)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
#[fail(display = "ChronoParseError: {}", _0)]
|
#[fail(display = "ChronoParseError: {}", _0)]
|
||||||
|
@ -73,6 +72,10 @@ pub enum ErrorKind {
|
||||||
/// An attribute is missing.
|
/// An attribute is missing.
|
||||||
MissingAttribute(String),
|
MissingAttribute(String),
|
||||||
|
|
||||||
|
#[fail(display = "Unexpected Attribute: {:?}", _0)]
|
||||||
|
/// An unexpected value.
|
||||||
|
UnexpectedAttribute(String),
|
||||||
|
|
||||||
/// Hints that destructuring should not be exhaustive.
|
/// Hints that destructuring should not be exhaustive.
|
||||||
///
|
///
|
||||||
/// This enum may grow additional variants, so this makes sure clients
|
/// This enum may grow additional variants, so this makes sure clients
|
||||||
|
@ -90,49 +93,34 @@ pub struct Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fail for Error {
|
impl Fail for Error {
|
||||||
fn cause(&self) -> Option<&dyn Fail> {
|
fn cause(&self) -> Option<&dyn Fail> { self.inner.cause() }
|
||||||
self.inner.cause()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backtrace(&self) -> Option<&Backtrace> {
|
fn backtrace(&self) -> Option<&Backtrace> { self.inner.backtrace() }
|
||||||
self.inner.backtrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) }
|
||||||
self.inner.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ErrorKind> for Error {
|
impl From<ErrorKind> for Error {
|
||||||
fn from(kind: ErrorKind) -> Error {
|
fn from(kind: ErrorKind) -> Error { Error::from(Context::new(kind)) }
|
||||||
Error::from(Context::new(kind))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Context<ErrorKind>> for Error {
|
impl From<Context<ErrorKind>> for Error {
|
||||||
fn from(inner: Context<ErrorKind>) -> Error {
|
fn from(inner: Context<ErrorKind>) -> Error { Error { inner } }
|
||||||
Error { inner }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub(crate) fn unknown<T>(value: T) -> Self
|
|
||||||
where
|
|
||||||
T: error::Error,
|
|
||||||
{
|
|
||||||
Self::from(ErrorKind::UnknownError(value.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn missing_value<T: ToString>(value: T) -> Self {
|
pub(crate) fn missing_value<T: ToString>(value: T) -> Self {
|
||||||
Self::from(ErrorKind::MissingValue(value.to_string()))
|
Self::from(ErrorKind::MissingValue(value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn invalid_input() -> Self {
|
pub(crate) fn unexpected_attribute<T: ToString>(value: T) -> Self {
|
||||||
Self::from(ErrorKind::InvalidInput)
|
Self::from(ErrorKind::UnexpectedAttribute(value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) }
|
||||||
|
|
||||||
pub(crate) fn parse_int_error<T: ToString>(value: T) -> Self {
|
pub(crate) fn parse_int_error<T: ToString>(value: T) -> Self {
|
||||||
Self::from(ErrorKind::ParseIntError(value.to_string()))
|
Self::from(ErrorKind::ParseIntError(value.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -167,9 +155,7 @@ impl Error {
|
||||||
Self::from(ErrorKind::UnknownProtocolVersion(value.to_string()))
|
Self::from(ErrorKind::UnknownProtocolVersion(value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn io<T: ToString>(value: T) -> Self {
|
pub(crate) fn io<T: ToString>(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) }
|
||||||
Self::from(ErrorKind::Io(value.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn required_version<T, U>(required_version: T, specified_version: U) -> Self
|
pub(crate) fn required_version<T, U>(required_version: T, specified_version: U) -> Self
|
||||||
where
|
where
|
||||||
|
@ -196,25 +182,23 @@ impl Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::std::num::ParseIntError> for Error {
|
impl From<::std::num::ParseIntError> for Error {
|
||||||
fn from(value: ::std::num::ParseIntError) -> Self {
|
fn from(value: ::std::num::ParseIntError) -> Self { Error::parse_int_error(value) }
|
||||||
Error::parse_int_error(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::std::num::ParseFloatError> for Error {
|
impl From<::std::num::ParseFloatError> for Error {
|
||||||
fn from(value: ::std::num::ParseFloatError) -> Self {
|
fn from(value: ::std::num::ParseFloatError) -> Self { Error::parse_float_error(value) }
|
||||||
Error::parse_float_error(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::std::io::Error> for Error {
|
impl From<::std::io::Error> for Error {
|
||||||
fn from(value: ::std::io::Error) -> Self {
|
fn from(value: ::std::io::Error) -> Self { Error::io(value) }
|
||||||
Error::io(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<::chrono::ParseError> for Error {
|
impl From<::chrono::ParseError> for Error {
|
||||||
fn from(value: ::chrono::ParseError) -> Self {
|
fn from(value: ::chrono::ParseError) -> Self { Error::chrono(value) }
|
||||||
Error::chrono(value)
|
}
|
||||||
|
|
||||||
|
impl From<::strum::ParseError> for Error {
|
||||||
|
fn from(value: ::strum::ParseError) -> Self {
|
||||||
|
Error::custom(value) // TODO!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -1,9 +1,17 @@
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![warn(
|
#![warn(
|
||||||
//clippy::pedantic,
|
//clippy::pedantic,
|
||||||
clippy::nursery,
|
clippy::nursery,
|
||||||
clippy::cargo
|
clippy::cargo
|
||||||
)]
|
)]
|
||||||
#![warn(missing_docs)]
|
#![allow(clippy::multiple_crate_versions)]
|
||||||
|
#![warn(
|
||||||
|
missing_docs,
|
||||||
|
missing_copy_implementations,
|
||||||
|
missing_debug_implementations,
|
||||||
|
trivial_casts, // TODO (needed?)
|
||||||
|
trivial_numeric_casts
|
||||||
|
)]
|
||||||
//! [HLS] m3u8 parser/generator.
|
//! [HLS] m3u8 parser/generator.
|
||||||
//!
|
//!
|
||||||
//! [HLS]: https://tools.ietf.org/html/rfc8216
|
//! [HLS]: https://tools.ietf.org/html/rfc8216
|
||||||
|
|
115
src/line.rs
115
src/line.rs
|
@ -9,9 +9,7 @@ use crate::Error;
|
||||||
pub struct Lines(Vec<Line>);
|
pub struct Lines(Vec<Line>);
|
||||||
|
|
||||||
impl Lines {
|
impl Lines {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self { Self::default() }
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Lines {
|
impl FromStr for Lines {
|
||||||
|
@ -26,8 +24,7 @@ impl FromStr for Lines {
|
||||||
for l in input.lines() {
|
for l in input.lines() {
|
||||||
let line = l.trim();
|
let line = l.trim();
|
||||||
|
|
||||||
// ignore empty lines
|
if line.is_empty() {
|
||||||
if line.len() == 0 {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +36,7 @@ impl FromStr for Lines {
|
||||||
continue;
|
continue;
|
||||||
} else if line.starts_with("#EXT") {
|
} else if line.starts_with("#EXT") {
|
||||||
Line::Tag(line.parse()?)
|
Line::Tag(line.parse()?)
|
||||||
} else if line.starts_with("#") {
|
} else if line.starts_with('#') {
|
||||||
continue; // ignore comments
|
continue; // ignore comments
|
||||||
} else {
|
} else {
|
||||||
// stream inf line needs special treatment
|
// stream inf line needs special treatment
|
||||||
|
@ -66,26 +63,20 @@ impl FromStr for Lines {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Lines {
|
impl IntoIterator for Lines {
|
||||||
type Item = Line;
|
|
||||||
type IntoIter = ::std::vec::IntoIter<Line>;
|
type IntoIter = ::std::vec::IntoIter<Line>;
|
||||||
|
type Item = Line;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
||||||
self.0.into_iter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Lines {
|
impl Deref for Lines {
|
||||||
type Target = Vec<Line>;
|
type Target = Vec<Line>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for Lines {
|
impl DerefMut for Lines {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -155,53 +146,53 @@ impl fmt::Display for Tag {
|
||||||
impl FromStr for Tag {
|
impl FromStr for Tag {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
if s.starts_with(tags::ExtM3u::PREFIX) {
|
if input.starts_with(tags::ExtM3u::PREFIX) {
|
||||||
s.parse().map(Tag::ExtM3u)
|
input.parse().map(Tag::ExtM3u)
|
||||||
} else if s.starts_with(tags::ExtXVersion::PREFIX) {
|
} else if input.starts_with(tags::ExtXVersion::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXVersion)
|
input.parse().map(Tag::ExtXVersion)
|
||||||
} else if s.starts_with(tags::ExtInf::PREFIX) {
|
} else if input.starts_with(tags::ExtInf::PREFIX) {
|
||||||
s.parse().map(Tag::ExtInf)
|
input.parse().map(Tag::ExtInf)
|
||||||
} else if s.starts_with(tags::ExtXByteRange::PREFIX) {
|
} else if input.starts_with(tags::ExtXByteRange::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXByteRange)
|
input.parse().map(Tag::ExtXByteRange)
|
||||||
} else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) {
|
} else if input.starts_with(tags::ExtXDiscontinuity::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXDiscontinuity)
|
input.parse().map(Tag::ExtXDiscontinuity)
|
||||||
} else if s.starts_with(tags::ExtXKey::PREFIX) {
|
} else if input.starts_with(tags::ExtXKey::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXKey)
|
input.parse().map(Tag::ExtXKey)
|
||||||
} else if s.starts_with(tags::ExtXMap::PREFIX) {
|
} else if input.starts_with(tags::ExtXMap::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXMap)
|
input.parse().map(Tag::ExtXMap)
|
||||||
} else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) {
|
} else if input.starts_with(tags::ExtXProgramDateTime::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXProgramDateTime)
|
input.parse().map(Tag::ExtXProgramDateTime)
|
||||||
} else if s.starts_with(tags::ExtXTargetDuration::PREFIX) {
|
} else if input.starts_with(tags::ExtXTargetDuration::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXTargetDuration)
|
input.parse().map(Tag::ExtXTargetDuration)
|
||||||
} else if s.starts_with(tags::ExtXDateRange::PREFIX) {
|
} else if input.starts_with(tags::ExtXDateRange::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXDateRange)
|
input.parse().map(Tag::ExtXDateRange)
|
||||||
} else if s.starts_with(tags::ExtXMediaSequence::PREFIX) {
|
} else if input.starts_with(tags::ExtXMediaSequence::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXMediaSequence)
|
input.parse().map(Tag::ExtXMediaSequence)
|
||||||
} else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
|
} else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXDiscontinuitySequence)
|
input.parse().map(Tag::ExtXDiscontinuitySequence)
|
||||||
} else if s.starts_with(tags::ExtXEndList::PREFIX) {
|
} else if input.starts_with(tags::ExtXEndList::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXEndList)
|
input.parse().map(Tag::ExtXEndList)
|
||||||
} else if s.starts_with(tags::ExtXPlaylistType::PREFIX) {
|
} else if input.starts_with(tags::ExtXPlaylistType::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXPlaylistType)
|
input.parse().map(Tag::ExtXPlaylistType)
|
||||||
} else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) {
|
} else if input.starts_with(tags::ExtXIFramesOnly::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXIFramesOnly)
|
input.parse().map(Tag::ExtXIFramesOnly)
|
||||||
} else if s.starts_with(tags::ExtXMedia::PREFIX) {
|
} else if input.starts_with(tags::ExtXMedia::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXMedia)
|
input.parse().map(Tag::ExtXMedia).map_err(Error::custom)
|
||||||
} else if s.starts_with(tags::ExtXStreamInf::PREFIX) {
|
} else if input.starts_with(tags::ExtXStreamInf::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXStreamInf)
|
input.parse().map(Tag::ExtXStreamInf)
|
||||||
} else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
|
} else if input.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXIFrameStreamInf)
|
input.parse().map(Tag::ExtXIFrameStreamInf)
|
||||||
} else if s.starts_with(tags::ExtXSessionData::PREFIX) {
|
} else if input.starts_with(tags::ExtXSessionData::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXSessionData)
|
input.parse().map(Tag::ExtXSessionData)
|
||||||
} else if s.starts_with(tags::ExtXSessionKey::PREFIX) {
|
} else if input.starts_with(tags::ExtXSessionKey::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXSessionKey)
|
input.parse().map(Tag::ExtXSessionKey)
|
||||||
} else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) {
|
} else if input.starts_with(tags::ExtXIndependentSegments::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXIndependentSegments)
|
input.parse().map(Tag::ExtXIndependentSegments)
|
||||||
} else if s.starts_with(tags::ExtXStart::PREFIX) {
|
} else if input.starts_with(tags::ExtXStart::PREFIX) {
|
||||||
s.parse().map(Tag::ExtXStart)
|
input.parse().map(Tag::ExtXStart)
|
||||||
} else {
|
} else {
|
||||||
Ok(Tag::Unknown(s.to_string()))
|
Ok(Tag::Unknown(input.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ pub struct MasterPlaylist {
|
||||||
#[builder(default, setter(name = "version"))]
|
#[builder(default, setter(name = "version"))]
|
||||||
/// Sets the protocol compatibility version of the resulting playlist.
|
/// Sets the protocol compatibility version of the resulting playlist.
|
||||||
///
|
///
|
||||||
/// If the resulting playlist has tags which requires a compatibility version greater than
|
/// If the resulting playlist has tags which requires a compatibility
|
||||||
/// `version`,
|
/// version greater than `version`,
|
||||||
/// `build()` method will fail with an `ErrorKind::InvalidInput` error.
|
/// `build()` method will fail with an `ErrorKind::InvalidInput` error.
|
||||||
///
|
///
|
||||||
/// The default is the maximum version among the tags in the playlist.
|
/// The default is the maximum version among the tags in the playlist.
|
||||||
|
@ -47,14 +47,10 @@ pub struct MasterPlaylist {
|
||||||
|
|
||||||
impl MasterPlaylist {
|
impl MasterPlaylist {
|
||||||
/// Returns a Builder for a MasterPlaylist.
|
/// Returns a Builder for a MasterPlaylist.
|
||||||
pub fn builder() -> MasterPlaylistBuilder {
|
pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() }
|
||||||
MasterPlaylistBuilder::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
||||||
pub const fn version_tag(&self) -> ExtXVersion {
|
pub const fn version_tag(&self) -> ExtXVersion { self.version_tag }
|
||||||
self.version_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
|
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
|
||||||
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
||||||
|
@ -62,19 +58,13 @@ impl MasterPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `EXT-X-START` tag contained in the playlist.
|
/// Returns the `EXT-X-START` tag contained in the playlist.
|
||||||
pub const fn start_tag(&self) -> Option<ExtXStart> {
|
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
|
||||||
self.start_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-MEDIA` tags contained in the playlist.
|
/// Returns the `EXT-X-MEDIA` tags contained in the playlist.
|
||||||
pub fn media_tags(&self) -> &[ExtXMedia] {
|
pub fn media_tags(&self) -> &[ExtXMedia] { &self.media_tags }
|
||||||
&self.media_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-STREAM-INF` tags contained in the playlist.
|
/// Returns the `EXT-X-STREAM-INF` tags contained in the playlist.
|
||||||
pub fn stream_inf_tags(&self) -> &[ExtXStreamInf] {
|
pub fn stream_inf_tags(&self) -> &[ExtXStreamInf] { &self.stream_inf_tags }
|
||||||
&self.stream_inf_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-I-FRAME-STREAM-INF` tags contained in the playlist.
|
/// Returns the `EXT-X-I-FRAME-STREAM-INF` tags contained in the playlist.
|
||||||
pub fn i_frame_stream_inf_tags(&self) -> &[ExtXIFrameStreamInf] {
|
pub fn i_frame_stream_inf_tags(&self) -> &[ExtXIFrameStreamInf] {
|
||||||
|
@ -82,20 +72,14 @@ impl MasterPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `EXT-X-SESSION-DATA` tags contained in the playlist.
|
/// Returns the `EXT-X-SESSION-DATA` tags contained in the playlist.
|
||||||
pub fn session_data_tags(&self) -> &[ExtXSessionData] {
|
pub fn session_data_tags(&self) -> &[ExtXSessionData] { &self.session_data_tags }
|
||||||
&self.session_data_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-SESSION-KEY` tags contained in the playlist.
|
/// Returns the `EXT-X-SESSION-KEY` tags contained in the playlist.
|
||||||
pub fn session_key_tags(&self) -> &[ExtXSessionKey] {
|
pub fn session_key_tags(&self) -> &[ExtXSessionKey] { &self.session_key_tags }
|
||||||
&self.session_key_tags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for MasterPlaylist {
|
impl RequiredVersion for MasterPlaylist {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { self.version_tag.version() }
|
||||||
self.version_tag.version()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPlaylistBuilder {
|
impl MasterPlaylistBuilder {
|
||||||
|
@ -103,7 +87,7 @@ impl MasterPlaylistBuilder {
|
||||||
let required_version = self.required_version();
|
let required_version = self.required_version();
|
||||||
let specified_version = self
|
let specified_version = self
|
||||||
.version_tag
|
.version_tag
|
||||||
.unwrap_or(required_version.into())
|
.unwrap_or_else(|| required_version.into())
|
||||||
.version();
|
.version();
|
||||||
|
|
||||||
if required_version > specified_version {
|
if required_version > specified_version {
|
||||||
|
@ -164,7 +148,7 @@ impl MasterPlaylistBuilder {
|
||||||
.flatten(),
|
.flatten(),
|
||||||
)
|
)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(ProtocolVersion::latest())
|
.unwrap_or_else(ProtocolVersion::latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_stream_inf_tags(&self) -> crate::Result<()> {
|
fn validate_stream_inf_tags(&self) -> crate::Result<()> {
|
||||||
|
@ -188,24 +172,23 @@ impl MasterPlaylistBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match t.closed_captions() {
|
match t.closed_captions() {
|
||||||
Some(&ClosedCaptions::GroupId(ref group_id)) => {
|
&Some(ClosedCaptions::GroupId(ref group_id)) => {
|
||||||
if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
|
if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
|
||||||
return Err(Error::unmatched_group(group_id));
|
return Err(Error::unmatched_group(group_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(&ClosedCaptions::None) => {
|
&Some(ClosedCaptions::None) => {
|
||||||
has_none_closed_captions = true;
|
has_none_closed_captions = true;
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if has_none_closed_captions {
|
if has_none_closed_captions
|
||||||
if !value
|
&& !value
|
||||||
.iter()
|
.iter()
|
||||||
.all(|t| t.closed_captions() == Some(&ClosedCaptions::None))
|
.all(|t| t.closed_captions() == &Some(ClosedCaptions::None))
|
||||||
{
|
{
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -53,7 +53,8 @@ pub struct MediaPlaylist {
|
||||||
end_list_tag: Option<ExtXEndList>,
|
end_list_tag: Option<ExtXEndList>,
|
||||||
/// Sets all [MediaSegment]s.
|
/// Sets all [MediaSegment]s.
|
||||||
segments: Vec<MediaSegment>,
|
segments: Vec<MediaSegment>,
|
||||||
/// Sets the allowable excess duration of each media segment in the associated playlist.
|
/// Sets the allowable excess duration of each media segment in the
|
||||||
|
/// associated playlist.
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
/// If there is a media segment of which duration exceeds
|
/// If there is a media segment of which duration exceeds
|
||||||
|
@ -70,7 +71,7 @@ impl MediaPlaylistBuilder {
|
||||||
let required_version = self.required_version();
|
let required_version = self.required_version();
|
||||||
let specified_version = self
|
let specified_version = self
|
||||||
.version_tag
|
.version_tag
|
||||||
.unwrap_or(required_version.into())
|
.unwrap_or_else(|| required_version.into())
|
||||||
.version();
|
.version();
|
||||||
|
|
||||||
if required_version > specified_version {
|
if required_version > specified_version {
|
||||||
|
@ -109,7 +110,7 @@ impl MediaPlaylistBuilder {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !(rounded_segment_duration <= max_segment_duration) {
|
if rounded_segment_duration > max_segment_duration {
|
||||||
return Err(Error::custom(format!(
|
return Err(Error::custom(format!(
|
||||||
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
||||||
segment_duration,
|
segment_duration,
|
||||||
|
@ -122,7 +123,7 @@ impl MediaPlaylistBuilder {
|
||||||
// CHECK: `#EXT-X-BYTE-RANGE`
|
// CHECK: `#EXT-X-BYTE-RANGE`
|
||||||
if let Some(tag) = s.byte_range_tag() {
|
if let Some(tag) = s.byte_range_tag() {
|
||||||
if tag.to_range().start().is_none() {
|
if tag.to_range().start().is_none() {
|
||||||
let last_uri = last_range_uri.ok_or(Error::invalid_input())?;
|
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
|
||||||
if last_uri != s.uri() {
|
if last_uri != s.uri() {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
@ -200,7 +201,7 @@ impl MediaPlaylistBuilder {
|
||||||
.unwrap_or(ProtocolVersion::V1)
|
.unwrap_or(ProtocolVersion::V1)
|
||||||
}))
|
}))
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(ProtocolVersion::latest())
|
.unwrap_or_else(ProtocolVersion::latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a media segment to the resulting playlist.
|
/// Adds a media segment to the resulting playlist.
|
||||||
|
@ -221,38 +222,28 @@ impl MediaPlaylistBuilder {
|
||||||
|
|
||||||
impl MediaPlaylist {
|
impl MediaPlaylist {
|
||||||
/// Creates a [MediaPlaylistBuilder].
|
/// Creates a [MediaPlaylistBuilder].
|
||||||
pub fn builder() -> MediaPlaylistBuilder {
|
pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() }
|
||||||
MediaPlaylistBuilder::default()
|
|
||||||
}
|
|
||||||
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
||||||
pub const fn version_tag(&self) -> ExtXVersion {
|
pub const fn version_tag(&self) -> ExtXVersion { self.version_tag }
|
||||||
self.version_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
|
/// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
|
||||||
pub const fn target_duration_tag(&self) -> ExtXTargetDuration {
|
pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag }
|
||||||
self.target_duration_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
|
/// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
|
||||||
pub const fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> {
|
pub const fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> { self.media_sequence_tag }
|
||||||
self.media_sequence_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the playlist.
|
/// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the
|
||||||
|
/// playlist.
|
||||||
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
|
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
|
||||||
self.discontinuity_sequence_tag
|
self.discontinuity_sequence_tag
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
|
/// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
|
||||||
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> {
|
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> { self.playlist_type_tag }
|
||||||
self.playlist_type_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist.
|
/// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist.
|
||||||
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> {
|
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> { self.i_frames_only_tag }
|
||||||
self.i_frames_only_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
|
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
|
||||||
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
||||||
|
@ -260,19 +251,13 @@ impl MediaPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `EXT-X-START` tag contained in the playlist.
|
/// Returns the `EXT-X-START` tag contained in the playlist.
|
||||||
pub const fn start_tag(&self) -> Option<ExtXStart> {
|
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
|
||||||
self.start_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-ENDLIST` tag contained in the playlist.
|
/// Returns the `EXT-X-ENDLIST` tag contained in the playlist.
|
||||||
pub const fn end_list_tag(&self) -> Option<ExtXEndList> {
|
pub const fn end_list_tag(&self) -> Option<ExtXEndList> { self.end_list_tag }
|
||||||
self.end_list_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the media segments contained in the playlist.
|
/// Returns the media segments contained in the playlist.
|
||||||
pub fn segments(&self) -> &[MediaSegment] {
|
pub fn segments(&self) -> &[MediaSegment] { &self.segments }
|
||||||
&self.segments
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MediaPlaylist {
|
impl fmt::Display for MediaPlaylist {
|
||||||
|
|
|
@ -76,48 +76,34 @@ impl fmt::Display for MediaSegment {
|
||||||
|
|
||||||
impl MediaSegment {
|
impl MediaSegment {
|
||||||
/// Creates a [MediaSegmentBuilder].
|
/// Creates a [MediaSegmentBuilder].
|
||||||
pub fn builder() -> MediaSegmentBuilder {
|
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
|
||||||
MediaSegmentBuilder::default()
|
|
||||||
}
|
|
||||||
/// Returns the URI of the media segment.
|
/// Returns the URI of the media segment.
|
||||||
pub const fn uri(&self) -> &String {
|
pub const fn uri(&self) -> &String { &self.uri }
|
||||||
&self.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-INF` tag associated with the media segment.
|
/// Returns the `EXT-X-INF` tag associated with the media segment.
|
||||||
pub const fn inf_tag(&self) -> &ExtInf {
|
pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag }
|
||||||
&self.inf_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-BYTERANGE` tag associated with the media segment.
|
/// Returns the `EXT-X-BYTERANGE` tag associated with the media segment.
|
||||||
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> {
|
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> { self.byte_range_tag }
|
||||||
self.byte_range_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-DATERANGE` tag associated with the media segment.
|
/// Returns the `EXT-X-DATERANGE` tag associated with the media segment.
|
||||||
pub fn date_range_tag(&self) -> Option<&ExtXDateRange> {
|
pub fn date_range_tag(&self) -> Option<&ExtXDateRange> { self.date_range_tag.as_ref() }
|
||||||
self.date_range_tag.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-DISCONTINUITY` tag associated with the media segment.
|
/// Returns the `EXT-X-DISCONTINUITY` tag associated with the media segment.
|
||||||
pub const fn discontinuity_tag(&self) -> Option<ExtXDiscontinuity> {
|
pub const fn discontinuity_tag(&self) -> Option<ExtXDiscontinuity> { self.discontinuity_tag }
|
||||||
self.discontinuity_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-PROGRAM-DATE-TIME` tag associated with the media segment.
|
/// Returns the `EXT-X-PROGRAM-DATE-TIME` tag associated with the media
|
||||||
|
/// segment.
|
||||||
pub fn program_date_time_tag(&self) -> Option<&ExtXProgramDateTime> {
|
pub fn program_date_time_tag(&self) -> Option<&ExtXProgramDateTime> {
|
||||||
self.program_date_time_tag.as_ref()
|
self.program_date_time_tag.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `EXT-X-MAP` tag associated with the media segment.
|
/// Returns the `EXT-X-MAP` tag associated with the media segment.
|
||||||
pub fn map_tag(&self) -> Option<&ExtXMap> {
|
pub fn map_tag(&self) -> Option<&ExtXMap> { self.map_tag.as_ref() }
|
||||||
self.map_tag.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `EXT-X-KEY` tags associated with the media segment.
|
/// Returns the `EXT-X-KEY` tags associated with the media segment.
|
||||||
pub fn key_tags(&self) -> &[ExtXKey] {
|
pub fn key_tags(&self) -> &[ExtXKey] { &self.key_tags }
|
||||||
&self.key_tags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for MediaSegment {
|
impl RequiredVersion for MediaSegment {
|
||||||
|
|
|
@ -5,17 +5,35 @@ use crate::types::{ProtocolVersion, RequiredVersion};
|
||||||
use crate::utils::tag;
|
use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.3.1.1. EXTM3U]
|
/// # [4.4.1.1. EXTM3U]
|
||||||
/// The [ExtM3u] tag indicates that the file is an Extended [M3U]
|
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
|
||||||
/// Playlist file.
|
/// Playlist file.
|
||||||
|
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// # Examples
|
||||||
/// ```text
|
/// Parsing from a [`str`]:
|
||||||
/// #EXTM3U
|
/// ```
|
||||||
|
/// # use failure::Error;
|
||||||
|
/// # use hls_m3u8::tags::ExtM3u;
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> Result<(), Error> {
|
||||||
|
/// assert_eq!("#EXTM3U".parse::<ExtM3u>()?, ExtM3u);
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
/// Converting to a [`str`]:
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtM3u;
|
||||||
|
/// #
|
||||||
|
/// assert_eq!("#EXTM3U".to_string(), ExtM3u.to_string());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [M3U]: https://en.wikipedia.org/wiki/M3U
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
|
/// [`Master Playlist`]: crate::MasterPlaylist
|
||||||
|
/// [`M3U`]: https://en.wikipedia.org/wiki/M3U
|
||||||
|
/// [4.4.1.1. EXTM3U]:
|
||||||
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.1
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct ExtM3u;
|
pub struct ExtM3u;
|
||||||
|
|
||||||
|
@ -23,16 +41,13 @@ impl ExtM3u {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXTM3U";
|
pub(crate) const PREFIX: &'static str = "#EXTM3U";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
impl RequiredVersion for ExtM3u {
|
impl RequiredVersion for ExtM3u {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtM3u {
|
impl fmt::Display for ExtM3u {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX) }
|
||||||
Self::PREFIX.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtM3u {
|
impl FromStr for ExtM3u {
|
||||||
|
@ -40,7 +55,7 @@ impl FromStr for ExtM3u {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
tag(input, Self::PREFIX)?;
|
tag(input, Self::PREFIX)?;
|
||||||
Ok(ExtM3u)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,40 +5,62 @@ use crate::types::{ProtocolVersion, RequiredVersion};
|
||||||
use crate::utils::tag;
|
use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.3.1.2. EXT-X-VERSION]
|
/// # [4.4.1.2. EXT-X-VERSION]
|
||||||
/// The [ExtXVersion] tag indicates the compatibility version of the
|
/// The [`ExtXVersion`] tag indicates the compatibility version of the
|
||||||
/// Playlist file, its associated media, and its server.
|
/// [`Master Playlist`] or [`Media Playlist`] file.
|
||||||
|
/// It applies to the entire Playlist.
|
||||||
///
|
///
|
||||||
/// The [ExtXVersion] tag applies to the entire Playlist file. Its
|
/// # Examples
|
||||||
/// format is:
|
/// Parsing from a [`str`]:
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// #EXT-X-VERSION:<n>
|
|
||||||
/// ```
|
/// ```
|
||||||
/// where `n` is an integer indicating the protocol compatibility version
|
/// # use failure::Error;
|
||||||
/// number.
|
/// # use hls_m3u8::tags::ExtXVersion;
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> Result<(), Error> {
|
||||||
|
/// use hls_m3u8::types::ProtocolVersion;
|
||||||
///
|
///
|
||||||
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
|
/// assert_eq!(
|
||||||
|
/// "#EXT-X-VERSION:5".parse::<ExtXVersion>()?,
|
||||||
|
/// ExtXVersion::new(ProtocolVersion::V5)
|
||||||
|
/// );
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
/// Converting to a [`str`]:
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXVersion;
|
||||||
|
/// #
|
||||||
|
/// use hls_m3u8::types::ProtocolVersion;
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// "#EXT-X-VERSION:5".to_string(),
|
||||||
|
/// ExtXVersion::new(ProtocolVersion::V5).to_string()
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
|
/// [`Master Playlist`]: crate::MasterPlaylist
|
||||||
|
/// [4.4.1.2. EXT-X-VERSION]:
|
||||||
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.2
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
pub struct ExtXVersion(ProtocolVersion);
|
pub struct ExtXVersion(ProtocolVersion);
|
||||||
|
|
||||||
impl ExtXVersion {
|
impl ExtXVersion {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:";
|
||||||
|
|
||||||
/// Makes a new [ExtXVersion] tag.
|
/// Makes a new [`ExtXVersion`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::tags::ExtXVersion;
|
/// # use hls_m3u8::tags::ExtXVersion;
|
||||||
/// use hls_m3u8::types::ProtocolVersion;
|
/// use hls_m3u8::types::ProtocolVersion;
|
||||||
///
|
///
|
||||||
/// let version_tag = ExtXVersion::new(ProtocolVersion::V2);
|
/// let version = ExtXVersion::new(ProtocolVersion::V2);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn new(version: ProtocolVersion) -> Self {
|
pub const fn new(version: ProtocolVersion) -> Self { Self(version) }
|
||||||
Self(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the protocol compatibility version of the playlist, containing this tag.
|
/// Returns the [`ProtocolVersion`] of the playlist, containing this tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -50,33 +72,24 @@ impl ExtXVersion {
|
||||||
/// ProtocolVersion::V6
|
/// ProtocolVersion::V6
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn version(&self) -> ProtocolVersion {
|
pub const fn version(self) -> ProtocolVersion { self.0 }
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
impl RequiredVersion for ExtXVersion {
|
impl RequiredVersion for ExtXVersion {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXVersion {
|
impl fmt::Display for ExtXVersion {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) }
|
||||||
write!(f, "{}{}", Self::PREFIX, self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExtXVersion {
|
impl Default for ExtXVersion {
|
||||||
fn default() -> Self {
|
fn default() -> Self { Self(ProtocolVersion::V1) }
|
||||||
Self(ProtocolVersion::V1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ProtocolVersion> for ExtXVersion {
|
impl From<ProtocolVersion> for ExtXVersion {
|
||||||
fn from(value: ProtocolVersion) -> Self {
|
fn from(value: ProtocolVersion) -> Self { Self(value) }
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXVersion {
|
impl FromStr for ExtXVersion {
|
||||||
|
@ -84,7 +97,7 @@ impl FromStr for ExtXVersion {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let version = tag(input, Self::PREFIX)?.parse()?;
|
let version = tag(input, Self::PREFIX)?.parse()?;
|
||||||
Ok(ExtXVersion::new(version))
|
Ok(Self::new(version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,4 +128,12 @@ mod test {
|
||||||
ProtocolVersion::V1
|
ProtocolVersion::V1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_and_from() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXVersion::default(),
|
||||||
|
ExtXVersion::from(ProtocolVersion::V1)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,18 @@ use crate::types::{ProtocolVersion, RequiredVersion, StreamInf};
|
||||||
use crate::utils::{quote, tag, unquote};
|
use crate::utils::{quote, tag, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
|
/// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]
|
||||||
/// The [ExtXIFrameStreamInf] tag identifies a [Media Playlist] file
|
/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,
|
||||||
/// containing the I-frames of a multimedia presentation. It stands
|
/// containing the I-frames of a multimedia presentation.
|
||||||
/// alone, in that it does not apply to a particular `URI` in the [Master Playlist].
|
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// I-frames are encoded video frames, whose decoding
|
||||||
|
/// does not depend on any other frame.
|
||||||
///
|
///
|
||||||
/// ```text
|
/// [`Master Playlist`]: crate::MasterPlaylist
|
||||||
/// #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// ```
|
/// [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]:
|
||||||
///
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.3
|
||||||
/// [Master Playlist]: crate::MasterPlaylist
|
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
/// [Media Playlist]: crate::MediaPlaylist
|
|
||||||
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct ExtXIFrameStreamInf {
|
pub struct ExtXIFrameStreamInf {
|
||||||
uri: String,
|
uri: String,
|
||||||
stream_inf: StreamInf,
|
stream_inf: StreamInf,
|
||||||
|
@ -30,7 +27,13 @@ pub struct ExtXIFrameStreamInf {
|
||||||
impl ExtXIFrameStreamInf {
|
impl ExtXIFrameStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||||
|
|
||||||
/// Makes a new [ExtXIFrameStreamInf] tag.
|
/// Makes a new [`ExtXIFrameStreamInf`] tag.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXIFrameStreamInf;
|
||||||
|
/// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20);
|
||||||
|
/// ```
|
||||||
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
|
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
|
||||||
ExtXIFrameStreamInf {
|
ExtXIFrameStreamInf {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
|
@ -38,20 +41,19 @@ impl ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `URI`, that identifies the associated media playlist.
|
/// Returns the `URI`, that identifies the associated [`media playlist`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::tags::ExtXIFrameStreamInf;
|
/// # use hls_m3u8::tags::ExtXIFrameStreamInf;
|
||||||
/// #
|
|
||||||
/// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20);
|
/// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20);
|
||||||
/// assert_eq!(stream.uri(), &"https://www.example.com".to_string());
|
/// assert_eq!(stream.uri(), &"https://www.example.com".to_string());
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn uri(&self) -> &String {
|
///
|
||||||
&self.uri
|
/// [`media playlist`]: crate::MediaPlaylist
|
||||||
}
|
pub const fn uri(&self) -> &String { &self.uri }
|
||||||
|
|
||||||
/// Sets the `URI`, that identifies the associated media playlist.
|
/// Sets the `URI`, that identifies the associated [`media playlist`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -62,16 +64,17 @@ impl ExtXIFrameStreamInf {
|
||||||
/// stream.set_uri("../new/uri");
|
/// stream.set_uri("../new/uri");
|
||||||
/// assert_eq!(stream.uri(), &"../new/uri".to_string());
|
/// assert_eq!(stream.uri(), &"../new/uri".to_string());
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`media playlist`]: crate::MediaPlaylist
|
||||||
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
|
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
|
||||||
self.uri = value.to_string();
|
self.uri = value.to_string();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
impl RequiredVersion for ExtXIFrameStreamInf {
|
impl RequiredVersion for ExtXIFrameStreamInf {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXIFrameStreamInf {
|
impl fmt::Display for ExtXIFrameStreamInf {
|
||||||
|
@ -91,13 +94,12 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
let mut uri = None;
|
let mut uri = None;
|
||||||
|
|
||||||
for (key, value) in input.parse::<AttributePairs>()? {
|
for (key, value) in input.parse::<AttributePairs>()? {
|
||||||
match key.as_str() {
|
if let "URI" = key.as_str() {
|
||||||
"URI" => uri = Some(unquote(value)),
|
uri = Some(unquote(value));
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let uri = uri.ok_or(Error::missing_value("URI"))?;
|
let uri = uri.ok_or_else(|| Error::missing_value("URI"))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
uri,
|
uri,
|
||||||
|
@ -109,15 +111,11 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
impl Deref for ExtXIFrameStreamInf {
|
impl Deref for ExtXIFrameStreamInf {
|
||||||
type Target = StreamInf;
|
type Target = StreamInf;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.stream_inf }
|
||||||
&self.stream_inf
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXIFrameStreamInf {
|
impl DerefMut for ExtXIFrameStreamInf {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stream_inf }
|
||||||
&mut self.stream_inf
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -140,6 +138,8 @@ mod test {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXIFrameStreamInf::new("foo", 1000)
|
ExtXIFrameStreamInf::new("foo", 1000)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!("garbage".parse::<ExtXIFrameStreamInf>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -149,4 +149,22 @@ mod test {
|
||||||
ProtocolVersion::V1
|
ProtocolVersion::V1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXIFrameStreamInf::new("https://www.example.com", 20).average_bandwidth(),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref_mut() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXIFrameStreamInf::new("https://www.example.com", 20)
|
||||||
|
.set_average_bandwidth(Some(4))
|
||||||
|
.average_bandwidth(),
|
||||||
|
Some(4)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,37 +11,43 @@ use crate::Error;
|
||||||
/// The data of an [ExtXSessionData] tag.
|
/// The data of an [ExtXSessionData] tag.
|
||||||
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
||||||
pub enum SessionData {
|
pub enum SessionData {
|
||||||
/// A String, that contains the data identified by [data_id](ExtXSessionData::data_id).
|
/// A String, that contains the data identified by
|
||||||
/// If a [language](ExtXSessionData::language) is specified, the value should
|
/// [`data_id`](ExtXSessionData::data_id).
|
||||||
/// contain a human-readable string written in the specified language.
|
/// If a [`language`](ExtXSessionData::language) is specified, the value
|
||||||
|
/// should contain a human-readable string written in the specified
|
||||||
|
/// language.
|
||||||
Value(String),
|
Value(String),
|
||||||
/// An [uri], which points to a [json].
|
/// An [`uri`], which points to a [`json`].
|
||||||
///
|
///
|
||||||
/// [json]: https://tools.ietf.org/html/rfc8259
|
/// [`json`]: https://tools.ietf.org/html/rfc8259
|
||||||
/// [uri]: https://tools.ietf.org/html/rfc3986
|
/// [`uri`]: https://tools.ietf.org/html/rfc3986
|
||||||
Uri(String),
|
Uri(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # [4.3.4.4. EXT-X-SESSION-DATA]
|
/// # [4.3.4.4. EXT-X-SESSION-DATA]
|
||||||
///
|
///
|
||||||
/// The [ExtXSessionData] tag allows arbitrary session data to be
|
/// The [`ExtXSessionData`] tag allows arbitrary session data to be
|
||||||
/// carried in a [Master Playlist].
|
/// carried in a [`Master Playlist`].
|
||||||
///
|
///
|
||||||
/// [Master Playlist]: crate::MasterPlaylist
|
/// [`Master Playlist`]: crate::MasterPlaylist
|
||||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||||
#[derive(Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
#[derive(Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
pub struct ExtXSessionData {
|
pub struct ExtXSessionData {
|
||||||
/// The identifier of the data. For more information look [here](ExtXSessionData::set_data_id).
|
/// The identifier of the data.
|
||||||
|
/// For more information look [`here`](ExtXSessionData::set_data_id).
|
||||||
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// This field is required.
|
/// This field is required.
|
||||||
data_id: String,
|
data_id: String,
|
||||||
/// The data associated with the [data_id](ExtXSessionDataBuilder::data_id).
|
/// The data associated with the
|
||||||
/// For more information look [here](SessionData).
|
/// [`data_id`](ExtXSessionDataBuilder::data_id).
|
||||||
|
/// For more information look [`here`](SessionData).
|
||||||
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// This field is required.
|
/// This field is required.
|
||||||
data: SessionData,
|
data: SessionData,
|
||||||
/// The language of the [data](ExtXSessionDataBuilder::data).
|
/// The language of the [`data`](ExtXSessionDataBuilder::data).
|
||||||
#[builder(setter(into, strip_option), default)]
|
#[builder(setter(into, strip_option), default)]
|
||||||
language: Option<String>,
|
language: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -49,7 +55,7 @@ pub struct ExtXSessionData {
|
||||||
impl ExtXSessionData {
|
impl ExtXSessionData {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
||||||
|
|
||||||
/// Makes a new [ExtXSessionData] tag.
|
/// Makes a new [`ExtXSessionData`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -57,18 +63,18 @@ impl ExtXSessionData {
|
||||||
///
|
///
|
||||||
/// ExtXSessionData::new(
|
/// ExtXSessionData::new(
|
||||||
/// "com.example.movie.title",
|
/// "com.example.movie.title",
|
||||||
/// SessionData::Uri("https://www.example.com/".to_string())
|
/// SessionData::Uri("https://www.example.com/".to_string()),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: ToString>(data_id: T, data: SessionData) -> Self {
|
pub fn new<T: ToString>(data_id: T, data: SessionData) -> Self {
|
||||||
ExtXSessionData {
|
Self {
|
||||||
data_id: data_id.to_string(),
|
data_id: data_id.to_string(),
|
||||||
data,
|
data,
|
||||||
language: None,
|
language: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new Builder for [ExtXSessionData].
|
/// Returns a new Builder for [`ExtXSessionData`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -90,11 +96,9 @@ impl ExtXSessionData {
|
||||||
/// )
|
/// )
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn builder() -> ExtXSessionDataBuilder {
|
pub fn builder() -> ExtXSessionDataBuilder { ExtXSessionDataBuilder::default() }
|
||||||
ExtXSessionDataBuilder::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a new [ExtXSessionData] tag, with the given language.
|
/// Makes a new [`ExtXSessionData`] tag, with the given language.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -103,11 +107,11 @@ impl ExtXSessionData {
|
||||||
/// let session_data = ExtXSessionData::with_language(
|
/// let session_data = ExtXSessionData::with_language(
|
||||||
/// "com.example.movie.title",
|
/// "com.example.movie.title",
|
||||||
/// SessionData::Value("some data".to_string()),
|
/// SessionData::Value("some data".to_string()),
|
||||||
/// "english"
|
/// "english",
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_language<T: ToString>(data_id: T, data: SessionData, language: T) -> Self {
|
pub fn with_language<T: ToString>(data_id: T, data: SessionData, language: T) -> Self {
|
||||||
ExtXSessionData {
|
Self {
|
||||||
data_id: data_id.to_string(),
|
data_id: data_id.to_string(),
|
||||||
data,
|
data,
|
||||||
language: Some(language.to_string()),
|
language: Some(language.to_string()),
|
||||||
|
@ -130,9 +134,7 @@ impl ExtXSessionData {
|
||||||
/// &"com.example.movie.title".to_string()
|
/// &"com.example.movie.title".to_string()
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn data_id(&self) -> &String {
|
pub const fn data_id(&self) -> &String { &self.data_id }
|
||||||
&self.data_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `data`.
|
/// Returns the `data`.
|
||||||
///
|
///
|
||||||
|
@ -150,11 +152,10 @@ impl ExtXSessionData {
|
||||||
/// &SessionData::Value("some data".to_string())
|
/// &SessionData::Value("some data".to_string())
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn data(&self) -> &SessionData {
|
pub const fn data(&self) -> &SessionData { &self.data }
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `language` tag, that identifies the language of [SessionData].
|
/// Returns the `language` tag, that identifies the language of
|
||||||
|
/// [`SessionData`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -171,12 +172,10 @@ impl ExtXSessionData {
|
||||||
/// &Some("english".to_string())
|
/// &Some("english".to_string())
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn language(&self) -> &Option<String> {
|
pub const fn language(&self) -> &Option<String> { &self.language }
|
||||||
&self.language
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `language` attribute, that identifies the language of [SessionData].
|
/// Sets the `language` attribute, that identifies the language of
|
||||||
/// See [rfc5646](https://tools.ietf.org/html/rfc5646).
|
/// [`SessionData`]. See [rfc5646](https://tools.ietf.org/html/rfc5646).
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -197,8 +196,8 @@ impl ExtXSessionData {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the `data_id` attribute, that should conform to a [reverse DNS] naming convention,
|
/// Sets the `data_id` attribute, that should conform to a [reverse DNS]
|
||||||
/// such as `com.example.movie.title`.
|
/// naming convention, such as `com.example.movie.title`.
|
||||||
///
|
///
|
||||||
/// # Note:
|
/// # Note:
|
||||||
/// There is no central registration authority, so a value
|
/// There is no central registration authority, so a value
|
||||||
|
@ -224,7 +223,7 @@ impl ExtXSessionData {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [data](ExtXSessionData::data) of this tag.
|
/// Sets the [`data`](ExtXSessionData::data) of this tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -247,22 +246,23 @@ impl ExtXSessionData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXSessionData {
|
impl RequiredVersion for ExtXSessionData {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXSessionData {
|
impl fmt::Display for ExtXSessionData {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "DATA-ID={}", quote(&self.data_id))?;
|
write!(f, "DATA-ID={}", quote(&self.data_id))?;
|
||||||
|
|
||||||
match &self.data {
|
match &self.data {
|
||||||
SessionData::Value(value) => write!(f, ",VALUE={}", quote(value))?,
|
SessionData::Value(value) => write!(f, ",VALUE={}", quote(value))?,
|
||||||
SessionData::Uri(value) => write!(f, ",URI={}", quote(value))?,
|
SessionData::Uri(value) => write!(f, ",URI={}", quote(value))?,
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.language {
|
if let Some(value) = &self.language {
|
||||||
write!(f, ",LANGUAGE={}", quote(value))?;
|
write!(f, ",LANGUAGE={}", quote(value))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,12 +286,13 @@ impl FromStr for ExtXSessionData {
|
||||||
"LANGUAGE" => language = Some(unquote(value)),
|
"LANGUAGE" => language = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized
|
||||||
|
// AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_id = data_id.ok_or(Error::missing_value("EXT-X-DATA-ID"))?;
|
let data_id = data_id.ok_or_else(|| Error::missing_value("EXT-X-DATA-ID"))?;
|
||||||
let data = {
|
let data = {
|
||||||
if let Some(value) = session_value {
|
if let Some(value) = session_value {
|
||||||
if uri.is_some() {
|
if uri.is_some() {
|
||||||
|
@ -306,7 +307,7 @@ impl FromStr for ExtXSessionData {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ExtXSessionData {
|
Ok(Self {
|
||||||
data_id,
|
data_id,
|
||||||
data,
|
data,
|
||||||
language,
|
language,
|
||||||
|
@ -321,7 +322,10 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_display() {
|
fn test_display() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.lyrics\",URI=\"lyrics.json\"".to_string(),
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"com.example.lyrics\",\
|
||||||
|
URI=\"lyrics.json\""
|
||||||
|
.to_string(),
|
||||||
ExtXSessionData::new(
|
ExtXSessionData::new(
|
||||||
"com.example.lyrics",
|
"com.example.lyrics",
|
||||||
SessionData::Uri("lyrics.json".to_string())
|
SessionData::Uri("lyrics.json".to_string())
|
||||||
|
@ -330,8 +334,10 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.title\",\
|
"#EXT-X-SESSION-DATA:\
|
||||||
VALUE=\"This is an example\",LANGUAGE=\"en\""
|
DATA-ID=\"com.example.title\",\
|
||||||
|
VALUE=\"This is an example\",\
|
||||||
|
LANGUAGE=\"en\""
|
||||||
.to_string(),
|
.to_string(),
|
||||||
ExtXSessionData::with_language(
|
ExtXSessionData::with_language(
|
||||||
"com.example.title",
|
"com.example.title",
|
||||||
|
@ -342,8 +348,10 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.title\",\
|
"#EXT-X-SESSION-DATA:\
|
||||||
VALUE=\"Este es un ejemplo\",LANGUAGE=\"es\""
|
DATA-ID=\"com.example.title\",\
|
||||||
|
VALUE=\"Este es un ejemplo\",\
|
||||||
|
LANGUAGE=\"es\""
|
||||||
.to_string(),
|
.to_string(),
|
||||||
ExtXSessionData::with_language(
|
ExtXSessionData::with_language(
|
||||||
"com.example.title",
|
"com.example.title",
|
||||||
|
@ -354,17 +362,27 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",VALUE=\"bar\"".to_string(),
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
VALUE=\"bar\""
|
||||||
|
.to_string(),
|
||||||
ExtXSessionData::new("foo", SessionData::Value("bar".into())).to_string()
|
ExtXSessionData::new("foo", SessionData::Value("bar".into())).to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",URI=\"bar\"".to_string(),
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
URI=\"bar\""
|
||||||
|
.to_string(),
|
||||||
ExtXSessionData::new("foo", SessionData::Uri("bar".into())).to_string()
|
ExtXSessionData::new("foo", SessionData::Uri("bar".into())).to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",VALUE=\"bar\",LANGUAGE=\"baz\"".to_string(),
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
VALUE=\"bar\",\
|
||||||
|
LANGUAGE=\"baz\""
|
||||||
|
.to_string(),
|
||||||
ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz")
|
ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz")
|
||||||
.to_string()
|
.to_string()
|
||||||
);
|
);
|
||||||
|
@ -373,7 +391,9 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.lyrics\",URI=\"lyrics.json\""
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"com.example.lyrics\",\
|
||||||
|
URI=\"lyrics.json\""
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::new(
|
ExtXSessionData::new(
|
||||||
|
@ -383,8 +403,10 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.title\",\
|
"#EXT-X-SESSION-DATA:\
|
||||||
LANGUAGE=\"en\", VALUE=\"This is an example\""
|
DATA-ID=\"com.example.title\",\
|
||||||
|
LANGUAGE=\"en\",\
|
||||||
|
VALUE=\"This is an example\""
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::with_language(
|
ExtXSessionData::with_language(
|
||||||
|
@ -395,8 +417,10 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"com.example.title\",\
|
"#EXT-X-SESSION-DATA:\
|
||||||
LANGUAGE=\"es\", VALUE=\"Este es un ejemplo\""
|
DATA-ID=\"com.example.title\",\
|
||||||
|
LANGUAGE=\"es\",\
|
||||||
|
VALUE=\"Este es un ejemplo\""
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::with_language(
|
ExtXSessionData::with_language(
|
||||||
|
@ -407,25 +431,47 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",VALUE=\"bar\""
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
VALUE=\"bar\""
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::new("foo", SessionData::Value("bar".into()))
|
ExtXSessionData::new("foo", SessionData::Value("bar".into()))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",URI=\"bar\""
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
URI=\"bar\""
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::new("foo", SessionData::Uri("bar".into()))
|
ExtXSessionData::new("foo", SessionData::Uri("bar".into()))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-DATA:DATA-ID=\"foo\",VALUE=\"bar\",LANGUAGE=\"baz\""
|
"#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
VALUE=\"bar\",\
|
||||||
|
LANGUAGE=\"baz\",\
|
||||||
|
UNKNOWN=TAG"
|
||||||
.parse::<ExtXSessionData>()
|
.parse::<ExtXSessionData>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz")
|
ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!("#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
LANGUAGE=\"baz\""
|
||||||
|
.parse::<ExtXSessionData>()
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
assert!("#EXT-X-SESSION-DATA:\
|
||||||
|
DATA-ID=\"foo\",\
|
||||||
|
LANGUAGE=\"baz\",\
|
||||||
|
VALUE=\"VALUE\",\
|
||||||
|
URI=\"https://www.example.com/\""
|
||||||
|
.parse::<ExtXSessionData>()
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -7,9 +7,9 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.3.4.5. EXT-X-SESSION-KEY]
|
/// # [4.3.4.5. EXT-X-SESSION-KEY]
|
||||||
/// The [ExtXSessionKey] tag allows encryption keys from [Media Playlist]s
|
/// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s
|
||||||
/// to be specified in a [Master Playlist]. This allows the client to
|
/// to be specified in a [`Master Playlist`]. This allows the client to
|
||||||
/// preload these keys without having to read the [Media Playlist]s
|
/// preload these keys without having to read the [`Media Playlist`]s
|
||||||
/// first.
|
/// first.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
|
@ -17,8 +17,8 @@ use crate::Error;
|
||||||
/// #EXT-X-SESSION-KEY:<attribute-list>
|
/// #EXT-X-SESSION-KEY:<attribute-list>
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [Media Playlist]: crate::MediaPlaylist
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [Master Playlist]: crate::MasterPlaylist
|
/// [`Master Playlist`]: crate::MasterPlaylist
|
||||||
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
|
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXSessionKey(DecryptionKey);
|
pub struct ExtXSessionKey(DecryptionKey);
|
||||||
|
@ -26,21 +26,20 @@ pub struct ExtXSessionKey(DecryptionKey);
|
||||||
impl ExtXSessionKey {
|
impl ExtXSessionKey {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
|
||||||
|
|
||||||
/// Makes a new [ExtXSessionKey] tag.
|
/// Makes a new [`ExtXSessionKey`] tag.
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// An [ExtXSessionKey] should only be used, if the segments of the stream are encrypted.
|
/// An [`ExtXSessionKey`] should only be used,
|
||||||
/// Therefore this function will panic, if the `method` is [EncryptionMethod::None].
|
/// if the segments of the stream are encrypted.
|
||||||
|
/// Therefore this function will panic,
|
||||||
|
/// if the `method` is [`EncryptionMethod::None`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::tags::ExtXSessionKey;
|
/// # use hls_m3u8::tags::ExtXSessionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let session_key = ExtXSessionKey::new(
|
/// let session_key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: ToString>(method: EncryptionMethod, uri: T) -> Self {
|
pub fn new<T: ToString>(method: EncryptionMethod, uri: T) -> Self {
|
||||||
if method == EncryptionMethod::None {
|
if method == EncryptionMethod::None {
|
||||||
|
@ -52,9 +51,7 @@ impl ExtXSessionKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXSessionKey {
|
impl RequiredVersion for ExtXSessionKey {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { self.0.required_version() }
|
||||||
self.0.required_version()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXSessionKey {
|
impl fmt::Display for ExtXSessionKey {
|
||||||
|
@ -78,15 +75,11 @@ impl FromStr for ExtXSessionKey {
|
||||||
impl Deref for ExtXSessionKey {
|
impl Deref for ExtXSessionKey {
|
||||||
type Target = DecryptionKey;
|
type Target = DecryptionKey;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXSessionKey {
|
impl DerefMut for ExtXSessionKey {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -100,9 +93,9 @@ mod test {
|
||||||
EncryptionMethod::Aes128,
|
EncryptionMethod::Aes128,
|
||||||
"https://www.example.com/hls-key/key.bin",
|
"https://www.example.com/hls-key/key.bin",
|
||||||
);
|
);
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
key.to_string(),
|
key.to_string(),
|
||||||
|
@ -129,9 +122,9 @@ mod test {
|
||||||
EncryptionMethod::Aes128,
|
EncryptionMethod::Aes128,
|
||||||
"https://www.example.com/hls-key/key.bin",
|
"https://www.example.com/hls-key/key.bin",
|
||||||
);
|
);
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"#EXT-X-SESSION-KEY:METHOD=AES-128,\
|
"#EXT-X-SESSION-KEY:METHOD=AES-128,\
|
||||||
|
@ -164,4 +157,34 @@ mod test {
|
||||||
ProtocolVersion::V1
|
ProtocolVersion::V1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
// ExtXSessionKey::new should panic, if the provided
|
||||||
|
// EncryptionMethod is None!
|
||||||
|
fn test_new_panic() { ExtXSessionKey::new(EncryptionMethod::None, ""); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_display_err() {
|
||||||
|
ExtXSessionKey(DecryptionKey::new(EncryptionMethod::None, "")).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
let key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
|
|
||||||
|
assert_eq!(key.method(), EncryptionMethod::Aes128);
|
||||||
|
assert_eq!(key.uri(), &Some("https://www.example.com/".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref_mut() {
|
||||||
|
let mut key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
|
|
||||||
|
key.set_method(EncryptionMethod::None);
|
||||||
|
assert_eq!(key.method(), EncryptionMethod::None);
|
||||||
|
key.set_uri(Some("https://www.github.com/"));
|
||||||
|
assert_eq!(key.uri(), &Some("https://www.github.com/".into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::Error;
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||||
///
|
///
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ExtXStreamInf {
|
pub struct ExtXStreamInf {
|
||||||
uri: String,
|
uri: String,
|
||||||
frame_rate: Option<DecimalFloatingPoint>,
|
frame_rate: Option<DecimalFloatingPoint>,
|
||||||
|
@ -25,16 +25,15 @@ pub struct ExtXStreamInf {
|
||||||
impl ExtXStreamInf {
|
impl ExtXStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||||
|
|
||||||
/// Creates a new [ExtXStreamInf] tag.
|
/// Creates a new [`ExtXStreamInf`] tag.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::tags::ExtXStreamInf;
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
/// #
|
|
||||||
/// let stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
/// let stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
|
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
|
||||||
ExtXStreamInf {
|
Self {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
frame_rate: None,
|
frame_rate: None,
|
||||||
audio: None,
|
audio: None,
|
||||||
|
@ -44,48 +43,155 @@ impl ExtXStreamInf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `URI` that identifies the associated media playlist.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
///
|
||||||
|
/// assert_eq!(stream.uri(), &"https://www.example.com/".to_string());
|
||||||
|
/// ```
|
||||||
|
pub const fn uri(&self) -> &String { &self.uri }
|
||||||
|
|
||||||
/// Sets the `URI` that identifies the associated media playlist.
|
/// Sets the `URI` that identifies the associated media playlist.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
///
|
||||||
|
/// stream.set_uri("https://www.google.com/");
|
||||||
|
/// assert_eq!(stream.uri(), &"https://www.google.com/".to_string());
|
||||||
|
/// ```
|
||||||
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
|
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
|
||||||
self.uri = value.to_string();
|
self.uri = value.to_string();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `URI` that identifies the associated media playlist.
|
|
||||||
pub const fn uri(&self) -> &String {
|
|
||||||
&self.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the maximum frame rate for all the video in the variant stream.
|
/// Sets the maximum frame rate for all the video in the variant stream.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.frame_rate(), None);
|
||||||
|
///
|
||||||
|
/// stream.set_frame_rate(Some(59.9));
|
||||||
|
/// assert_eq!(stream.frame_rate(), Some(59.9));
|
||||||
|
/// ```
|
||||||
pub fn set_frame_rate(&mut self, value: Option<f64>) -> &mut Self {
|
pub fn set_frame_rate(&mut self, value: Option<f64>) -> &mut Self {
|
||||||
self.frame_rate = value.map(|v| v.into());
|
self.frame_rate = value.map(|v| v.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the maximum frame rate for all the video in the variant stream.
|
/// Returns the maximum frame rate for all the video in the variant stream.
|
||||||
pub fn frame_rate(&self) -> Option<f64> {
|
///
|
||||||
self.frame_rate.map_or(None, |v| Some(v.as_f64()))
|
/// # Example
|
||||||
}
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.frame_rate(), None);
|
||||||
|
///
|
||||||
|
/// stream.set_frame_rate(Some(59.9));
|
||||||
|
/// assert_eq!(stream.frame_rate(), Some(59.9));
|
||||||
|
/// ```
|
||||||
|
pub fn frame_rate(&self) -> Option<f64> { self.frame_rate.map(|v| v.as_f64()) }
|
||||||
|
|
||||||
/// Returns the group identifier for the audio in the variant stream.
|
/// Returns the group identifier for the audio in the variant stream.
|
||||||
pub fn audio(&self) -> Option<&String> {
|
///
|
||||||
self.audio.as_ref()
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.audio(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_audio(Some("audio"));
|
||||||
|
/// assert_eq!(stream.audio(), &Some("audio".to_string()));
|
||||||
|
/// ```
|
||||||
|
pub const fn audio(&self) -> &Option<String> { &self.audio }
|
||||||
|
|
||||||
|
/// Sets the group identifier for the audio in the variant stream.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.audio(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_audio(Some("audio"));
|
||||||
|
/// assert_eq!(stream.audio(), &Some("audio".to_string()));
|
||||||
|
/// ```
|
||||||
|
pub fn set_audio<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
|
||||||
|
self.audio = value.map(|v| v.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group identifier for the subtitles in the variant stream.
|
/// Returns the group identifier for the subtitles in the variant stream.
|
||||||
pub fn subtitles(&self) -> Option<&String> {
|
///
|
||||||
self.subtitles.as_ref()
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.subtitles(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_subtitles(Some("subs"));
|
||||||
|
/// assert_eq!(stream.subtitles(), &Some("subs".to_string()));
|
||||||
|
/// ```
|
||||||
|
pub const fn subtitles(&self) -> &Option<String> { &self.subtitles }
|
||||||
|
|
||||||
|
/// Sets the group identifier for the subtitles in the variant stream.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.subtitles(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_subtitles(Some("subs"));
|
||||||
|
/// assert_eq!(stream.subtitles(), &Some("subs".to_string()));
|
||||||
|
/// ```
|
||||||
|
pub fn set_subtitles<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
|
||||||
|
self.subtitles = value.map(|v| v.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the value of `CLOSED-CAPTIONS` attribute.
|
/// Returns the value of [`ClosedCaptions`] attribute.
|
||||||
pub fn closed_captions(&self) -> Option<&ClosedCaptions> {
|
///
|
||||||
self.closed_captions.as_ref()
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// use hls_m3u8::types::ClosedCaptions;
|
||||||
|
///
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.closed_captions(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_closed_captions(Some(ClosedCaptions::None));
|
||||||
|
/// assert_eq!(stream.closed_captions(), &Some(ClosedCaptions::None));
|
||||||
|
/// ```
|
||||||
|
pub const fn closed_captions(&self) -> &Option<ClosedCaptions> { &self.closed_captions }
|
||||||
|
|
||||||
|
/// Returns the value of [`ClosedCaptions`] attribute.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStreamInf;
|
||||||
|
/// use hls_m3u8::types::ClosedCaptions;
|
||||||
|
///
|
||||||
|
/// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20);
|
||||||
|
/// # assert_eq!(stream.closed_captions(), &None);
|
||||||
|
///
|
||||||
|
/// stream.set_closed_captions(Some(ClosedCaptions::None));
|
||||||
|
/// assert_eq!(stream.closed_captions(), &Some(ClosedCaptions::None));
|
||||||
|
/// ```
|
||||||
|
pub fn set_closed_captions(&mut self, value: Option<ClosedCaptions>) -> &mut Self {
|
||||||
|
self.closed_captions = value;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXStreamInf {
|
impl RequiredVersion for ExtXStreamInf {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXStreamInf {
|
impl fmt::Display for ExtXStreamInf {
|
||||||
|
@ -113,8 +219,10 @@ impl FromStr for ExtXStreamInf {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let mut lines = input.lines();
|
let mut lines = input.lines();
|
||||||
let first_line = lines.next().ok_or(Error::missing_value("first_line"))?;
|
let first_line = lines
|
||||||
let uri = lines.next().ok_or(Error::missing_value("URI"))?;
|
.next()
|
||||||
|
.ok_or_else(|| Error::missing_value("first_line"))?;
|
||||||
|
let uri = lines.next().ok_or_else(|| Error::missing_value("URI"))?;
|
||||||
|
|
||||||
let input = tag(first_line, Self::PREFIX)?;
|
let input = tag(first_line, Self::PREFIX)?;
|
||||||
|
|
||||||
|
@ -128,7 +236,7 @@ impl FromStr for ExtXStreamInf {
|
||||||
"FRAME-RATE" => frame_rate = Some((value.parse())?),
|
"FRAME-RATE" => frame_rate = Some((value.parse())?),
|
||||||
"AUDIO" => audio = Some(unquote(value)),
|
"AUDIO" => audio = Some(unquote(value)),
|
||||||
"SUBTITLES" => subtitles = Some(unquote(value)),
|
"SUBTITLES" => subtitles = Some(unquote(value)),
|
||||||
"CLOSED-CAPTIONS" => closed_captions = Some((value.parse())?),
|
"CLOSED-CAPTIONS" => closed_captions = Some(value.parse()?),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,15 +255,11 @@ impl FromStr for ExtXStreamInf {
|
||||||
impl Deref for ExtXStreamInf {
|
impl Deref for ExtXStreamInf {
|
||||||
type Target = StreamInf;
|
type Target = StreamInf;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.stream_inf }
|
||||||
&self.stream_inf
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXStreamInf {
|
impl DerefMut for ExtXStreamInf {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stream_inf }
|
||||||
&mut self.stream_inf
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -174,6 +278,14 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXStreamInf::new("http://www.example.com/", 1000).to_string(),
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=1000\nhttp://www.example.com/".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_required_version() {
|
fn test_required_version() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -183,10 +295,20 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_display() {
|
fn test_deref() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ExtXStreamInf::new("http://www.example.com/", 1000).to_string(),
|
ExtXStreamInf::new("http://www.example.com", 1000).bandwidth(),
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=1000\nhttp://www.example.com/".to_string()
|
1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref_mut() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXStreamInf::new("http://www.example.com", 1000)
|
||||||
|
.set_bandwidth(1)
|
||||||
|
.bandwidth(),
|
||||||
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use crate::utils::tag;
|
||||||
|
|
||||||
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
|
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
|
||||||
///
|
///
|
||||||
/// The [ExtXDiscontinuitySequence] tag allows synchronization between
|
/// The [`ExtXDiscontinuitySequence`] tag allows synchronization between
|
||||||
/// different Renditions of the same Variant Stream or different Variant
|
/// different Renditions of the same Variant Stream or different Variant
|
||||||
/// Streams that have [ExtXDiscontinuity] tags in their [Media Playlist]s.
|
/// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
|
@ -16,8 +16,8 @@ use crate::utils::tag;
|
||||||
/// ```
|
/// ```
|
||||||
/// where `number` is a [u64].
|
/// where `number` is a [u64].
|
||||||
///
|
///
|
||||||
/// [ExtXDiscontinuity]: crate::tags::ExtXDiscontinuity
|
/// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity
|
||||||
/// [Media Playlist]: crate::MediaPlaylist
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:
|
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.3
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.3
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
|
@ -33,9 +33,7 @@ impl ExtXDiscontinuitySequence {
|
||||||
/// # use hls_m3u8::tags::ExtXDiscontinuitySequence;
|
/// # use hls_m3u8::tags::ExtXDiscontinuitySequence;
|
||||||
/// let discontinuity_sequence = ExtXDiscontinuitySequence::new(5);
|
/// let discontinuity_sequence = ExtXDiscontinuitySequence::new(5);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn new(seq_num: u64) -> Self {
|
pub const fn new(seq_num: u64) -> Self { Self(seq_num) }
|
||||||
Self(seq_num)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the discontinuity sequence number of
|
/// Returns the discontinuity sequence number of
|
||||||
/// the first media segment that appears in the associated playlist.
|
/// the first media segment that appears in the associated playlist.
|
||||||
|
@ -47,9 +45,7 @@ impl ExtXDiscontinuitySequence {
|
||||||
///
|
///
|
||||||
/// assert_eq!(discontinuity_sequence.seq_num(), 5);
|
/// assert_eq!(discontinuity_sequence.seq_num(), 5);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn seq_num(&self) -> u64 {
|
pub const fn seq_num(self) -> u64 { self.0 }
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the sequence number.
|
/// Sets the sequence number.
|
||||||
///
|
///
|
||||||
|
@ -68,15 +64,11 @@ impl ExtXDiscontinuitySequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXDiscontinuitySequence {
|
impl RequiredVersion for ExtXDiscontinuitySequence {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXDiscontinuitySequence {
|
impl fmt::Display for ExtXDiscontinuitySequence {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) }
|
||||||
write!(f, "{}{}", Self::PREFIX, self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXDiscontinuitySequence {
|
impl FromStr for ExtXDiscontinuitySequence {
|
||||||
|
@ -115,4 +107,12 @@ mod test {
|
||||||
"#EXT-X-DISCONTINUITY-SEQUENCE:123".parse().unwrap()
|
"#EXT-X-DISCONTINUITY-SEQUENCE:123".parse().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_num() {
|
||||||
|
let mut sequence = ExtXDiscontinuitySequence::new(123);
|
||||||
|
assert_eq!(sequence.seq_num(), 123);
|
||||||
|
sequence.set_seq_num(1);
|
||||||
|
assert_eq!(sequence.seq_num(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,16 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.3.4. EXT-X-ENDLIST]
|
/// # [4.4.3.4. EXT-X-ENDLIST]
|
||||||
/// The [ExtXEndList] tag indicates, that no more [Media Segment]s will be
|
/// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be
|
||||||
/// added to the [Media Playlist] file.
|
/// added to the [`Media Playlist`] file.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// #EXT-X-ENDLIST
|
/// #EXT-X-ENDLIST
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [Media Playlist]: crate::MediaPlaylist
|
/// [`Media Playlist`]: crate::MediaPlaylist
|
||||||
/// [4.4.3.4. EXT-X-ENDLIST]:
|
/// [4.4.3.4. EXT-X-ENDLIST]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -26,15 +26,11 @@ impl ExtXEndList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXEndList {
|
impl RequiredVersion for ExtXEndList {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXEndList {
|
impl fmt::Display for ExtXEndList {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Self::PREFIX.fmt(f) }
|
||||||
Self::PREFIX.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXEndList {
|
impl FromStr for ExtXEndList {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY]
|
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY]
|
||||||
/// The [ExtXIFramesOnly] tag indicates that each [Media Segment] in the
|
/// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the
|
||||||
/// Playlist describes a single I-frame. I-frames are encoded video
|
/// Playlist describes a single I-frame. I-frames are encoded video
|
||||||
/// frames, whose decoding does not depend on any other frame. I-frame
|
/// frames, whose decoding does not depend on any other frame. I-frame
|
||||||
/// Playlists can be used for trick play, such as fast forward, rapid
|
/// Playlists can be used for trick play, such as fast forward, rapid
|
||||||
|
@ -17,7 +17,7 @@ use crate::Error;
|
||||||
/// #EXT-X-I-FRAMES-ONLY
|
/// #EXT-X-I-FRAMES-ONLY
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
|
/// [4.4.3.6. EXT-X-I-FRAMES-ONLY]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -28,15 +28,11 @@ impl ExtXIFramesOnly {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXIFramesOnly {
|
impl RequiredVersion for ExtXIFramesOnly {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 }
|
||||||
ProtocolVersion::V4
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXIFramesOnly {
|
impl fmt::Display for ExtXIFramesOnly {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Self::PREFIX.fmt(f) }
|
||||||
Self::PREFIX.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXIFramesOnly {
|
impl FromStr for ExtXIFramesOnly {
|
||||||
|
@ -61,9 +57,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() { assert_eq!(ExtXIFramesOnly, "#EXT-X-I-FRAMES-ONLY".parse().unwrap(),) }
|
||||||
assert_eq!(ExtXIFramesOnly, "#EXT-X-I-FRAMES-ONLY".parse().unwrap(),)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_required_version() {
|
fn test_required_version() {
|
||||||
|
|
|
@ -6,14 +6,14 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE]
|
/// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE]
|
||||||
/// The [ExtXMediaSequence] tag indicates the Media Sequence Number of
|
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
|
||||||
/// the first [Media Segment] that appears in a Playlist file.
|
/// the first [`Media Segment`] that appears in a Playlist file.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// #EXT-X-MEDIA-SEQUENCE:<number>
|
/// #EXT-X-MEDIA-SEQUENCE:<number>
|
||||||
/// ```
|
/// ```
|
||||||
/// where `number` is a [u64].
|
/// where `number` is a [`u64`].
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [Media Segment]: crate::MediaSegment
|
||||||
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
|
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
|
||||||
|
@ -24,16 +24,14 @@ pub struct ExtXMediaSequence(u64);
|
||||||
impl ExtXMediaSequence {
|
impl ExtXMediaSequence {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
|
||||||
|
|
||||||
/// Makes a new [ExtXMediaSequence] tag.
|
/// Makes a new [`ExtXMediaSequence`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::tags::ExtXMediaSequence;
|
/// # use hls_m3u8::tags::ExtXMediaSequence;
|
||||||
/// let media_sequence = ExtXMediaSequence::new(5);
|
/// let media_sequence = ExtXMediaSequence::new(5);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn new(seq_num: u64) -> Self {
|
pub const fn new(seq_num: u64) -> Self { Self(seq_num) }
|
||||||
Self(seq_num)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the sequence number of the first media segment,
|
/// Returns the sequence number of the first media segment,
|
||||||
/// that appears in the associated playlist.
|
/// that appears in the associated playlist.
|
||||||
|
@ -45,9 +43,7 @@ impl ExtXMediaSequence {
|
||||||
///
|
///
|
||||||
/// assert_eq!(media_sequence.seq_num(), 5);
|
/// assert_eq!(media_sequence.seq_num(), 5);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn seq_num(&self) -> u64 {
|
pub const fn seq_num(self) -> u64 { self.0 }
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the sequence number.
|
/// Sets the sequence number.
|
||||||
///
|
///
|
||||||
|
@ -66,15 +62,11 @@ impl ExtXMediaSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXMediaSequence {
|
impl RequiredVersion for ExtXMediaSequence {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXMediaSequence {
|
impl fmt::Display for ExtXMediaSequence {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) }
|
||||||
write!(f, "{}{}", Self::PREFIX, self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXMediaSequence {
|
impl FromStr for ExtXMediaSequence {
|
||||||
|
@ -113,4 +105,12 @@ mod test {
|
||||||
"#EXT-X-MEDIA-SEQUENCE:123".parse().unwrap()
|
"#EXT-X-MEDIA-SEQUENCE:123".parse().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_num() {
|
||||||
|
let mut sequence = ExtXMediaSequence::new(123);
|
||||||
|
assert_eq!(sequence.seq_num(), 123);
|
||||||
|
sequence.set_seq_num(1);
|
||||||
|
assert_eq!(sequence.seq_num(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
|
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
|
||||||
///
|
///
|
||||||
/// The [ExtXPlaylistType] tag provides mutability information about the
|
/// The [`ExtXPlaylistType`] tag provides mutability information about the
|
||||||
/// [Media Playlist]. It applies to the entire [Media Playlist].
|
/// [`Media Playlist`]. It applies to the entire [`Media Playlist`].
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
|
@ -20,10 +20,10 @@ use crate::Error;
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum ExtXPlaylistType {
|
pub enum ExtXPlaylistType {
|
||||||
/// If the [ExtXPlaylistType] is Event, Media Segments can only be added to
|
/// If the [`ExtXPlaylistType`] is Event, Media Segments
|
||||||
/// the end of the Media Playlist.
|
/// can only be added to the end of the Media Playlist.
|
||||||
Event,
|
Event,
|
||||||
/// If the [ExtXPlaylistType] is Video On Demand (Vod),
|
/// If the [`ExtXPlaylistType`] is Video On Demand (Vod),
|
||||||
/// the Media Playlist cannot change.
|
/// the Media Playlist cannot change.
|
||||||
Vod,
|
Vod,
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,7 @@ impl ExtXPlaylistType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXPlaylistType {
|
impl RequiredVersion for ExtXPlaylistType {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXPlaylistType {
|
impl fmt::Display for ExtXPlaylistType {
|
||||||
|
|
|
@ -7,43 +7,52 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.3.1. EXT-X-TARGETDURATION]
|
/// # [4.4.3.1. EXT-X-TARGETDURATION]
|
||||||
/// The [ExtXTargetDuration] tag specifies the maximum [Media Segment]
|
/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]
|
||||||
/// duration.
|
/// duration.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// ```text
|
|
||||||
/// #EXT-X-TARGETDURATION:<s>
|
|
||||||
/// ```
|
|
||||||
/// where `s` is the target [Duration] in seconds.
|
|
||||||
///
|
|
||||||
/// [Media Segment]: crate::MediaSegment
|
|
||||||
/// [4.4.3.1. EXT-X-TARGETDURATION]:
|
/// [4.4.3.1. EXT-X-TARGETDURATION]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.1
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||||
pub struct ExtXTargetDuration(Duration);
|
pub struct ExtXTargetDuration(Duration);
|
||||||
|
|
||||||
impl ExtXTargetDuration {
|
impl ExtXTargetDuration {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
|
||||||
|
|
||||||
/// Makes a new [ExtXTargetduration] tag.
|
/// Makes a new [`ExtXTargetDuration`] tag.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXTargetDuration;
|
||||||
|
/// use std::time::Duration;
|
||||||
|
///
|
||||||
|
/// let target_duration = ExtXTargetDuration::new(Duration::from_secs(20));
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// The nanoseconds part of the [Duration] will be discarded.
|
/// The nanoseconds part of the [`Duration`] will be discarded.
|
||||||
pub const fn new(duration: Duration) -> Self {
|
pub const fn new(duration: Duration) -> Self {
|
||||||
// TOOD: round instead of discarding?
|
// TOOD: round instead of discarding?
|
||||||
Self(Duration::from_secs(duration.as_secs()))
|
Self(Duration::from_secs(duration.as_secs()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the maximum media segment duration in the associated playlist.
|
/// Returns the maximum media segment duration.
|
||||||
pub const fn duration(&self) -> Duration {
|
///
|
||||||
self.0
|
/// # Example
|
||||||
}
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXTargetDuration;
|
||||||
|
/// use std::time::Duration;
|
||||||
|
///
|
||||||
|
/// let target_duration = ExtXTargetDuration::new(Duration::from_nanos(2_000_000_000));
|
||||||
|
///
|
||||||
|
/// assert_eq!(target_duration.duration(), Duration::from_secs(2));
|
||||||
|
/// ```
|
||||||
|
pub const fn duration(&self) -> Duration { self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
impl RequiredVersion for ExtXTargetDuration {
|
impl RequiredVersion for ExtXTargetDuration {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXTargetDuration {
|
impl fmt::Display for ExtXTargetDuration {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.2.2. EXT-X-BYTERANGE]
|
/// # [4.4.2.2. EXT-X-BYTERANGE]
|
||||||
///
|
///
|
||||||
/// The [ExtXByteRange] tag indicates that a [Media Segment] is a sub-range
|
/// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range
|
||||||
/// of the resource identified by its `URI`.
|
/// of the resource identified by its `URI`.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
|
@ -20,7 +20,7 @@ use crate::Error;
|
||||||
/// If present, `o` is a [usize] indicating the start of the sub-range,
|
/// If present, `o` is a [usize] indicating the start of the sub-range,
|
||||||
/// as a byte offset from the beginning of the resource.
|
/// as a byte offset from the beginning of the resource.
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.2.2. EXT-X-BYTERANGE]:
|
/// [4.4.2.2. EXT-X-BYTERANGE]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -29,7 +29,7 @@ pub struct ExtXByteRange(ByteRange);
|
||||||
impl ExtXByteRange {
|
impl ExtXByteRange {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
|
||||||
|
|
||||||
/// Makes a new [ExtXByteRange] tag.
|
/// Makes a new [`ExtXByteRange`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -40,7 +40,7 @@ impl ExtXByteRange {
|
||||||
Self(ByteRange::new(length, start))
|
Self(ByteRange::new(length, start))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the [ExtXByteRange] to a [ByteRange].
|
/// Converts the [`ExtXByteRange`] to a [`ByteRange`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -50,29 +50,21 @@ impl ExtXByteRange {
|
||||||
/// let byte_range = ExtXByteRange::new(20, Some(5));
|
/// let byte_range = ExtXByteRange::new(20, Some(5));
|
||||||
/// let range: ByteRange = byte_range.to_range();
|
/// let range: ByteRange = byte_range.to_range();
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn to_range(&self) -> ByteRange {
|
pub const fn to_range(&self) -> ByteRange { self.0 }
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXByteRange {
|
impl RequiredVersion for ExtXByteRange {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 }
|
||||||
ProtocolVersion::V4
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ExtXByteRange {
|
impl Deref for ExtXByteRange {
|
||||||
type Target = ByteRange;
|
type Target = ByteRange;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXByteRange {
|
impl DerefMut for ExtXByteRange {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXByteRange {
|
impl fmt::Display for ExtXByteRange {
|
||||||
|
@ -98,11 +90,11 @@ impl FromStr for ExtXByteRange {
|
||||||
let length = tokens[0].parse()?;
|
let length = tokens[0].parse()?;
|
||||||
|
|
||||||
let start = {
|
let start = {
|
||||||
let mut result = None;
|
|
||||||
if tokens.len() == 2 {
|
if tokens.len() == 2 {
|
||||||
result = Some(tokens[1].parse()?);
|
Some(tokens[1].parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
result
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ExtXByteRange::new(length, start))
|
Ok(ExtXByteRange::new(length, start))
|
||||||
|
|
|
@ -6,29 +6,30 @@ use std::time::Duration;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{DecimalFloatingPoint, ProtocolVersion, RequiredVersion};
|
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||||
use crate::utils::{quote, tag, unquote};
|
use crate::utils::{quote, tag, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.7. EXT-X-DATERANGE]
|
/// # [4.3.2.7. EXT-X-DATERANGE]
|
||||||
///
|
///
|
||||||
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
||||||
///
|
///
|
||||||
/// TODO: Implement properly
|
/// TODO: Implement properly
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXDateRange {
|
pub struct ExtXDateRange {
|
||||||
/// A string that uniquely identifies a [ExtXDateRange] in the Playlist.
|
/// A string that uniquely identifies a [`ExtXDateRange`] in the Playlist.
|
||||||
/// This attribute is required.
|
/// This attribute is required.
|
||||||
id: String,
|
id: String,
|
||||||
/// A client-defined string that specifies some set of attributes and their associated value
|
/// A client-defined string that specifies some set of attributes and their
|
||||||
/// semantics. All Date Ranges with the same CLASS attribute value MUST adhere to these
|
/// associated value semantics. All Date Ranges with the same CLASS
|
||||||
/// semantics. This attribute is OPTIONAL.
|
/// attribute value MUST adhere to these semantics. This attribute is
|
||||||
|
/// OPTIONAL.
|
||||||
class: Option<String>,
|
class: Option<String>,
|
||||||
/// The date at which the Date Range begins. This attribute is REQUIRED.
|
/// The date at which the Date Range begins. This attribute is REQUIRED.
|
||||||
start_date: DateTime<FixedOffset>,
|
start_date: DateTime<FixedOffset>,
|
||||||
/// The date at which the Date Range ends. It MUST be equal to or later than the value of the
|
/// The date at which the Date Range ends. It MUST be equal to or later than
|
||||||
/// START-DATE attribute. This attribute is OPTIONAL.
|
/// the value of the START-DATE attribute. This attribute is OPTIONAL.
|
||||||
end_date: Option<DateTime<FixedOffset>>,
|
end_date: Option<DateTime<FixedOffset>>,
|
||||||
/// The duration of the Date Range. It MUST NOT be negative. A single
|
/// The duration of the Date Range. It MUST NOT be negative. A single
|
||||||
/// instant in time (e.g., crossing a finish line) SHOULD be
|
/// instant in time (e.g., crossing a finish line) SHOULD be
|
||||||
|
@ -45,11 +46,11 @@ pub struct ExtXDateRange {
|
||||||
scte35_out: Option<String>,
|
scte35_out: Option<String>,
|
||||||
///
|
///
|
||||||
scte35_in: Option<String>,
|
scte35_in: Option<String>,
|
||||||
/// This attribute indicates that the end of the range containing it is equal to the
|
/// This attribute indicates that the end of the range containing it is
|
||||||
/// START-DATE of its Following Range. The Following Range is the
|
/// equal to the START-DATE of its Following Range. The Following Range
|
||||||
/// Date Range of the same CLASS, that has the earliest START-DATE
|
/// is the Date Range of the same CLASS, that has the earliest
|
||||||
/// after the START-DATE of the range in question. This attribute is
|
/// START-DATE after the START-DATE of the range in question. This
|
||||||
/// OPTIONAL.
|
/// attribute is OPTIONAL.
|
||||||
end_on_next: bool,
|
end_on_next: bool,
|
||||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
/// The "X-" prefix defines a namespace reserved for client-defined
|
||||||
/// attributes. The client-attribute MUST be a legal AttributeName.
|
/// attributes. The client-attribute MUST be a legal AttributeName.
|
||||||
|
@ -63,12 +64,62 @@ pub struct ExtXDateRange {
|
||||||
|
|
||||||
impl ExtXDateRange {
|
impl ExtXDateRange {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
|
||||||
|
|
||||||
|
/// Makes a new [`ExtXDateRange`] tag.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||||
|
/// use chrono::offset::TimeZone;
|
||||||
|
/// use chrono::{DateTime, FixedOffset};
|
||||||
|
///
|
||||||
|
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||||
|
///
|
||||||
|
/// let date_range = ExtXDateRange::new(
|
||||||
|
/// "id",
|
||||||
|
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||||
|
/// .ymd(2010, 2, 19)
|
||||||
|
/// .and_hms_milli(14, 54, 23, 31),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn new<T: ToString>(id: T, start_date: DateTime<FixedOffset>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.to_string(),
|
||||||
|
class: None,
|
||||||
|
start_date,
|
||||||
|
end_date: None,
|
||||||
|
duration: None,
|
||||||
|
planned_duration: None,
|
||||||
|
scte35_cmd: None,
|
||||||
|
scte35_out: None,
|
||||||
|
scte35_in: None,
|
||||||
|
end_on_next: false,
|
||||||
|
client_attributes: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This tag requires [`ProtocolVersion::V1`].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||||
|
/// use chrono::offset::TimeZone;
|
||||||
|
/// use chrono::{DateTime, FixedOffset};
|
||||||
|
/// use hls_m3u8::types::{ProtocolVersion, RequiredVersion};
|
||||||
|
///
|
||||||
|
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||||
|
///
|
||||||
|
/// let date_range = ExtXDateRange::new(
|
||||||
|
/// "id",
|
||||||
|
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||||
|
/// .ymd(2010, 2, 19)
|
||||||
|
/// .and_hms_milli(14, 54, 23, 31),
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(date_range.required_version(), ProtocolVersion::V1);
|
||||||
|
/// ```
|
||||||
impl RequiredVersion for ExtXDateRange {
|
impl RequiredVersion for ExtXDateRange {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXDateRange {
|
impl fmt::Display for ExtXDateRange {
|
||||||
|
@ -82,15 +133,11 @@ impl fmt::Display for ExtXDateRange {
|
||||||
if let Some(value) = &self.end_date {
|
if let Some(value) = &self.end_date {
|
||||||
write!(f, ",END-DATE={}", quote(value))?;
|
write!(f, ",END-DATE={}", quote(value))?;
|
||||||
}
|
}
|
||||||
if let Some(x) = self.duration {
|
if let Some(value) = &self.duration {
|
||||||
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
|
write!(f, ",DURATION={}", value.as_secs_f64())?;
|
||||||
}
|
}
|
||||||
if let Some(x) = self.planned_duration {
|
if let Some(value) = &self.planned_duration {
|
||||||
write!(
|
write!(f, ",PLANNED-DURATION={}", value.as_secs_f64())?;
|
||||||
f,
|
|
||||||
",PLANNED-DURATION={}",
|
|
||||||
DecimalFloatingPoint::from_duration(x)
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
if let Some(value) = &self.scte35_cmd {
|
if let Some(value) = &self.scte35_cmd {
|
||||||
write!(f, ",SCTE35-CMD={}", quote(value))?;
|
write!(f, ",SCTE35-CMD={}", quote(value))?;
|
||||||
|
@ -137,12 +184,10 @@ impl FromStr for ExtXDateRange {
|
||||||
"START-DATE" => start_date = Some(unquote(value)),
|
"START-DATE" => start_date = Some(unquote(value)),
|
||||||
"END-DATE" => end_date = Some(unquote(value).parse()?),
|
"END-DATE" => end_date = Some(unquote(value).parse()?),
|
||||||
"DURATION" => {
|
"DURATION" => {
|
||||||
let seconds: DecimalFloatingPoint = (value.parse())?;
|
duration = Some(Duration::from_secs_f64(value.parse()?));
|
||||||
duration = Some(seconds.to_duration());
|
|
||||||
}
|
}
|
||||||
"PLANNED-DURATION" => {
|
"PLANNED-DURATION" => {
|
||||||
let seconds: DecimalFloatingPoint = (value.parse())?;
|
planned_duration = Some(Duration::from_secs_f64(value.parse()?));
|
||||||
planned_duration = Some(seconds.to_duration());
|
|
||||||
}
|
}
|
||||||
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
|
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
|
||||||
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
|
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
|
||||||
|
@ -158,23 +203,22 @@ impl FromStr for ExtXDateRange {
|
||||||
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
||||||
} else {
|
} else {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an
|
||||||
|
// unrecognized AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = id.ok_or(Error::missing_value("EXT-X-ID"))?;
|
let id = id.ok_or_else(|| Error::missing_value("ID"))?;
|
||||||
let start_date = start_date
|
let start_date = start_date
|
||||||
.ok_or(Error::missing_value("EXT-X-START-DATE"))?
|
.ok_or_else(|| Error::missing_value("START-DATE"))?
|
||||||
.parse()?;
|
.parse()?;
|
||||||
|
|
||||||
if end_on_next {
|
if end_on_next && class.is_none() {
|
||||||
if class.is_none() {
|
return Err(Error::invalid_input());
|
||||||
return Err(Error::invalid_input());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(ExtXDateRange {
|
Ok(Self {
|
||||||
id,
|
id,
|
||||||
class,
|
class,
|
||||||
start_date,
|
start_date,
|
||||||
|
@ -193,7 +237,21 @@ impl FromStr for ExtXDateRange {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use chrono::offset::TimeZone;
|
||||||
|
|
||||||
#[test] // TODO; write some tests
|
const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||||
fn it_works() {}
|
|
||||||
|
#[test]
|
||||||
|
fn test_required_version() {
|
||||||
|
assert_eq!(
|
||||||
|
ExtXDateRange::new(
|
||||||
|
"id",
|
||||||
|
FixedOffset::east(8 * HOURS_IN_SECS)
|
||||||
|
.ymd(2010, 2, 19)
|
||||||
|
.and_hms_milli(14, 54, 23, 31)
|
||||||
|
)
|
||||||
|
.required_version(),
|
||||||
|
ProtocolVersion::V1
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,15 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.2.3. EXT-X-DISCONTINUITY]
|
/// # [4.4.2.3. EXT-X-DISCONTINUITY]
|
||||||
/// The [ExtXDiscontinuity] tag indicates a discontinuity between the
|
/// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the
|
||||||
/// [Media Segment] that follows it and the one that preceded it.
|
/// [`Media Segment`] that follows it and the one that preceded it.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// #EXT-X-DISCONTINUITY
|
/// #EXT-X-DISCONTINUITY
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.2.3. EXT-X-DISCONTINUITY]:
|
/// [4.4.2.3. EXT-X-DISCONTINUITY]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -25,15 +25,11 @@ impl ExtXDiscontinuity {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXDiscontinuity {
|
impl RequiredVersion for ExtXDiscontinuity {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXDiscontinuity {
|
impl fmt::Display for ExtXDiscontinuity {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Self::PREFIX.fmt(f) }
|
||||||
Self::PREFIX.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXDiscontinuity {
|
impl FromStr for ExtXDiscontinuity {
|
||||||
|
@ -58,9 +54,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() { assert_eq!(ExtXDiscontinuity, "#EXT-X-DISCONTINUITY".parse().unwrap()) }
|
||||||
assert_eq!(ExtXDiscontinuity, "#EXT-X-DISCONTINUITY".parse().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_required_version() {
|
fn test_required_version() {
|
||||||
|
|
|
@ -2,14 +2,14 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::types::{DecimalFloatingPoint, ProtocolVersion, RequiredVersion};
|
use crate::types::{ProtocolVersion, RequiredVersion};
|
||||||
use crate::utils::tag;
|
use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.2.1. EXTINF]
|
/// # [4.4.2.1. EXTINF]
|
||||||
///
|
///
|
||||||
/// The [ExtInf] tag specifies the duration of a [Media Segment]. It applies
|
/// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies
|
||||||
/// only to the next [Media Segment].
|
/// only to the next [`Media Segment`].
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
|
@ -17,7 +17,7 @@ use crate::Error;
|
||||||
/// ```
|
/// ```
|
||||||
/// The title is an optional informative title about the [Media Segment].
|
/// The title is an optional informative title about the [Media Segment].
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::media_segment::MediaSegment
|
/// [`Media Segment`]: crate::media_segment::MediaSegment
|
||||||
/// [4.4.2.1. EXTINF]:
|
/// [4.4.2.1. EXTINF]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
@ -29,7 +29,7 @@ pub struct ExtInf {
|
||||||
impl ExtInf {
|
impl ExtInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXTINF:";
|
pub(crate) const PREFIX: &'static str = "#EXTINF:";
|
||||||
|
|
||||||
/// Makes a new [ExtInf] tag.
|
/// Makes a new [`ExtInf`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -39,13 +39,13 @@ impl ExtInf {
|
||||||
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn new(duration: Duration) -> Self {
|
pub const fn new(duration: Duration) -> Self {
|
||||||
ExtInf {
|
Self {
|
||||||
duration,
|
duration,
|
||||||
title: None,
|
title: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new [ExtInf] tag with the given title.
|
/// Makes a new [`ExtInf`] tag with the given title.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -55,7 +55,7 @@ impl ExtInf {
|
||||||
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_title<T: ToString>(duration: Duration, title: T) -> Self {
|
pub fn with_title<T: ToString>(duration: Duration, title: T) -> Self {
|
||||||
ExtInf {
|
Self {
|
||||||
duration,
|
duration,
|
||||||
title: Some(title.to_string()),
|
title: Some(title.to_string()),
|
||||||
}
|
}
|
||||||
|
@ -70,14 +70,9 @@ impl ExtInf {
|
||||||
///
|
///
|
||||||
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
/// let ext_inf = ExtInf::new(Duration::from_secs(5));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(ext_inf.duration(), Duration::from_secs(5));
|
||||||
/// ext_inf.duration(),
|
|
||||||
/// Duration::from_secs(5)
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn duration(&self) -> Duration {
|
pub const fn duration(&self) -> Duration { self.duration }
|
||||||
self.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the duration of the associated media segment.
|
/// Sets the duration of the associated media segment.
|
||||||
///
|
///
|
||||||
|
@ -90,10 +85,7 @@ impl ExtInf {
|
||||||
///
|
///
|
||||||
/// ext_inf.set_duration(Duration::from_secs(10));
|
/// ext_inf.set_duration(Duration::from_secs(10));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(ext_inf.duration(), Duration::from_secs(10));
|
||||||
/// ext_inf.duration(),
|
|
||||||
/// Duration::from_secs(10)
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_duration(&mut self, value: Duration) -> &mut Self {
|
pub fn set_duration(&mut self, value: Duration) -> &mut Self {
|
||||||
self.duration = value;
|
self.duration = value;
|
||||||
|
@ -109,14 +101,9 @@ impl ExtInf {
|
||||||
///
|
///
|
||||||
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
/// let ext_inf = ExtInf::with_title(Duration::from_secs(5), "title");
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(ext_inf.title(), &Some("title".to_string()));
|
||||||
/// ext_inf.title(),
|
|
||||||
/// &Some("title".to_string())
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn title(&self) -> &Option<String> {
|
pub const fn title(&self) -> &Option<String> { &self.title }
|
||||||
&self.title
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the title of the associated media segment.
|
/// Sets the title of the associated media segment.
|
||||||
///
|
///
|
||||||
|
@ -129,10 +116,7 @@ impl ExtInf {
|
||||||
///
|
///
|
||||||
/// ext_inf.set_title(Some("better title"));
|
/// ext_inf.set_title(Some("better title"));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(ext_inf.title(), &Some("better title".to_string()));
|
||||||
/// ext_inf.title(),
|
|
||||||
/// &Some("better title".to_string())
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_title<T: ToString>(&mut self, value: Option<T>) -> &mut Self {
|
pub fn set_title<T: ToString>(&mut self, value: Option<T>) -> &mut Self {
|
||||||
self.title = value.map(|v| v.to_string());
|
self.title = value.map(|v| v.to_string());
|
||||||
|
@ -170,17 +154,16 @@ impl FromStr for ExtInf {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let input = tag(input, Self::PREFIX)?;
|
let input = tag(input, Self::PREFIX)?;
|
||||||
dbg!(&input);
|
|
||||||
let tokens = input.splitn(2, ',').collect::<Vec<_>>();
|
let tokens = input.splitn(2, ',').collect::<Vec<_>>();
|
||||||
|
|
||||||
if tokens.len() == 0 {
|
if tokens.is_empty() {
|
||||||
return Err(Error::custom(format!(
|
return Err(Error::custom(format!(
|
||||||
"failed to parse #EXTINF tag, couldn't split input: {:?}",
|
"failed to parse #EXTINF tag, couldn't split input: {:?}",
|
||||||
input
|
input
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = tokens[0].parse::<DecimalFloatingPoint>()?.to_duration();
|
let duration = Duration::from_secs_f64(tokens[0].parse()?);
|
||||||
|
|
||||||
let title = {
|
let title = {
|
||||||
if tokens.len() >= 2 {
|
if tokens.len() >= 2 {
|
||||||
|
@ -194,14 +177,12 @@ impl FromStr for ExtInf {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ExtInf { duration, title })
|
Ok(Self { duration, title })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Duration> for ExtInf {
|
impl From<Duration> for ExtInf {
|
||||||
fn from(value: Duration) -> Self {
|
fn from(value: Duration) -> Self { Self::new(value) }
|
||||||
Self::new(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,16 +2,16 @@ use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::types::{DecryptionKey, EncryptionMethod, KeyFormatVersions};
|
use crate::types::{DecryptionKey, EncryptionMethod};
|
||||||
use crate::utils::tag;
|
use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.2.4. EXT-X-KEY]
|
/// # [4.4.2.4. EXT-X-KEY]
|
||||||
/// [Media Segment]s may be encrypted. The [ExtXKey] tag specifies how to
|
/// [`Media Segment`]s may be encrypted. The [`ExtXKey`] tag specifies how to
|
||||||
/// decrypt them. It applies to every [Media Segment] and to every Media
|
/// decrypt them. It applies to every [`Media Segment`] and to every Media
|
||||||
/// Initialization Section declared by an [ExtXMap] tag, that appears
|
/// Initialization Section declared by an [`ExtXMap`] tag, that appears
|
||||||
/// between it and the next [ExtXKey] tag in the Playlist file with the
|
/// between it and the next [`ExtXKey`] tag in the Playlist file with the
|
||||||
/// same [KeyFormat] attribute (or the end of the Playlist file).
|
/// same [`KeyFormat`] attribute (or the end of the Playlist file).
|
||||||
///
|
///
|
||||||
/// The format is:
|
/// The format is:
|
||||||
/// ```text
|
/// ```text
|
||||||
|
@ -19,11 +19,12 @@ use crate::Error;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// In case of an empty key (`EncryptionMethod::None`), all attributes will be ignored.
|
/// In case of an empty key (`EncryptionMethod::None`),
|
||||||
|
/// all attributes will be ignored.
|
||||||
///
|
///
|
||||||
/// [KeyFormat]: crate::types::KeyFormat
|
/// [`KeyFormat`]: crate::types::KeyFormat
|
||||||
/// [ExtXMap]: crate::tags::ExtXMap
|
/// [`ExtXMap`]: crate::tags::ExtXMap
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.4.2.4. EXT-X-KEY]:
|
/// [4.4.2.4. EXT-X-KEY]:
|
||||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -32,16 +33,14 @@ pub struct ExtXKey(DecryptionKey);
|
||||||
impl ExtXKey {
|
impl ExtXKey {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
|
||||||
|
|
||||||
/// Makes a new `ExtXKey` tag.
|
/// Makes a new [`ExtXKey`] tag.
|
||||||
/// # Examples
|
///
|
||||||
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use hls_m3u8::tags::ExtXKey;
|
/// use hls_m3u8::tags::ExtXKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let key = ExtXKey::new(
|
/// let key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// key.to_string(),
|
/// key.to_string(),
|
||||||
|
@ -52,44 +51,39 @@ impl ExtXKey {
|
||||||
Self(DecryptionKey::new(method, uri))
|
Self(DecryptionKey::new(method, uri))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXKey` tag without a decryption key.
|
/// Makes a new [`ExtXKey`] tag without a decryption key.
|
||||||
/// # Examples
|
///
|
||||||
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use hls_m3u8::tags::ExtXKey;
|
/// use hls_m3u8::tags::ExtXKey;
|
||||||
///
|
///
|
||||||
/// let key = ExtXKey::empty();
|
/// let key = ExtXKey::empty();
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE");
|
||||||
/// key.to_string(),
|
|
||||||
/// "#EXT-X-KEY:METHOD=NONE"
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn empty() -> Self {
|
pub const fn empty() -> Self {
|
||||||
Self(DecryptionKey {
|
Self(DecryptionKey {
|
||||||
method: EncryptionMethod::None,
|
method: EncryptionMethod::None,
|
||||||
uri: None,
|
uri: None,
|
||||||
iv: None,
|
iv: None,
|
||||||
key_format: None,
|
key_format: None,
|
||||||
key_format_versions: KeyFormatVersions::new(),
|
key_format_versions: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the [EncryptionMethod] is [None](EncryptionMethod::None).
|
/// Returns whether the [`EncryptionMethod`] is
|
||||||
/// # Examples
|
/// [`None`](EncryptionMethod::None).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use hls_m3u8::tags::ExtXKey;
|
/// use hls_m3u8::tags::ExtXKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let key = ExtXKey::empty();
|
/// let key = ExtXKey::empty();
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.method() == EncryptionMethod::None, key.is_empty());
|
||||||
/// key.method() == EncryptionMethod::None,
|
|
||||||
/// key.is_empty()
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool { self.0.method() == EncryptionMethod::None }
|
||||||
self.0.method() == EncryptionMethod::None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXKey {
|
impl FromStr for ExtXKey {
|
||||||
|
@ -102,23 +96,17 @@ impl FromStr for ExtXKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXKey {
|
impl fmt::Display for ExtXKey {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) }
|
||||||
write!(f, "{}{}", Self::PREFIX, self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ExtXKey {
|
impl Deref for ExtXKey {
|
||||||
type Target = DecryptionKey;
|
type Target = DecryptionKey;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXKey {
|
impl DerefMut for ExtXKey {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -134,13 +122,13 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut key = ExtXKey::empty();
|
let mut key = ExtXKey::empty();
|
||||||
// it is expected, that all attributes will be ignored in an empty key!
|
// it is expected, that all attributes will be ignored for an empty key!
|
||||||
key.set_key_format(Some(KeyFormat::Identity));
|
key.set_key_format(Some(KeyFormat::Identity));
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
key.set_uri(Some("https://www.example.com"));
|
key.set_uri(Some("https://www.example.com"));
|
||||||
key.set_key_format_versions(vec![1, 2, 3]);
|
key.set_key_format_versions(Some(vec![1, 2, 3]));
|
||||||
|
|
||||||
assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE".to_string());
|
assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE".to_string());
|
||||||
}
|
}
|
||||||
|
@ -163,8 +151,8 @@ mod test {
|
||||||
EncryptionMethod::Aes128,
|
EncryptionMethod::Aes128,
|
||||||
"https://www.example.com/hls-key/key.bin",
|
"https://www.example.com/hls-key/key.bin",
|
||||||
);
|
);
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::utils::{quote, tag, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.4.2.5. EXT-X-MAP]
|
/// # [4.4.2.5. EXT-X-MAP]
|
||||||
/// The [ExtXMap] tag specifies how to obtain the Media Initialization
|
/// The [`ExtXMap`] tag specifies how to obtain the Media Initialization
|
||||||
/// Section, required to parse the applicable [Media Segment]s.
|
/// Section, required to parse the applicable [Media Segment]s.
|
||||||
///
|
///
|
||||||
/// Its format is:
|
/// Its format is:
|
||||||
|
@ -27,7 +27,7 @@ pub struct ExtXMap {
|
||||||
impl ExtXMap {
|
impl ExtXMap {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
|
||||||
|
|
||||||
/// Makes a new `ExtXMap` tag.
|
/// Makes a new [`ExtXMap`] tag.
|
||||||
pub fn new<T: ToString>(uri: T) -> Self {
|
pub fn new<T: ToString>(uri: T) -> Self {
|
||||||
ExtXMap {
|
ExtXMap {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
|
@ -35,7 +35,7 @@ impl ExtXMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXMap` tag with the given range.
|
/// Makes a new [`ExtXMap`] tag with the given range.
|
||||||
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
|
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
|
||||||
ExtXMap {
|
ExtXMap {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
|
@ -43,21 +43,16 @@ impl ExtXMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the URI that identifies a resource that contains the media initialization section.
|
/// Returns the `URI` that identifies a resource,
|
||||||
pub const fn uri(&self) -> &String {
|
/// that contains the media initialization section.
|
||||||
&self.uri
|
pub const fn uri(&self) -> &String { &self.uri }
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the range of the media initialization section.
|
/// Returns the range of the media initialization section.
|
||||||
pub const fn range(&self) -> Option<ByteRange> {
|
pub const fn range(&self) -> Option<ByteRange> { self.range }
|
||||||
self.range
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXMap {
|
impl RequiredVersion for ExtXMap {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V6 }
|
||||||
ProtocolVersion::V6
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXMap {
|
impl fmt::Display for ExtXMap {
|
||||||
|
@ -88,12 +83,13 @@ impl FromStr for ExtXMap {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized
|
||||||
|
// AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?;
|
let uri = uri.ok_or_else(|| Error::missing_value("EXT-X-URI"))?;
|
||||||
Ok(ExtXMap { uri, range })
|
Ok(ExtXMap { uri, range })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ use crate::utils::tag;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
|
/// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
|
||||||
/// The [ExtXProgramDateTime] tag associates the first sample of a
|
/// The [`ExtXProgramDateTime`] tag associates the first sample of a
|
||||||
/// [Media Segment] with an absolute date and/or time.
|
/// [`Media Segment`] with an absolute date and/or time.
|
||||||
///
|
///
|
||||||
/// [Media Segment]: crate::MediaSegment
|
/// [`Media Segment`]: crate::MediaSegment
|
||||||
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
|
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
|
pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
|
||||||
|
@ -20,29 +20,26 @@ pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
|
||||||
impl ExtXProgramDateTime {
|
impl ExtXProgramDateTime {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
|
||||||
|
|
||||||
/// Makes a new `ExtXProgramDateTime` tag.
|
/// Makes a new [`ExtXProgramDateTime`] tag.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use hls_m3u8::tags::ExtXProgramDateTime;
|
|
||||||
/// use chrono::{FixedOffset, TimeZone};
|
/// use chrono::{FixedOffset, TimeZone};
|
||||||
|
/// use hls_m3u8::tags::ExtXProgramDateTime;
|
||||||
///
|
///
|
||||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||||
///
|
///
|
||||||
/// let program_date_time = ExtXProgramDateTime::new(
|
/// let program_date_time = ExtXProgramDateTime::new(
|
||||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||||
/// .ymd(2010, 2, 19)
|
/// .ymd(2010, 2, 19)
|
||||||
/// .and_hms_milli(14, 54, 23, 31)
|
/// .and_hms_milli(14, 54, 23, 31),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn new(date_time: DateTime<FixedOffset>) -> Self {
|
pub const fn new(date_time: DateTime<FixedOffset>) -> Self { Self(date_time) }
|
||||||
Self(date_time)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the date-time of the first sample of the associated media segment.
|
/// Returns the date-time of the first sample of the associated media
|
||||||
pub const fn date_time(&self) -> DateTime<FixedOffset> {
|
/// segment.
|
||||||
self.0
|
pub const fn date_time(&self) -> DateTime<FixedOffset> { self.0 }
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the date-time of the first sample of the associated media segment.
|
/// Sets the date-time of the first sample of the associated media segment.
|
||||||
pub fn set_date_time(&mut self, value: DateTime<FixedOffset>) -> &mut Self {
|
pub fn set_date_time(&mut self, value: DateTime<FixedOffset>) -> &mut Self {
|
||||||
|
@ -52,9 +49,7 @@ impl ExtXProgramDateTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXProgramDateTime {
|
impl RequiredVersion for ExtXProgramDateTime {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXProgramDateTime {
|
impl fmt::Display for ExtXProgramDateTime {
|
||||||
|
@ -78,15 +73,11 @@ impl FromStr for ExtXProgramDateTime {
|
||||||
impl Deref for ExtXProgramDateTime {
|
impl Deref for ExtXProgramDateTime {
|
||||||
type Target = DateTime<FixedOffset>;
|
type Target = DateTime<FixedOffset>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for ExtXProgramDateTime {
|
impl DerefMut for ExtXProgramDateTime {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::Error;
|
||||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||||
///
|
///
|
||||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
pub struct ExtXIndependentSegments;
|
pub struct ExtXIndependentSegments;
|
||||||
|
|
||||||
impl ExtXIndependentSegments {
|
impl ExtXIndependentSegments {
|
||||||
|
@ -16,15 +16,11 @@ impl ExtXIndependentSegments {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXIndependentSegments {
|
impl RequiredVersion for ExtXIndependentSegments {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXIndependentSegments {
|
impl fmt::Display for ExtXIndependentSegments {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Self::PREFIX.fmt(f) }
|
||||||
Self::PREFIX.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ExtXIndependentSegments {
|
impl FromStr for ExtXIndependentSegments {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::Error;
|
||||||
/// [4.3.5.2. EXT-X-START]
|
/// [4.3.5.2. EXT-X-START]
|
||||||
///
|
///
|
||||||
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
|
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(PartialOrd, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct ExtXStart {
|
pub struct ExtXStart {
|
||||||
time_offset: SignedDecimalFloatingPoint,
|
time_offset: SignedDecimalFloatingPoint,
|
||||||
precise: bool,
|
precise: bool,
|
||||||
|
@ -18,52 +18,99 @@ pub struct ExtXStart {
|
||||||
impl ExtXStart {
|
impl ExtXStart {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
|
||||||
|
|
||||||
/// Makes a new `ExtXStart` tag.
|
/// Makes a new [`ExtXStart`] tag.
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// Panics if the time_offset value is infinite.
|
/// Panics if the time_offset value is infinite.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// ExtXStart::new(20.123456);
|
||||||
|
/// ```
|
||||||
pub fn new(time_offset: f64) -> Self {
|
pub fn new(time_offset: f64) -> Self {
|
||||||
if time_offset.is_infinite() {
|
Self {
|
||||||
panic!("EXT-X-START: Floating point value must be finite!");
|
time_offset: SignedDecimalFloatingPoint::new(time_offset),
|
||||||
}
|
|
||||||
|
|
||||||
ExtXStart {
|
|
||||||
time_offset: SignedDecimalFloatingPoint::new(time_offset).unwrap(),
|
|
||||||
precise: false,
|
precise: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXStart` tag with the given `precise` flag.
|
/// Makes a new [`ExtXStart`] tag with the given `precise` flag.
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// Panics if the time_offset value is infinite.
|
/// Panics if the time_offset value is infinite.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// let start = ExtXStart::with_precise(20.123456, true);
|
||||||
|
/// assert_eq!(start.precise(), true);
|
||||||
|
/// ```
|
||||||
pub fn with_precise(time_offset: f64, precise: bool) -> Self {
|
pub fn with_precise(time_offset: f64, precise: bool) -> Self {
|
||||||
if time_offset.is_infinite() {
|
Self {
|
||||||
panic!("EXT-X-START: Floating point value must be finite!");
|
time_offset: SignedDecimalFloatingPoint::new(time_offset),
|
||||||
}
|
|
||||||
|
|
||||||
ExtXStart {
|
|
||||||
time_offset: SignedDecimalFloatingPoint::new(time_offset).unwrap(),
|
|
||||||
precise,
|
precise,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the time offset of the media segments in the playlist.
|
/// Returns the time offset of the media segments in the playlist.
|
||||||
pub const fn time_offset(&self) -> f64 {
|
///
|
||||||
self.time_offset.as_f64()
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// let start = ExtXStart::new(20.123456);
|
||||||
|
/// assert_eq!(start.time_offset(), 20.123456);
|
||||||
|
/// ```
|
||||||
|
pub const fn time_offset(&self) -> f64 { self.time_offset.as_f64() }
|
||||||
|
|
||||||
|
/// Sets the time offset of the media segments in the playlist.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// let mut start = ExtXStart::new(20.123456);
|
||||||
|
/// # assert_eq!(start.time_offset(), 20.123456);
|
||||||
|
///
|
||||||
|
/// start.set_time_offset(1.0);
|
||||||
|
///
|
||||||
|
/// assert_eq!(start.time_offset(), 1.0);
|
||||||
|
/// ```
|
||||||
|
pub fn set_time_offset(&mut self, value: f64) -> &mut Self {
|
||||||
|
self.time_offset = SignedDecimalFloatingPoint::new(value);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether clients should not render media stream whose presentation times are
|
/// Returns whether clients should not render media stream whose
|
||||||
/// prior to the specified time offset.
|
/// presentation times are prior to the specified time offset.
|
||||||
pub const fn precise(&self) -> bool {
|
///
|
||||||
self.precise
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// let start = ExtXStart::with_precise(20.123456, true);
|
||||||
|
/// assert_eq!(start.precise(), true);
|
||||||
|
/// ```
|
||||||
|
pub const fn precise(&self) -> bool { self.precise }
|
||||||
|
|
||||||
|
/// Sets the `precise` flag.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::tags::ExtXStart;
|
||||||
|
/// let mut start = ExtXStart::new(20.123456);
|
||||||
|
/// # assert_eq!(start.precise(), false);
|
||||||
|
///
|
||||||
|
/// start.set_precise(true);
|
||||||
|
///
|
||||||
|
/// assert_eq!(start.precise(), true);
|
||||||
|
/// ```
|
||||||
|
pub fn set_precise(&mut self, value: bool) -> &mut Self {
|
||||||
|
self.precise = value;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for ExtXStart {
|
impl RequiredVersion for ExtXStart {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||||
ProtocolVersion::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ExtXStart {
|
impl fmt::Display for ExtXStart {
|
||||||
|
@ -92,14 +139,15 @@ impl FromStr for ExtXStart {
|
||||||
"PRECISE" => precise = (parse_yes_or_no(value))?,
|
"PRECISE" => precise = (parse_yes_or_no(value))?,
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized
|
||||||
|
// AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let time_offset = time_offset.ok_or(Error::missing_value("EXT-X-TIME-OFFSET"))?;
|
let time_offset = time_offset.ok_or_else(|| Error::missing_value("EXT-X-TIME-OFFSET"))?;
|
||||||
|
|
||||||
Ok(ExtXStart {
|
Ok(Self {
|
||||||
time_offset,
|
time_offset,
|
||||||
precise,
|
precise,
|
||||||
})
|
})
|
||||||
|
@ -147,5 +195,12 @@ mod test {
|
||||||
ExtXStart::with_precise(1.23, true),
|
ExtXStart::with_precise(1.23, true),
|
||||||
"#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES".parse().unwrap(),
|
"#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES".parse().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ExtXStart::with_precise(1.23, true),
|
||||||
|
"#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES,UNKNOWN=TAG"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,27 @@ pub struct ByteRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ByteRange {
|
impl ByteRange {
|
||||||
/// Creates a new [ByteRange].
|
/// Creates a new [`ByteRange`].
|
||||||
pub const fn new(length: usize, start: Option<usize>) -> Self {
|
///
|
||||||
Self { length, start }
|
/// # Example
|
||||||
}
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::ByteRange;
|
||||||
|
/// ByteRange::new(22, Some(12));
|
||||||
|
/// ```
|
||||||
|
pub const fn new(length: usize, start: Option<usize>) -> Self { Self { length, start } }
|
||||||
|
|
||||||
/// Returns the length of the range.
|
/// Returns the length of the range.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::ByteRange;
|
/// # use hls_m3u8::types::ByteRange;
|
||||||
/// #
|
/// #
|
||||||
/// assert_eq!(ByteRange::new(20, Some(3)).length(), 20);
|
/// assert_eq!(ByteRange::new(20, Some(3)).length(), 20);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn length(&self) -> usize {
|
pub const fn length(&self) -> usize { self.length }
|
||||||
self.length
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the length of the range.
|
/// Sets the length of the range.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::ByteRange;
|
/// # use hls_m3u8::types::ByteRange;
|
||||||
|
@ -48,17 +52,17 @@ impl ByteRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the start of the range.
|
/// Returns the start of the range.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::ByteRange;
|
/// # use hls_m3u8::types::ByteRange;
|
||||||
/// #
|
/// #
|
||||||
/// assert_eq!(ByteRange::new(20, Some(3)).start(), Some(3));
|
/// assert_eq!(ByteRange::new(20, Some(3)).start(), Some(3));
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn start(&self) -> Option<usize> {
|
pub const fn start(&self) -> Option<usize> { self.start }
|
||||||
self.start
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the start of the range.
|
/// Sets the start of the range.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::ByteRange;
|
/// # use hls_m3u8::types::ByteRange;
|
||||||
|
@ -97,13 +101,13 @@ impl FromStr for ByteRange {
|
||||||
let length = tokens[0].parse()?;
|
let length = tokens[0].parse()?;
|
||||||
|
|
||||||
let start = {
|
let start = {
|
||||||
let mut result = None;
|
|
||||||
if tokens.len() == 2 {
|
if tokens.len() == 2 {
|
||||||
result = Some(tokens[1].parse()?);
|
Some(tokens[1].parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
result
|
|
||||||
};
|
};
|
||||||
Ok(ByteRange::new(length, start))
|
Ok(Self::new(length, start))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,22 +138,30 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() {
|
||||||
let byte_range = ByteRange {
|
assert_eq!(
|
||||||
length: 99999,
|
ByteRange {
|
||||||
start: Some(2),
|
length: 99999,
|
||||||
};
|
start: Some(2),
|
||||||
assert_eq!(byte_range, "99999@2".parse::<ByteRange>().unwrap());
|
},
|
||||||
|
"99999@2".parse::<ByteRange>().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let byte_range = ByteRange {
|
assert_eq!(
|
||||||
length: 99999,
|
ByteRange {
|
||||||
start: Some(2),
|
length: 99999,
|
||||||
};
|
start: Some(2),
|
||||||
assert_eq!(byte_range, "99999@2".parse::<ByteRange>().unwrap());
|
},
|
||||||
|
"99999@2".parse::<ByteRange>().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let byte_range = ByteRange {
|
assert_eq!(
|
||||||
length: 99999,
|
ByteRange {
|
||||||
start: None,
|
length: 99999,
|
||||||
};
|
start: None,
|
||||||
assert_eq!(byte_range, "99999".parse::<ByteRange>().unwrap());
|
},
|
||||||
|
"99999".parse::<ByteRange>().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!("".parse::<ByteRange>().is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
193
src/types/channels.rs
Normal file
193
src/types/channels.rs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
use core::fmt;
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Specifies a list of parameters.
|
||||||
|
///
|
||||||
|
/// # `MediaType::Audio`
|
||||||
|
/// The first parameter is a count of audio channels expressed as a [`u64`],
|
||||||
|
/// indicating the maximum number of independent, simultaneous audio channels
|
||||||
|
/// present in any [`MediaSegment`] in the rendition. For example, an
|
||||||
|
/// `AC-3 5.1` rendition would have a `CHANNELS="6"` attribute.
|
||||||
|
///
|
||||||
|
/// The second parameter identifies the encoding of object-based audio used by
|
||||||
|
/// the rendition. This parameter is a comma-separated list of Audio
|
||||||
|
/// Object Coding Identifiers. It is optional. An Audio Object
|
||||||
|
/// Coding Identifier is a string containing characters from the set
|
||||||
|
/// `[A..Z]`, `[0..9]`, and `'-'`. They are codec-specific. A parameter
|
||||||
|
/// value of consisting solely of the dash character (`'-'`) indicates
|
||||||
|
/// that the audio is not object-based.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Creating a `CHANNELS="6"` attribute
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(6);
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// format!("CHANNELS=\"{}\"", channels),
|
||||||
|
/// "CHANNELS=\"6\"".to_string()
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// Currently there are no example playlists in the documentation,
|
||||||
|
/// or in popular m3u8 libraries, showing a usage for the second parameter
|
||||||
|
/// of [`Channels`], so if you have one please open an issue on github!
|
||||||
|
///
|
||||||
|
/// [`MediaSegment`]: crate::MediaSegment
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct Channels {
|
||||||
|
first_parameter: u64,
|
||||||
|
second_parameter: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channels {
|
||||||
|
/// Makes a new [`Channels`] struct.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(6);
|
||||||
|
/// ```
|
||||||
|
pub const fn new(value: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
first_parameter: value,
|
||||||
|
second_parameter: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first parameter.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(6);
|
||||||
|
///
|
||||||
|
/// assert_eq!(channels.first_parameter(), 6);
|
||||||
|
/// ```
|
||||||
|
pub const fn first_parameter(&self) -> u64 { self.first_parameter }
|
||||||
|
|
||||||
|
/// Sets the first parameter.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(3);
|
||||||
|
///
|
||||||
|
/// channels.set_first_parameter(6);
|
||||||
|
/// assert_eq!(channels.first_parameter(), 6)
|
||||||
|
/// ```
|
||||||
|
pub fn set_first_parameter(&mut self, value: u64) -> &mut Self {
|
||||||
|
self.first_parameter = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the second parameter, if there is any!
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(3);
|
||||||
|
/// # assert_eq!(channels.second_parameter(), &None);
|
||||||
|
///
|
||||||
|
/// channels.set_second_parameter(Some(vec!["AAC", "MP3"]));
|
||||||
|
/// assert_eq!(
|
||||||
|
/// channels.second_parameter(),
|
||||||
|
/// &Some(vec!["AAC".to_string(), "MP3".to_string()])
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// Currently there is no use for this parameter.
|
||||||
|
pub const fn second_parameter(&self) -> &Option<Vec<String>> { &self.second_parameter }
|
||||||
|
|
||||||
|
/// Sets the second parameter.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::Channels;
|
||||||
|
/// let mut channels = Channels::new(3);
|
||||||
|
/// # assert_eq!(channels.second_parameter(), &None);
|
||||||
|
///
|
||||||
|
/// channels.set_second_parameter(Some(vec!["AAC", "MP3"]));
|
||||||
|
/// assert_eq!(
|
||||||
|
/// channels.second_parameter(),
|
||||||
|
/// &Some(vec!["AAC".to_string(), "MP3".to_string()])
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// Currently there is no use for this parameter.
|
||||||
|
pub fn set_second_parameter<T: ToString>(&mut self, value: Option<Vec<T>>) -> &mut Self {
|
||||||
|
self.second_parameter = value.map(|v| v.into_iter().map(|s| s.to_string()).collect());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Channels {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parameters = input.split('/').collect::<Vec<_>>();
|
||||||
|
let first_parameter = parameters
|
||||||
|
.first()
|
||||||
|
.ok_or_else(|| Error::missing_attribute("First parameter of channels!"))?
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
|
let second_parameter = parameters
|
||||||
|
.get(1)
|
||||||
|
.map(|v| v.split(',').map(|v| v.to_string()).collect());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
first_parameter,
|
||||||
|
second_parameter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Channels {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.first_parameter)?;
|
||||||
|
|
||||||
|
if let Some(second) = &self.second_parameter {
|
||||||
|
write!(f, "/{}", second.join(","))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
let mut channels = Channels::new(6);
|
||||||
|
assert_eq!(channels.to_string(), "6".to_string());
|
||||||
|
|
||||||
|
channels.set_first_parameter(7);
|
||||||
|
assert_eq!(channels.to_string(), "7".to_string());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"6/P,K,J".to_string(),
|
||||||
|
Channels::new(6)
|
||||||
|
.set_second_parameter(Some(vec!["P", "K", "J"]))
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parser() {
|
||||||
|
assert_eq!("6".parse::<Channels>().unwrap(), Channels::new(6));
|
||||||
|
let mut result = Channels::new(6);
|
||||||
|
result.set_second_parameter(Some(vec!["P", "K", "J"]));
|
||||||
|
|
||||||
|
assert_eq!("6/P,K,J".parse::<Channels>().unwrap(), result);
|
||||||
|
|
||||||
|
assert!("garbage".parse::<Channels>().is_err());
|
||||||
|
assert!("".parse::<Channels>().is_err());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::utils::{quote, unquote};
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, Result};
|
use crate::Error;
|
||||||
|
|
||||||
/// The identifier of a closed captions group or its absence.
|
/// The identifier of a closed captions group or its absence.
|
||||||
///
|
///
|
||||||
|
@ -10,7 +10,7 @@ use crate::{Error, Result};
|
||||||
///
|
///
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
pub enum ClosedCaptions {
|
pub enum ClosedCaptions {
|
||||||
GroupId(String),
|
GroupId(String),
|
||||||
None,
|
None,
|
||||||
|
@ -19,19 +19,20 @@ pub enum ClosedCaptions {
|
||||||
impl fmt::Display for ClosedCaptions {
|
impl fmt::Display for ClosedCaptions {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
ClosedCaptions::GroupId(value) => write!(f, "{}", quote(value)),
|
Self::GroupId(value) => write!(f, "{}", quote(value)),
|
||||||
ClosedCaptions::None => "NONE".fmt(f),
|
Self::None => write!(f, "NONE"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ClosedCaptions {
|
impl FromStr for ClosedCaptions {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
if s == "NONE" {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(ClosedCaptions::None)
|
if input.trim() == "NONE" {
|
||||||
|
Ok(Self::None)
|
||||||
} else {
|
} else {
|
||||||
Ok(ClosedCaptions::GroupId(unquote(s)))
|
Ok(Self::GroupId(unquote(input)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,21 +43,23 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_display() {
|
fn test_display() {
|
||||||
let closed_captions = ClosedCaptions::None;
|
assert_eq!(ClosedCaptions::None.to_string(), "NONE".to_string());
|
||||||
assert_eq!(closed_captions.to_string(), "NONE".to_string());
|
|
||||||
|
|
||||||
let closed_captions = ClosedCaptions::GroupId("value".into());
|
assert_eq!(
|
||||||
assert_eq!(closed_captions.to_string(), "\"value\"".to_string());
|
ClosedCaptions::GroupId("value".into()).to_string(),
|
||||||
|
"\"value\"".to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() {
|
||||||
let closed_captions = ClosedCaptions::None;
|
|
||||||
assert_eq!(closed_captions, "NONE".parse::<ClosedCaptions>().unwrap());
|
|
||||||
|
|
||||||
let closed_captions = ClosedCaptions::GroupId("value".into());
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
closed_captions,
|
ClosedCaptions::None,
|
||||||
|
"NONE".parse::<ClosedCaptions>().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ClosedCaptions::GroupId("value".into()),
|
||||||
"\"value\"".parse::<ClosedCaptions>().unwrap()
|
"\"value\"".parse::<ClosedCaptions>().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt;
|
use core::ops::Deref;
|
||||||
use std::str::FromStr;
|
use core::str::FromStr;
|
||||||
use std::time::Duration;
|
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
@ -8,84 +9,87 @@ use crate::Error;
|
||||||
///
|
///
|
||||||
/// See: [4.2. Attribute Lists]
|
/// See: [4.2. Attribute Lists]
|
||||||
///
|
///
|
||||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
/// [4.2. Attribute Lists]:
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.2
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display)]
|
||||||
pub(crate) struct DecimalFloatingPoint(f64);
|
pub(crate) struct DecimalFloatingPoint(f64);
|
||||||
|
|
||||||
impl DecimalFloatingPoint {
|
impl DecimalFloatingPoint {
|
||||||
/// Makes a new `DecimalFloatingPoint` instance.
|
/// Makes a new [`DecimalFloatingPoint`] instance.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// The given value must have a positive sign and be finite,
|
/// The given value must have a positive sign and be finite,
|
||||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
/// otherwise this function will return an error that has the kind
|
||||||
pub fn new(n: f64) -> crate::Result<Self> {
|
/// `ErrorKind::InvalidInput`.
|
||||||
if !n.is_sign_positive() || !n.is_finite() {
|
pub fn new(value: f64) -> crate::Result<Self> {
|
||||||
|
if value.is_sign_negative() || value.is_infinite() {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
Ok(DecimalFloatingPoint(n))
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts `DecimalFloatingPoint` to `f64`.
|
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
|
||||||
pub const fn as_f64(self) -> f64 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn to_duration(self) -> Duration {
|
/// Converts [`DecimalFloatingPoint`] to [`f64`].
|
||||||
let secs = self.0 as u64;
|
pub const fn as_f64(self) -> f64 { self.0 }
|
||||||
let nanos = (self.0.fract() * 1_000_000_000.0) as u32;
|
|
||||||
Duration::new(secs, nanos)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_duration(duration: Duration) -> Self {
|
|
||||||
let n =
|
|
||||||
(duration.as_secs() as f64) + (f64::from(duration.subsec_nanos()) / 1_000_000_000.0);
|
|
||||||
DecimalFloatingPoint(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for DecimalFloatingPoint {
|
|
||||||
fn from(f: u32) -> Self {
|
|
||||||
DecimalFloatingPoint(f64::from(f))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for DecimalFloatingPoint {}
|
impl Eq for DecimalFloatingPoint {}
|
||||||
|
|
||||||
impl fmt::Display for DecimalFloatingPoint {
|
// this trait is implemented manually, so it doesn't construct a
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
// [`DecimalFloatingPoint`], with a negative value.
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for DecimalFloatingPoint {
|
impl FromStr for DecimalFloatingPoint {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> { Self::new(input.parse()?) }
|
||||||
if !input.chars().all(|c| c.is_digit(10) || c == '.') {
|
}
|
||||||
return Err(Error::invalid_input());
|
|
||||||
}
|
impl Deref for DecimalFloatingPoint {
|
||||||
let n = input.parse()?;
|
type Target = f64;
|
||||||
DecimalFloatingPoint::new(n)
|
|
||||||
}
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for DecimalFloatingPoint {
|
impl From<f64> for DecimalFloatingPoint {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
Self(value)
|
let mut result = value;
|
||||||
|
|
||||||
|
// guard against the unlikely case of an infinite value...
|
||||||
|
if result.is_infinite() {
|
||||||
|
result = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(result.abs())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f32> for DecimalFloatingPoint {
|
impl From<f32> for DecimalFloatingPoint {
|
||||||
fn from(value: f32) -> Self {
|
fn from(value: f32) -> Self { (value as f64).into() }
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! test_from {
|
||||||
|
( $($input:expr),* ) => {
|
||||||
|
use ::core::convert::From;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from() {
|
||||||
|
$(
|
||||||
|
assert_eq!(
|
||||||
|
DecimalFloatingPoint::from($input),
|
||||||
|
DecimalFloatingPoint::new(1.0).unwrap(),
|
||||||
|
);
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_from![1u8, 1u16, 1u32, 1.0f32, -1.0f32, 1.0f64, -1.0f64];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_display() {
|
pub fn test_display() {
|
||||||
let decimal_floating_point = DecimalFloatingPoint::new(22.0).unwrap();
|
let decimal_floating_point = DecimalFloatingPoint::new(22.0).unwrap();
|
||||||
|
@ -108,5 +112,32 @@ mod tests {
|
||||||
decimal_floating_point,
|
decimal_floating_point,
|
||||||
"4.1".parse::<DecimalFloatingPoint>().unwrap()
|
"4.1".parse::<DecimalFloatingPoint>().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!("1#".parse::<DecimalFloatingPoint>().is_err());
|
||||||
|
assert!("-1.0".parse::<DecimalFloatingPoint>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new() {
|
||||||
|
assert!(DecimalFloatingPoint::new(::std::f64::INFINITY).is_err());
|
||||||
|
assert!(DecimalFloatingPoint::new(-1.0).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_as_f64() {
|
||||||
|
assert_eq!(DecimalFloatingPoint::new(1.0).unwrap().as_f64(), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_inf() {
|
||||||
|
assert_eq!(
|
||||||
|
DecimalFloatingPoint::from(::std::f64::INFINITY),
|
||||||
|
DecimalFloatingPoint::new(0.0).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
assert_eq!(DecimalFloatingPoint::from(0.1).floor(), 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// Decimal resolution.
|
/// Decimal resolution.
|
||||||
|
@ -8,22 +9,19 @@ use crate::Error;
|
||||||
/// See: [4.2. Attribute Lists]
|
/// See: [4.2. Attribute Lists]
|
||||||
///
|
///
|
||||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
|
||||||
|
#[display(fmt = "{}x{}", width, height)]
|
||||||
pub(crate) struct DecimalResolution {
|
pub(crate) struct DecimalResolution {
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecimalResolution {
|
impl DecimalResolution {
|
||||||
/// Creates a new DecimalResolution.
|
/// Creates a new [`DecimalResolution`].
|
||||||
pub const fn new(width: usize, height: usize) -> Self {
|
pub const fn new(width: usize, height: usize) -> Self { Self { width, height } }
|
||||||
Self { width, height }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Horizontal pixel dimension.
|
/// Horizontal pixel dimension.
|
||||||
pub const fn width(&self) -> usize {
|
pub const fn width(&self) -> usize { self.width }
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets Horizontal pixel dimension.
|
/// Sets Horizontal pixel dimension.
|
||||||
pub fn set_width(&mut self, value: usize) -> &mut Self {
|
pub fn set_width(&mut self, value: usize) -> &mut Self {
|
||||||
|
@ -32,9 +30,7 @@ impl DecimalResolution {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vertical pixel dimension.
|
/// Vertical pixel dimension.
|
||||||
pub const fn height(&self) -> usize {
|
pub const fn height(&self) -> usize { self.height }
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets Vertical pixel dimension.
|
/// Sets Vertical pixel dimension.
|
||||||
pub fn set_height(&mut self, value: usize) -> &mut Self {
|
pub fn set_height(&mut self, value: usize) -> &mut Self {
|
||||||
|
@ -43,10 +39,9 @@ impl DecimalResolution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DecimalResolution {
|
/// [`DecimalResolution`] can be constructed from a tuple; `(width, height)`.
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
impl From<(usize, usize)> for DecimalResolution {
|
||||||
write!(f, "{}x{}", self.width, self.height)
|
fn from(value: (usize, usize)) -> Self { DecimalResolution::new(value.0, value.1) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for DecimalResolution {
|
impl FromStr for DecimalResolution {
|
||||||
|
@ -62,12 +57,9 @@ impl FromStr for DecimalResolution {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = tokens[0];
|
Ok(Self {
|
||||||
let height = tokens[1];
|
width: tokens[0].parse()?,
|
||||||
|
height: tokens[1].parse()?,
|
||||||
Ok(DecimalResolution {
|
|
||||||
width: width.parse()?,
|
|
||||||
height: height.parse()?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,4 +110,12 @@ mod tests {
|
||||||
12
|
12
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from() {
|
||||||
|
assert_eq!(
|
||||||
|
DecimalResolution::from((1920, 1080)),
|
||||||
|
DecimalResolution::new(1920, 1080)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,12 @@ use crate::utils::{quote, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into), build_fn(validate = "Self::validate"))]
|
||||||
/// [DecryptionKey] contains data, that is shared between [ExtXSessionKey] and [ExtXKey].
|
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
|
||||||
|
/// and [`ExtXKey`].
|
||||||
///
|
///
|
||||||
/// [ExtXSessionKey]: crate::tags::ExtXSessionKey
|
/// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey
|
||||||
/// [ExtXKey]: crate::tags::ExtXKey
|
/// [`ExtXKey`]: crate::tags::ExtXKey
|
||||||
pub struct DecryptionKey {
|
pub struct DecryptionKey {
|
||||||
/// The [EncryptionMethod].
|
/// The [EncryptionMethod].
|
||||||
pub(crate) method: EncryptionMethod,
|
pub(crate) method: EncryptionMethod,
|
||||||
|
@ -30,9 +31,18 @@ pub struct DecryptionKey {
|
||||||
/// A string that specifies how the key is
|
/// A string that specifies how the key is
|
||||||
/// represented in the resource identified by the `URI`.
|
/// represented in the resource identified by the `URI`.
|
||||||
pub(crate) key_format: Option<KeyFormat>,
|
pub(crate) key_format: Option<KeyFormat>,
|
||||||
#[builder(setter(into), default)]
|
#[builder(setter(into, strip_option), default)]
|
||||||
/// The `KEYFORMATVERSIONS` attribute.
|
/// The [KeyFormatVersions] attribute.
|
||||||
pub(crate) key_format_versions: KeyFormatVersions,
|
pub(crate) key_format_versions: Option<KeyFormatVersions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecryptionKeyBuilder {
|
||||||
|
fn validate(&self) -> Result<(), String> {
|
||||||
|
if self.method != Some(EncryptionMethod::None) && self.uri.is_none() {
|
||||||
|
return Err(Error::custom("Missing URL").to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecryptionKey {
|
impl DecryptionKey {
|
||||||
|
@ -43,10 +53,7 @@ impl DecryptionKey {
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let key = DecryptionKey::new(
|
/// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: ToString>(method: EncryptionMethod, uri: T) -> Self {
|
pub fn new<T: ToString>(method: EncryptionMethod, uri: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -54,7 +61,7 @@ impl DecryptionKey {
|
||||||
uri: Some(uri.to_string()),
|
uri: Some(uri.to_string()),
|
||||||
iv: None,
|
iv: None,
|
||||||
key_format: None,
|
key_format: None,
|
||||||
key_format_versions: KeyFormatVersions::new(),
|
key_format_versions: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,24 +72,14 @@ impl DecryptionKey {
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let key = DecryptionKey::new(
|
/// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.method(), EncryptionMethod::Aes128);
|
||||||
/// key.method(),
|
|
||||||
/// EncryptionMethod::Aes128
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn method(&self) -> EncryptionMethod {
|
pub const fn method(&self) -> EncryptionMethod { self.method }
|
||||||
self.method
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a Builder to build a [DecryptionKey].
|
/// Returns a Builder to build a [DecryptionKey].
|
||||||
pub fn builder() -> DecryptionKeyBuilder {
|
pub fn builder() -> DecryptionKeyBuilder { DecryptionKeyBuilder::default() }
|
||||||
DecryptionKeyBuilder::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the [EncryptionMethod].
|
/// Sets the [EncryptionMethod].
|
||||||
///
|
///
|
||||||
|
@ -91,10 +88,7 @@ impl DecryptionKey {
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_method(EncryptionMethod::SampleAes);
|
/// key.set_method(EncryptionMethod::SampleAes);
|
||||||
///
|
///
|
||||||
|
@ -103,32 +97,26 @@ impl DecryptionKey {
|
||||||
/// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string()
|
/// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string()
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_method(&mut self, value: EncryptionMethod) {
|
pub fn set_method(&mut self, value: EncryptionMethod) -> &mut Self {
|
||||||
self.method = value;
|
self.method = value;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `URI`, that specifies how to obtain the key.
|
/// Returns an `URI`, that specifies how to obtain the key.
|
||||||
///
|
///
|
||||||
/// This attribute is required, if the [EncryptionMethod] is not None.
|
/// # Note
|
||||||
|
/// This attribute is required, if the [EncryptionMethod] is not `None`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let key = DecryptionKey::new(
|
/// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.uri(), &Some("https://www.example.com/".to_string()));
|
||||||
/// key.uri(),
|
|
||||||
/// &Some("https://www.example.com/".to_string())
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn uri(&self) -> &Option<String> {
|
pub const fn uri(&self) -> &Option<String> { &self.uri }
|
||||||
&self.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `URI` attribute.
|
/// Sets the `URI` attribute.
|
||||||
///
|
///
|
||||||
|
@ -140,10 +128,7 @@ impl DecryptionKey {
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_uri(Some("http://www.google.com/"));
|
/// key.set_uri(Some("http://www.google.com/"));
|
||||||
///
|
///
|
||||||
|
@ -152,27 +137,22 @@ impl DecryptionKey {
|
||||||
/// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string()
|
/// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string()
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_uri<T: ToString>(&mut self, value: Option<T>) {
|
pub fn set_uri<T: ToString>(&mut self, value: Option<T>) -> &mut Self {
|
||||||
self.uri = value.map(|v| v.to_string());
|
self.uri = value.map(|v| v.to_string());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the IV (Initialization Vector) attribute.
|
/// Returns the IV (Initialization Vector) attribute.
|
||||||
///
|
///
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_iv([
|
/// # assert_eq!(key.iv(), None);
|
||||||
/// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7
|
/// key.set_iv(Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]));
|
||||||
/// ]);
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// key.iv(),
|
/// key.iv(),
|
||||||
|
@ -189,139 +169,112 @@ impl DecryptionKey {
|
||||||
|
|
||||||
/// Sets the `IV` attribute.
|
/// Sets the `IV` attribute.
|
||||||
///
|
///
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_iv([
|
/// key.set_iv(Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]));
|
||||||
/// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7
|
|
||||||
/// ]);
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// key.to_string(),
|
/// key.to_string(),
|
||||||
/// "METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607".to_string()
|
/// "METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607"
|
||||||
|
/// .to_string()
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_iv<T>(&mut self, value: T)
|
pub fn set_iv<T>(&mut self, value: Option<T>) -> &mut Self
|
||||||
where
|
where
|
||||||
T: Into<[u8; 16]>,
|
T: Into<[u8; 16]>,
|
||||||
{
|
{
|
||||||
self.iv = Some(InitializationVector(value.into()));
|
self.iv = value.map(|v| InitializationVector(v.into()));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string that specifies how the key is
|
/// Returns a string that specifies how the key is
|
||||||
/// represented in the resource identified by the `URI`.
|
/// represented in the resource identified by the `URI`.
|
||||||
///
|
///
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::{KeyFormat, EncryptionMethod};
|
/// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_key_format(Some(KeyFormat::Identity));
|
/// key.set_key_format(Some(KeyFormat::Identity));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.key_format(), Some(KeyFormat::Identity));
|
||||||
/// key.key_format(),
|
|
||||||
/// Some(KeyFormat::Identity)
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn key_format(&self) -> Option<KeyFormat> {
|
pub const fn key_format(&self) -> Option<KeyFormat> { self.key_format }
|
||||||
self.key_format
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `KEYFORMAT` attribute.
|
/// Sets the [KeyFormat] attribute.
|
||||||
///
|
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::{KeyFormat, EncryptionMethod};
|
/// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_key_format(Some(KeyFormat::Identity));
|
/// key.set_key_format(Some(KeyFormat::Identity));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(key.key_format(), Some(KeyFormat::Identity));
|
||||||
/// key.key_format(),
|
|
||||||
/// Some(KeyFormat::Identity)
|
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_key_format<T: Into<KeyFormat>>(&mut self, value: Option<T>) {
|
pub fn set_key_format<T: Into<KeyFormat>>(&mut self, value: Option<T>) -> &mut Self {
|
||||||
self.key_format = value.map(|v| v.into());
|
self.key_format = value.map(|v| v.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [KeyFormatVersions] attribute.
|
/// Returns the [KeyFormatVersions] attribute.
|
||||||
///
|
///
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::{KeyFormatVersions, EncryptionMethod};
|
/// use hls_m3u8::types::{EncryptionMethod, KeyFormatVersions};
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_key_format_versions(vec![1, 2, 3, 4, 5]);
|
/// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5]));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// key.key_format_versions(),
|
/// key.key_format_versions(),
|
||||||
/// &KeyFormatVersions::from(vec![1, 2, 3, 4, 5])
|
/// &Some(KeyFormatVersions::from(vec![1, 2, 3, 4, 5]))
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn key_format_versions(&self) -> &KeyFormatVersions {
|
pub const fn key_format_versions(&self) -> &Option<KeyFormatVersions> {
|
||||||
&self.key_format_versions
|
&self.key_format_versions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [KeyFormatVersions] attribute.
|
/// Sets the [KeyFormatVersions] attribute.
|
||||||
///
|
///
|
||||||
/// This attribute is optional.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hls_m3u8::types::DecryptionKey;
|
/// # use hls_m3u8::types::DecryptionKey;
|
||||||
/// use hls_m3u8::types::EncryptionMethod;
|
/// use hls_m3u8::types::EncryptionMethod;
|
||||||
///
|
///
|
||||||
/// let mut key = DecryptionKey::new(
|
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||||
/// EncryptionMethod::Aes128,
|
|
||||||
/// "https://www.example.com/"
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// key.set_key_format_versions(vec![1, 2, 3, 4, 5]);
|
/// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5]));
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// key.to_string(),
|
/// key.to_string(),
|
||||||
/// "METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\"".to_string()
|
/// "METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\""
|
||||||
|
/// .to_string()
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_key_format_versions<T: Into<KeyFormatVersions>>(&mut self, value: T) {
|
pub fn set_key_format_versions<T: Into<KeyFormatVersions>>(
|
||||||
self.key_format_versions = value.into();
|
&mut self,
|
||||||
|
value: Option<T>,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.key_format_versions = value.map(|v| v.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for DecryptionKey {
|
impl RequiredVersion for DecryptionKey {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion {
|
||||||
if self.key_format.is_some() || !self.key_format_versions.is_default() {
|
if self.key_format.is_some() || self.key_format_versions.is_some() {
|
||||||
ProtocolVersion::V5
|
ProtocolVersion::V5
|
||||||
} else if self.iv.is_some() {
|
} else if self.iv.is_some() {
|
||||||
ProtocolVersion::V2
|
ProtocolVersion::V2
|
||||||
|
@ -350,22 +303,23 @@ impl FromStr for DecryptionKey {
|
||||||
"KEYFORMATVERSIONS" => key_format_versions = Some(value.parse()?),
|
"KEYFORMATVERSIONS" => key_format_versions = Some(value.parse()?),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized
|
||||||
|
// AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let method = method.ok_or(Error::missing_value("METHOD"))?;
|
let method = method.ok_or_else(|| Error::missing_value("METHOD"))?;
|
||||||
if method != EncryptionMethod::None && uri.is_none() {
|
if method != EncryptionMethod::None && uri.is_none() {
|
||||||
return Err(Error::missing_value("URI"));
|
return Err(Error::missing_value("URI"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DecryptionKey {
|
Ok(Self {
|
||||||
method,
|
method,
|
||||||
uri,
|
uri,
|
||||||
iv,
|
iv,
|
||||||
key_format,
|
key_format,
|
||||||
key_format_versions: key_format_versions.unwrap_or(KeyFormatVersions::new()),
|
key_format_versions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,8 +340,11 @@ impl fmt::Display for DecryptionKey {
|
||||||
if let Some(value) = &self.key_format {
|
if let Some(value) = &self.key_format {
|
||||||
write!(f, ",KEYFORMAT={}", quote(value))?;
|
write!(f, ",KEYFORMAT={}", quote(value))?;
|
||||||
}
|
}
|
||||||
if !self.key_format_versions.is_default() {
|
|
||||||
write!(f, ",KEYFORMATVERSIONS={}", &self.key_format_versions)?;
|
if let Some(key_format_versions) = &self.key_format_versions {
|
||||||
|
if !key_format_versions.is_default() {
|
||||||
|
write!(f, ",KEYFORMATVERSIONS={}", key_format_versions)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -410,6 +367,7 @@ mod test {
|
||||||
.key_format_versions(vec![1, 2, 3, 4, 5])
|
.key_format_versions(vec![1, 2, 3, 4, 5])
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
key.to_string(),
|
key.to_string(),
|
||||||
"METHOD=AES-128,\
|
"METHOD=AES-128,\
|
||||||
|
@ -419,7 +377,13 @@ mod test {
|
||||||
KEYFORMATVERSIONS=\"1/2/3/4/5\"\
|
KEYFORMATVERSIONS=\"1/2/3/4/5\"\
|
||||||
"
|
"
|
||||||
.to_string()
|
.to_string()
|
||||||
)
|
);
|
||||||
|
|
||||||
|
assert!(DecryptionKey::builder().build().is_err());
|
||||||
|
assert!(DecryptionKey::builder()
|
||||||
|
.method(EncryptionMethod::Aes128)
|
||||||
|
.build()
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -428,9 +392,9 @@ mod test {
|
||||||
EncryptionMethod::Aes128,
|
EncryptionMethod::Aes128,
|
||||||
"https://www.example.com/hls-key/key.bin",
|
"https://www.example.com/hls-key/key.bin",
|
||||||
);
|
);
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
key.to_string(),
|
key.to_string(),
|
||||||
|
@ -458,9 +422,9 @@ mod test {
|
||||||
EncryptionMethod::Aes128,
|
EncryptionMethod::Aes128,
|
||||||
"https://www.example.com/hls-key/key.bin",
|
"https://www.example.com/hls-key/key.bin",
|
||||||
);
|
);
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"METHOD=AES-128,\
|
"METHOD=AES-128,\
|
||||||
|
@ -472,9 +436,9 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com");
|
let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com");
|
||||||
key.set_iv([
|
key.set_iv(Some([
|
||||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||||
]);
|
]));
|
||||||
key.set_key_format(Some(KeyFormat::Identity));
|
key.set_key_format(Some(KeyFormat::Identity));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -485,7 +449,30 @@ mod test {
|
||||||
.parse::<DecryptionKey>()
|
.parse::<DecryptionKey>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
key
|
key
|
||||||
)
|
);
|
||||||
|
|
||||||
|
key.set_key_format_versions(Some(vec![1, 2, 3]));
|
||||||
|
assert_eq!(
|
||||||
|
"METHOD=AES-128,\
|
||||||
|
URI=\"http://www.example.com\",\
|
||||||
|
IV=0x10ef8f758ca555115584bb5b3c687f52,\
|
||||||
|
KEYFORMAT=\"identity\",\
|
||||||
|
KEYFORMATVERSIONS=\"1/2/3\""
|
||||||
|
.parse::<DecryptionKey>()
|
||||||
|
.unwrap(),
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"METHOD=AES-128,\
|
||||||
|
URI=\"http://www.example.com\",\
|
||||||
|
UNKNOWNTAG=abcd"
|
||||||
|
.parse::<DecryptionKey>()
|
||||||
|
.unwrap(),
|
||||||
|
DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com")
|
||||||
|
);
|
||||||
|
assert!("METHOD=AES-128,URI=".parse::<DecryptionKey>().is_err());
|
||||||
|
assert!("garbage".parse::<DecryptionKey>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -494,6 +481,29 @@ mod test {
|
||||||
DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
|
DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
|
||||||
.required_version(),
|
.required_version(),
|
||||||
ProtocolVersion::V1
|
ProtocolVersion::V1
|
||||||
)
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
DecryptionKey::builder()
|
||||||
|
.method(EncryptionMethod::Aes128)
|
||||||
|
.uri("https://www.example.com/")
|
||||||
|
.key_format(KeyFormat::Identity)
|
||||||
|
.key_format_versions(vec![1, 2, 3])
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.required_version(),
|
||||||
|
ProtocolVersion::V5
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
DecryptionKey::builder()
|
||||||
|
.method(EncryptionMethod::Aes128)
|
||||||
|
.uri("https://www.example.com/")
|
||||||
|
.iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.required_version(),
|
||||||
|
ProtocolVersion::V2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::fmt;
|
use strum::{Display, EnumString};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// Encryption method.
|
/// Encryption method.
|
||||||
///
|
///
|
||||||
|
@ -9,7 +6,8 @@ use crate::Error;
|
||||||
///
|
///
|
||||||
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
|
||||||
|
#[strum(serialize_all = "SCREAMING-KEBAB-CASE")]
|
||||||
pub enum EncryptionMethod {
|
pub enum EncryptionMethod {
|
||||||
/// `None` means that [MediaSegment]s are not encrypted.
|
/// `None` means that [MediaSegment]s are not encrypted.
|
||||||
///
|
///
|
||||||
|
@ -26,6 +24,7 @@ pub enum EncryptionMethod {
|
||||||
/// [MediaSegment]: crate::MediaSegment
|
/// [MediaSegment]: crate::MediaSegment
|
||||||
/// [AES_128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf
|
/// [AES_128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf
|
||||||
/// [Public-Key Cryptography Standards #7 (PKCS7)]: https://tools.ietf.org/html/rfc5652
|
/// [Public-Key Cryptography Standards #7 (PKCS7)]: https://tools.ietf.org/html/rfc5652
|
||||||
|
#[strum(serialize = "AES-128")]
|
||||||
Aes128,
|
Aes128,
|
||||||
/// `SampleAes` means that the [MediaSegment]s
|
/// `SampleAes` means that the [MediaSegment]s
|
||||||
/// contain media samples, such as audio or video, that are encrypted
|
/// contain media samples, such as audio or video, that are encrypted
|
||||||
|
@ -48,32 +47,6 @@ pub enum EncryptionMethod {
|
||||||
SampleAes,
|
SampleAes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for EncryptionMethod {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match &self {
|
|
||||||
EncryptionMethod::Aes128 => "AES-128".fmt(f),
|
|
||||||
EncryptionMethod::SampleAes => "SAMPLE-AES".fmt(f),
|
|
||||||
EncryptionMethod::None => "NONE".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for EncryptionMethod {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
||||||
match input {
|
|
||||||
"AES-128" => Ok(EncryptionMethod::Aes128),
|
|
||||||
"SAMPLE-AES" => Ok(EncryptionMethod::SampleAes),
|
|
||||||
"NONE" => Ok(EncryptionMethod::None),
|
|
||||||
_ => Err(Error::custom(format!(
|
|
||||||
"Unknown encryption method: {:?}",
|
|
||||||
input
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -104,5 +77,7 @@ mod tests {
|
||||||
EncryptionMethod::None,
|
EncryptionMethod::None,
|
||||||
"NONE".parse::<EncryptionMethod>().unwrap()
|
"NONE".parse::<EncryptionMethod>().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!("unknown".parse::<EncryptionMethod>().is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::fmt;
|
use strum::{Display, EnumString};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// HDCP level.
|
/// HDCP level.
|
||||||
///
|
///
|
||||||
|
@ -9,33 +6,14 @@ use crate::Error;
|
||||||
///
|
///
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
|
||||||
|
#[strum(serialize_all = "SCREAMING-KEBAB-CASE")]
|
||||||
pub enum HdcpLevel {
|
pub enum HdcpLevel {
|
||||||
|
#[strum(serialize = "TYPE-0")]
|
||||||
Type0,
|
Type0,
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HdcpLevel {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match &self {
|
|
||||||
HdcpLevel::Type0 => "TYPE-0".fmt(f),
|
|
||||||
HdcpLevel::None => "NONE".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for HdcpLevel {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
||||||
match input {
|
|
||||||
"TYPE-0" => Ok(HdcpLevel::Type0),
|
|
||||||
"NONE" => Ok(HdcpLevel::None),
|
|
||||||
_ => Err(Error::custom(format!("Unknown HDCP level: {:?}", input))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -56,5 +34,7 @@ mod tests {
|
||||||
|
|
||||||
let level = HdcpLevel::None;
|
let level = HdcpLevel::None;
|
||||||
assert_eq!(level, "NONE".parse::<HdcpLevel>().unwrap());
|
assert_eq!(level, "NONE".parse::<HdcpLevel>().unwrap());
|
||||||
|
|
||||||
|
assert!("unk".parse::<HdcpLevel>().is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::fmt;
|
use strum::{Display, EnumString};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// Identifier of a rendition within the segments in a media playlist.
|
/// Identifier of a rendition within the segments in a media playlist.
|
||||||
///
|
///
|
||||||
|
@ -9,7 +6,8 @@ use crate::Error;
|
||||||
///
|
///
|
||||||
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
|
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
|
||||||
|
#[strum(serialize_all = "UPPERCASE")]
|
||||||
pub enum InStreamId {
|
pub enum InStreamId {
|
||||||
Cc1,
|
Cc1,
|
||||||
Cc2,
|
Cc2,
|
||||||
|
@ -80,85 +78,96 @@ pub enum InStreamId {
|
||||||
Service63,
|
Service63,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for InStreamId {
|
#[cfg(test)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
mod tests {
|
||||||
format!("{:?}", self).to_uppercase().fmt(f)
|
use super::*;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for InStreamId {
|
macro_rules! gen_tests {
|
||||||
type Err = Error;
|
( $($string:expr => $enum:expr),* ) => {
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
$(
|
||||||
|
assert_eq!($enum.to_string(), $string.to_string());
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
#[test]
|
||||||
Ok(match input {
|
fn test_parser() {
|
||||||
"CC1" => InStreamId::Cc1,
|
$(
|
||||||
"CC2" => InStreamId::Cc2,
|
assert_eq!($enum, $string.parse::<InStreamId>().unwrap());
|
||||||
"CC3" => InStreamId::Cc3,
|
)*
|
||||||
"CC4" => InStreamId::Cc4,
|
assert!("invalid_input".parse::<InStreamId>().is_err());
|
||||||
"SERVICE1" => InStreamId::Service1,
|
}
|
||||||
"SERVICE2" => InStreamId::Service2,
|
};
|
||||||
"SERVICE3" => InStreamId::Service3,
|
|
||||||
"SERVICE4" => InStreamId::Service4,
|
|
||||||
"SERVICE5" => InStreamId::Service5,
|
|
||||||
"SERVICE6" => InStreamId::Service6,
|
|
||||||
"SERVICE7" => InStreamId::Service7,
|
|
||||||
"SERVICE8" => InStreamId::Service8,
|
|
||||||
"SERVICE9" => InStreamId::Service9,
|
|
||||||
"SERVICE10" => InStreamId::Service10,
|
|
||||||
"SERVICE11" => InStreamId::Service11,
|
|
||||||
"SERVICE12" => InStreamId::Service12,
|
|
||||||
"SERVICE13" => InStreamId::Service13,
|
|
||||||
"SERVICE14" => InStreamId::Service14,
|
|
||||||
"SERVICE15" => InStreamId::Service15,
|
|
||||||
"SERVICE16" => InStreamId::Service16,
|
|
||||||
"SERVICE17" => InStreamId::Service17,
|
|
||||||
"SERVICE18" => InStreamId::Service18,
|
|
||||||
"SERVICE19" => InStreamId::Service19,
|
|
||||||
"SERVICE20" => InStreamId::Service20,
|
|
||||||
"SERVICE21" => InStreamId::Service21,
|
|
||||||
"SERVICE22" => InStreamId::Service22,
|
|
||||||
"SERVICE23" => InStreamId::Service23,
|
|
||||||
"SERVICE24" => InStreamId::Service24,
|
|
||||||
"SERVICE25" => InStreamId::Service25,
|
|
||||||
"SERVICE26" => InStreamId::Service26,
|
|
||||||
"SERVICE27" => InStreamId::Service27,
|
|
||||||
"SERVICE28" => InStreamId::Service28,
|
|
||||||
"SERVICE29" => InStreamId::Service29,
|
|
||||||
"SERVICE30" => InStreamId::Service30,
|
|
||||||
"SERVICE31" => InStreamId::Service31,
|
|
||||||
"SERVICE32" => InStreamId::Service32,
|
|
||||||
"SERVICE33" => InStreamId::Service33,
|
|
||||||
"SERVICE34" => InStreamId::Service34,
|
|
||||||
"SERVICE35" => InStreamId::Service35,
|
|
||||||
"SERVICE36" => InStreamId::Service36,
|
|
||||||
"SERVICE37" => InStreamId::Service37,
|
|
||||||
"SERVICE38" => InStreamId::Service38,
|
|
||||||
"SERVICE39" => InStreamId::Service39,
|
|
||||||
"SERVICE40" => InStreamId::Service40,
|
|
||||||
"SERVICE41" => InStreamId::Service41,
|
|
||||||
"SERVICE42" => InStreamId::Service42,
|
|
||||||
"SERVICE43" => InStreamId::Service43,
|
|
||||||
"SERVICE44" => InStreamId::Service44,
|
|
||||||
"SERVICE45" => InStreamId::Service45,
|
|
||||||
"SERVICE46" => InStreamId::Service46,
|
|
||||||
"SERVICE47" => InStreamId::Service47,
|
|
||||||
"SERVICE48" => InStreamId::Service48,
|
|
||||||
"SERVICE49" => InStreamId::Service49,
|
|
||||||
"SERVICE50" => InStreamId::Service50,
|
|
||||||
"SERVICE51" => InStreamId::Service51,
|
|
||||||
"SERVICE52" => InStreamId::Service52,
|
|
||||||
"SERVICE53" => InStreamId::Service53,
|
|
||||||
"SERVICE54" => InStreamId::Service54,
|
|
||||||
"SERVICE55" => InStreamId::Service55,
|
|
||||||
"SERVICE56" => InStreamId::Service56,
|
|
||||||
"SERVICE57" => InStreamId::Service57,
|
|
||||||
"SERVICE58" => InStreamId::Service58,
|
|
||||||
"SERVICE59" => InStreamId::Service59,
|
|
||||||
"SERVICE60" => InStreamId::Service60,
|
|
||||||
"SERVICE61" => InStreamId::Service61,
|
|
||||||
"SERVICE62" => InStreamId::Service62,
|
|
||||||
"SERVICE63" => InStreamId::Service63,
|
|
||||||
_ => return Err(Error::custom(format!("Unknown instream id: {:?}", input))),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gen_tests![
|
||||||
|
"CC1" => InStreamId::Cc1,
|
||||||
|
"CC2" => InStreamId::Cc2,
|
||||||
|
"CC3" => InStreamId::Cc3,
|
||||||
|
"CC4" => InStreamId::Cc4,
|
||||||
|
"SERVICE1" => InStreamId::Service1,
|
||||||
|
"SERVICE2" => InStreamId::Service2,
|
||||||
|
"SERVICE3" => InStreamId::Service3,
|
||||||
|
"SERVICE4" => InStreamId::Service4,
|
||||||
|
"SERVICE5" => InStreamId::Service5,
|
||||||
|
"SERVICE6" => InStreamId::Service6,
|
||||||
|
"SERVICE7" => InStreamId::Service7,
|
||||||
|
"SERVICE8" => InStreamId::Service8,
|
||||||
|
"SERVICE9" => InStreamId::Service9,
|
||||||
|
"SERVICE10" => InStreamId::Service10,
|
||||||
|
"SERVICE11" => InStreamId::Service11,
|
||||||
|
"SERVICE12" => InStreamId::Service12,
|
||||||
|
"SERVICE13" => InStreamId::Service13,
|
||||||
|
"SERVICE14" => InStreamId::Service14,
|
||||||
|
"SERVICE15" => InStreamId::Service15,
|
||||||
|
"SERVICE16" => InStreamId::Service16,
|
||||||
|
"SERVICE17" => InStreamId::Service17,
|
||||||
|
"SERVICE18" => InStreamId::Service18,
|
||||||
|
"SERVICE19" => InStreamId::Service19,
|
||||||
|
"SERVICE20" => InStreamId::Service20,
|
||||||
|
"SERVICE21" => InStreamId::Service21,
|
||||||
|
"SERVICE22" => InStreamId::Service22,
|
||||||
|
"SERVICE23" => InStreamId::Service23,
|
||||||
|
"SERVICE24" => InStreamId::Service24,
|
||||||
|
"SERVICE25" => InStreamId::Service25,
|
||||||
|
"SERVICE26" => InStreamId::Service26,
|
||||||
|
"SERVICE27" => InStreamId::Service27,
|
||||||
|
"SERVICE28" => InStreamId::Service28,
|
||||||
|
"SERVICE29" => InStreamId::Service29,
|
||||||
|
"SERVICE30" => InStreamId::Service30,
|
||||||
|
"SERVICE31" => InStreamId::Service31,
|
||||||
|
"SERVICE32" => InStreamId::Service32,
|
||||||
|
"SERVICE33" => InStreamId::Service33,
|
||||||
|
"SERVICE34" => InStreamId::Service34,
|
||||||
|
"SERVICE35" => InStreamId::Service35,
|
||||||
|
"SERVICE36" => InStreamId::Service36,
|
||||||
|
"SERVICE37" => InStreamId::Service37,
|
||||||
|
"SERVICE38" => InStreamId::Service38,
|
||||||
|
"SERVICE39" => InStreamId::Service39,
|
||||||
|
"SERVICE40" => InStreamId::Service40,
|
||||||
|
"SERVICE41" => InStreamId::Service41,
|
||||||
|
"SERVICE42" => InStreamId::Service42,
|
||||||
|
"SERVICE43" => InStreamId::Service43,
|
||||||
|
"SERVICE44" => InStreamId::Service44,
|
||||||
|
"SERVICE45" => InStreamId::Service45,
|
||||||
|
"SERVICE46" => InStreamId::Service46,
|
||||||
|
"SERVICE47" => InStreamId::Service47,
|
||||||
|
"SERVICE48" => InStreamId::Service48,
|
||||||
|
"SERVICE49" => InStreamId::Service49,
|
||||||
|
"SERVICE50" => InStreamId::Service50,
|
||||||
|
"SERVICE51" => InStreamId::Service51,
|
||||||
|
"SERVICE52" => InStreamId::Service52,
|
||||||
|
"SERVICE53" => InStreamId::Service53,
|
||||||
|
"SERVICE54" => InStreamId::Service54,
|
||||||
|
"SERVICE55" => InStreamId::Service55,
|
||||||
|
"SERVICE56" => InStreamId::Service56,
|
||||||
|
"SERVICE57" => InStreamId::Service57,
|
||||||
|
"SERVICE58" => InStreamId::Service58,
|
||||||
|
"SERVICE59" => InStreamId::Service59,
|
||||||
|
"SERVICE60" => InStreamId::Service60,
|
||||||
|
"SERVICE61" => InStreamId::Service61,
|
||||||
|
"SERVICE62" => InStreamId::Service62,
|
||||||
|
"SERVICE63" => InStreamId::Service63
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,29 +13,22 @@ use crate::Error;
|
||||||
pub struct InitializationVector(pub [u8; 16]);
|
pub struct InitializationVector(pub [u8; 16]);
|
||||||
|
|
||||||
impl InitializationVector {
|
impl InitializationVector {
|
||||||
/// Converts the initialization vector to a slice.
|
/// Converts the [InitializationVector] to a slice.
|
||||||
pub const fn to_slice(&self) -> [u8; 16] {
|
pub const fn to_slice(&self) -> [u8; 16] { self.0 }
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[u8; 16]> for InitializationVector {
|
impl From<[u8; 16]> for InitializationVector {
|
||||||
fn from(value: [u8; 16]) -> Self {
|
fn from(value: [u8; 16]) -> Self { Self(value) }
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for InitializationVector {
|
impl Deref for InitializationVector {
|
||||||
type Target = [u8];
|
type Target = [u8];
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for InitializationVector {
|
impl AsRef<[u8]> for InitializationVector {
|
||||||
fn as_ref(&self) -> &[u8] {
|
fn as_ref(&self) -> &[u8] { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for InitializationVector {
|
impl fmt::Display for InitializationVector {
|
||||||
|
@ -51,21 +44,106 @@ impl fmt::Display for InitializationVector {
|
||||||
impl FromStr for InitializationVector {
|
impl FromStr for InitializationVector {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
if !(s.starts_with("0x") || s.starts_with("0X")) {
|
if !(input.starts_with("0x") || input.starts_with("0X")) {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
if s.len() - 2 != 32 {
|
if input.len() - 2 != 32 {
|
||||||
return Err(Error::invalid_input());
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut v = [0; 16];
|
let mut result = [0; 16];
|
||||||
for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
|
for (i, c) in input.as_bytes().chunks(2).skip(1).enumerate() {
|
||||||
let d = std::str::from_utf8(c).map_err(|e| Error::custom(e))?;
|
let d = std::str::from_utf8(c).map_err(Error::custom)?;
|
||||||
let b = u8::from_str_radix(d, 16).map_err(|e| Error::custom(e))?;
|
let b = u8::from_str_radix(d, 16).map_err(Error::custom)?;
|
||||||
v[i] = b;
|
result[i] = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(InitializationVector(v))
|
Ok(Self(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
assert_eq!(
|
||||||
|
"0x10ef8f758ca555115584bb5b3c687f52".to_string(),
|
||||||
|
InitializationVector([
|
||||||
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||||
|
])
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parser() {
|
||||||
|
assert_eq!(
|
||||||
|
"0x10ef8f758ca555115584bb5b3c687f52"
|
||||||
|
.parse::<InitializationVector>()
|
||||||
|
.unwrap(),
|
||||||
|
InitializationVector([
|
||||||
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"0X10ef8f758ca555115584bb5b3c687f52"
|
||||||
|
.parse::<InitializationVector>()
|
||||||
|
.unwrap(),
|
||||||
|
InitializationVector([
|
||||||
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"0X10EF8F758CA555115584BB5B3C687F52"
|
||||||
|
.parse::<InitializationVector>()
|
||||||
|
.unwrap(),
|
||||||
|
InitializationVector([
|
||||||
|
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!("garbage".parse::<InitializationVector>().is_err());
|
||||||
|
assert!("0xgarbage".parse::<InitializationVector>().is_err());
|
||||||
|
assert!("0x12".parse::<InitializationVector>().is_err());
|
||||||
|
assert!("0X10EF8F758CA555115584BB5B3C687F5Z"
|
||||||
|
.parse::<InitializationVector>()
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_as_ref() {
|
||||||
|
assert_eq!(
|
||||||
|
InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).as_ref(),
|
||||||
|
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
assert_eq!(
|
||||||
|
InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).deref(),
|
||||||
|
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from() {
|
||||||
|
assert_eq!(
|
||||||
|
InitializationVector::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||||
|
InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_slice() {
|
||||||
|
assert_eq!(
|
||||||
|
InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_slice(),
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,15 @@ use crate::utils::{quote, tag, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
/// KeyFormat specifies, how the key is represented in the resource identified by the URI
|
/// [`KeyFormat`] specifies, how the key is represented in the
|
||||||
|
/// resource identified by the `URI`.
|
||||||
pub enum KeyFormat {
|
pub enum KeyFormat {
|
||||||
/// The key is a single packed array of 16 octets in binary format.
|
/// The key is a single packed array of 16 octets in binary format.
|
||||||
Identity,
|
Identity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeyFormat {
|
impl Default for KeyFormat {
|
||||||
fn default() -> Self {
|
fn default() -> Self { Self::Identity }
|
||||||
Self::Identity
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KeyFormat {
|
impl FromStr for KeyFormat {
|
||||||
|
@ -29,15 +28,11 @@ impl FromStr for KeyFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for KeyFormat {
|
impl fmt::Display for KeyFormat {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", quote("identity")) }
|
||||||
write!(f, "{}", quote("identity"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for KeyFormat {
|
impl RequiredVersion for KeyFormat {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V5 }
|
||||||
ProtocolVersion::V5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -54,6 +49,8 @@ mod tests {
|
||||||
assert_eq!(KeyFormat::Identity, quote("identity").parse().unwrap());
|
assert_eq!(KeyFormat::Identity, quote("identity").parse().unwrap());
|
||||||
|
|
||||||
assert_eq!(KeyFormat::Identity, "identity".parse().unwrap());
|
assert_eq!(KeyFormat::Identity, "identity".parse().unwrap());
|
||||||
|
|
||||||
|
assert!("garbage".parse::<KeyFormat>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -8,25 +8,21 @@ use crate::Error;
|
||||||
|
|
||||||
/// A list of [usize], that can be used to indicate which version(s)
|
/// A list of [usize], that can be used to indicate which version(s)
|
||||||
/// this instance complies with, if more than one version of a particular
|
/// this instance complies with, if more than one version of a particular
|
||||||
/// [KeyFormat] is defined.
|
/// [`KeyFormat`] is defined.
|
||||||
///
|
///
|
||||||
/// [KeyFormat]: crate::types::KeyFormat
|
/// [`KeyFormat`]: crate::types::KeyFormat
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
pub struct KeyFormatVersions(Vec<usize>);
|
pub struct KeyFormatVersions(Vec<usize>);
|
||||||
|
|
||||||
impl Default for KeyFormatVersions {
|
impl Default for KeyFormatVersions {
|
||||||
fn default() -> Self {
|
fn default() -> Self { Self(vec![1]) }
|
||||||
Self(vec![1])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyFormatVersions {
|
impl KeyFormatVersions {
|
||||||
/// Makes a new [KeyFormatVersions].
|
/// Makes a new [`KeyFormatVersions`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self { Self::default() }
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a value to the [KeyFormatVersions].
|
/// Add a value to the [`KeyFormatVersions`].
|
||||||
pub fn push(&mut self, value: usize) {
|
pub fn push(&mut self, value: usize) {
|
||||||
if self.is_default() {
|
if self.is_default() {
|
||||||
self.0 = vec![value];
|
self.0 = vec![value];
|
||||||
|
@ -35,30 +31,23 @@ impl KeyFormatVersions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true`, if [KeyFormatVersions] has the default value of `vec![1]`.
|
/// Returns `true`, if [`KeyFormatVersions`] has the default value of
|
||||||
pub fn is_default(&self) -> bool {
|
/// `vec![1]`.
|
||||||
self.0 == vec![1] || self.0.is_empty()
|
pub fn is_default(&self) -> bool { self.0 == vec![1] && self.0.len() == 1 || self.0.is_empty() }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for KeyFormatVersions {
|
impl Deref for KeyFormatVersions {
|
||||||
type Target = Vec<usize>;
|
type Target = Vec<usize>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for KeyFormatVersions {
|
impl DerefMut for KeyFormatVersions {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredVersion for KeyFormatVersions {
|
impl RequiredVersion for KeyFormatVersions {
|
||||||
fn required_version(&self) -> ProtocolVersion {
|
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V5 }
|
||||||
ProtocolVersion::V5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KeyFormatVersions {
|
impl FromStr for KeyFormatVersions {
|
||||||
|
@ -66,7 +55,7 @@ impl FromStr for KeyFormatVersions {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let mut result = unquote(input)
|
let mut result = unquote(input)
|
||||||
.split("/")
|
.split('/')
|
||||||
.filter_map(|v| v.parse().ok())
|
.filter_map(|v| v.parse().ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -100,9 +89,7 @@ impl fmt::Display for KeyFormatVersions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<Vec<usize>>> From<T> for KeyFormatVersions {
|
impl<T: Into<Vec<usize>>> From<T> for KeyFormatVersions {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self { Self(value.into()) }
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
use std::fmt;
|
use strum::{Display, EnumString};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::Error;
|
/// Specifies the media type.
|
||||||
|
|
||||||
/// Media type.
|
|
||||||
///
|
|
||||||
/// See: [4.3.4.1. EXT-X-MEDIA]
|
|
||||||
///
|
|
||||||
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Ord, PartialOrd, Display, EnumString, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[strum(serialize_all = "SCREAMING-KEBAB-CASE")]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
Audio,
|
Audio,
|
||||||
Video,
|
Video,
|
||||||
|
@ -17,29 +11,29 @@ pub enum MediaType {
|
||||||
ClosedCaptions,
|
ClosedCaptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MediaType {
|
#[cfg(test)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
mod tests {
|
||||||
match &self {
|
use super::*;
|
||||||
MediaType::Audio => "AUDIO".fmt(f),
|
|
||||||
MediaType::Video => "VIDEO".fmt(f),
|
#[test]
|
||||||
MediaType::Subtitles => "SUBTITLES".fmt(f),
|
fn test_parser() {
|
||||||
MediaType::ClosedCaptions => "CLOSED-CAPTIONS".fmt(f),
|
assert_eq!(MediaType::Audio, "AUDIO".parse().unwrap());
|
||||||
}
|
assert_eq!(MediaType::Video, "VIDEO".parse().unwrap());
|
||||||
}
|
assert_eq!(MediaType::Subtitles, "SUBTITLES".parse().unwrap());
|
||||||
}
|
assert_eq!(
|
||||||
|
MediaType::ClosedCaptions,
|
||||||
impl FromStr for MediaType {
|
"CLOSED-CAPTIONS".parse().unwrap()
|
||||||
type Err = Error;
|
);
|
||||||
|
}
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(match input {
|
#[test]
|
||||||
"AUDIO" => MediaType::Audio,
|
fn test_display() {
|
||||||
"VIDEO" => MediaType::Video,
|
assert_eq!(MediaType::Audio.to_string(), "AUDIO".to_string());
|
||||||
"SUBTITLES" => MediaType::Subtitles,
|
assert_eq!(MediaType::Video.to_string(), "VIDEO".to_string());
|
||||||
"CLOSED-CAPTIONS" => MediaType::ClosedCaptions,
|
assert_eq!(MediaType::Subtitles.to_string(), "SUBTITLES".to_string());
|
||||||
_ => {
|
assert_eq!(
|
||||||
return Err(Error::invalid_input());
|
MediaType::ClosedCaptions.to_string(),
|
||||||
}
|
"CLOSED-CAPTIONS".to_string()
|
||||||
})
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Miscellaneous types.
|
//! Miscellaneous types.
|
||||||
mod byte_range;
|
mod byte_range;
|
||||||
|
mod channels;
|
||||||
mod closed_captions;
|
mod closed_captions;
|
||||||
mod decimal_floating_point;
|
mod decimal_floating_point;
|
||||||
mod decimal_resolution;
|
mod decimal_resolution;
|
||||||
|
@ -16,6 +17,7 @@ mod signed_decimal_floating_point;
|
||||||
mod stream_inf;
|
mod stream_inf;
|
||||||
|
|
||||||
pub use byte_range::*;
|
pub use byte_range::*;
|
||||||
|
pub use channels::*;
|
||||||
pub use closed_captions::*;
|
pub use closed_captions::*;
|
||||||
pub(crate) use decimal_floating_point::*;
|
pub(crate) use decimal_floating_point::*;
|
||||||
pub(crate) use decimal_resolution::*;
|
pub(crate) use decimal_resolution::*;
|
||||||
|
|
|
@ -27,9 +27,12 @@ pub trait RequiredVersion {
|
||||||
fn required_version(&self) -> ProtocolVersion;
|
fn required_version(&self) -> ProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [7. Protocol Version Compatibility]
|
/// # [7. Protocol Version Compatibility]
|
||||||
|
/// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse
|
||||||
|
/// a certain tag correctly.
|
||||||
///
|
///
|
||||||
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
/// [7. Protocol Version Compatibility]:
|
||||||
|
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-7
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum ProtocolVersion {
|
pub enum ProtocolVersion {
|
||||||
|
@ -43,26 +46,28 @@ pub enum ProtocolVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProtocolVersion {
|
impl ProtocolVersion {
|
||||||
/// Returns the newest ProtocolVersion, that is supported by this library.
|
/// Returns the newest [`ProtocolVersion`], that is supported by
|
||||||
pub const fn latest() -> Self {
|
/// this library.
|
||||||
Self::V7
|
///
|
||||||
}
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use hls_m3u8::types::ProtocolVersion;
|
||||||
|
/// assert_eq!(ProtocolVersion::latest(), ProtocolVersion::V7);
|
||||||
|
/// ```
|
||||||
|
pub const fn latest() -> Self { Self::V7 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ProtocolVersion {
|
impl fmt::Display for ProtocolVersion {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let n = {
|
match &self {
|
||||||
match &self {
|
Self::V1 => write!(f, "1"),
|
||||||
Self::V1 => 1,
|
Self::V2 => write!(f, "2"),
|
||||||
Self::V2 => 2,
|
Self::V3 => write!(f, "3"),
|
||||||
Self::V3 => 3,
|
Self::V4 => write!(f, "4"),
|
||||||
Self::V4 => 4,
|
Self::V5 => write!(f, "5"),
|
||||||
Self::V5 => 5,
|
Self::V6 => write!(f, "6"),
|
||||||
Self::V6 => 6,
|
Self::V7 => write!(f, "7"),
|
||||||
Self::V7 => 7,
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
write!(f, "{}", n)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +91,7 @@ impl FromStr for ProtocolVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProtocolVersion {
|
impl Default for ProtocolVersion {
|
||||||
fn default() -> Self {
|
fn default() -> Self { Self::V1 }
|
||||||
Self::V1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -115,5 +118,18 @@ mod tests {
|
||||||
assert_eq!(ProtocolVersion::V5, "5".parse().unwrap());
|
assert_eq!(ProtocolVersion::V5, "5".parse().unwrap());
|
||||||
assert_eq!(ProtocolVersion::V6, "6".parse().unwrap());
|
assert_eq!(ProtocolVersion::V6, "6".parse().unwrap());
|
||||||
assert_eq!(ProtocolVersion::V7, "7".parse().unwrap());
|
assert_eq!(ProtocolVersion::V7, "7".parse().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(ProtocolVersion::V7, " 7 ".parse().unwrap());
|
||||||
|
assert!("garbage".parse::<ProtocolVersion>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default() {
|
||||||
|
assert_eq!(ProtocolVersion::default(), ProtocolVersion::V1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_latest() {
|
||||||
|
assert_eq!(ProtocolVersion::latest(), ProtocolVersion::V7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,100 @@
|
||||||
use std::fmt;
|
use core::ops::Deref;
|
||||||
use std::str::FromStr;
|
use derive_more::{Display, FromStr};
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// Signed decimal floating-point number.
|
/// Signed decimal floating-point number.
|
||||||
///
|
///
|
||||||
/// See: [4.2. Attribute Lists]
|
/// See: [4.2. Attribute Lists]
|
||||||
///
|
///
|
||||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display, FromStr)]
|
||||||
pub(crate) struct SignedDecimalFloatingPoint(f64);
|
pub(crate) struct SignedDecimalFloatingPoint(f64);
|
||||||
|
|
||||||
impl SignedDecimalFloatingPoint {
|
impl SignedDecimalFloatingPoint {
|
||||||
/// Makes a new `SignedDecimalFloatingPoint` instance.
|
/// Makes a new [SignedDecimalFloatingPoint] instance.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Panics
|
||||||
///
|
/// The given value must be finite, otherwise this function will panic!
|
||||||
/// The given value must be finite,
|
pub fn new(value: f64) -> Self {
|
||||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
if value.is_infinite() {
|
||||||
pub fn new(n: f64) -> crate::Result<Self> {
|
panic!("Floating point value must be finite!");
|
||||||
if !n.is_finite() {
|
|
||||||
Err(Error::invalid_input())
|
|
||||||
} else {
|
|
||||||
Ok(SignedDecimalFloatingPoint(n))
|
|
||||||
}
|
}
|
||||||
|
Self(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts `DecimalFloatingPoint` to `f64`.
|
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
|
||||||
pub const fn as_f64(self) -> f64 {
|
|
||||||
self.0
|
/// Converts [DecimalFloatingPoint] to [f64].
|
||||||
}
|
pub const fn as_f64(self) -> f64 { self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for SignedDecimalFloatingPoint {
|
impl Deref for SignedDecimalFloatingPoint {
|
||||||
fn from(f: i32) -> Self {
|
type Target = f64;
|
||||||
SignedDecimalFloatingPoint(f64::from(f))
|
|
||||||
}
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for SignedDecimalFloatingPoint {}
|
impl Eq for SignedDecimalFloatingPoint {}
|
||||||
|
|
||||||
impl fmt::Display for SignedDecimalFloatingPoint {
|
#[cfg(test)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
mod tests {
|
||||||
self.0.fmt(f)
|
use super::*;
|
||||||
}
|
|
||||||
}
|
macro_rules! test_from {
|
||||||
|
( $( $input:expr => $output:expr ),* ) => {
|
||||||
impl FromStr for SignedDecimalFloatingPoint {
|
use ::core::convert::From;
|
||||||
type Err = Error;
|
|
||||||
|
#[test]
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn test_from() {
|
||||||
SignedDecimalFloatingPoint::new(input.parse().map_err(Error::parse_float_error)?)
|
$(
|
||||||
|
assert_eq!(
|
||||||
|
$input,
|
||||||
|
$output,
|
||||||
|
);
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_from![
|
||||||
|
SignedDecimalFloatingPoint::from(1u8) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1i8) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1u16) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1i16) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1u32) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1i32) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1.0f32) => SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
SignedDecimalFloatingPoint::from(1.0f64) => SignedDecimalFloatingPoint::new(1.0)
|
||||||
|
];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
assert_eq!(
|
||||||
|
SignedDecimalFloatingPoint::new(1.0).to_string(),
|
||||||
|
1.0f64.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_new_panic() { SignedDecimalFloatingPoint::new(::std::f64::INFINITY); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parser() {
|
||||||
|
assert_eq!(
|
||||||
|
SignedDecimalFloatingPoint::new(1.0),
|
||||||
|
"1.0".parse::<SignedDecimalFloatingPoint>().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!("garbage".parse::<SignedDecimalFloatingPoint>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_as_f64() {
|
||||||
|
assert_eq!(SignedDecimalFloatingPoint::new(1.0).as_f64(), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deref() {
|
||||||
|
assert_eq!(SignedDecimalFloatingPoint::from(0.1).floor(), 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{DecimalResolution, HdcpLevel};
|
use crate::types::{DecimalResolution, HdcpLevel};
|
||||||
use crate::utils::parse_u64;
|
|
||||||
use crate::utils::{quote, unquote};
|
use crate::utils::{quote, unquote};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||||
///
|
///
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct StreamInf {
|
pub struct StreamInf {
|
||||||
bandwidth: u64,
|
bandwidth: u64,
|
||||||
average_bandwidth: Option<u64>,
|
average_bandwidth: Option<u64>,
|
||||||
|
@ -48,9 +47,7 @@ impl StreamInf {
|
||||||
/// let stream = StreamInf::new(20);
|
/// let stream = StreamInf::new(20);
|
||||||
/// assert_eq!(stream.bandwidth(), 20);
|
/// assert_eq!(stream.bandwidth(), 20);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn bandwidth(&self) -> u64 {
|
pub const fn bandwidth(&self) -> u64 { self.bandwidth }
|
||||||
self.bandwidth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the peak segment bit rate of the variant stream.
|
/// Sets the peak segment bit rate of the variant stream.
|
||||||
///
|
///
|
||||||
|
@ -77,9 +74,7 @@ impl StreamInf {
|
||||||
/// let stream = StreamInf::new(20);
|
/// let stream = StreamInf::new(20);
|
||||||
/// assert_eq!(stream.video(), &None);
|
/// assert_eq!(stream.video(), &None);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn video(&self) -> &Option<String> {
|
pub const fn video(&self) -> &Option<String> { &self.video }
|
||||||
&self.video
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the group identifier for the video in the variant stream.
|
/// Sets the group identifier for the video in the variant stream.
|
||||||
///
|
///
|
||||||
|
@ -106,9 +101,7 @@ impl StreamInf {
|
||||||
/// let stream = StreamInf::new(20);
|
/// let stream = StreamInf::new(20);
|
||||||
/// assert_eq!(stream.average_bandwidth(), None);
|
/// assert_eq!(stream.average_bandwidth(), None);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn average_bandwidth(&self) -> Option<u64> {
|
pub const fn average_bandwidth(&self) -> Option<u64> { self.average_bandwidth }
|
||||||
self.average_bandwidth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the average segment bit rate of the variant stream.
|
/// Sets the average segment bit rate of the variant stream.
|
||||||
///
|
///
|
||||||
|
@ -126,7 +119,8 @@ impl StreamInf {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A string that represents the list of codec types contained the variant stream.
|
/// A string that represents the list of codec types contained the variant
|
||||||
|
/// stream.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -135,11 +129,10 @@ impl StreamInf {
|
||||||
/// let stream = StreamInf::new(20);
|
/// let stream = StreamInf::new(20);
|
||||||
/// assert_eq!(stream.codecs(), &None);
|
/// assert_eq!(stream.codecs(), &None);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn codecs(&self) -> &Option<String> {
|
pub const fn codecs(&self) -> &Option<String> { &self.codecs }
|
||||||
&self.codecs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A string that represents the list of codec types contained the variant stream.
|
/// A string that represents the list of codec types contained the variant
|
||||||
|
/// stream.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -182,6 +175,8 @@ impl StreamInf {
|
||||||
///
|
///
|
||||||
/// stream.set_resolution(1920, 1080);
|
/// stream.set_resolution(1920, 1080);
|
||||||
/// assert_eq!(stream.resolution(), Some((1920, 1080)));
|
/// assert_eq!(stream.resolution(), Some((1920, 1080)));
|
||||||
|
/// # stream.set_resolution(1280, 10);
|
||||||
|
/// # assert_eq!(stream.resolution(), Some((1280, 10)));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_resolution(&mut self, width: usize, height: usize) -> &mut Self {
|
pub fn set_resolution(&mut self, width: usize, height: usize) -> &mut Self {
|
||||||
if let Some(res) = &mut self.resolution {
|
if let Some(res) = &mut self.resolution {
|
||||||
|
@ -202,9 +197,7 @@ impl StreamInf {
|
||||||
/// let stream = StreamInf::new(20);
|
/// let stream = StreamInf::new(20);
|
||||||
/// assert_eq!(stream.hdcp_level(), None);
|
/// assert_eq!(stream.hdcp_level(), None);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn hdcp_level(&self) -> Option<HdcpLevel> {
|
pub const fn hdcp_level(&self) -> Option<HdcpLevel> { self.hdcp_level }
|
||||||
self.hdcp_level
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The HDCP level of the variant stream.
|
/// The HDCP level of the variant stream.
|
||||||
///
|
///
|
||||||
|
@ -259,20 +252,21 @@ impl FromStr for StreamInf {
|
||||||
|
|
||||||
for (key, value) in input.parse::<AttributePairs>()? {
|
for (key, value) in input.parse::<AttributePairs>()? {
|
||||||
match key.as_str() {
|
match key.as_str() {
|
||||||
"BANDWIDTH" => bandwidth = Some(parse_u64(value)?),
|
"BANDWIDTH" => bandwidth = Some(value.parse::<u64>()?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(value.parse::<u64>()?),
|
||||||
"CODECS" => codecs = Some(unquote(value)),
|
"CODECS" => codecs = Some(unquote(value)),
|
||||||
"RESOLUTION" => resolution = Some(value.parse()?),
|
"RESOLUTION" => resolution = Some(value.parse()?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(value.parse()?),
|
"HDCP-LEVEL" => hdcp_level = Some(value.parse()?),
|
||||||
"VIDEO" => video = Some(unquote(value)),
|
"VIDEO" => video = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized
|
||||||
|
// AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?;
|
let bandwidth = bandwidth.ok_or_else(|| Error::missing_value("BANDWIDTH"))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
@ -284,3 +278,66 @@ impl FromStr for StreamInf {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
let mut stream_inf = StreamInf::new(200);
|
||||||
|
stream_inf.set_average_bandwidth(Some(15));
|
||||||
|
stream_inf.set_codecs(Some("mp4a.40.2,avc1.4d401e"));
|
||||||
|
stream_inf.set_resolution(1920, 1080);
|
||||||
|
stream_inf.set_hdcp_level(Some(HdcpLevel::Type0));
|
||||||
|
stream_inf.set_video(Some("video"));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stream_inf.to_string(),
|
||||||
|
"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_inf = StreamInf::new(200);
|
||||||
|
stream_inf.set_average_bandwidth(Some(15));
|
||||||
|
stream_inf.set_codecs(Some("mp4a.40.2,avc1.4d401e"));
|
||||||
|
stream_inf.set_resolution(1920, 1080);
|
||||||
|
stream_inf.set_hdcp_level(Some(HdcpLevel::Type0));
|
||||||
|
stream_inf.set_video(Some("video"));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stream_inf,
|
||||||
|
"BANDWIDTH=200,\
|
||||||
|
AVERAGE-BANDWIDTH=15,\
|
||||||
|
CODECS=\"mp4a.40.2,avc1.4d401e\",\
|
||||||
|
RESOLUTION=1920x1080,\
|
||||||
|
HDCP-LEVEL=TYPE-0,\
|
||||||
|
VIDEO=\"video\""
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stream_inf,
|
||||||
|
"BANDWIDTH=200,\
|
||||||
|
AVERAGE-BANDWIDTH=15,\
|
||||||
|
CODECS=\"mp4a.40.2,avc1.4d401e\",\
|
||||||
|
RESOLUTION=1920x1080,\
|
||||||
|
HDCP-LEVEL=TYPE-0,\
|
||||||
|
VIDEO=\"video\",\
|
||||||
|
UNKNOWN=\"value\""
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!("garbage".parse::<StreamInf>().is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
49
src/utils.rs
49
src/utils.rs
|
@ -1,5 +1,26 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
macro_rules! impl_from {
|
||||||
|
( $($( $type:tt ),* => $target:path ),* ) => {
|
||||||
|
use ::core::convert::From;
|
||||||
|
|
||||||
|
$( // repeat $target
|
||||||
|
$( // repeat $type
|
||||||
|
impl From<$type> for $target {
|
||||||
|
fn from(value: $type) -> Self {
|
||||||
|
Self::from_f64_unchecked(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from![
|
||||||
|
u8, u16, u32 => crate::types::DecimalFloatingPoint,
|
||||||
|
u8, i8, u16, i16, u32, i32, f32, f64 => crate::types::SignedDecimalFloatingPoint
|
||||||
|
];
|
||||||
|
|
||||||
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"YES" => Ok(true),
|
"YES" => Ok(true),
|
||||||
|
@ -8,11 +29,6 @@ pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> crate::Result<u64> {
|
|
||||||
let n = s.as_ref().parse().map_err(Error::unknown)?; // TODO: Error::number
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// According to the documentation the following characters are forbidden
|
/// According to the documentation the following characters are forbidden
|
||||||
/// inside a quoted string:
|
/// inside a quoted string:
|
||||||
/// - carriage return (`\r`)
|
/// - carriage return (`\r`)
|
||||||
|
@ -31,17 +47,17 @@ pub(crate) fn unquote<T: ToString>(value: T) -> String {
|
||||||
|
|
||||||
/// Puts a string inside quotes.
|
/// Puts a string inside quotes.
|
||||||
pub(crate) fn quote<T: ToString>(value: T) -> String {
|
pub(crate) fn quote<T: ToString>(value: T) -> String {
|
||||||
// the replace is for the case, that quote is called on an already quoted string, which could
|
// the replace is for the case, that quote is called on an already quoted
|
||||||
// cause problems!
|
// string, which could cause problems!
|
||||||
format!("\"{}\"", value.to_string().replace("\"", ""))
|
format!("\"{}\"", value.to_string().replace("\"", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks, if the given tag is at the start of the input. If this is the case, it will remove it
|
/// Checks, if the given tag is at the start of the input. If this is the case,
|
||||||
/// and return the rest of the input.
|
/// it will remove it and return the rest of the input.
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
/// This function will return `Error::MissingTag`, if the input doesn't start with the tag, that
|
/// This function will return `Error::MissingTag`, if the input doesn't start
|
||||||
/// has been passed to this function.
|
/// with the tag, that has been passed to this function.
|
||||||
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
|
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
|
||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
|
@ -61,14 +77,7 @@ mod tests {
|
||||||
fn test_parse_yes_or_no() {
|
fn test_parse_yes_or_no() {
|
||||||
assert!(parse_yes_or_no("YES").unwrap());
|
assert!(parse_yes_or_no("YES").unwrap());
|
||||||
assert!(!parse_yes_or_no("NO").unwrap());
|
assert!(!parse_yes_or_no("NO").unwrap());
|
||||||
// TODO: test for error
|
assert!(parse_yes_or_no("garbage").is_err());
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_u64() {
|
|
||||||
assert_eq!(parse_u64("1").unwrap(), 1);
|
|
||||||
assert_eq!(parse_u64("25").unwrap(), 25);
|
|
||||||
// TODO: test for error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -99,5 +108,7 @@ mod tests {
|
||||||
|
|
||||||
let input = tag(input, "A").unwrap();
|
let input = tag(input, "A").unwrap();
|
||||||
assert_eq!(input, "SampleString");
|
assert_eq!(input, "SampleString");
|
||||||
|
|
||||||
|
assert!(tag(input, "B").is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue