1use std::collections::HashMap;
11use std::fmt;
12
13use url::Url;
14
15use crate::description::common::*;
16use crate::extmap::*;
17use crate::util::{Codec, merge_codecs, parse_fmtp, parse_rtcp_fb, parse_rtpmap};
18
19pub const EXT_MAP_VALUE_TRANSPORT_CC_KEY: u16 = 3;
21pub const EXT_MAP_VALUE_TRANSPORT_CC_URI: &str =
23 "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
24
25fn ext_map_uri() -> HashMap<u16, &'static str> {
26 let mut m = HashMap::new();
27 m.insert(
28 EXT_MAP_VALUE_TRANSPORT_CC_KEY,
29 EXT_MAP_VALUE_TRANSPORT_CC_URI,
30 );
31 m
32}
33
34#[derive(Debug, Default, Clone)]
42pub struct MediaDescription {
43 pub media_name: MediaName,
47
48 pub media_title: Option<Information>,
52
53 pub connection_information: Option<ConnectionInformation>,
57
58 pub bandwidth: Vec<Bandwidth>,
62
63 pub encryption_key: Option<EncryptionKey>,
69
70 pub attributes: Vec<Attribute>,
76}
77
78impl MediaDescription {
79 pub fn has_attribute(&self, key: &str) -> bool {
81 self.attributes.iter().any(|a| a.key == key)
82 }
83
84 pub fn attribute(&self, key: &str) -> Option<Option<&str>> {
86 for a in &self.attributes {
87 if a.key == key {
88 return Some(a.value.as_ref().map(|s| s.as_ref()));
89 }
90 }
91 None
92 }
93
94 pub fn codecs(&self) -> HashMap<u8, Codec> {
99 let mut codecs: HashMap<u8, Codec> = HashMap::new();
100
101 for a in &self.attributes {
102 let attr = a.to_string();
103 if attr.starts_with("rtpmap:") {
104 if let Ok(codec) = parse_rtpmap(&attr) {
105 merge_codecs(codec, &mut codecs);
106 }
107 } else if attr.starts_with("fmtp:") {
108 if let Ok(codec) = parse_fmtp(&attr) {
109 merge_codecs(codec, &mut codecs);
110 }
111 } else if attr.starts_with("rtcp-fb:")
112 && let Ok(codec) = parse_rtcp_fb(&attr)
113 {
114 merge_codecs(codec, &mut codecs);
115 }
116 }
117
118 codecs
119 }
120
121 pub fn new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self {
124 MediaDescription {
125 media_name: MediaName {
126 media: codec_type,
127 port: RangedPort {
128 value: 9,
129 range: None,
130 },
131 protos: vec![
132 "UDP".to_string(),
133 "TLS".to_string(),
134 "RTP".to_string(),
135 "SAVPF".to_string(),
136 ],
137 formats: vec![],
138 },
139 media_title: None,
140 connection_information: Some(ConnectionInformation {
141 network_type: "IN".to_string(),
142 address_type: "IP4".to_string(),
143 address: Some(Address {
144 address: "0.0.0.0".to_string(),
145 ttl: None,
146 range: None,
147 }),
148 }),
149 bandwidth: vec![],
150 encryption_key: None,
151 attributes: vec![],
152 }
153 }
154
155 pub fn with_property_attribute(mut self, key: String) -> Self {
157 self.attributes.push(Attribute::new(key, None));
158 self
159 }
160
161 pub fn with_value_attribute(mut self, key: String, value: String) -> Self {
163 self.attributes.push(Attribute::new(key, Some(value)));
164 self
165 }
166
167 pub fn with_fingerprint(self, algorithm: String, value: String) -> Self {
169 self.with_value_attribute("fingerprint".to_owned(), algorithm + " " + &value)
170 }
171
172 pub fn with_ice_credentials(self, username: String, password: String) -> Self {
174 self.with_value_attribute("ice-ufrag".to_string(), username)
175 .with_value_attribute("ice-pwd".to_string(), password)
176 }
177
178 pub fn with_codec(
180 mut self,
181 payload_type: u8,
182 name: String,
183 clockrate: u32,
184 channels: u16,
185 fmtp: String,
186 ) -> Self {
187 self.media_name.formats.push(payload_type.to_string());
188 let rtpmap = if channels > 0 {
189 format!("{payload_type} {name}/{clockrate}/{channels}")
190 } else {
191 format!("{payload_type} {name}/{clockrate}")
192 };
193
194 if !fmtp.is_empty() {
195 self.with_value_attribute("rtpmap".to_string(), rtpmap)
196 .with_value_attribute("fmtp".to_string(), format!("{payload_type} {fmtp}"))
197 } else {
198 self.with_value_attribute("rtpmap".to_string(), rtpmap)
199 }
200 }
201
202 pub fn with_media_source(
204 self,
205 ssrc: u32,
206 cname: String,
207 stream_id: String,
208 track_id: String,
209 ) -> Self {
210 self.
211 with_value_attribute("ssrc".to_string(), format!("{ssrc} cname:{cname}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} msid:{stream_id} {track_id}")).
213 with_value_attribute("ssrc".to_string(), format!("{ssrc} mslabel:{stream_id}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} label:{track_id}"))
215 }
217
218 pub fn with_candidate(self, value: String) -> Self {
221 self.with_value_attribute("candidate".to_string(), value)
222 }
223
224 pub fn with_extmap(self, e: ExtMap) -> Self {
226 self.with_property_attribute(e.marshal())
227 }
228
229 pub fn with_transport_cc_extmap(self) -> Self {
231 let uri = {
232 let m = ext_map_uri();
233 if let Some(uri_str) = m.get(&EXT_MAP_VALUE_TRANSPORT_CC_KEY) {
234 Url::parse(uri_str).ok()
235 } else {
236 None
237 }
238 };
239
240 let e = ExtMap {
241 value: EXT_MAP_VALUE_TRANSPORT_CC_KEY,
242 uri,
243 ..Default::default()
244 };
245
246 self.with_extmap(e)
247 }
248}
249
250#[derive(Debug, Default, Clone)]
255pub struct RangedPort {
256 pub value: isize,
258 pub range: Option<isize>,
260}
261
262impl fmt::Display for RangedPort {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 if let Some(range) = self.range {
265 write!(f, "{}/{}", self.value, range)
266 } else {
267 write!(f, "{}", self.value)
268 }
269 }
270}
271
272#[derive(Debug, Default, Clone)]
274pub struct MediaName {
275 pub media: String,
277 pub port: RangedPort,
279 pub protos: Vec<String>,
281 pub formats: Vec<String>,
283}
284
285impl fmt::Display for MediaName {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 write!(f, "{} {}", self.media, self.port)?;
288
289 let mut first = true;
290 for part in &self.protos {
291 if first {
292 first = false;
293 write!(f, " {part}")?;
294 } else {
295 write!(f, "/{part}")?;
296 }
297 }
298
299 for part in &self.formats {
300 write!(f, " {part}")?;
301 }
302
303 Ok(())
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::MediaDescription;
310
311 #[test]
312 fn test_attribute_missing() {
313 let media_description = MediaDescription::default();
314
315 assert_eq!(media_description.attribute("recvonly"), None);
316 }
317
318 #[test]
319 fn test_attribute_present_with_no_value() {
320 let media_description =
321 MediaDescription::default().with_property_attribute("recvonly".to_owned());
322
323 assert_eq!(media_description.attribute("recvonly"), Some(None));
324 }
325
326 #[test]
327 fn test_attribute_present_with_value() {
328 let media_description =
329 MediaDescription::default().with_value_attribute("ptime".to_owned(), "1".to_owned());
330
331 assert_eq!(media_description.attribute("ptime"), Some(Some("1")));
332 }
333}