1package eu.siacs.conversations.xmpp.jingle.stanzas;
2
3import com.google.common.base.Preconditions;
4import com.google.common.collect.ImmutableList;
5
6import java.util.List;
7import java.util.Locale;
8
9import eu.siacs.conversations.xml.Element;
10import eu.siacs.conversations.xml.Namespace;
11
12public class RtpDescription extends GenericDescription {
13
14
15 private RtpDescription(String name, String namespace) {
16 super(name, namespace);
17 }
18
19 public Media getMedia() {
20 return Media.of(this.getAttribute("media"));
21 }
22
23 public List<PayloadType> getPayloadTypes() {
24 final ImmutableList.Builder<PayloadType> builder = new ImmutableList.Builder<>();
25 for(Element child : getChildren()) {
26 if ("payload-type".equals(child.getName())) {
27 builder.add(PayloadType.of(child));
28 }
29 }
30 return builder.build();
31 }
32
33 public List<RtpHeaderExtension> getHeaderExtensions() {
34 final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>();
35 for(final Element child : getChildren()) {
36 if ("rtp-hdrext".equals(child.getName()) && Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(child.getNamespace())) {
37 builder.add(RtpHeaderExtension.upgrade(child));
38 }
39 }
40 return builder.build();
41 }
42
43 public static RtpDescription upgrade(final Element element) {
44 Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description");
45 Preconditions.checkArgument(Namespace.JINGLE_APPS_RTP.equals(element.getNamespace()), "Element does not match the jingle rtp namespace");
46 final RtpDescription description = new RtpDescription("description", Namespace.JINGLE_APPS_RTP);
47 description.setAttributes(element.getAttributes());
48 description.setChildren(element.getChildren());
49 return description;
50 }
51
52 //TODO: support for https://xmpp.org/extensions/xep-0293.html
53
54
55 //XEP-0294: Jingle RTP Header Extensions Negotiation
56 //maps to `extmap:$id $uri`
57 public static class RtpHeaderExtension extends Element {
58
59 private RtpHeaderExtension() {
60 super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
61 }
62
63 public String getId() {
64 return this.getAttribute("id");
65 }
66
67 public String getUri() {
68 return this.getAttribute("uri");
69 }
70
71 public static RtpHeaderExtension upgrade(final Element element) {
72 Preconditions.checkArgument("rtp-hdrext".equals(element.getName()));
73 Preconditions.checkArgument(Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(element.getNamespace()));
74 final RtpHeaderExtension extension = new RtpHeaderExtension();
75 extension.setAttributes(element.getAttributes());
76 extension.setChildren(element.getChildren());
77 return extension;
78 }
79 }
80
81 //maps to `rtpmap $id $name/$clockrate/$channels`
82 public static class PayloadType extends Element {
83
84 private PayloadType(String name, String xmlns) {
85 super(name, xmlns);
86 }
87 public String getId() {
88 return this.getAttribute("id");
89 }
90
91 public String getPayloadTypeName() {
92 return this.getAttribute("name");
93 }
94
95 public int getClockRate() {
96 final String clockRate = this.getAttribute("clockrate");
97 if (clockRate == null) {
98 return 0;
99 }
100 try {
101 return Integer.parseInt(clockRate);
102 } catch (NumberFormatException e) {
103 return 0;
104 }
105 }
106
107 public int getChannels() {
108 final String channels = this.getAttribute("channels");
109 if (channels == null) {
110 return 1; // The number of channels; if omitted, it MUST be assumed to contain one channel
111 }
112 try {
113 return Integer.parseInt(channels);
114 } catch (NumberFormatException e) {
115 return 1;
116 }
117 }
118
119 public List<Parameter> getParameters() {
120 final ImmutableList.Builder<Parameter> builder = new ImmutableList.Builder<>();
121 for (Element child : getChildren()) {
122 if ("parameter".equals(child.getName())) {
123 builder.add(Parameter.of(child));
124 }
125 }
126 return builder.build();
127 }
128
129 public static PayloadType of(final Element element) {
130 Preconditions.checkArgument("payload-type".equals(element.getName()), "element name must be called payload-type");
131 PayloadType payloadType = new PayloadType("payload-type", Namespace.JINGLE_APPS_RTP);
132 payloadType.setAttributes(element.getAttributes());
133 payloadType.setChildren(element.getChildren());
134 return payloadType;
135 }
136 }
137
138 //map to `fmtp $id key=value;key=value
139 //where id is the id of the parent payload-type
140 public static class Parameter extends Element {
141
142 private Parameter() {
143 super("parameter", Namespace.JINGLE_APPS_RTP);
144 }
145
146 public Parameter(String name, String value) {
147 super("parameter", Namespace.JINGLE_APPS_RTP);
148 this.setAttribute("name", name);
149 this.setAttribute("value", value);
150 }
151
152 public String getParameterName() {
153 return this.getAttribute("name");
154 }
155
156 public String getParameterValue() {
157 return this.getAttribute("value");
158 }
159
160 public static Parameter of(final Element element) {
161 Preconditions.checkArgument("parameter".equals(element.getName()), "element name must be called parameter");
162 Parameter parameter = new Parameter();
163 parameter.setAttributes(element.getAttributes());
164 parameter.setChildren(element.getChildren());
165 return parameter;
166 }
167 }
168
169 public enum Media {
170 VIDEO, AUDIO, UNKNOWN;
171
172 @Override
173 public String toString() {
174 return super.toString().toLowerCase(Locale.ROOT);
175 }
176
177 public static Media of(String value) {
178 try {
179 return value == null ? UNKNOWN : Media.valueOf(value.toUpperCase(Locale.ROOT));
180 } catch (IllegalArgumentException e) {
181 return UNKNOWN;
182 }
183 }
184 }
185}