1use std::collections::HashMap;
2use std::fmt;
3
4use url::Url;
5
6use crate::description::common::*;
7use crate::extmap::*;
8
9pub const EXT_MAP_VALUE_TRANSPORT_CC_KEY: u16 = 3;
11pub const EXT_MAP_VALUE_TRANSPORT_CC_URI: &str =
12 "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
13
14fn ext_map_uri() -> HashMap<u16, &'static str> {
15 let mut m = HashMap::new();
16 m.insert(
17 EXT_MAP_VALUE_TRANSPORT_CC_KEY,
18 EXT_MAP_VALUE_TRANSPORT_CC_URI,
19 );
20 m
21}
22
23#[derive(Debug, Default, Clone)]
31pub struct MediaDescription {
32 pub media_name: MediaName,
36
37 pub media_title: Option<Information>,
41
42 pub connection_information: Option<ConnectionInformation>,
46
47 pub bandwidth: Vec<Bandwidth>,
51
52 pub encryption_key: Option<EncryptionKey>,
58
59 pub attributes: Vec<Attribute>,
65}
66
67impl MediaDescription {
68 pub fn has_attribute(&self, key: &str) -> bool {
70 self.attributes.iter().any(|a| a.key == key)
71 }
72
73 pub fn attribute(&self, key: &str) -> Option<Option<&str>> {
75 for a in &self.attributes {
76 if a.key == key {
77 return Some(a.value.as_ref().map(|s| s.as_ref()));
78 }
79 }
80 None
81 }
82
83 pub fn new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self {
86 MediaDescription {
87 media_name: MediaName {
88 media: codec_type,
89 port: RangedPort {
90 value: 9,
91 range: None,
92 },
93 protos: vec![
94 "UDP".to_string(),
95 "TLS".to_string(),
96 "RTP".to_string(),
97 "SAVPF".to_string(),
98 ],
99 formats: vec![],
100 },
101 media_title: None,
102 connection_information: Some(ConnectionInformation {
103 network_type: "IN".to_string(),
104 address_type: "IP4".to_string(),
105 address: Some(Address {
106 address: "0.0.0.0".to_string(),
107 ttl: None,
108 range: None,
109 }),
110 }),
111 bandwidth: vec![],
112 encryption_key: None,
113 attributes: vec![],
114 }
115 }
116
117 pub fn with_property_attribute(mut self, key: String) -> Self {
119 self.attributes.push(Attribute::new(key, None));
120 self
121 }
122
123 pub fn with_value_attribute(mut self, key: String, value: String) -> Self {
125 self.attributes.push(Attribute::new(key, Some(value)));
126 self
127 }
128
129 pub fn with_fingerprint(self, algorithm: String, value: String) -> Self {
131 self.with_value_attribute("fingerprint".to_owned(), algorithm + " " + &value)
132 }
133
134 pub fn with_ice_credentials(self, username: String, password: String) -> Self {
136 self.with_value_attribute("ice-ufrag".to_string(), username)
137 .with_value_attribute("ice-pwd".to_string(), password)
138 }
139
140 pub fn with_codec(
142 mut self,
143 payload_type: u8,
144 name: String,
145 clockrate: u32,
146 channels: u16,
147 fmtp: String,
148 ) -> Self {
149 self.media_name.formats.push(payload_type.to_string());
150 let rtpmap = if channels > 0 {
151 format!("{payload_type} {name}/{clockrate}/{channels}")
152 } else {
153 format!("{payload_type} {name}/{clockrate}")
154 };
155
156 if !fmtp.is_empty() {
157 self.with_value_attribute("rtpmap".to_string(), rtpmap)
158 .with_value_attribute("fmtp".to_string(), format!("{payload_type} {fmtp}"))
159 } else {
160 self.with_value_attribute("rtpmap".to_string(), rtpmap)
161 }
162 }
163
164 pub fn with_media_source(
166 self,
167 ssrc: u32,
168 cname: String,
169 stream_label: String,
170 label: String,
171 ) -> Self {
172 self.
173 with_value_attribute("ssrc".to_string(), format!("{ssrc} cname:{cname}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} msid:{stream_label} {label}")).
175 with_value_attribute("ssrc".to_string(), format!("{ssrc} mslabel:{stream_label}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} label:{label}"))
177 }
179
180 pub fn with_candidate(self, value: String) -> Self {
183 self.with_value_attribute("candidate".to_string(), value)
184 }
185
186 pub fn with_extmap(self, e: ExtMap) -> Self {
187 self.with_property_attribute(e.marshal())
188 }
189
190 pub fn with_transport_cc_extmap(self) -> Self {
192 let uri = {
193 let m = ext_map_uri();
194 if let Some(uri_str) = m.get(&EXT_MAP_VALUE_TRANSPORT_CC_KEY) {
195 Url::parse(uri_str).ok()
196 } else {
197 None
198 }
199 };
200
201 let e = ExtMap {
202 value: EXT_MAP_VALUE_TRANSPORT_CC_KEY,
203 uri,
204 ..Default::default()
205 };
206
207 self.with_extmap(e)
208 }
209}
210
211#[derive(Debug, Default, Clone)]
216pub struct RangedPort {
217 pub value: isize,
218 pub range: Option<isize>,
219}
220
221impl fmt::Display for RangedPort {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 if let Some(range) = self.range {
224 write!(f, "{}/{}", self.value, range)
225 } else {
226 write!(f, "{}", self.value)
227 }
228 }
229}
230
231#[derive(Debug, Default, Clone)]
233pub struct MediaName {
234 pub media: String,
235 pub port: RangedPort,
236 pub protos: Vec<String>,
237 pub formats: Vec<String>,
238}
239
240impl fmt::Display for MediaName {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "{} {}", self.media, self.port)?;
243
244 let mut first = true;
245 for part in &self.protos {
246 if first {
247 first = false;
248 write!(f, " {part}")?;
249 } else {
250 write!(f, "/{part}")?;
251 }
252 }
253
254 for part in &self.formats {
255 write!(f, " {part}")?;
256 }
257
258 Ok(())
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::MediaDescription;
265
266 #[test]
267 fn test_attribute_missing() {
268 let media_description = MediaDescription::default();
269
270 assert_eq!(media_description.attribute("recvonly"), None);
271 }
272
273 #[test]
274 fn test_attribute_present_with_no_value() {
275 let media_description =
276 MediaDescription::default().with_property_attribute("recvonly".to_owned());
277
278 assert_eq!(media_description.attribute("recvonly"), Some(None));
279 }
280
281 #[test]
282 fn test_attribute_present_with_value() {
283 let media_description =
284 MediaDescription::default().with_value_attribute("ptime".to_owned(), "1".to_owned());
285
286 assert_eq!(media_description.attribute("ptime"), Some(Some("1")));
287 }
288}