use std::collections::HashMap;
use std::fmt;
use url::Url;
use crate::description::common::*;
use crate::extmap::*;
pub const EXT_MAP_VALUE_TRANSPORT_CC_KEY: isize = 3;
pub const EXT_MAP_VALUE_TRANSPORT_CC_URI: &str =
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
fn ext_map_uri() -> HashMap<isize, &'static str> {
let mut m = HashMap::new();
m.insert(
EXT_MAP_VALUE_TRANSPORT_CC_KEY,
EXT_MAP_VALUE_TRANSPORT_CC_URI,
);
m
}
#[derive(Debug, Default, Clone)]
pub struct MediaDescription {
pub media_name: MediaName,
pub media_title: Option<Information>,
pub connection_information: Option<ConnectionInformation>,
pub bandwidth: Vec<Bandwidth>,
pub encryption_key: Option<EncryptionKey>,
pub attributes: Vec<Attribute>,
}
impl MediaDescription {
pub fn attribute(&self, key: &str) -> Option<Option<&str>> {
for a in &self.attributes {
if a.key == key {
return Some(a.value.as_ref().map(|s| s.as_ref()));
}
}
None
}
pub fn new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self {
MediaDescription {
media_name: MediaName {
media: codec_type,
port: RangedPort {
value: 9,
range: None,
},
protos: vec![
"UDP".to_string(),
"TLS".to_string(),
"RTP".to_string(),
"SAVPF".to_string(),
],
formats: vec![],
},
media_title: None,
connection_information: Some(ConnectionInformation {
network_type: "IN".to_string(),
address_type: "IP4".to_string(),
address: Some(Address {
address: "0.0.0.0".to_string(),
ttl: None,
range: None,
}),
}),
bandwidth: vec![],
encryption_key: None,
attributes: vec![],
}
}
pub fn with_property_attribute(mut self, key: String) -> Self {
self.attributes.push(Attribute::new(key, None));
self
}
pub fn with_value_attribute(mut self, key: String, value: String) -> Self {
self.attributes.push(Attribute::new(key, Some(value)));
self
}
pub fn with_fingerprint(self, algorithm: String, value: String) -> Self {
self.with_value_attribute("fingerprint".to_owned(), algorithm + " " + &value)
}
pub fn with_ice_credentials(self, username: String, password: String) -> Self {
self.with_value_attribute("ice-ufrag".to_string(), username)
.with_value_attribute("ice-pwd".to_string(), password)
}
pub fn with_codec(
mut self,
payload_type: u8,
name: String,
clockrate: u32,
channels: u16,
fmtp: String,
) -> Self {
self.media_name.formats.push(payload_type.to_string());
let mut rtpmap = format!("{payload_type} {name}/{clockrate}");
if channels > 0 {
rtpmap += format!("/{channels}").as_str();
}
if !fmtp.is_empty() {
self.with_value_attribute("rtpmap".to_string(), rtpmap)
.with_value_attribute("fmtp".to_string(), format!("{payload_type} {fmtp}"))
} else {
self.with_value_attribute("rtpmap".to_string(), rtpmap)
}
}
pub fn with_media_source(
self,
ssrc: u32,
cname: String,
stream_label: String,
label: String,
) -> Self {
self.
with_value_attribute("ssrc".to_string(), format!("{ssrc} cname:{cname}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} msid:{stream_label} {label}")).
with_value_attribute("ssrc".to_string(), format!("{ssrc} mslabel:{stream_label}")). with_value_attribute("ssrc".to_string(), format!("{ssrc} label:{label}"))
}
pub fn with_candidate(self, value: String) -> Self {
self.with_value_attribute("candidate".to_string(), value)
}
pub fn with_extmap(self, e: ExtMap) -> Self {
self.with_property_attribute(e.marshal())
}
pub fn with_transport_cc_extmap(self) -> Self {
let uri = {
let m = ext_map_uri();
if let Some(uri_str) = m.get(&EXT_MAP_VALUE_TRANSPORT_CC_KEY) {
match Url::parse(uri_str) {
Ok(uri) => Some(uri),
Err(_) => None,
}
} else {
None
}
};
let e = ExtMap {
value: EXT_MAP_VALUE_TRANSPORT_CC_KEY,
uri,
..Default::default()
};
self.with_extmap(e)
}
}
#[derive(Debug, Default, Clone)]
pub struct RangedPort {
pub value: isize,
pub range: Option<isize>,
}
impl fmt::Display for RangedPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(range) = self.range {
write!(f, "{}/{}", self.value, range)
} else {
write!(f, "{}", self.value)
}
}
}
#[derive(Debug, Default, Clone)]
pub struct MediaName {
pub media: String,
pub port: RangedPort,
pub protos: Vec<String>,
pub formats: Vec<String>,
}
impl fmt::Display for MediaName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = [
self.media.clone(),
self.port.to_string(),
self.protos.join("/"),
self.formats.join(" "),
];
write!(f, "{}", s.join(" "))
}
}
#[cfg(test)]
mod tests {
use super::MediaDescription;
#[test]
fn test_attribute_missing() {
let media_description = MediaDescription::default();
assert_eq!(media_description.attribute("recvonly"), None);
}
#[test]
fn test_attribute_present_with_no_value() {
let media_description =
MediaDescription::default().with_property_attribute("recvonly".to_owned());
assert_eq!(media_description.attribute("recvonly"), Some(None));
}
#[test]
fn test_attribute_present_with_value() {
let media_description =
MediaDescription::default().with_value_attribute("ptime".to_owned(), "1".to_owned());
assert_eq!(media_description.attribute("ptime"), Some(Some("1")));
}
}