msf_sdp/
media.rs

1//! Media description.
2
3use std::{
4    fmt::{self, Display, Formatter},
5    str::FromStr,
6};
7
8use str_reader::StringReader;
9
10use crate::{
11    attribute::{Attribute, Attributes},
12    bandwidth::Bandwidth,
13    connection::ConnectionInfo,
14    key::EncryptionKey,
15    parser::{FromSessionDescriptionLines, SessionDescriptionLines},
16    ParseError,
17};
18
19/// Builder for the media description.
20#[derive(Clone)]
21pub struct MediaDescriptionBuilder {
22    inner: MediaDescription,
23}
24
25impl MediaDescriptionBuilder {
26    /// Create a new media description builder.
27    const fn new(media_type: String, port: u16, protocol: String) -> Self {
28        let inner = MediaDescription {
29            media_type,
30            port,
31            port_count: None,
32            protocol,
33            formats: Vec::new(),
34            title: None,
35            bandwidth: Vec::new(),
36            key: None,
37            connection: Vec::new(),
38            attributes: Attributes::new(),
39        };
40
41        Self { inner }
42    }
43
44    /// Set number of ports.
45    #[inline]
46    pub fn port_count(&mut self, port_count: u16) -> &mut Self {
47        self.inner.port_count = Some(port_count);
48        self
49    }
50
51    /// Add a given format.
52    #[inline]
53    pub fn format<T>(&mut self, fmt: T) -> &mut Self
54    where
55        T: ToString,
56    {
57        self.inner.formats.push(fmt.to_string());
58        self
59    }
60
61    /// Set media title.
62    #[inline]
63    pub fn title<T>(&mut self, title: T) -> &mut Self
64    where
65        T: ToString,
66    {
67        self.inner.title = Some(title.to_string());
68        self
69    }
70
71    /// Add a given bandwidth info.
72    #[inline]
73    pub fn bandwidth(&mut self, bandwidth: Bandwidth) -> &mut Self {
74        self.inner.bandwidth.push(bandwidth);
75        self
76    }
77
78    /// Set encryption key.
79    #[inline]
80    pub fn key(&mut self, key: EncryptionKey) -> &mut Self {
81        self.inner.key = Some(key);
82        self
83    }
84
85    /// Add a given connection info.
86    #[inline]
87    pub fn connection(&mut self, connection: ConnectionInfo) -> &mut Self {
88        self.inner.connection.push(connection);
89        self
90    }
91
92    /// Add a given flag.
93    #[inline]
94    pub fn flag<T>(&mut self, name: T) -> &mut Self
95    where
96        T: ToString,
97    {
98        self.inner.attributes.push(Attribute::new_flag(name));
99        self
100    }
101
102    /// Add a given attribute.
103    #[inline]
104    pub fn attribute<T, U>(&mut self, name: T, value: U) -> &mut Self
105    where
106        T: ToString,
107        U: ToString,
108    {
109        self.inner
110            .attributes
111            .push(Attribute::new_attribute(name, value));
112        self
113    }
114
115    /// Build the media description.
116    #[inline]
117    pub fn build(self) -> MediaDescription {
118        self.inner
119    }
120}
121
122/// Media format.
123pub type MediaFormat = String;
124
125/// Media description.
126#[derive(Clone)]
127pub struct MediaDescription {
128    media_type: String,
129    port: u16,
130    port_count: Option<u16>,
131    protocol: String,
132    formats: Vec<MediaFormat>,
133    title: Option<String>,
134    bandwidth: Vec<Bandwidth>,
135    key: Option<EncryptionKey>,
136    connection: Vec<ConnectionInfo>,
137    attributes: Attributes,
138}
139
140impl MediaDescription {
141    /// Create a new empty media description.
142    const fn new() -> Self {
143        Self {
144            media_type: String::new(),
145            port: 0,
146            port_count: None,
147            protocol: String::new(),
148            formats: Vec::new(),
149            title: None,
150            bandwidth: Vec::new(),
151            key: None,
152            connection: Vec::new(),
153            attributes: Attributes::new(),
154        }
155    }
156
157    /// Get a new media description builder.
158    #[inline]
159    pub fn builder<T, U>(media_type: T, port: u16, protocol: U) -> MediaDescriptionBuilder
160    where
161        T: ToString,
162        U: ToString,
163    {
164        MediaDescriptionBuilder::new(media_type.to_string(), port, protocol.to_string())
165    }
166
167    /// Get the media type (e.g. "audio" or "video").
168    #[inline]
169    pub fn media_type(&self) -> &str {
170        &self.media_type
171    }
172
173    /// Get the port number for the media.
174    #[inline]
175    pub fn port(&self) -> u16 {
176        self.port
177    }
178
179    /// Get the number of ports (if specified).
180    #[inline]
181    pub fn port_count(&self) -> Option<u16> {
182        self.port_count
183    }
184
185    /// Get the protocol.
186    #[inline]
187    pub fn protocol(&self) -> &str {
188        &self.protocol
189    }
190
191    /// Get the media formats.
192    #[inline]
193    pub fn formats(&self) -> &[MediaFormat] {
194        &self.formats
195    }
196
197    /// Get the media title.
198    #[inline]
199    pub fn title(&self) -> Option<&str> {
200        self.title.as_deref()
201    }
202
203    /// Get bandwidth fields.
204    #[inline]
205    pub fn bandwidth(&self) -> &[Bandwidth] {
206        &self.bandwidth
207    }
208
209    /// Get encryption key.
210    #[inline]
211    pub fn encryption_key(&self) -> Option<&EncryptionKey> {
212        self.key.as_ref()
213    }
214
215    /// Get the connection info.
216    #[inline]
217    pub fn connection(&self) -> &[ConnectionInfo] {
218        &self.connection
219    }
220
221    /// Get the attributes.
222    #[inline]
223    pub fn attributes(&self) -> &Attributes {
224        &self.attributes
225    }
226}
227
228impl Display for MediaDescription {
229    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
230        write!(f, "m={} {}", self.media_type, self.port)?;
231
232        if let Some(count) = self.port_count {
233            write!(f, "/{count}")?;
234        }
235
236        write!(f, " {}", self.protocol)?;
237
238        for fmt in &self.formats {
239            write!(f, " {fmt}")?;
240        }
241
242        f.write_str("\r\n")?;
243
244        if let Some(title) = self.title.as_ref() {
245            write!(f, "i={title}\r\n")?;
246        }
247
248        for connection in &self.connection {
249            write!(f, "c={connection}\r\n")?;
250        }
251
252        for bw in &self.bandwidth {
253            write!(f, "b={bw}\r\n")?;
254        }
255
256        if let Some(k) = self.key.as_ref() {
257            write!(f, "k={k}\r\n")?;
258        }
259
260        for attr in self.attributes.iter() {
261            write!(f, "a={attr}\r\n")?;
262        }
263
264        Ok(())
265    }
266}
267
268impl FromSessionDescriptionLines for MediaDescription {
269    fn from_sdp_lines(lines: &mut SessionDescriptionLines) -> Result<Self, ParseError> {
270        let (t, v) = lines.current().unwrap();
271
272        debug_assert_eq!(t, 'm');
273
274        let mut mdp = MediaDescription::new();
275
276        let mut reader = StringReader::new(v);
277
278        mdp.media_type = String::from(reader.read_word());
279
280        mdp.port = reader.read_u16()?;
281
282        reader.skip_whitespace();
283
284        if reader.current_char() == Some('/') {
285            reader.skip_char();
286
287            mdp.port_count = Some(reader.read_u16()?);
288        }
289
290        mdp.protocol = String::from(reader.read_word());
291
292        loop {
293            reader.skip_whitespace();
294
295            if reader.is_empty() {
296                break;
297            }
298
299            mdp.formats.push(String::from(reader.read_word()));
300        }
301
302        lines.next()?;
303
304        while let Some((t, _)) = lines.current() {
305            match t {
306                'm' => break,
307                'i' => mdp.title = Some(lines.parse()?),
308                'c' => mdp.connection.push(lines.parse()?),
309                'b' => mdp.bandwidth.push(lines.parse()?),
310                'k' => mdp.key = Some(lines.parse()?),
311                'a' => mdp.attributes.push(lines.parse()?),
312                _ => {
313                    return Err(ParseError::with_msg(format!(
314                        "unknown media description field: {t}",
315                    )))
316                }
317            }
318        }
319
320        Ok(mdp)
321    }
322}
323
324impl FromStr for MediaDescription {
325    type Err = ParseError;
326
327    fn from_str(s: &str) -> Result<Self, Self::Err> {
328        let mut lines = SessionDescriptionLines::new(s)?;
329
330        MediaDescription::from_sdp_lines(&mut lines)
331    }
332}