Skip to main content

msf_rtsp/header/
range.rs

1//! Range and Media-Range types.
2
3use std::{
4    fmt::{self, Display, Formatter},
5    num::ParseFloatError,
6    str::FromStr,
7};
8
9use crate::{Error, header::HeaderFieldValue};
10
11/// NPT range.
12///
13/// It can be used either as the `Range` header or as the `Media-Range` header.
14#[derive(Debug, Copy, Clone)]
15pub enum NptRange {
16    StartFrom(NptTime),
17    EndAt(NptTime),
18    Range(NptTime, NptTime),
19}
20
21impl Display for NptRange {
22    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23        write!(f, "npt=")?;
24
25        match self {
26            Self::StartFrom(start) => write!(f, "{start}-"),
27            Self::EndAt(end) => write!(f, "-{end}"),
28            Self::Range(start, end) => write!(f, "{start}-{end}"),
29        }
30    }
31}
32
33impl From<NptRange> for HeaderFieldValue {
34    #[inline]
35    fn from(value: NptRange) -> Self {
36        HeaderFieldValue::from(value.to_string())
37    }
38}
39
40impl FromStr for NptRange {
41    type Err = Error;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        let range = s
45            .strip_prefix("npt=")
46            .ok_or_else(|| Error::from_static_msg("not an NPT range"))?;
47
48        let (start, end) = range
49            .split_once('-')
50            .ok_or_else(|| Error::from_static_msg("invalid NPT range"))?;
51
52        let start = if start.is_empty() {
53            None
54        } else {
55            start
56                .parse()
57                .map(Some)
58                .map_err(|_| Error::from_static_msg("invalid NPT range"))?
59        };
60
61        let end = if end.is_empty() {
62            None
63        } else {
64            end.parse()
65                .map(Some)
66                .map_err(|_| Error::from_static_msg("invalid NPT range"))?
67        };
68
69        if let Some(start) = start {
70            if let Some(end) = end {
71                Ok(Self::Range(start, end))
72            } else {
73                Ok(Self::StartFrom(start))
74            }
75        } else if let Some(end) = end {
76            Ok(Self::EndAt(end))
77        } else {
78            Err(Error::from_static_msg("invalid NPT range"))
79        }
80    }
81}
82
83impl TryFrom<&HeaderFieldValue> for NptRange {
84    type Error = Error;
85
86    fn try_from(value: &HeaderFieldValue) -> Result<Self, Self::Error> {
87        value
88            .to_str()
89            .map_err(|_| Error::from_static_msg("header field is not UTF-8 encoded"))?
90            .parse()
91    }
92}
93
94/// NPT time.
95#[derive(Debug, Copy, Clone)]
96pub enum NptTime {
97    Now,
98    Timestamp(f64),
99}
100
101impl Display for NptTime {
102    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::Now => f.write_str("now"),
105            Self::Timestamp(t) => write!(f, "{t:.3}"),
106        }
107    }
108}
109
110impl FromStr for NptTime {
111    type Err = ParseFloatError;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        if s == "now" {
115            Ok(Self::Now)
116        } else {
117            s.parse().map(Self::Timestamp)
118        }
119    }
120}