parse sdp to jingle (yet w/o transport)

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/xml/Namespace.java                            |   1 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java |  24 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java          |  74 
src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java                | 108 
src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java           |   2 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Group.java                |  64 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java  |   6 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java         |  17 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java       |   7 
9 files changed, 222 insertions(+), 81 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/xml/Namespace.java 🔗

@@ -36,6 +36,7 @@ public final class Namespace {
     public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
     public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
     public static final String JINGLE_APPS_DTLS = "urn:xmpp:jingle:apps:dtls:0";
+    public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
     public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
     public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
     public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java 🔗

@@ -592,7 +592,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
             content.setTransport(new S5BTransportInfo(this.transportId, candidates));
             Log.d(Config.LOGTAG, String.format("%s: sending S5B offer with %d candidates", id.account.getJid().asBareJid(), candidates.size()));
         }
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sendJinglePacket(packet, (account, response) -> {
             if (response.getType() == IqPacket.TYPE.RESULT) {
                 Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": other party received offer");
@@ -617,7 +617,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         hash.setAttribute("algo", "sha-1").setContent(Base64.encodeToString(file.getSha1Sum(), Base64.NO_WRAP));
 
         final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INFO);
-        packet.setJingleChild(checksum);
+        packet.addJingleChild(checksum);
         this.sendJinglePacket(packet);
     }
 
@@ -651,7 +651,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
                     public void failed() {
                         Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
                         content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
-                        packet.setJingleContent(content);
+                        packet.addJingleContent(content);
                         sendJinglePacket(packet);
                         connectNextCandidate();
                     }
@@ -661,7 +661,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
                         Log.d(Config.LOGTAG, "connected to proxy65 candidate");
                         mergeCandidate(candidate);
                         content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
-                        packet.setJingleContent(content);
+                        packet.addJingleContent(content);
                         sendJinglePacket(packet);
                         connectNextCandidate();
                     }
@@ -669,7 +669,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
             } else {
                 Log.d(Config.LOGTAG, "did not find a proxy65 candidate for ourselves");
                 content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
-                packet.setJingleContent(content);
+                packet.addJingleContent(content);
                 sendJinglePacket(packet);
                 connectNextCandidate();
             }
@@ -682,7 +682,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         final Content content = new Content(contentCreator, contentName);
         content.setDescription(this.description);
         content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.transport.receive(file, onFileTransmissionStatusChanged);
         this.sendJinglePacket(packet);
     }
@@ -909,7 +909,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         Content content = new Content(this.contentCreator, this.contentName);
         this.transportId = JingleConnectionManager.nextRandomId();
         content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sendJinglePacket(packet);
     }
 
@@ -941,7 +941,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
 
         final Content content = new Content(contentCreator, contentName);
         content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
-        answer.setJingleContent(content);
+        answer.addJingleContent(content);
 
         respondToIq(packet, true);
 
@@ -1122,7 +1122,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
         final Content content = new Content(this.contentCreator, this.contentName);
         content.setTransport(new S5BTransportInfo(this.transportId, new Element("activated").setAttribute("cid", cid)));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sendJinglePacket(packet);
     }
 
@@ -1130,7 +1130,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
         final Content content = new Content(this.contentCreator, this.contentName);
         content.setTransport(new S5BTransportInfo(this.transportId, new Element("proxy-error")));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sendJinglePacket(packet);
     }
 
@@ -1138,7 +1138,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
         final Content content = new Content(this.contentCreator, this.contentName);
         content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-used").setAttribute("cid", cid)));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sentCandidate = true;
         if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
             connect();
@@ -1151,7 +1151,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
         JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
         Content content = new Content(this.contentCreator, this.contentName);
         content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-error")));
-        packet.setJingleContent(content);
+        packet.addJingleContent(content);
         this.sentCandidate = true;
         this.sendJinglePacket(packet);
         if (receivedCandidate && mJingleStatus == JINGLE_STATUS_ACCEPTED) {

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -2,12 +2,9 @@ package eu.siacs.conversations.xmpp.jingle;
 
 import android.util.Log;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 
-import org.checkerframework.checker.nullness.compatqual.NullableDecl;
 import org.webrtc.AudioSource;
 import org.webrtc.AudioTrack;
 import org.webrtc.DataChannel;
@@ -26,9 +23,6 @@ import java.util.Map;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
-import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
-import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
 import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
 import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
 import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
@@ -73,18 +67,18 @@ public class JingleRtpConnection extends AbstractJingleConnection {
             //TODO respond with out-of-order
             return;
         }
-        final Map<String, DescriptionTransport> contents;
+        final RtpContentMap contentMap;
         try {
-            contents = DescriptionTransport.of(jinglePacket.getJingleContents());
+            contentMap = RtpContentMap.of(jinglePacket);
         } catch (IllegalArgumentException | NullPointerException e) {
             Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
             return;
         }
-        Log.d(Config.LOGTAG, "processing session-init with " + contents.size() + " contents");
+        Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents");
         final State oldState = this.state;
         if (transition(State.SESSION_INITIALIZED)) {
             if (oldState == State.PROCEED) {
-                processContents(contents);
+                processContents(contentMap);
                 sendSessionAccept();
             } else {
                 //TODO start ringing
@@ -94,9 +88,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
         }
     }
 
-    private void processContents(final Map<String, DescriptionTransport> contents) {
-        for (Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
-            final DescriptionTransport descriptionTransport = content.getValue();
+    private void processContents(final RtpContentMap contentMap) {
+        for (Map.Entry<String, RtpContentMap.DescriptionTransport> content : contentMap.contents.entrySet()) {
+            final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
             final RtpDescription rtpDescription = descriptionTransport.description;
             Log.d(Config.LOGTAG, "receive content with name " + content.getKey() + " and media=" + rtpDescription.getMedia());
             for (RtpDescription.PayloadType payloadType : rtpDescription.getPayloadTypes()) {
@@ -154,7 +148,11 @@ public class JingleRtpConnection extends AbstractJingleConnection {
 
     private void sendSessionInitiate() {
         setupWebRTC();
-        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending session-initiate");
+        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate");
+    }
+
+    private void sendSessionInitiate(RtpContentMap rtpContentMap) {
+        Log.d(Config.LOGTAG, rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId).toString());
     }
 
     private void sendSessionAccept() {
@@ -252,11 +250,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
             @Override
             public void onCreateSuccess(org.webrtc.SessionDescription description) {
                 final SessionDescription sessionDescription = SessionDescription.parse(description.description);
-                Log.d(Config.LOGTAG,"description: "+description.description);
-                for (SessionDescription.Media media : sessionDescription.media) {
-                    Log.d(Config.LOGTAG, RtpDescription.of(media).toString());
-                }
-                Log.d(Config.LOGTAG, sessionDescription.toString());
+                Log.d(Config.LOGTAG, "description: " + description.description);
+                final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
+                sendSessionInitiate(rtpContentMap);
             }
 
             @Override
@@ -306,44 +302,4 @@ public class JingleRtpConnection extends AbstractJingleConnection {
             throw new IllegalStateException(String.format("Unable to transition from %s to %s", this.state, target));
         }
     }
-
-    public static class DescriptionTransport {
-        private final RtpDescription description;
-        private final IceUdpTransportInfo transport;
-
-        public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
-            this.description = description;
-            this.transport = transport;
-        }
-
-        public static DescriptionTransport of(final Content content) {
-            final GenericDescription description = content.getDescription();
-            final GenericTransportInfo transportInfo = content.getTransport();
-            final RtpDescription rtpDescription;
-            final IceUdpTransportInfo iceUdpTransportInfo;
-            if (description instanceof RtpDescription) {
-                rtpDescription = (RtpDescription) description;
-            } else {
-                Log.d(Config.LOGTAG, "description was " + description);
-                throw new IllegalArgumentException("Content does not contain RtpDescription");
-            }
-            if (transportInfo instanceof IceUdpTransportInfo) {
-                iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
-            } else {
-                throw new IllegalArgumentException("Content does not contain ICE-UDP transport");
-            }
-            return new DescriptionTransport(rtpDescription, iceUdpTransportInfo);
-        }
-
-        public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
-            return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
-                @NullableDecl
-                @Override
-                public DescriptionTransport apply(@NullableDecl Content content) {
-                    return content == null ? null : of(content);
-                }
-            }));
-        }
-    }
-
 }

src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java 🔗

@@ -0,0 +1,108 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import android.util.Log;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+import java.util.Map;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
+import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
+import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
+import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
+
+public class RtpContentMap {
+
+    public final Group group;
+    public final Map<String, DescriptionTransport> contents;
+
+    private RtpContentMap(Group group, Map<String, DescriptionTransport> contents) {
+        this.group = group;
+        this.contents = contents;
+    }
+
+    public static RtpContentMap of(final JinglePacket jinglePacket) {
+        return new RtpContentMap(jinglePacket.getGroup(), DescriptionTransport.of(jinglePacket.getJingleContents()));
+    }
+
+    public static RtpContentMap of(final SessionDescription sessionDescription) {
+        final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
+        for (SessionDescription.Media media : sessionDescription.media) {
+            final String id = Iterables.getFirst(media.attributes.get("mid"), null);
+            Preconditions.checkNotNull(id, "media has no mid");
+            contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
+        }
+        final String groupAttribute = Iterables.getFirst(sessionDescription.attributes.get("group"), null);
+        final Group group = groupAttribute == null ? null : Group.ofSdpString(groupAttribute);
+        return new RtpContentMap(group, contentMapBuilder.build());
+    }
+
+    public JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) {
+        final JinglePacket jinglePacket = new JinglePacket(action, sessionId);
+        if (this.group != null) {
+            jinglePacket.addGroup(this.group);
+        }
+        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
+            final Content content = new Content(Content.Creator.INITIATOR, entry.getKey());
+            content.addChild(entry.getValue().description);
+            content.addChild(entry.getValue().transport);
+            jinglePacket.addJingleContent(content);
+        }
+        return jinglePacket;
+    }
+
+    public static class DescriptionTransport {
+        public final RtpDescription description;
+        public final IceUdpTransportInfo transport;
+
+        public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
+            this.description = description;
+            this.transport = transport;
+        }
+
+        public static DescriptionTransport of(final Content content) {
+            final GenericDescription description = content.getDescription();
+            final GenericTransportInfo transportInfo = content.getTransport();
+            final RtpDescription rtpDescription;
+            final IceUdpTransportInfo iceUdpTransportInfo;
+            if (description instanceof RtpDescription) {
+                rtpDescription = (RtpDescription) description;
+            } else {
+                Log.d(Config.LOGTAG, "description was " + description);
+                throw new IllegalArgumentException("Content does not contain RtpDescription");
+            }
+            if (transportInfo instanceof IceUdpTransportInfo) {
+                iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
+            } else {
+                throw new IllegalArgumentException("Content does not contain ICE-UDP transport");
+            }
+            return new DescriptionTransport(rtpDescription, iceUdpTransportInfo);
+        }
+
+        public static DescriptionTransport of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
+            final RtpDescription rtpDescription = RtpDescription.of(media);
+            final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
+            return new DescriptionTransport(rtpDescription, transportInfo);
+        }
+
+        public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
+            return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
+                @NullableDecl
+                @Override
+                public DescriptionTransport apply(@NullableDecl Content content) {
+                    return content == null ? null : of(content);
+                }
+            }));
+        }
+    }
+}

src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java 🔗

@@ -28,7 +28,7 @@ public class SessionDescription {
         this.media = media;
     }
 
-    public static SessionDescription parse(final Map<String, JingleRtpConnection.DescriptionTransport> contents) {
+    public static SessionDescription parse(final Map<String, RtpContentMap.DescriptionTransport> contents) {
         final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
         return sessionDescriptionBuilder.createSessionDescription();
     }

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Group.java 🔗

@@ -0,0 +1,64 @@
+package eu.siacs.conversations.xmpp.jingle.stanzas;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.Namespace;
+
+public class Group extends Element {
+
+    private Group() {
+        super("group", Namespace.JINGLE_APPS_GROUPING);
+    }
+
+    public Group(final String semantics, final Collection<String> identificationTags) {
+        super("group", Namespace.JINGLE_APPS_GROUPING);
+        this.setAttribute("semantics", semantics);
+        for (String tag : identificationTags) {
+            this.addChild(new Element("content").setAttribute("name", tag));
+        }
+    }
+
+    public String getSemantics() {
+        return this.getAttribute("semantics");
+    }
+
+    public List<String> getIdentificationTags() {
+        final ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
+        for (final Element child : this.children) {
+            if ("content".equals(child.getName())) {
+                final String name = child.getAttribute("name");
+                if (name != null) {
+                    builder.add(name);
+                }
+            }
+        }
+        return builder.build();
+    }
+
+    public static Group ofSdpString(final String input) {
+        ImmutableList.Builder<String> tagBuilder = new ImmutableList.Builder<>();
+        final String[] parts = input.split(" ");
+        if (parts.length >= 2) {
+            final String semantics = parts[0];
+            for(int i = 1; i < parts.length; ++i) {
+                tagBuilder.add(parts[i]);
+            }
+            return new Group(semantics,tagBuilder.build());
+        }
+        return null;
+    }
+
+    public static Group upgrade(final Element element) {
+        Preconditions.checkArgument("group".equals(element.getName()));
+        Preconditions.checkArgument(Namespace.JINGLE_APPS_GROUPING.equals(element.getNamespace()));
+        final Group group = new Group();
+        group.setAttributes(element.getAttributes());
+        group.setChildren(element.getChildren());
+        return group;
+    }
+}

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java 🔗

@@ -10,8 +10,8 @@ import eu.siacs.conversations.xml.Namespace;
 
 public class IceUdpTransportInfo extends GenericTransportInfo {
 
-    private IceUdpTransportInfo(final String name, final String xmlns) {
-        super(name, xmlns);
+    public IceUdpTransportInfo() {
+        super("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
     }
 
     public Fingerprint getFingerprint() {
@@ -32,7 +32,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
     public static IceUdpTransportInfo upgrade(final Element element) {
         Preconditions.checkArgument("transport".equals(element.getName()), "Name of provided element is not transport");
         Preconditions.checkArgument(Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(element.getNamespace()), "Element does not match ice-udp transport namespace");
-        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
+        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
         transportInfo.setAttributes(element.getAttributes());
         transportInfo.setChildren(element.getChildren());
         return transportInfo;

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java 🔗

@@ -1,7 +1,6 @@
 package eu.siacs.conversations.xmpp.jingle.stanzas;
 
 import android.support.annotation.NonNull;
-import android.util.Log;
 
 import com.google.common.base.CaseFormat;
 import com.google.common.base.Preconditions;
@@ -9,7 +8,6 @@ import com.google.common.collect.ImmutableMap;
 
 import java.util.Map;
 
-import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -44,6 +42,15 @@ public class JinglePacket extends IqPacket {
         return content == null ? null : Content.upgrade(content);
     }
 
+    public Group getGroup() {
+        final Element group = this.findChild("group", Namespace.JINGLE_APPS_GROUPING);
+        return group == null ? null : Group.upgrade(group);
+    }
+
+    public void addGroup(final Group group) {
+        this.addJingleChild(group);
+    }
+
     public Map<String, Content> getJingleContents() {
         final Element jingle = findChild("jingle", Namespace.JINGLE);
         ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
@@ -56,8 +63,8 @@ public class JinglePacket extends IqPacket {
         return builder.build();
     }
 
-    public void setJingleContent(final Content content) { //take content interface
-        setJingleChild(content);
+    public void addJingleContent(final Content content) { //take content interface
+        addJingleChild(content);
     }
 
     public Reason getReason() {
@@ -87,7 +94,7 @@ public class JinglePacket extends IqPacket {
         return jingle == null ? null : jingle.findChild(name);
     }
 
-    public void setJingleChild(final Element child) {
+    public void addJingleChild(final Element child) {
         final Element jingle = findChild("jingle", Namespace.JINGLE);
         jingle.addChild(child);
     }

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java 🔗

@@ -19,6 +19,11 @@ import eu.siacs.conversations.xmpp.jingle.SessionDescription;
 public class RtpDescription extends GenericDescription {
 
 
+    private RtpDescription(final String media) {
+        super("description", Namespace.JINGLE_APPS_RTP);
+        this.setAttribute("media", media);
+    }
+
     private RtpDescription() {
         super("description", Namespace.JINGLE_APPS_RTP);
     }
@@ -447,7 +452,7 @@ public class RtpDescription extends GenericDescription {
     }
 
     public static RtpDescription of(final SessionDescription.Media media) {
-        final RtpDescription rtpDescription = new RtpDescription();
+        final RtpDescription rtpDescription = new RtpDescription(media.media);
         final Map<String, List<Parameter>> parameterMap = new HashMap<>();
         final ArrayListMultimap<String, Element> feedbackNegotiationMap = ArrayListMultimap.create();
         final ArrayListMultimap<String, Source.Parameter> sourceParameterMap = ArrayListMultimap.create();