webrtc: Update codes based on 1.18.4 88/250688/7
authorSangchul Lee <sangchul1011@gmail.com>
Thu, 31 Dec 2020 11:31:09 +0000 (20:31 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Thu, 3 Jun 2021 07:27:22 +0000 (16:27 +0900)
files below are updated.
 - ext/webrtc/*
 - gst-libs/gst/webrtc/*

Change-Id: Ic2622a1e0275a4e11ccd806a631d923a5601308e
Signed-off-by: Sangchul Lee <sangchul1011@gmail.com>
41 files changed:
ext/webrtc/gstwebrtcbin.c
ext/webrtc/gstwebrtcbin.h
ext/webrtc/gstwebrtcice.c
ext/webrtc/gstwebrtcice.h
ext/webrtc/gstwebrtcstats.c
ext/webrtc/meson.build
ext/webrtc/nicetransport.c
ext/webrtc/sctptransport.c
ext/webrtc/sctptransport.h
ext/webrtc/transportreceivebin.c
ext/webrtc/transportreceivebin.h
ext/webrtc/transportsendbin.c
ext/webrtc/transportstream.c
ext/webrtc/transportstream.h
ext/webrtc/utils.c
ext/webrtc/utils.h
ext/webrtc/webrtcdatachannel.c
ext/webrtc/webrtcdatachannel.h
ext/webrtc/webrtcsdp.c
ext/webrtc/webrtcsdp.h
ext/webrtc/webrtctransceiver.c
ext/webrtc/webrtctransceiver.h
gst-libs/gst/webrtc/Makefile.am
gst-libs/gst/webrtc/datachannel.c [new file with mode: 0644]
gst-libs/gst/webrtc/datachannel.h [new file with mode: 0644]
gst-libs/gst/webrtc/dtlstransport.c
gst-libs/gst/webrtc/dtlstransport.h
gst-libs/gst/webrtc/icetransport.c
gst-libs/gst/webrtc/icetransport.h
gst-libs/gst/webrtc/meson.build
gst-libs/gst/webrtc/rtcsessiondescription.c
gst-libs/gst/webrtc/rtcsessiondescription.h
gst-libs/gst/webrtc/rtpreceiver.c
gst-libs/gst/webrtc/rtpreceiver.h
gst-libs/gst/webrtc/rtpsender.c
gst-libs/gst/webrtc/rtpsender.h
gst-libs/gst/webrtc/rtptransceiver.c
gst-libs/gst/webrtc/rtptransceiver.h
gst-libs/gst/webrtc/webrtc.h
gst-libs/gst/webrtc/webrtc_fwd.h
packaging/gst-plugins-bad.spec

index eddf6ef..c261c8b 100644 (file)
 #define PC_COND_BROADCAST(w) (g_cond_broadcast(PC_GET_COND(w)))
 #define PC_COND_SIGNAL(w) (g_cond_signal(PC_GET_COND(w)))
 
+#define ICE_GET_LOCK(w) (&w->priv->ice_lock)
+#define ICE_LOCK(w) (g_mutex_lock (ICE_GET_LOCK(w)))
+#define ICE_UNLOCK(w) (g_mutex_unlock (ICE_GET_LOCK(w)))
+
+
+/* The extra time for the rtpstorage compared to the RTP jitterbuffer (in ms) */
+#define RTPSTORAGE_EXTRA_TIME (50)
+
 /*
  * This webrtcbin implements the majority of the W3's peerconnection API and
  * implementation guide where possible. Generating offers, answers and setting
@@ -87,7 +95,7 @@
  * balanced bundle policy
  * setting custom DTLS certificates
  *
- * seperate session id's from mlineindex properly
+ * separate session id's from mlineindex properly
  * how to deal with replacing a input/output track/stream
  */
 
@@ -96,6 +104,21 @@ static void _update_need_negotiation (GstWebRTCBin * webrtc);
 #define GST_CAT_DEFAULT gst_webrtc_bin_debug
 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
 
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS ("application/x-rtp"));
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("application/x-rtp"));
+
+enum
+{
+  PROP_PAD_TRANSCEIVER = 1,
+};
+
 static gboolean
 _have_nice_elements (GstWebRTCBin * webrtc)
 {
@@ -191,7 +214,12 @@ static void
 gst_webrtc_bin_pad_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec)
 {
+  GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object);
+
   switch (prop_id) {
+    case PROP_PAD_TRANSCEIVER:
+      g_value_set_object (value, pad->trans);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -222,24 +250,41 @@ gst_webrtc_bin_pad_class_init (GstWebRTCBinPadClass * klass)
   gobject_class->get_property = gst_webrtc_bin_pad_get_property;
   gobject_class->set_property = gst_webrtc_bin_pad_set_property;
   gobject_class->finalize = gst_webrtc_bin_pad_finalize;
+
+  g_object_class_install_property (gobject_class,
+      PROP_PAD_TRANSCEIVER,
+      g_param_spec_object ("transceiver", "Transceiver",
+          "Transceiver associated with this pad",
+          GST_TYPE_WEBRTC_RTP_TRANSCEIVER,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 }
 
 static gboolean
 gst_webrtcbin_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
 {
   GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (pad);
+  GstWebRTCBin *webrtc = GST_WEBRTC_BIN (parent);
+  gboolean check_negotiation = FALSE;
 
   if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
     GstCaps *caps;
-    gboolean do_update;
 
     gst_event_parse_caps (event, &caps);
-    do_update = (!wpad->received_caps
+    check_negotiation = (!wpad->received_caps
         || gst_caps_is_equal (wpad->received_caps, caps));
     gst_caps_replace (&wpad->received_caps, caps);
 
-    if (do_update)
-      _update_need_negotiation (GST_WEBRTC_BIN (parent));
+    GST_DEBUG_OBJECT (parent,
+        "On %" GST_PTR_FORMAT " checking negotiation? %u, caps %"
+        GST_PTR_FORMAT, pad, check_negotiation, caps);
+  } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+    check_negotiation = TRUE;
+  }
+
+  if (check_negotiation) {
+    PC_LOCK (webrtc);
+    _update_need_negotiation (webrtc);
+    PC_UNLOCK (webrtc);
   }
 
   return gst_pad_event_default (pad, parent, event);
@@ -253,17 +298,23 @@ gst_webrtc_bin_pad_init (GstWebRTCBinPad * pad)
 static GstWebRTCBinPad *
 gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction)
 {
-  GstWebRTCBinPad *pad =
+  GstWebRTCBinPad *pad;
+  GstPadTemplate *template;
+
+  if (direction == GST_PAD_SINK)
+    template = gst_static_pad_template_get (&sink_template);
+  else if (direction == GST_PAD_SRC)
+    template = gst_static_pad_template_get (&src_template);
+  else
+    g_assert_not_reached ();
+
+  pad =
       g_object_new (gst_webrtc_bin_pad_get_type (), "name", name, "direction",
-      direction, NULL);
+      direction, "template", template, NULL);
+  gst_object_unref (template);
 
   gst_pad_set_event_function (GST_PAD (pad), gst_webrtcbin_sink_event);
 
-  if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) {
-    gst_object_unref (pad);
-    return NULL;
-  }
-
   GST_DEBUG_OBJECT (pad, "new visible pad with direction %s",
       direction == GST_PAD_SRC ? "src" : "sink");
   return pad;
@@ -273,22 +324,11 @@ gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction)
 G_DEFINE_TYPE_WITH_CODE (GstWebRTCBin, gst_webrtc_bin, GST_TYPE_BIN,
     G_ADD_PRIVATE (GstWebRTCBin)
     GST_DEBUG_CATEGORY_INIT (gst_webrtc_bin_debug, "webrtcbin", 0,
-        "webrtcbin element");
-    );
+        "webrtcbin element"););
 
 static GstPad *_connect_input_stream (GstWebRTCBin * webrtc,
     GstWebRTCBinPad * pad);
 
-static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
-    GST_PAD_SINK,
-    GST_PAD_REQUEST,
-    GST_STATIC_CAPS ("application/x-rtp"));
-
-static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
-    GST_PAD_SRC,
-    GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("application/x-rtp"));
-
 enum
 {
   SIGNAL_0,
@@ -327,6 +367,8 @@ enum
   PROP_TURN_SERVER,
   PROP_BUNDLE_POLICY,
   PROP_ICE_TRANSPORT_POLICY,
+  PROP_ICE_AGENT,
+  PROP_LATENCY
 };
 
 static guint gst_webrtc_bin_signals[LAST_SIGNAL] = { 0 };
@@ -393,8 +435,7 @@ _find_transceiver (GstWebRTCBin * webrtc, gconstpointer data,
 
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *transceiver =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+        g_ptr_array_index (webrtc->priv->transceivers, i);
 
     if (func (transceiver, data))
       return transceiver;
@@ -440,9 +481,7 @@ _find_transport (GstWebRTCBin * webrtc, gconstpointer data,
   int i;
 
   for (i = 0; i < webrtc->priv->transports->len; i++) {
-    TransportStream *stream =
-        g_array_index (webrtc->priv->transports, TransportStream *,
-        i);
+    TransportStream *stream = g_ptr_array_index (webrtc->priv->transports, i);
 
     if (func (stream, data))
       return stream;
@@ -506,19 +545,18 @@ _find_pad (GstWebRTCBin * webrtc, gconstpointer data, FindPadFunc func)
   return NULL;
 }
 
-typedef gboolean (*FindDataChannelFunc) (GstWebRTCDataChannel * p1,
+typedef gboolean (*FindDataChannelFunc) (WebRTCDataChannel * p1,
     gconstpointer data);
 
-static GstWebRTCDataChannel *
+static WebRTCDataChannel *
 _find_data_channel (GstWebRTCBin * webrtc, gconstpointer data,
     FindDataChannelFunc func)
 {
   int i;
 
   for (i = 0; i < webrtc->priv->data_channels->len; i++) {
-    GstWebRTCDataChannel *channel =
-        g_array_index (webrtc->priv->data_channels, GstWebRTCDataChannel *,
-        i);
+    WebRTCDataChannel *channel =
+        g_ptr_array_index (webrtc->priv->data_channels, i);
 
     if (func (channel, data))
       return channel;
@@ -528,15 +566,15 @@ _find_data_channel (GstWebRTCBin * webrtc, gconstpointer data,
 }
 
 static gboolean
-data_channel_match_for_id (GstWebRTCDataChannel * channel, gint * id)
+data_channel_match_for_id (WebRTCDataChannel * channel, gint * id)
 {
-  return channel->id == *id;
+  return channel->parent.id == *id;
 }
 
-static GstWebRTCDataChannel *
+static WebRTCDataChannel *
 _find_data_channel_for_id (GstWebRTCBin * webrtc, gint id)
 {
-  GstWebRTCDataChannel *channel;
+  WebRTCDataChannel *channel;
 
   channel = _find_data_channel (webrtc, &id,
       (FindDataChannelFunc) data_channel_match_for_id);
@@ -661,9 +699,12 @@ _gst_pc_thread (GstWebRTCBin * webrtc)
    * tasks */
   g_main_loop_run (webrtc->priv->loop);
 
-  PC_LOCK (webrtc);
+  GST_OBJECT_LOCK (webrtc);
   g_main_context_unref (webrtc->priv->main_context);
   webrtc->priv->main_context = NULL;
+  GST_OBJECT_UNLOCK (webrtc);
+
+  PC_LOCK (webrtc);
   g_main_loop_unref (webrtc->priv->loop);
   webrtc->priv->loop = NULL;
   PC_COND_BROADCAST (webrtc);
@@ -675,9 +716,13 @@ _gst_pc_thread (GstWebRTCBin * webrtc)
 static void
 _start_thread (GstWebRTCBin * webrtc)
 {
+  gchar *name;
+
   PC_LOCK (webrtc);
-  webrtc->priv->thread = g_thread_new ("gst-pc-ops",
-      (GThreadFunc) _gst_pc_thread, webrtc);
+  name = g_strdup_printf ("%s:pc", GST_OBJECT_NAME (webrtc));
+  webrtc->priv->thread = g_thread_new (name, (GThreadFunc) _gst_pc_thread,
+      webrtc);
+  g_free (name);
 
   while (!webrtc->priv->loop)
     PC_COND_WAIT (webrtc);
@@ -688,8 +733,11 @@ _start_thread (GstWebRTCBin * webrtc)
 static void
 _stop_thread (GstWebRTCBin * webrtc)
 {
-  PC_LOCK (webrtc);
+  GST_OBJECT_LOCK (webrtc);
   webrtc->priv->is_closed = TRUE;
+  GST_OBJECT_UNLOCK (webrtc);
+
+  PC_LOCK (webrtc);
   g_main_loop_quit (webrtc->priv->loop);
   while (webrtc->priv->loop)
     PC_COND_WAIT (webrtc);
@@ -703,6 +751,18 @@ _execute_op (GstWebRTCBinTask * op)
 {
   PC_LOCK (op->webrtc);
   if (op->webrtc->priv->is_closed) {
+    if (op->promise) {
+      GError *error =
+          g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+          "webrtcbin is closed. aborting execution.");
+      GstStructure *s =
+          gst_structure_new ("application/x-gstwebrtcbin-promise-error",
+          "error", G_TYPE_ERROR, error, NULL);
+
+      gst_promise_reply (op->promise, s);
+
+      g_clear_error (&error);
+    }
     GST_DEBUG_OBJECT (op->webrtc,
         "Peerconnection is closed, aborting execution");
     goto out;
@@ -720,36 +780,55 @@ _free_op (GstWebRTCBinTask * op)
 {
   if (op->notify)
     op->notify (op->data);
+  if (op->promise)
+    gst_promise_unref (op->promise);
   g_free (op);
 }
 
-void
+/*
+ * @promise is for correctly signalling the failure case to the caller when
+ * the user supplies it.  Without passing it in, the promise would never
+ * be replied to in the case that @webrtc becomes closed between the idle
+ * source addition and the the execution of the idle source.
+ */
+gboolean
 gst_webrtc_bin_enqueue_task (GstWebRTCBin * webrtc, GstWebRTCBinFunc func,
-    gpointer data, GDestroyNotify notify)
+    gpointer data, GDestroyNotify notify, GstPromise * promise)
 {
   GstWebRTCBinTask *op;
+  GMainContext *ctx;
   GSource *source;
 
-  g_return_if_fail (GST_IS_WEBRTC_BIN (webrtc));
+  g_return_val_if_fail (GST_IS_WEBRTC_BIN (webrtc), FALSE);
 
+  GST_OBJECT_LOCK (webrtc);
   if (webrtc->priv->is_closed) {
+    GST_OBJECT_UNLOCK (webrtc);
     GST_DEBUG_OBJECT (webrtc, "Peerconnection is closed, aborting execution");
     if (notify)
       notify (data);
-    return;
+    return FALSE;
   }
+  ctx = g_main_context_ref (webrtc->priv->main_context);
+  GST_OBJECT_UNLOCK (webrtc);
+
   op = g_new0 (GstWebRTCBinTask, 1);
   op->webrtc = webrtc;
   op->op = func;
   op->data = data;
   op->notify = notify;
+  if (promise)
+    op->promise = gst_promise_ref (promise);
 
   source = g_idle_source_new ();
   g_source_set_priority (source, G_PRIORITY_DEFAULT);
   g_source_set_callback (source, (GSourceFunc) _execute_op, op,
       (GDestroyNotify) _free_op);
-  g_source_attach (source, webrtc->priv->main_context);
+  g_source_attach (source, ctx);
   g_source_unref (source);
+  g_main_context_unref (ctx);
+
+  return TRUE;
 }
 
 /* https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate */
@@ -758,23 +837,29 @@ _collate_ice_connection_states (GstWebRTCBin * webrtc)
 {
 #define STATE(val) GST_WEBRTC_ICE_CONNECTION_STATE_ ## val
   GstWebRTCICEConnectionState any_state = 0;
-  gboolean all_closed = TRUE;
+  gboolean all_new_or_closed = TRUE;
+  gboolean all_completed_or_closed = TRUE;
+  gboolean all_connected_completed_or_closed = TRUE;
   int i;
 
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *rtp_trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+        g_ptr_array_index (webrtc->priv->transceivers, i);
     WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
     TransportStream *stream = trans->stream;
     GstWebRTCICETransport *transport, *rtcp_transport;
     GstWebRTCICEConnectionState ice_state;
     gboolean rtcp_mux = FALSE;
 
-    if (rtp_trans->stopped)
+    if (rtp_trans->stopped) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p stopped", rtp_trans);
       continue;
-    if (!rtp_trans->mid)
+    }
+
+    if (!rtp_trans->mid) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p has no mid", rtp_trans);
       continue;
+    }
 
     g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
 
@@ -782,18 +867,34 @@ _collate_ice_connection_states (GstWebRTCBin * webrtc)
 
     /* get transport state */
     g_object_get (transport, "state", &ice_state, NULL);
+    GST_TRACE_OBJECT (webrtc, "transceiver %p state 0x%x", rtp_trans,
+        ice_state);
     any_state |= (1 << ice_state);
-    if (ice_state != STATE (CLOSED))
-      all_closed = FALSE;
+
+    if (ice_state != STATE (NEW) && ice_state != STATE (CLOSED))
+      all_new_or_closed = FALSE;
+    if (ice_state != STATE (COMPLETED) && ice_state != STATE (CLOSED))
+      all_completed_or_closed = FALSE;
+    if (ice_state != STATE (CONNECTED) && ice_state != STATE (COMPLETED)
+        && ice_state != STATE (CLOSED))
+      all_connected_completed_or_closed = FALSE;
 
     rtcp_transport =
         webrtc_transceiver_get_rtcp_dtls_transport (rtp_trans)->transport;
 
     if (!rtcp_mux && rtcp_transport && transport != rtcp_transport) {
       g_object_get (rtcp_transport, "state", &ice_state, NULL);
+      GST_TRACE_OBJECT (webrtc, "transceiver %p RTCP state 0x%x", rtp_trans,
+          ice_state);
       any_state |= (1 << ice_state);
-      if (ice_state != STATE (CLOSED))
-        all_closed = FALSE;
+
+      if (ice_state != STATE (NEW) && ice_state != STATE (CLOSED))
+        all_new_or_closed = FALSE;
+      if (ice_state != STATE (COMPLETED) && ice_state != STATE (CLOSED))
+        all_completed_or_closed = FALSE;
+      if (ice_state != STATE (CONNECTED) && ice_state != STATE (COMPLETED)
+          && ice_state != STATE (CLOSED))
+        all_connected_completed_or_closed = FALSE;
     }
   }
 
@@ -803,47 +904,40 @@ _collate_ice_connection_states (GstWebRTCBin * webrtc)
     GST_TRACE_OBJECT (webrtc, "returning closed");
     return STATE (CLOSED);
   }
-  /* Any of the RTCIceTransport s are in the failed state. */
+  /* Any of the RTCIceTransports are in the failed state. */
   if (any_state & (1 << STATE (FAILED))) {
     GST_TRACE_OBJECT (webrtc, "returning failed");
     return STATE (FAILED);
   }
-  /* Any of the RTCIceTransport s are in the disconnected state and
-   * none of them are in the failed state. */
+  /* Any of the RTCIceTransports are in the disconnected state. */
   if (any_state & (1 << STATE (DISCONNECTED))) {
     GST_TRACE_OBJECT (webrtc, "returning disconnected");
     return STATE (DISCONNECTED);
   }
-  /* Any of the RTCIceTransport's are in the checking state and none of them
-   * are in the failed or disconnected state. */
-  if (any_state & (1 << STATE (CHECKING))) {
-    GST_TRACE_OBJECT (webrtc, "returning checking");
-    return STATE (CHECKING);
-  }
-  /* Any of the RTCIceTransport s are in the new state and none of them are
-   * in the checking, failed or disconnected state, or all RTCIceTransport's
-   * are in the closed state. */
-  if ((any_state & (1 << STATE (NEW))) || all_closed) {
+  /* All of the RTCIceTransports are in the new or closed state, or there are
+   * no transports. */
+  if (all_new_or_closed || webrtc->priv->transceivers->len == 0) {
     GST_TRACE_OBJECT (webrtc, "returning new");
     return STATE (NEW);
   }
-  /* All RTCIceTransport s are in the connected, completed or closed state
-   * and at least one of them is in the connected state. */
-  if (any_state & (1 << STATE (CONNECTED) | 1 << STATE (COMPLETED) | 1 <<
-          STATE (CLOSED)) && any_state & (1 << STATE (CONNECTED))) {
-    GST_TRACE_OBJECT (webrtc, "returning connected");
-    return STATE (CONNECTED);
+  /* Any of the RTCIceTransports are in the checking or new state. */
+  if ((any_state & (1 << STATE (CHECKING))) || (any_state & (1 << STATE (NEW)))) {
+    GST_TRACE_OBJECT (webrtc, "returning checking");
+    return STATE (CHECKING);
   }
-  /* All RTCIceTransport s are in the completed or closed state and at least
-   * one of them is in the completed state. */
-  if (any_state & (1 << STATE (COMPLETED) | 1 << STATE (CLOSED))
-      && any_state & (1 << STATE (COMPLETED))) {
+  /* All RTCIceTransports are in the completed or closed state. */
+  if (all_completed_or_closed) {
+    GST_TRACE_OBJECT (webrtc, "returning completed");
+    return STATE (COMPLETED);
+  }
+  /* All RTCIceTransports are in the connected, completed or closed state. */
+  if (all_connected_completed_or_closed) {
     GST_TRACE_OBJECT (webrtc, "returning connected");
     return STATE (CONNECTED);
   }
 
-  GST_FIXME ("unspecified situation, returning new");
-  return STATE (NEW);
+  GST_FIXME ("unspecified situation, returning old state");
+  return webrtc->ice_connection_state;
 #undef STATE
 }
 
@@ -858,34 +952,55 @@ _collate_ice_gathering_states (GstWebRTCBin * webrtc)
 
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *rtp_trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+        g_ptr_array_index (webrtc->priv->transceivers, i);
     WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
     TransportStream *stream = trans->stream;
+    GstWebRTCDTLSTransport *dtls_transport;
     GstWebRTCICETransport *transport, *rtcp_transport;
     GstWebRTCICEGatheringState ice_state;
     gboolean rtcp_mux = FALSE;
 
-    if (rtp_trans->stopped)
-      continue;
-    if (!rtp_trans->mid)
+    if (rtp_trans->stopped || stream == NULL) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p stopped or unassociated",
+          rtp_trans);
       continue;
+    }
+
+    /* We only have a mid in the transceiver after we got the SDP answer,
+     * which is usually long after gathering has finished */
+    if (!rtp_trans->mid) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p has no mid", rtp_trans);
+    }
 
     g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
 
-    transport = webrtc_transceiver_get_dtls_transport (rtp_trans)->transport;
+    dtls_transport = webrtc_transceiver_get_dtls_transport (rtp_trans);
+    if (dtls_transport == NULL) {
+      GST_WARNING ("Transceiver %p has no DTLS transport", rtp_trans);
+      continue;
+    }
+
+    transport = dtls_transport->transport;
 
     /* get gathering state */
     g_object_get (transport, "gathering-state", &ice_state, NULL);
+    GST_TRACE_OBJECT (webrtc, "transceiver %p gathering state: 0x%x", rtp_trans,
+        ice_state);
     any_state |= (1 << ice_state);
     if (ice_state != STATE (COMPLETE))
       all_completed = FALSE;
 
-    rtcp_transport =
-        webrtc_transceiver_get_rtcp_dtls_transport (rtp_trans)->transport;
+    dtls_transport = webrtc_transceiver_get_rtcp_dtls_transport (rtp_trans);
+    if (dtls_transport == NULL) {
+      GST_WARNING ("Transceiver %p has no DTLS RTCP transport", rtp_trans);
+      continue;
+    }
+    rtcp_transport = dtls_transport->transport;
 
     if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) {
       g_object_get (rtcp_transport, "gathering-state", &ice_state, NULL);
+      GST_TRACE_OBJECT (webrtc, "transceiver %p RTCP gathering state: 0x%x",
+          rtp_trans, ice_state);
       any_state |= (1 << ice_state);
       if (ice_state != STATE (COMPLETE))
         all_completed = FALSE;
@@ -922,40 +1037,93 @@ _collate_peer_connection_states (GstWebRTCBin * webrtc)
 #define DTLS_STATE(v) GST_WEBRTC_DTLS_TRANSPORT_STATE_ ## v
   GstWebRTCICEConnectionState any_ice_state = 0;
   GstWebRTCDTLSTransportState any_dtls_state = 0;
+  gboolean ice_all_new_or_closed = TRUE;
+  gboolean dtls_all_new_or_closed = TRUE;
+  gboolean ice_all_new_connecting_or_checking = TRUE;
+  gboolean dtls_all_new_connecting_or_checking = TRUE;
+  gboolean ice_all_connected_completed_or_closed = TRUE;
+  gboolean dtls_all_connected_completed_or_closed = TRUE;
   int i;
 
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *rtp_trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+        g_ptr_array_index (webrtc->priv->transceivers, i);
     WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
     TransportStream *stream = trans->stream;
     GstWebRTCDTLSTransport *transport, *rtcp_transport;
-    GstWebRTCICEGatheringState ice_state;
+    GstWebRTCICEConnectionState ice_state;
     GstWebRTCDTLSTransportState dtls_state;
     gboolean rtcp_mux = FALSE;
 
-    if (rtp_trans->stopped)
+    if (rtp_trans->stopped) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p stopped", rtp_trans);
       continue;
-    if (!rtp_trans->mid)
+    }
+    if (!rtp_trans->mid) {
+      GST_TRACE_OBJECT (webrtc, "transceiver %p has no mid", rtp_trans);
       continue;
+    }
 
     g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
     transport = webrtc_transceiver_get_dtls_transport (rtp_trans);
 
     /* get transport state */
     g_object_get (transport, "state", &dtls_state, NULL);
+    GST_TRACE_OBJECT (webrtc, "transceiver %p DTLS state: 0x%x", rtp_trans,
+        dtls_state);
     any_dtls_state |= (1 << dtls_state);
+
+    if (dtls_state != DTLS_STATE (NEW) && dtls_state != DTLS_STATE (CLOSED))
+      dtls_all_new_or_closed = FALSE;
+    if (dtls_state != DTLS_STATE (NEW) && dtls_state != DTLS_STATE (CONNECTING))
+      dtls_all_new_connecting_or_checking = FALSE;
+    if (dtls_state != DTLS_STATE (CONNECTED)
+        && dtls_state != DTLS_STATE (CLOSED))
+      dtls_all_connected_completed_or_closed = FALSE;
+
     g_object_get (transport->transport, "state", &ice_state, NULL);
+    GST_TRACE_OBJECT (webrtc, "transceiver %p ICE state: 0x%x", rtp_trans,
+        ice_state);
     any_ice_state |= (1 << ice_state);
 
+    if (ice_state != ICE_STATE (NEW) && ice_state != ICE_STATE (CLOSED))
+      ice_all_new_or_closed = FALSE;
+    if (ice_state != ICE_STATE (NEW) && ice_state != ICE_STATE (CHECKING))
+      ice_all_new_connecting_or_checking = FALSE;
+    if (ice_state != ICE_STATE (CONNECTED) && ice_state != ICE_STATE (COMPLETED)
+        && ice_state != ICE_STATE (CLOSED))
+      ice_all_connected_completed_or_closed = FALSE;
+
     rtcp_transport = webrtc_transceiver_get_rtcp_dtls_transport (rtp_trans);
 
     if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) {
       g_object_get (rtcp_transport, "state", &dtls_state, NULL);
+      GST_TRACE_OBJECT (webrtc, "transceiver %p RTCP DTLS state: 0x%x",
+          rtp_trans, dtls_state);
       any_dtls_state |= (1 << dtls_state);
+
+      if (dtls_state != DTLS_STATE (NEW) && dtls_state != DTLS_STATE (CLOSED))
+        dtls_all_new_or_closed = FALSE;
+      if (dtls_state != DTLS_STATE (NEW)
+          && dtls_state != DTLS_STATE (CONNECTING))
+        dtls_all_new_connecting_or_checking = FALSE;
+      if (dtls_state != DTLS_STATE (CONNECTED)
+          && dtls_state != DTLS_STATE (CLOSED))
+        dtls_all_connected_completed_or_closed = FALSE;
+
       g_object_get (rtcp_transport->transport, "state", &ice_state, NULL);
+      GST_TRACE_OBJECT (webrtc, "transceiver %p RTCP ICE state: 0x%x",
+          rtp_trans, ice_state);
       any_ice_state |= (1 << ice_state);
+
+      if (ice_state != ICE_STATE (NEW) && ice_state != ICE_STATE (CLOSED))
+        ice_all_new_or_closed = FALSE;
+      if (ice_state != ICE_STATE (NEW) && ice_state != ICE_STATE (CHECKING))
+        ice_all_new_connecting_or_checking = FALSE;
+      if (ice_state != ICE_STATE (CONNECTED)
+          && ice_state != ICE_STATE (COMPLETED)
+          && ice_state != ICE_STATE (CLOSED))
+        ice_all_connected_completed_or_closed = FALSE;
     }
   }
 
@@ -978,57 +1146,48 @@ _collate_peer_connection_states (GstWebRTCBin * webrtc)
     return STATE (FAILED);
   }
 
-  /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the connecting
-   * or checking state and none of them is in the failed state. */
-  if (any_ice_state & (1 << ICE_STATE (CHECKING))) {
-    GST_TRACE_OBJECT (webrtc, "returning connecting");
-    return STATE (CONNECTING);
-  }
-  if (any_dtls_state & (1 << DTLS_STATE (CONNECTING))) {
-    GST_TRACE_OBJECT (webrtc, "returning connecting");
-    return STATE (CONNECTING);
-  }
-
   /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the disconnected
-   * state and none of them are in the failed or connecting or checking state. */
+   * state. */
   if (any_ice_state & (1 << ICE_STATE (DISCONNECTED))) {
     GST_TRACE_OBJECT (webrtc, "returning disconnected");
     return STATE (DISCONNECTED);
   }
 
-  /* All RTCIceTransport's and RTCDtlsTransport's are in the connected,
-   * completed or closed state and at least of them is in the connected or
-   * completed state. */
-  if (!(any_ice_state & ~(1 << ICE_STATE (CONNECTED) | 1 <<
-              ICE_STATE (COMPLETED) | 1 << ICE_STATE (CLOSED)))
-      && !(any_dtls_state & ~(1 << DTLS_STATE (CONNECTED) | 1 <<
-              DTLS_STATE (CLOSED)))
-      && (any_ice_state & (1 << ICE_STATE (CONNECTED) | 1 <<
-              ICE_STATE (COMPLETED))
-          || any_dtls_state & (1 << DTLS_STATE (CONNECTED)))) {
+  /* All RTCIceTransports and RTCDtlsTransports are in the new or closed
+   * state, or there are no transports. */
+  if ((dtls_all_new_or_closed && ice_all_new_or_closed)
+      || webrtc->priv->transceivers->len == 0) {
+    GST_TRACE_OBJECT (webrtc, "returning new");
+    return STATE (NEW);
+  }
+
+  /* All RTCIceTransports and RTCDtlsTransports are in the new, connecting
+   * or checking state. */
+  if (dtls_all_new_connecting_or_checking && ice_all_new_connecting_or_checking) {
+    GST_TRACE_OBJECT (webrtc, "returning connecting");
+    return STATE (CONNECTING);
+  }
+
+  /* All RTCIceTransports and RTCDtlsTransports are in the connected,
+   * completed or closed state. */
+  if (dtls_all_connected_completed_or_closed
+      && ice_all_connected_completed_or_closed) {
     GST_TRACE_OBJECT (webrtc, "returning connected");
     return STATE (CONNECTED);
   }
 
-  /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the new state
-   * and none of the transports are in the connecting, checking, failed or
-   * disconnected state, or all transports are in the closed state. */
-  if (!(any_ice_state & ~(1 << ICE_STATE (CLOSED)))) {
-    GST_TRACE_OBJECT (webrtc, "returning new");
-    return STATE (NEW);
-  }
-  if ((any_ice_state & (1 << ICE_STATE (NEW))
-          || any_dtls_state & (1 << DTLS_STATE (NEW)))
-      && !(any_ice_state & (1 << ICE_STATE (CHECKING) | 1 << ICE_STATE (FAILED)
-              | (1 << ICE_STATE (DISCONNECTED))))
-      && !(any_dtls_state & (1 << DTLS_STATE (CONNECTING) | 1 <<
-              DTLS_STATE (FAILED)))) {
-    GST_TRACE_OBJECT (webrtc, "returning new");
-    return STATE (NEW);
+  /* FIXME: Unspecified state that happens for us */
+  if ((dtls_all_new_connecting_or_checking
+          || dtls_all_connected_completed_or_closed)
+      && (ice_all_new_connecting_or_checking
+          || ice_all_connected_completed_or_closed)) {
+    GST_TRACE_OBJECT (webrtc, "returning connecting");
+    return STATE (CONNECTING);
   }
 
-  GST_FIXME_OBJECT (webrtc, "Undefined situation detected, returning new");
-  return STATE (NEW);
+  GST_FIXME_OBJECT (webrtc,
+      "Undefined situation detected, returning old state");
+  return webrtc->peer_connection_state;
 #undef DTLS_STATE
 #undef ICE_STATE
 #undef STATE
@@ -1042,6 +1201,19 @@ _update_ice_gathering_state_task (GstWebRTCBin * webrtc, gpointer data)
 
   new_state = _collate_ice_gathering_states (webrtc);
 
+  /* If the new state is complete, before we update the public state,
+   * check if anyone published more ICE candidates while we were collating
+   * and stop if so, because it means there's a new later
+   * ice_gathering_state_task queued */
+  if (new_state == GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
+    ICE_LOCK (webrtc);
+    if (webrtc->priv->pending_local_ice_candidates->len != 0) {
+      /* ICE candidates queued for emissiong -> we're gathering, not complete */
+      new_state = GST_WEBRTC_ICE_GATHERING_STATE_GATHERING;
+    }
+    ICE_UNLOCK (webrtc);
+  }
+
   if (new_state != webrtc->ice_gathering_state) {
     gchar *old_s, *new_s;
 
@@ -1065,7 +1237,7 @@ static void
 _update_ice_gathering_state (GstWebRTCBin * webrtc)
 {
   gst_webrtc_bin_enqueue_task (webrtc, _update_ice_gathering_state_task, NULL,
-      NULL);
+      NULL, NULL);
 }
 
 static void
@@ -1100,7 +1272,7 @@ static void
 _update_ice_connection_state (GstWebRTCBin * webrtc)
 {
   gst_webrtc_bin_enqueue_task (webrtc, _update_ice_connection_state_task, NULL,
-      NULL);
+      NULL, NULL);
 }
 
 static void
@@ -1135,7 +1307,7 @@ static void
 _update_peer_connection_state (GstWebRTCBin * webrtc)
 {
   gst_webrtc_bin_enqueue_task (webrtc, _update_peer_connection_state_task,
-      NULL, NULL);
+      NULL, NULL, NULL);
 }
 
 static gboolean
@@ -1147,16 +1319,23 @@ _all_sinks_have_caps (GstWebRTCBin * webrtc)
   GST_OBJECT_LOCK (webrtc);
   l = GST_ELEMENT (webrtc)->pads;
   for (; l; l = g_list_next (l)) {
+    GstWebRTCBinPad *wpad;
+
     if (!GST_IS_WEBRTC_BIN_PAD (l->data))
       continue;
-    if (!GST_WEBRTC_BIN_PAD (l->data)->received_caps)
+
+    wpad = GST_WEBRTC_BIN_PAD (l->data);
+    if (GST_PAD_DIRECTION (l->data) == GST_PAD_SINK && !wpad->received_caps
+        && (!wpad->trans || !wpad->trans->stopped)) {
       goto done;
+    }
   }
 
   l = webrtc->priv->pending_pads;
   for (; l; l = g_list_next (l)) {
-    if (!GST_IS_WEBRTC_BIN_PAD (l->data))
+    if (!GST_IS_WEBRTC_BIN_PAD (l->data)) {
       goto done;
+    }
   }
 
   res = TRUE;
@@ -1187,10 +1366,6 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
    * FIXME */
   /* FIXME: emit when input caps/format changes? */
 
-  /* If connection has created any RTCDataChannel's, and no m= section has
-   * been negotiated yet for data, return "true". 
-   * FIXME */
-
   if (!webrtc->current_local_description) {
     GST_LOG_OBJECT (webrtc, "no local description set");
     return TRUE;
@@ -1201,12 +1376,22 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
     return TRUE;
   }
 
+  /* If connection has created any RTCDataChannel's, and no m= section has
+   * been negotiated yet for data, return "true". */
+  if (webrtc->priv->data_channels->len > 0) {
+    if (_message_get_datachannel_index (webrtc->current_local_description->
+            sdp) >= G_MAXUINT) {
+      GST_LOG_OBJECT (webrtc,
+          "no data channel media section and have %u " "transports",
+          webrtc->priv->data_channels->len);
+      return TRUE;
+    }
+  }
+
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *trans;
 
-    trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+    trans = g_ptr_array_index (webrtc->priv->transceivers, i);
 
     if (trans->stopped) {
       /* FIXME: If t is stopped and is associated with an m= section according to
@@ -1219,9 +1404,9 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
       const GstSDPMedia *media;
       GstWebRTCRTPTransceiverDirection local_dir, remote_dir;
 
-      if (trans->mline == -1) {
-        GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT,
-            i, trans);
+      if (trans->mline == -1 || trans->mid == NULL) {
+        GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT
+            " mid %s", i, trans, trans->mid);
         return TRUE;
       }
       /* internal inconsistency */
@@ -1251,8 +1436,26 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
          * nor answer matches t's direction, return "true". */
 
         if (local_dir != trans->direction && remote_dir != trans->direction) {
-          GST_LOG_OBJECT (webrtc,
-              "transceiver direction doesn't match description");
+          gchar *local_str, *remote_str, *dir_str;
+
+          local_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              local_dir);
+          remote_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              remote_dir);
+          dir_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              trans->direction);
+
+          GST_LOG_OBJECT (webrtc, "transceiver direction (%s) doesn't match "
+              "description (local %s remote %s)", dir_str, local_str,
+              remote_str);
+
+          g_free (dir_str);
+          g_free (local_str);
+          g_free (remote_str);
+
           return TRUE;
         }
       } else if (webrtc->current_local_description->type ==
@@ -1268,8 +1471,30 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
         intersect_dir = _intersect_answer_directions (remote_dir, local_dir);
 
         if (intersect_dir != trans->direction) {
-          GST_LOG_OBJECT (webrtc,
-              "transceiver direction doesn't match description");
+          gchar *local_str, *remote_str, *inter_str, *dir_str;
+
+          local_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              local_dir);
+          remote_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              remote_dir);
+          dir_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              trans->direction);
+          inter_str =
+              _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+              intersect_dir);
+
+          GST_LOG_OBJECT (webrtc, "transceiver direction (%s) doesn't match "
+              "description intersected direction %s (local %s remote %s)",
+              dir_str, local_str, inter_str, remote_str);
+
+          g_free (dir_str);
+          g_free (local_str);
+          g_free (remote_str);
+          g_free (inter_str);
+
           return TRUE;
         }
       }
@@ -1318,25 +1543,36 @@ _update_need_negotiation (GstWebRTCBin * webrtc)
   /* Queue a task to check connection's [[ needNegotiation]] slot and, if still
    * true, fire a simple event named negotiationneeded at connection. */
   gst_webrtc_bin_enqueue_task (webrtc, _check_need_negotiation_task, NULL,
-      NULL);
+      NULL, NULL);
 }
 
 static GstCaps *
-_find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans,
-    GstPadDirection direction, guint media_idx)
+_find_codec_preferences (GstWebRTCBin * webrtc,
+    GstWebRTCRTPTransceiver * rtp_trans, GstPadDirection direction,
+    guint media_idx)
 {
+  WebRTCTransceiver *trans = (WebRTCTransceiver *) rtp_trans;
   GstCaps *ret = NULL;
 
-  GST_LOG_OBJECT (webrtc, "retreiving codec preferences from %" GST_PTR_FORMAT,
+  GST_LOG_OBJECT (webrtc, "retrieving codec preferences from %" GST_PTR_FORMAT,
       trans);
 
-  if (trans && trans->codec_preferences) {
+  if (rtp_trans && rtp_trans->codec_preferences) {
     GST_LOG_OBJECT (webrtc, "Using codec preferences: %" GST_PTR_FORMAT,
-        trans->codec_preferences);
-    ret = gst_caps_ref (trans->codec_preferences);
+        rtp_trans->codec_preferences);
+    ret = gst_caps_ref (rtp_trans->codec_preferences);
   } else {
-    GstWebRTCBinPad *pad = _find_pad_for_mline (webrtc, direction, media_idx);
-    if (pad) {
+    GstWebRTCBinPad *pad = NULL;
+
+    /* try to find a pad */
+    if (!trans
+        || !(pad = _find_pad_for_transceiver (webrtc, direction, rtp_trans)))
+      pad = _find_pad_for_mline (webrtc, direction, media_idx);
+
+    if (!pad) {
+      if (trans && trans->last_configured_caps)
+        ret = gst_caps_ref (trans->last_configured_caps);
+    } else {
       GstCaps *caps = NULL;
 
       if (pad->received_caps) {
@@ -1349,12 +1585,20 @@ _find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans,
           GST_LOG_OBJECT (webrtc, "Using peer query caps: %" GST_PTR_FORMAT,
               caps);
       }
-      if (caps)
+      if (caps) {
+        if (trans)
+          gst_caps_replace (&trans->last_configured_caps, caps);
+
         ret = caps;
+      }
+
       gst_object_unref (pad);
     }
   }
 
+  if (!ret)
+    GST_DEBUG_OBJECT (trans, "Could not find caps for mline %u", media_idx);
+
   return ret;
 }
 
@@ -1380,7 +1624,7 @@ _add_supported_attributes_to_caps (GstWebRTCBin * webrtc,
     /*if (!gst_structure_has_field (s, "rtcp-fb-transport-cc"))
        gst_structure_set (s, "rtcp-fb-nack-pli", G_TYPE_BOOLEAN, TRUE, NULL); */
 
-    /* FIXME: codec-specific paramters? */
+    /* FIXME: codec-specific parameters? */
   }
 
   return ret;
@@ -1423,8 +1667,10 @@ _create_webrtc_transceiver (GstWebRTCBin * webrtc,
   rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
   rtp_trans->direction = direction;
   rtp_trans->mline = mline;
+  /* FIXME: We don't support stopping transceiver yet so they're always not stopped */
+  rtp_trans->stopped = FALSE;
 
-  g_array_append_val (webrtc->priv->transceivers, trans);
+  g_ptr_array_add (webrtc->priv->transceivers, trans);
 
   gst_object_unref (sender);
   gst_object_unref (receiver);
@@ -1481,7 +1727,7 @@ _get_or_create_rtp_transport_channel (GstWebRTCBin * webrtc, guint session_id)
     ret = _create_transport_channel (webrtc, session_id);
     gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->send_bin));
     gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->receive_bin));
-    g_array_append_val (webrtc->priv->transports, ret);
+    g_ptr_array_add (webrtc->priv->transports, ret);
 
     pad_name = g_strdup_printf ("recv_rtcp_sink_%u", ret->session_id);
     if (!gst_element_link_pads (GST_ELEMENT (ret->receive_bin), "rtcp_src",
@@ -1504,7 +1750,7 @@ _get_or_create_rtp_transport_channel (GstWebRTCBin * webrtc, guint session_id)
 
 /* this is called from the webrtc thread with the pc lock held */
 static void
-_on_data_channel_ready_state (GstWebRTCDataChannel * channel,
+_on_data_channel_ready_state (WebRTCDataChannel * channel,
     GParamSpec * pspec, GstWebRTCBin * webrtc)
 {
   GstWebRTCDataChannelState ready_state;
@@ -1516,13 +1762,12 @@ _on_data_channel_ready_state (GstWebRTCDataChannel * channel,
     gboolean found = FALSE;
 
     for (i = 0; i < webrtc->priv->pending_data_channels->len; i++) {
-      GstWebRTCDataChannel *c;
+      WebRTCDataChannel *c;
 
-      c = g_array_index (webrtc->priv->pending_data_channels,
-          GstWebRTCDataChannel *, i);
+      c = g_ptr_array_index (webrtc->priv->pending_data_channels, i);
       if (c == channel) {
         found = TRUE;
-        g_array_remove_index (webrtc->priv->pending_data_channels, i);
+        g_ptr_array_remove_index (webrtc->priv->pending_data_channels, i);
         break;
       }
     }
@@ -1531,7 +1776,7 @@ _on_data_channel_ready_state (GstWebRTCDataChannel * channel,
       return;
     }
 
-    g_array_append_val (webrtc->priv->data_channels, channel);
+    g_ptr_array_add (webrtc->priv->data_channels, channel);
 
     g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_DATA_CHANNEL_SIGNAL], 0,
         gst_object_ref (channel));
@@ -1539,33 +1784,10 @@ _on_data_channel_ready_state (GstWebRTCDataChannel * channel,
 }
 
 static void
-_link_data_channel_to_sctp (GstWebRTCBin * webrtc,
-    GstWebRTCDataChannel * channel)
-{
-  if (webrtc->priv->sctp_transport && !channel->sctp_transport) {
-    gint id;
-
-    g_object_get (channel, "id", &id, NULL);
-
-    if (webrtc->priv->sctp_transport->association_established && id != -1) {
-      gchar *pad_name;
-
-      gst_webrtc_data_channel_set_sctp_transport (channel,
-          webrtc->priv->sctp_transport);
-      pad_name = g_strdup_printf ("sink_%u", id);
-      if (!gst_element_link_pads (channel->appsrc, "src",
-              channel->sctp_transport->sctpenc, pad_name))
-        g_warn_if_reached ();
-      g_free (pad_name);
-    }
-  }
-}
-
-static void
 _on_sctpdec_pad_added (GstElement * sctpdec, GstPad * pad,
     GstWebRTCBin * webrtc)
 {
-  GstWebRTCDataChannel *channel;
+  WebRTCDataChannel *channel;
   guint stream_id;
   GstPad *sink_pad;
 
@@ -1575,8 +1797,8 @@ _on_sctpdec_pad_added (GstElement * sctpdec, GstPad * pad,
   PC_LOCK (webrtc);
   channel = _find_data_channel_for_id (webrtc, stream_id);
   if (!channel) {
-    channel = g_object_new (GST_TYPE_WEBRTC_DATA_CHANNEL, NULL);
-    channel->id = stream_id;
+    channel = g_object_new (WEBRTC_TYPE_DATA_CHANNEL, NULL);
+    channel->parent.id = stream_id;
     channel->webrtcbin = webrtc;
 
     gst_bin_add (GST_BIN (webrtc), channel->appsrc);
@@ -1585,9 +1807,9 @@ _on_sctpdec_pad_added (GstElement * sctpdec, GstPad * pad,
     gst_element_sync_state_with_parent (channel->appsrc);
     gst_element_sync_state_with_parent (channel->appsink);
 
-    _link_data_channel_to_sctp (webrtc, channel);
+    webrtc_data_channel_link_to_sctp (channel, webrtc->priv->sctp_transport);
 
-    g_array_append_val (webrtc->priv->pending_data_channels, channel);
+    g_ptr_array_add (webrtc->priv->pending_data_channels, channel);
   }
 
   g_signal_connect (channel, "notify::ready-state",
@@ -1616,21 +1838,110 @@ _on_sctp_state_notify (GstWebRTCSCTPTransport * sctp, GParamSpec * pspec,
     GST_DEBUG_OBJECT (webrtc, "SCTP association established");
 
     for (i = 0; i < webrtc->priv->data_channels->len; i++) {
-      GstWebRTCDataChannel *channel;
+      WebRTCDataChannel *channel;
 
-      channel =
-          g_array_index (webrtc->priv->data_channels, GstWebRTCDataChannel *,
-          i);
+      channel = g_ptr_array_index (webrtc->priv->data_channels, i);
 
-      _link_data_channel_to_sctp (webrtc, channel);
+      webrtc_data_channel_link_to_sctp (channel, webrtc->priv->sctp_transport);
 
-      if (!channel->negotiated && !channel->opened)
-        gst_webrtc_data_channel_start_negotiation (channel);
+      if (!channel->parent.negotiated && !channel->opened)
+        webrtc_data_channel_start_negotiation (channel);
     }
     PC_UNLOCK (webrtc);
   }
 }
 
+/* Forward declaration so we can easily disconnect the signal handler */
+static void _on_sctp_notify_dtls_state (GstWebRTCDTLSTransport * transport,
+    GParamSpec * pspec, GstWebRTCBin * webrtc);
+
+static void
+_sctp_check_dtls_state_task (GstWebRTCBin * webrtc, gpointer unused)
+{
+  TransportStream *stream;
+  GstWebRTCDTLSTransport *transport;
+  GstWebRTCDTLSTransportState dtls_state;
+  GstWebRTCSCTPTransport *sctp_transport;
+
+  stream = webrtc->priv->data_channel_transport;
+  transport = stream->transport;
+
+  g_object_get (transport, "state", &dtls_state, NULL);
+  /* Not connected yet so just return */
+  if (dtls_state != GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+    GST_DEBUG_OBJECT (webrtc,
+        "Data channel DTLS connection is not ready yet: %d", dtls_state);
+    return;
+  }
+
+  GST_DEBUG_OBJECT (webrtc, "Data channel DTLS connection is now ready");
+  sctp_transport = webrtc->priv->sctp_transport;
+
+  /* Not locked state anymore so this was already taken care of before */
+  if (!gst_element_is_locked_state (sctp_transport->sctpdec))
+    return;
+
+  /* Start up the SCTP elements now that the DTLS connection is established */
+  gst_element_set_locked_state (sctp_transport->sctpdec, FALSE);
+  gst_element_set_locked_state (sctp_transport->sctpenc, FALSE);
+
+  gst_element_sync_state_with_parent (GST_ELEMENT (sctp_transport->sctpdec));
+  gst_element_sync_state_with_parent (GST_ELEMENT (sctp_transport->sctpenc));
+
+  if (sctp_transport->sctpdec_block_id) {
+    GstPad *receive_srcpad;
+
+    receive_srcpad =
+        gst_element_get_static_pad (GST_ELEMENT (stream->receive_bin),
+        "data_src");
+    gst_pad_remove_probe (receive_srcpad, sctp_transport->sctpdec_block_id);
+
+    sctp_transport->sctpdec_block_id = 0;
+    gst_object_unref (receive_srcpad);
+  }
+
+  g_signal_handlers_disconnect_by_func (transport, _on_sctp_notify_dtls_state,
+      webrtc);
+}
+
+static void
+_on_sctp_notify_dtls_state (GstWebRTCDTLSTransport * transport,
+    GParamSpec * pspec, GstWebRTCBin * webrtc)
+{
+  GstWebRTCDTLSTransportState dtls_state;
+
+  g_object_get (transport, "state", &dtls_state, NULL);
+
+  GST_TRACE_OBJECT (webrtc, "Data channel DTLS state changed to %d",
+      dtls_state);
+
+  /* Connected now, so schedule a task to update the state of the SCTP
+   * elements */
+  if (dtls_state == GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED) {
+    gst_webrtc_bin_enqueue_task (webrtc,
+        (GstWebRTCBinFunc) _sctp_check_dtls_state_task, NULL, NULL, NULL);
+  }
+}
+
+static GstPadProbeReturn
+sctp_pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
+{
+  /* Drop all events: we don't care about them and don't want to block on
+   * them. Sticky events would be forwarded again later once we unblock
+   * and we don't want to forward them here already because that might
+   * cause a spurious GST_FLOW_FLUSHING */
+  if (GST_IS_EVENT (info->data))
+    return GST_PAD_PROBE_DROP;
+
+  /* But block on any actual data-flow so we don't accidentally send that
+   * to a pad that is not ready yet, causing GST_FLOW_FLUSHING and everything
+   * to silently stop.
+   */
+  GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data);
+
+  return GST_PAD_PROBE_OK;
+}
+
 static TransportStream *
 _get_or_create_data_channel_transports (GstWebRTCBin * webrtc, guint session_id)
 {
@@ -1645,7 +1956,7 @@ _get_or_create_data_channel_transports (GstWebRTCBin * webrtc, guint session_id)
       stream = _create_transport_channel (webrtc, session_id);
       gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (stream->send_bin));
       gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (stream->receive_bin));
-      g_array_append_val (webrtc->priv->transports, stream);
+      g_ptr_array_add (webrtc->priv->transports, stream);
     }
 
     webrtc->priv->data_channel_transport = stream;
@@ -1658,6 +1969,11 @@ _get_or_create_data_channel_transports (GstWebRTCBin * webrtc, guint session_id)
           g_object_ref (webrtc->priv->data_channel_transport->transport);
       sctp_transport->webrtcbin = webrtc;
 
+      /* Don't automatically start SCTP elements as part of webrtcbin. We
+       * need to delay this until the DTLS transport is fully connected! */
+      gst_element_set_locked_state (sctp_transport->sctpdec, TRUE);
+      gst_element_set_locked_state (sctp_transport->sctpenc, TRUE);
+
       gst_bin_add (GST_BIN (webrtc), sctp_transport->sctpdec);
       gst_bin_add (GST_BIN (webrtc), sctp_transport->sctpenc);
     }
@@ -1667,6 +1983,18 @@ _get_or_create_data_channel_transports (GstWebRTCBin * webrtc, guint session_id)
     g_signal_connect (sctp_transport, "notify::state",
         G_CALLBACK (_on_sctp_state_notify), webrtc);
 
+    if (sctp_transport->sctpdec_block_id == 0) {
+      GstPad *receive_srcpad;
+      receive_srcpad =
+          gst_element_get_static_pad (GST_ELEMENT (stream->receive_bin),
+          "data_src");
+      sctp_transport->sctpdec_block_id =
+          gst_pad_add_probe (receive_srcpad,
+          GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+          (GstPadProbeCallback) sctp_pad_block, NULL, NULL);
+      gst_object_unref (receive_srcpad);
+    }
+
     if (!gst_element_link_pads (GST_ELEMENT (stream->receive_bin), "data_src",
             GST_ELEMENT (sctp_transport->sctpdec), "sink"))
       g_warn_if_reached ();
@@ -1676,23 +2004,27 @@ _get_or_create_data_channel_transports (GstWebRTCBin * webrtc, guint session_id)
       g_warn_if_reached ();
 
     for (i = 0; i < webrtc->priv->data_channels->len; i++) {
-      GstWebRTCDataChannel *channel;
+      WebRTCDataChannel *channel;
 
-      channel =
-          g_array_index (webrtc->priv->data_channels, GstWebRTCDataChannel *,
-          i);
+      channel = g_ptr_array_index (webrtc->priv->data_channels, i);
 
-      _link_data_channel_to_sctp (webrtc, channel);
+      webrtc_data_channel_link_to_sctp (channel, webrtc->priv->sctp_transport);
     }
 
     gst_element_sync_state_with_parent (GST_ELEMENT (stream->send_bin));
     gst_element_sync_state_with_parent (GST_ELEMENT (stream->receive_bin));
 
     if (!webrtc->priv->sctp_transport) {
-      gst_element_sync_state_with_parent (GST_ELEMENT
-          (sctp_transport->sctpdec));
-      gst_element_sync_state_with_parent (GST_ELEMENT
-          (sctp_transport->sctpenc));
+      /* Connect to the notify::state signal to get notified when the DTLS
+       * connection is established. Only then can we start the SCTP elements */
+      g_signal_connect (stream->transport, "notify::state",
+          G_CALLBACK (_on_sctp_notify_dtls_state), webrtc);
+
+      /* As this would be racy otherwise, also schedule a task that checks the
+       * current state of the connection already without getting the signal
+       * called */
+      gst_webrtc_bin_enqueue_task (webrtc,
+          (GstWebRTCBinFunc) _sctp_check_dtls_state_task, NULL, NULL, NULL);
     }
 
     webrtc->priv->sctp_transport = sctp_transport;
@@ -1946,8 +2278,8 @@ _add_fingerprint_to_media (GstWebRTCDTLSTransport * transport,
 static gboolean
 sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
     GstWebRTCRTPTransceiver * trans, GstWebRTCSDPType type, guint media_idx,
-    GString * bundled_mids, guint bundle_idx, gboolean bundle_only,
-    GArray * reserved_pts)
+    GString * bundled_mids, guint bundle_idx, gchar * bundle_ufrag,
+    gchar * bundle_pwd, GArray * reserved_pts)
 {
   /* TODO:
    * rtp header extensions
@@ -1959,19 +2291,48 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
    * dtls fingerprints
    * multiple dtls fingerprints https://tools.ietf.org/html/draft-ietf-mmusic-4572-update-05
    */
-  gchar *direction, *sdp_mid;
+  GstSDPMessage *last_offer = _get_latest_self_generated_sdp (webrtc);
+  gchar *direction, *sdp_mid, *ufrag, *pwd;
+  gboolean bundle_only;
   GstCaps *caps;
   int i;
 
-  /* "An m= section is generated for each RtpTransceiver that has been added
-   * to the Bin, excluding any stopped RtpTransceivers." */
-  if (trans->stopped)
-    return FALSE;
   if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
       || trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE)
     return FALSE;
 
-  gst_sdp_media_set_port_info (media, bundle_only ? 0 : 9, 0);
+  g_assert (trans->mline == -1 || trans->mline == media_idx);
+
+  bundle_only = bundled_mids && bundle_idx != media_idx
+      && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE;
+
+  /* mandated by JSEP */
+  gst_sdp_media_add_attribute (media, "setup", "actpass");
+
+  /* FIXME: deal with ICE restarts */
+  if (last_offer && trans->mline != -1 && trans->mid) {
+    ufrag = g_strdup (_media_get_ice_ufrag (last_offer, trans->mline));
+    pwd = g_strdup (_media_get_ice_pwd (last_offer, trans->mline));
+    GST_DEBUG_OBJECT (trans, "%u Using previous ice parameters", media_idx);
+  } else {
+    GST_DEBUG_OBJECT (trans,
+        "%u Generating new ice parameters mline %i, mid %s", media_idx,
+        trans->mline, trans->mid);
+    if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
+      _generate_ice_credentials (&ufrag, &pwd);
+    } else {
+      g_assert (bundle_ufrag && bundle_pwd);
+      ufrag = g_strdup (bundle_ufrag);
+      pwd = g_strdup (bundle_pwd);
+    }
+  }
+
+  gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
+  gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
+  g_free (ufrag);
+  g_free (pwd);
+
+  gst_sdp_media_set_port_info (media, bundle_only || trans->stopped ? 0 : 9, 0);
   gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
   gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
 
@@ -1998,9 +2359,6 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
     caps =
         _add_supported_attributes_to_caps (webrtc, WEBRTC_TRANSCEIVER (trans),
         caps);
-  } else if (type == GST_WEBRTC_SDP_TYPE_ANSWER) {
-    caps = _find_codec_preferences (webrtc, trans, GST_PAD_SRC, media_idx);
-    /* FIXME: add rtcp-fb paramaters */
   } else {
     g_assert_not_reached ();
   }
@@ -2060,10 +2418,18 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
   _media_add_ssrcs (media, caps, webrtc, WEBRTC_TRANSCEIVER (trans));
 
   /* Some identifier; we also add the media name to it so it's identifiable */
-  sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
-      webrtc->priv->media_counter++);
-  gst_sdp_media_add_attribute (media, "mid", sdp_mid);
-  g_free (sdp_mid);
+  if (trans->mid) {
+    gst_sdp_media_add_attribute (media, "mid", trans->mid);
+  } else {
+    sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
+        webrtc->priv->media_counter++);
+    gst_sdp_media_add_attribute (media, "mid", sdp_mid);
+    g_free (sdp_mid);
+  }
+
+  /* TODO:
+   * - add a=candidate lines for gathered candidates
+   */
 
   if (trans->sender) {
     if (!trans->sender->transport) {
@@ -2079,6 +2445,13 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
     _add_fingerprint_to_media (trans->sender->transport, media);
   }
 
+  if (bundled_mids) {
+    const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+    g_assert (mid);
+    g_string_append_printf (bundled_mids, " %s", mid);
+  }
+
   gst_caps_unref (caps);
 
   return TRUE;
@@ -2112,25 +2485,122 @@ gather_reserved_pts (GstWebRTCBin * webrtc)
   return reserved_pts;
 }
 
-/* TODO: use the options argument */
-static GstSDPMessage *
-_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+static gboolean
+_add_data_channel_offer (GstWebRTCBin * webrtc, GstSDPMessage * msg,
+    GstSDPMedia * media, GString * bundled_mids, guint bundle_idx,
+    gchar * bundle_ufrag, gchar * bundle_pwd)
 {
-  GstSDPMessage *ret;
-  int i;
-  GString *bundled_mids = NULL;
-  gchar *bundle_ufrag = NULL;
-  gchar *bundle_pwd = NULL;
-  GArray *reserved_pts = NULL;
+  GstSDPMessage *last_offer = _get_latest_self_generated_sdp (webrtc);
+  gchar *ufrag, *pwd, *sdp_mid;
+  gboolean bundle_only = bundled_mids
+      && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
+      && gst_sdp_message_medias_len (msg) != bundle_idx;
+  guint last_data_index = G_MAXUINT;
 
-  gst_sdp_message_new (&ret);
+  /* add data channel support */
+  if (webrtc->priv->data_channels->len == 0)
+    return FALSE;
+
+  if (last_offer) {
+    last_data_index = _message_get_datachannel_index (last_offer);
+    if (last_data_index < G_MAXUINT) {
+      g_assert (last_data_index < gst_sdp_message_medias_len (last_offer));
+      /* XXX: is this always true when recycling transceivers?
+       * i.e. do we always put the data channel in the same mline */
+      g_assert (last_data_index == gst_sdp_message_medias_len (msg));
+    }
+  }
+
+  /* mandated by JSEP */
+  gst_sdp_media_add_attribute (media, "setup", "actpass");
+
+  /* FIXME: only needed when restarting ICE */
+  if (last_offer && last_data_index < G_MAXUINT) {
+    ufrag = g_strdup (_media_get_ice_ufrag (last_offer, last_data_index));
+    pwd = g_strdup (_media_get_ice_pwd (last_offer, last_data_index));
+  } else {
+    if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
+      _generate_ice_credentials (&ufrag, &pwd);
+    } else {
+      ufrag = g_strdup (bundle_ufrag);
+      pwd = g_strdup (bundle_pwd);
+    }
+  }
+  gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
+  gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
+  g_free (ufrag);
+  g_free (pwd);
+
+  gst_sdp_media_set_media (media, "application");
+  gst_sdp_media_set_port_info (media, bundle_only ? 0 : 9, 0);
+  gst_sdp_media_set_proto (media, "UDP/DTLS/SCTP");
+  gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
+  gst_sdp_media_add_format (media, "webrtc-datachannel");
+
+  if (bundle_idx != gst_sdp_message_medias_len (msg))
+    gst_sdp_media_add_attribute (media, "bundle-only", NULL);
+
+  if (last_offer && last_data_index < G_MAXUINT) {
+    const GstSDPMedia *last_data_media;
+    const gchar *mid;
+
+    last_data_media = gst_sdp_message_get_media (last_offer, last_data_index);
+    mid = gst_sdp_media_get_attribute_val (last_data_media, "mid");
+
+    gst_sdp_media_add_attribute (media, "mid", mid);
+  } else {
+    sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
+        webrtc->priv->media_counter++);
+    gst_sdp_media_add_attribute (media, "mid", sdp_mid);
+    g_free (sdp_mid);
+  }
+
+  if (bundled_mids) {
+    const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+    g_assert (mid);
+    g_string_append_printf (bundled_mids, " %s", mid);
+  }
+
+  /* FIXME: negotiate this properly */
+  gst_sdp_media_add_attribute (media, "sctp-port", "5000");
+
+  _get_or_create_data_channel_transports (webrtc,
+      bundled_mids ? 0 : webrtc->priv->transceivers->len);
+  _add_fingerprint_to_media (webrtc->priv->sctp_transport->transport, media);
+
+  return TRUE;
+}
+
+/* TODO: use the options argument */
+static GstSDPMessage *
+_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+{
+  GstSDPMessage *ret;
+  GString *bundled_mids = NULL;
+  gchar *bundle_ufrag = NULL;
+  gchar *bundle_pwd = NULL;
+  GArray *reserved_pts = NULL;
+  GstSDPMessage *last_offer = _get_latest_self_generated_sdp (webrtc);
+  GList *seen_transceivers = NULL;
+  guint media_idx = 0;
+  int i;
+
+  gst_sdp_message_new (&ret);
 
   gst_sdp_message_set_version (ret, "0");
   {
-    /* FIXME: session id and version need special handling depending on the state we're in */
-    gchar *sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID);
-    gst_sdp_message_set_origin (ret, "-", sess_id, "0", "IN", "IP4", "0.0.0.0");
+    gchar *v, *sess_id;
+    v = g_strdup_printf ("%u", webrtc->priv->offer_count++);
+    if (last_offer) {
+      const GstSDPOrigin *origin = gst_sdp_message_get_origin (last_offer);
+      sess_id = g_strdup (origin->sess_id);
+    } else {
+      sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID);
+    }
+    gst_sdp_message_set_origin (ret, "-", sess_id, v, "IN", "IP4", "0.0.0.0");
     g_free (sess_id);
+    g_free (v);
   }
   gst_sdp_message_set_session_name (ret, "-");
   gst_sdp_message_add_time (ret, "0", "0", NULL);
@@ -2143,53 +2613,115 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
   }
 
   if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE) {
-    _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+    GStrv last_bundle = NULL;
+    guint bundle_media_index;
+
     reserved_pts = gather_reserved_pts (webrtc);
+    if (last_offer && _parse_bundle (last_offer, &last_bundle) && last_bundle
+        && last_bundle && last_bundle[0]
+        && _get_bundle_index (last_offer, last_bundle, &bundle_media_index)) {
+      bundle_ufrag =
+          g_strdup (_media_get_ice_ufrag (last_offer, bundle_media_index));
+      bundle_pwd =
+          g_strdup (_media_get_ice_pwd (last_offer, bundle_media_index));
+    } else {
+      _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+    }
+
+    g_strfreev (last_bundle);
+  }
+
+  /* FIXME: recycle transceivers */
+
+  /* Fill up the renegotiated streams first */
+  if (last_offer) {
+    for (i = 0; i < gst_sdp_message_medias_len (last_offer); i++) {
+      GstWebRTCRTPTransceiver *trans = NULL;
+      const GstSDPMedia *last_media;
+
+      last_media = gst_sdp_message_get_media (last_offer, i);
+
+      if (g_strcmp0 (gst_sdp_media_get_media (last_media), "audio") == 0
+          || g_strcmp0 (gst_sdp_media_get_media (last_media), "video") == 0) {
+        const gchar *last_mid;
+        int j;
+        last_mid = gst_sdp_media_get_attribute_val (last_media, "mid");
+
+        for (j = 0; j < webrtc->priv->transceivers->len; j++) {
+          trans = g_ptr_array_index (webrtc->priv->transceivers, j);
+
+          if (trans->mid && g_strcmp0 (trans->mid, last_mid) == 0) {
+            GstSDPMedia *media;
+
+            g_assert (!g_list_find (seen_transceivers, trans));
+
+            GST_LOG_OBJECT (webrtc, "using previous negotiatied transceiver %"
+                GST_PTR_FORMAT " with mid %s into media index %u", trans,
+                trans->mid, media_idx);
+
+            /* FIXME: deal with format changes */
+            gst_sdp_media_copy (last_media, &media);
+            _media_replace_direction (media, trans->direction);
+
+            if (bundled_mids) {
+              const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+              g_assert (mid);
+              g_string_append_printf (bundled_mids, " %s", mid);
+            }
+
+            gst_sdp_message_add_media (ret, media);
+            media_idx++;
+
+            gst_sdp_media_free (media);
+            seen_transceivers = g_list_prepend (seen_transceivers, trans);
+            break;
+          }
+        }
+      } else if (g_strcmp0 (gst_sdp_media_get_media (last_media),
+              "application") == 0) {
+        GstSDPMedia media = { 0, };
+        gst_sdp_media_init (&media);
+        if (_add_data_channel_offer (webrtc, ret, &media, bundled_mids, 0,
+                bundle_ufrag, bundle_pwd)) {
+          gst_sdp_message_add_media (ret, &media);
+          media_idx++;
+        } else {
+          gst_sdp_media_uninit (&media);
+        }
+      }
+    }
   }
 
-  /* for each rtp transceiver */
+  /* add any extra streams */
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *trans;
     GstSDPMedia media = { 0, };
-    gchar *ufrag, *pwd;
-    gboolean bundle_only = bundled_mids
-        && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
-        && i != 0;
 
-    trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+    trans = g_ptr_array_index (webrtc->priv->transceivers, i);
+
+    /* don't add transceivers twice */
+    if (g_list_find (seen_transceivers, trans))
+      continue;
+
+    /* don't add stopped transceivers */
+    if (trans->stopped)
+      continue;
 
     gst_sdp_media_init (&media);
-    /* mandated by JSEP */
-    gst_sdp_media_add_attribute (&media, "setup", "actpass");
 
-    /* FIXME: only needed when restarting ICE */
     if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
       reserved_pts = g_array_new (FALSE, FALSE, sizeof (guint));
-      _generate_ice_credentials (&ufrag, &pwd);
-    } else {
-      ufrag = g_strdup (bundle_ufrag);
-      pwd = g_strdup (bundle_pwd);
     }
 
-    gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag);
-    gst_sdp_media_add_attribute (&media, "ice-pwd", pwd);
-    g_free (ufrag);
-    g_free (pwd);
-
-    g_assert (reserved_pts != NULL);
+    GST_LOG_OBJECT (webrtc, "adding transceiver %" GST_PTR_FORMAT " at media "
+        "index %u", trans, media_idx);
 
     if (sdp_media_from_transceiver (webrtc, &media, trans,
-            GST_WEBRTC_SDP_TYPE_OFFER, i, bundled_mids, 0, bundle_only,
-            reserved_pts)) {
-      if (bundled_mids) {
-        const gchar *mid = gst_sdp_media_get_attribute_val (&media, "mid");
-
-        g_assert (mid);
-        g_string_append_printf (bundled_mids, " %s", mid);
-      }
+            GST_WEBRTC_SDP_TYPE_OFFER, media_idx, bundled_mids, 0, bundle_ufrag,
+            bundle_pwd, reserved_pts)) {
       gst_sdp_message_add_media (ret, &media);
+      media_idx++;
     } else {
       gst_sdp_media_uninit (&media);
     }
@@ -2197,62 +2729,28 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
     if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
       g_array_free (reserved_pts, TRUE);
     }
+    seen_transceivers = g_list_prepend (seen_transceivers, trans);
   }
 
   if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE) {
     g_array_free (reserved_pts, TRUE);
   }
 
-  /* add data channel support */
-  if (webrtc->priv->data_channels->len > 0) {
+  /* add a data channel if exists and not renegotiated */
+  if (_message_get_datachannel_index (ret) == G_MAXUINT) {
     GstSDPMedia media = { 0, };
-    gchar *ufrag, *pwd, *sdp_mid;
-    gboolean bundle_only = bundled_mids
-        && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
-        && webrtc->priv->transceivers->len != 0;
-
     gst_sdp_media_init (&media);
-    /* mandated by JSEP */
-    gst_sdp_media_add_attribute (&media, "setup", "actpass");
-
-    /* FIXME: only needed when restarting ICE */
-    if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
-      _generate_ice_credentials (&ufrag, &pwd);
+    if (_add_data_channel_offer (webrtc, ret, &media, bundled_mids, 0,
+            bundle_ufrag, bundle_pwd)) {
+      gst_sdp_message_add_media (ret, &media);
+      media_idx++;
     } else {
-      ufrag = g_strdup (bundle_ufrag);
-      pwd = g_strdup (bundle_pwd);
+      gst_sdp_media_uninit (&media);
     }
-    gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag);
-    gst_sdp_media_add_attribute (&media, "ice-pwd", pwd);
-    g_free (ufrag);
-    g_free (pwd);
-
-    gst_sdp_media_set_media (&media, "application");
-    gst_sdp_media_set_port_info (&media, bundle_only ? 0 : 9, 0);
-    gst_sdp_media_set_proto (&media, "UDP/DTLS/SCTP");
-    gst_sdp_media_add_connection (&media, "IN", "IP4", "0.0.0.0", 0, 0);
-    gst_sdp_media_add_format (&media, "webrtc-datachannel");
-
-    if (bundle_only)
-      gst_sdp_media_add_attribute (&media, "bundle-only", NULL);
-
-    sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (&media),
-        webrtc->priv->media_counter++);
-    gst_sdp_media_add_attribute (&media, "mid", sdp_mid);
-    if (bundled_mids)
-      g_string_append_printf (bundled_mids, " %s", sdp_mid);
-    g_free (sdp_mid);
-
-    /* FIXME: negotiate this properly */
-    gst_sdp_media_add_attribute (&media, "sctp-port", "5000");
-
-    _get_or_create_data_channel_transports (webrtc,
-        bundled_mids ? 0 : webrtc->priv->transceivers->len);
-    _add_fingerprint_to_media (webrtc->priv->sctp_transport->transport, &media);
-
-    gst_sdp_message_add_media (ret, &media);
   }
 
+  g_assert (media_idx == gst_sdp_message_medias_len (ret));
+
   if (bundled_mids) {
     gchar *mids = g_string_free (bundled_mids, FALSE);
 
@@ -2268,8 +2766,19 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
 
   /* FIXME: pre-emptively setup receiving elements when needed */
 
-  /* XXX: only true for the initial offerer */
-  g_object_set (webrtc->priv->ice, "controller", TRUE, NULL);
+  g_list_free (seen_transceivers);
+
+  if (webrtc->priv->last_generated_answer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_answer);
+  webrtc->priv->last_generated_answer = NULL;
+  if (webrtc->priv->last_generated_offer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_offer);
+  {
+    GstSDPMessage *copy;
+    gst_sdp_message_copy (ret, &copy);
+    webrtc->priv->last_generated_offer =
+        gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_OFFER, copy);
+  }
 
   return ret;
 }
@@ -2396,6 +2905,8 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
   GString *bundled_mids = NULL;
   gchar *bundle_ufrag = NULL;
   gchar *bundle_pwd = NULL;
+  GList *seen_transceivers = NULL;
+  GstSDPMessage *last_answer = _get_latest_self_generated_sdp (webrtc);
 
   if (!webrtc->pending_remote_description) {
     GST_ERROR_OBJECT (webrtc,
@@ -2407,6 +2918,9 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
     goto out;
 
   if (bundled) {
+    GStrv last_bundle = NULL;
+    guint bundle_media_index;
+
     if (!_get_bundle_index (pending_remote->sdp, bundled, &bundle_idx)) {
       GST_ERROR_OBJECT (webrtc, "Bundle tag is %s but no media found matching",
           bundled[0]);
@@ -2417,18 +2931,28 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
       bundled_mids = g_string_new ("BUNDLE");
     }
 
-    _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+    if (last_answer && _parse_bundle (last_answer, &last_bundle)
+        && last_bundle && last_bundle[0]
+        && _get_bundle_index (last_answer, last_bundle, &bundle_media_index)) {
+      bundle_ufrag =
+          g_strdup (_media_get_ice_ufrag (last_answer, bundle_media_index));
+      bundle_pwd =
+          g_strdup (_media_get_ice_pwd (last_answer, bundle_media_index));
+    } else {
+      _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+    }
+
+    g_strfreev (last_bundle);
   }
 
   gst_sdp_message_new (&ret);
 
-  /* FIXME: session id and version need special handling depending on the state we're in */
   gst_sdp_message_set_version (ret, "0");
   {
     const GstSDPOrigin *offer_origin =
         gst_sdp_message_get_origin (pending_remote->sdp);
-    gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id, "0", "IN",
-        "IP4", "0.0.0.0");
+    gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id,
+        offer_origin->sess_version, "IN", "IP4", "0.0.0.0");
   }
   gst_sdp_message_set_session_name (ret, "-");
 
@@ -2444,17 +2968,10 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
   for (i = 0; i < gst_sdp_message_medias_len (pending_remote->sdp); i++) {
     GstSDPMedia *media = NULL;
     GstSDPMedia *offer_media;
-    GstWebRTCRTPTransceiver *rtp_trans = NULL;
-    WebRTCTransceiver *trans = NULL;
-    GstWebRTCRTPTransceiverDirection offer_dir, answer_dir;
     GstWebRTCDTLSSetup offer_setup, answer_setup;
-    GstCaps *offer_caps, *answer_caps = NULL;
-    guint j;
-    guint k;
-    gint target_pt = -1;
-    gint original_target_pt = -1;
-    guint target_ssrc = 0;
+    guint j, k;
     gboolean bundle_only;
+    const gchar *mid;
 
     offer_media =
         (GstSDPMedia *) gst_sdp_message_get_media (pending_remote->sdp, i);
@@ -2468,13 +2985,19 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
     gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
 
     {
-      /* FIXME: only needed when restarting ICE */
       gchar *ufrag, *pwd;
-      if (!bundled) {
-        _generate_ice_credentials (&ufrag, &pwd);
+
+      /* FIXME: deal with ICE restarts */
+      if (last_answer && i < gst_sdp_message_medias_len (last_answer)) {
+        ufrag = g_strdup (_media_get_ice_ufrag (last_answer, i));
+        pwd = g_strdup (_media_get_ice_pwd (last_answer, i));
       } else {
-        ufrag = g_strdup (bundle_ufrag);
-        pwd = g_strdup (bundle_pwd);
+        if (!bundled) {
+          _generate_ice_credentials (&ufrag, &pwd);
+        } else {
+          ufrag = g_strdup (bundle_ufrag);
+          pwd = g_strdup (bundle_pwd);
+        }
       }
       gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
       gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
@@ -2493,6 +3016,10 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
       }
     }
 
+    mid = gst_sdp_media_get_attribute_val (media, "mid");
+    /* XXX: not strictly required but a lot of functionality requires a mid */
+    g_assert (mid);
+
     /* set the a=setup: attribute */
     offer_setup = _get_dtls_setup_from_media (offer_media);
     answer_setup = _intersect_dtls_setup (offer_setup);
@@ -2511,13 +3038,6 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
             "for webrtc-datachannel");
         goto rejected;
       }
-      if (g_strcmp0 (gst_sdp_media_get_format (offer_media, 0),
-              "webrtc-datachannel") != 0) {
-        GST_WARNING_OBJECT (webrtc,
-            "format field of data channel m= line "
-            "is not \'webrtc-datachannel\'");
-        goto rejected;
-      }
       sctp_port = _get_sctp_port_from_media (offer_media);
       if (sctp_port == -1) {
         GST_WARNING_OBJECT (webrtc, "media does not contain a sctp port");
@@ -2539,8 +3059,6 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
           bundled_mids ? bundle_idx : i);
 
       if (bundled_mids) {
-        const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
-
         g_assert (mid);
         g_string_append_printf (bundled_mids, " %s", mid);
       }
@@ -2549,62 +3067,95 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
           media);
     } else if (g_strcmp0 (gst_sdp_media_get_media (offer_media), "audio") == 0
         || g_strcmp0 (gst_sdp_media_get_media (offer_media), "video") == 0) {
-      gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
-
-      offer_caps = gst_caps_new_empty ();
-      for (j = 0; j < gst_sdp_media_formats_len (offer_media); j++) {
-        guint pt = atoi (gst_sdp_media_get_format (offer_media, j));
-        GstCaps *caps;
+      GstCaps *offer_caps, *answer_caps = NULL;
+      GstWebRTCRTPTransceiver *rtp_trans = NULL;
+      WebRTCTransceiver *trans = NULL;
+      GstWebRTCRTPTransceiverDirection offer_dir, answer_dir;
+      gint target_pt = -1;
+      gint original_target_pt = -1;
+      guint target_ssrc = 0;
 
-        caps = gst_sdp_media_get_caps_from_media (offer_media, pt);
+      gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
+      offer_caps = _rtp_caps_from_media (offer_media);
+
+      if (last_answer && i < gst_sdp_message_medias_len (last_answer)
+          && (rtp_trans =
+              _find_transceiver (webrtc, mid,
+                  (FindTransceiverFunc) match_for_mid))) {
+        const GstSDPMedia *last_media =
+            gst_sdp_message_get_media (last_answer, i);
+        const gchar *last_mid =
+            gst_sdp_media_get_attribute_val (last_media, "mid");
+
+        /* FIXME: assumes no shenanigans with recycling transceivers */
+        g_assert (g_strcmp0 (mid, last_mid) == 0);
+
+        if (!answer_caps
+            && (rtp_trans->direction ==
+                GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV
+                || rtp_trans->direction ==
+                GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY))
+          answer_caps =
+              _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, i);
+        if (!answer_caps
+            && (rtp_trans->direction ==
+                GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV
+                || rtp_trans->direction ==
+                GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY))
+          answer_caps =
+              _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SRC, i);
+        if (!answer_caps)
+          answer_caps = _rtp_caps_from_media (last_media);
+
+        /* XXX: In theory we're meant to use the sendrecv formats for the
+         * inactive direction however we don't know what that may be and would
+         * require asking outside what it expects to possibly send later */
+
+        GST_LOG_OBJECT (webrtc, "Found existing previously negotiated "
+            "transceiver %" GST_PTR_FORMAT " from mid %s for mline %u "
+            "using caps %" GST_PTR_FORMAT, rtp_trans, mid, i, answer_caps);
+      } else {
+        for (j = 0; j < webrtc->priv->transceivers->len; j++) {
+          GstCaps *trans_caps;
 
-        /* gst_sdp_media_get_caps_from_media() produces caps with name
-         * "application/x-unknown" which will fail intersection with
-         * "application/x-rtp" caps so mangle the returns caps to have the
-         * correct name here */
-        for (k = 0; k < gst_caps_get_size (caps); k++) {
-          GstStructure *s = gst_caps_get_structure (caps, k);
-          gst_structure_set_name (s, "application/x-rtp");
-        }
+          rtp_trans = g_ptr_array_index (webrtc->priv->transceivers, j);
 
-        gst_caps_append (offer_caps, caps);
-      }
+          if (g_list_find (seen_transceivers, rtp_trans)) {
+            /* Don't double allocate a transceiver to multiple mlines */
+            rtp_trans = NULL;
+            continue;
+          }
 
-      for (j = 0; j < webrtc->priv->transceivers->len; j++) {
-        GstCaps *trans_caps;
-
-        rtp_trans =
-            g_array_index (webrtc->priv->transceivers,
-            GstWebRTCRTPTransceiver *, j);
-        trans_caps =
-            _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, j);
-
-        GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT
-            " and %" GST_PTR_FORMAT, offer_caps, trans_caps);
-
-        /* FIXME: technically this is a little overreaching as some fields we
-         * we can deal with not having and/or we may have unrecognized fields
-         * that we cannot actually support */
-        if (trans_caps) {
-          answer_caps = gst_caps_intersect (offer_caps, trans_caps);
-          if (answer_caps && !gst_caps_is_empty (answer_caps)) {
-            GST_LOG_OBJECT (webrtc,
-                "found compatible transceiver %" GST_PTR_FORMAT
-                " for offer media %u", trans, i);
-            if (trans_caps)
-              gst_caps_unref (trans_caps);
-            break;
-          } else {
-            if (answer_caps) {
-              gst_caps_unref (answer_caps);
-              answer_caps = NULL;
+          trans_caps =
+              _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, j);
+
+          GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT
+              " and %" GST_PTR_FORMAT, offer_caps, trans_caps);
+
+          /* FIXME: technically this is a little overreaching as some fields we
+           * we can deal with not having and/or we may have unrecognized fields
+           * that we cannot actually support */
+          if (trans_caps) {
+            answer_caps = gst_caps_intersect (offer_caps, trans_caps);
+            if (answer_caps && !gst_caps_is_empty (answer_caps)) {
+              GST_LOG_OBJECT (webrtc,
+                  "found compatible transceiver %" GST_PTR_FORMAT
+                  " for offer media %u", rtp_trans, i);
+              if (trans_caps)
+                gst_caps_unref (trans_caps);
+              break;
+            } else {
+              if (answer_caps) {
+                gst_caps_unref (answer_caps);
+                answer_caps = NULL;
+              }
+              if (trans_caps)
+                gst_caps_unref (trans_caps);
+              rtp_trans = NULL;
             }
-            if (trans_caps)
-              gst_caps_unref (trans_caps);
+          } else {
             rtp_trans = NULL;
           }
-        } else {
-          rtp_trans = NULL;
         }
       }
 
@@ -2620,9 +3171,22 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
         answer_caps = gst_caps_ref (offer_caps);
       }
 
+      if (gst_caps_is_empty (answer_caps)) {
+        GST_WARNING_OBJECT (webrtc, "Could not create caps for media");
+        if (rtp_trans)
+          gst_object_unref (rtp_trans);
+        gst_caps_unref (answer_caps);
+        goto rejected;
+      }
+
+      seen_transceivers = g_list_prepend (seen_transceivers, rtp_trans);
+
       if (!rtp_trans) {
         trans = _create_webrtc_transceiver (webrtc, answer_dir, i);
         rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
+
+        GST_LOG_OBJECT (webrtc, "Created new transceiver %" GST_PTR_FORMAT
+            " for mline %u", trans, i);
       } else {
         trans = WEBRTC_TRANSCEIVER (rtp_trans);
       }
@@ -2670,18 +3234,19 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
       if (!trans->stream) {
         TransportStream *item;
 
-        if (bundled_mids) {
-          const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
-          item = _get_or_create_transport_stream (webrtc, bundle_idx, FALSE);
-
-          g_assert (mid);
-          g_string_append_printf (bundled_mids, " %s", mid);
-        } else {
-          item = _get_or_create_transport_stream (webrtc, i, FALSE);
-        }
+        item =
+            _get_or_create_transport_stream (webrtc,
+            bundled_mids ? bundle_idx : i, FALSE);
         webrtc_transceiver_set_transport (trans, item);
       }
 
+      if (bundled_mids) {
+        const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+        g_assert (mid);
+        g_string_append_printf (bundled_mids, " %s", mid);
+      }
+
       /* set the a=fingerprint: for this transport */
       _add_fingerprint_to_media (trans->stream->transport, media);
 
@@ -2718,11 +3283,24 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
   /* FIXME: can we add not matched transceivers? */
 
   /* XXX: only true for the initial offerer */
-  g_object_set (webrtc->priv->ice, "controller", FALSE, NULL);
+  gst_webrtc_ice_set_is_controller (webrtc->priv->ice, FALSE);
 
 out:
-  if (bundled)
-    g_strfreev (bundled);
+  g_strfreev (bundled);
+
+  g_list_free (seen_transceivers);
+
+  if (webrtc->priv->last_generated_offer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_offer);
+  webrtc->priv->last_generated_offer = NULL;
+  if (webrtc->priv->last_generated_answer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_answer);
+  {
+    GstSDPMessage *copy;
+    gst_sdp_message_copy (ret, &copy);
+    webrtc->priv->last_generated_answer =
+        gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, copy);
+  }
 
   return ret;
 }
@@ -2789,8 +3367,19 @@ gst_webrtc_bin_create_offer (GstWebRTCBin * webrtc,
   data->promise = gst_promise_ref (promise);
   data->type = GST_WEBRTC_SDP_TYPE_OFFER;
 
-  gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
-      data, (GDestroyNotify) _free_create_sdp_data);
+  if (!gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
+          data, (GDestroyNotify) _free_create_sdp_data, promise)) {
+    GError *error =
+        g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+        "Could not create offer. webrtcbin is closed");
+    GstStructure *s =
+        gst_structure_new ("application/x-gstwebrtcbin-promise-error",
+        "error", G_TYPE_ERROR, error, NULL);
+
+    gst_promise_reply (promise, s);
+
+    g_clear_error (&error);
+  }
 }
 
 static void
@@ -2804,8 +3393,19 @@ gst_webrtc_bin_create_answer (GstWebRTCBin * webrtc,
   data->promise = gst_promise_ref (promise);
   data->type = GST_WEBRTC_SDP_TYPE_ANSWER;
 
-  gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
-      data, (GDestroyNotify) _free_create_sdp_data);
+  if (!gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
+          data, (GDestroyNotify) _free_create_sdp_data, promise)) {
+    GError *error =
+        g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+        "Could not create answer. webrtcbin is closed.");
+    GstStructure *s =
+        gst_structure_new ("application/x-gstwebrtcbin-promise-error",
+        "error", G_TYPE_ERROR, error, NULL);
+
+    gst_promise_reply (promise, s);
+
+    g_clear_error (&error);
+  }
 }
 
 static GstWebRTCBinPad *
@@ -2951,7 +3551,14 @@ _connect_output_stream (GstWebRTCBin * webrtc,
  */
   gchar *pad_name;
 
-  GST_INFO_OBJECT (webrtc, "linking output stream %u", session_id);
+  if (stream->output_connected) {
+    GST_DEBUG_OBJECT (webrtc, "stream %" GST_PTR_FORMAT " is already "
+        "connected to rtpbin.  Not connecting", stream);
+    return;
+  }
+
+  GST_INFO_OBJECT (webrtc, "linking output stream %u %" GST_PTR_FORMAT,
+      session_id, stream);
 
   pad_name = g_strdup_printf ("recv_rtp_sink_%u", session_id);
   if (!gst_element_link_pads (GST_ELEMENT (stream->receive_bin),
@@ -2963,6 +3570,8 @@ _connect_output_stream (GstWebRTCBin * webrtc,
 
   /* The webrtcbin src_%u output pads will be created when rtpbin receives
    * data on that stream in on_rtpbin_pad_added() */
+
+  stream->output_connected = TRUE;
 }
 
 typedef struct
@@ -2972,20 +3581,32 @@ typedef struct
 } IceCandidateItem;
 
 static void
-_clear_ice_candidate_item (IceCandidateItem ** item)
+_clear_ice_candidate_item (IceCandidateItem * item)
 {
-  g_free ((*item)->candidate);
-  g_free (*item);
+  g_free (item->candidate);
 }
 
 static void
-_add_ice_candidate (GstWebRTCBin * webrtc, IceCandidateItem * item)
+_add_ice_candidate (GstWebRTCBin * webrtc, IceCandidateItem * item,
+    gboolean drop_invalid)
 {
   GstWebRTCICEStream *stream;
 
   stream = _find_ice_stream_for_session (webrtc, item->mlineindex);
   if (stream == NULL) {
-    GST_WARNING_OBJECT (webrtc, "Unknown mline %u, ignoring", item->mlineindex);
+    if (drop_invalid) {
+      GST_WARNING_OBJECT (webrtc, "Unknown mline %u, dropping",
+          item->mlineindex);
+    } else {
+      IceCandidateItem new;
+      new.mlineindex = item->mlineindex;
+      new.candidate = g_strdup (item->candidate);
+      GST_INFO_OBJECT (webrtc, "Unknown mline %u, deferring", item->mlineindex);
+
+      ICE_LOCK (webrtc);
+      g_array_append_val (webrtc->priv->pending_remote_ice_candidates, new);
+      ICE_UNLOCK (webrtc);
+    }
     return;
   }
 
@@ -2995,6 +3616,61 @@ _add_ice_candidate (GstWebRTCBin * webrtc, IceCandidateItem * item)
   gst_webrtc_ice_add_candidate (webrtc->priv->ice, stream, item->candidate);
 }
 
+static void
+_add_ice_candidates_from_sdp (GstWebRTCBin * webrtc, gint mlineindex,
+    const GstSDPMedia * media)
+{
+  gint a;
+  GstWebRTCICEStream *stream = NULL;
+
+  for (a = 0; a < gst_sdp_media_attributes_len (media); a++) {
+    const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, a);
+    if (g_strcmp0 (attr->key, "candidate") == 0) {
+      gchar *candidate;
+
+      if (stream == NULL)
+        stream = _find_ice_stream_for_session (webrtc, mlineindex);
+      if (stream == NULL) {
+        GST_WARNING_OBJECT (webrtc,
+            "Unknown mline %u, dropping ICE candidates from SDP", mlineindex);
+        return;
+      }
+
+      candidate = g_strdup_printf ("a=candidate:%s", attr->value);
+      GST_LOG_OBJECT (webrtc, "adding ICE candidate with mline:%u, %s",
+          mlineindex, candidate);
+      gst_webrtc_ice_add_candidate (webrtc->priv->ice, stream, candidate);
+      g_free (candidate);
+    }
+  }
+}
+
+static void
+_add_ice_candidate_to_sdp (GstWebRTCBin * webrtc,
+    GstSDPMessage * sdp, gint mline_index, const gchar * candidate)
+{
+  GstSDPMedia *media = NULL;
+
+  if (mline_index < sdp->medias->len) {
+    media = &g_array_index (sdp->medias, GstSDPMedia, mline_index);
+  }
+
+  if (media == NULL) {
+    GST_WARNING_OBJECT (webrtc, "Couldn't find mline %d to merge ICE candidate",
+        mline_index);
+    return;
+  }
+  // Add the candidate as an attribute, first stripping off the existing
+  // candidate: key from the string description
+  if (strlen (candidate) < 10) {
+    GST_WARNING_OBJECT (webrtc,
+        "Dropping invalid ICE candidate for mline %d: %s", mline_index,
+        candidate);
+    return;
+  }
+  gst_sdp_media_add_attribute (media, "candidate", candidate + 10);
+}
+
 static gboolean
 _filter_sdp_fields (GQuark field_id, const GValue * value,
     GstStructure * new_structure)
@@ -3006,6 +3682,40 @@ _filter_sdp_fields (GQuark field_id, const GValue * value,
 }
 
 static void
+_set_rtx_ptmap_from_stream (GstWebRTCBin * webrtc, TransportStream * stream)
+{
+  gint *rtx_pt;
+  gsize rtx_count;
+
+  rtx_pt = transport_stream_get_all_pt (stream, "RTX", &rtx_count);
+  GST_LOG_OBJECT (stream, "have %" G_GSIZE_FORMAT " rtx payloads", rtx_count);
+  if (rtx_pt) {
+    GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+    gsize i;
+
+    for (i = 0; i < rtx_count; i++) {
+      GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt[i]);
+      const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
+      const gchar *apt = gst_structure_get_string (s, "apt");
+
+      GST_LOG_OBJECT (stream, "setting rtx mapping: %s -> %u", apt, rtx_pt[i]);
+      gst_structure_set (pt_map, apt, G_TYPE_UINT, rtx_pt[i], NULL);
+    }
+
+    GST_DEBUG_OBJECT (stream, "setting payload map on %" GST_PTR_FORMAT " : %"
+        GST_PTR_FORMAT " and %" GST_PTR_FORMAT, stream->rtxreceive,
+        stream->rtxsend, pt_map);
+
+    if (stream->rtxreceive)
+      g_object_set (stream->rtxreceive, "payload-type-map", pt_map, NULL);
+    if (stream->rtxsend)
+      g_object_set (stream->rtxsend, "payload-type-map", pt_map, NULL);
+
+    gst_structure_free (pt_map);
+  }
+}
+
+static void
 _update_transport_ptmap_from_media (GstWebRTCBin * webrtc,
     TransportStream * stream, const GstSDPMessage * sdp, guint media_idx)
 {
@@ -3078,7 +3788,7 @@ static void
 _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
     const GstSDPMessage * sdp, guint media_idx,
     TransportStream * stream, GstWebRTCRTPTransceiver * rtp_trans,
-    GStrv bundled, guint bundle_idx, gboolean * should_connect_bundle_stream)
+    GStrv bundled, guint bundle_idx)
 {
   WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
   GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction;
@@ -3086,6 +3796,7 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
   const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
   GstWebRTCDTLSSetup new_setup;
   gboolean new_rtcp_mux, new_rtcp_rsize;
+  ReceiveState receive_state = RECEIVE_STATE_UNSET;
   int i;
 
   rtp_trans->mline = media_idx;
@@ -3125,6 +3836,7 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
       return;
 
     if (prev_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
+        && new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE
         && prev_dir != new_dir) {
       GST_FIXME_OBJECT (webrtc, "implement transceiver direction changes");
       return;
@@ -3150,10 +3862,59 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
     }
   }
 
+  if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE) {
+    if (!bundled) {
+      /* Not a bundled stream means this entire transport is inactive,
+       * so set the receive state to BLOCK below */
+      stream->active = FALSE;
+      receive_state = RECEIVE_STATE_BLOCK;
+    }
+  } else {
+    /* If this transceiver is active for sending or receiving,
+     * we still need receive at least RTCP, so need to unblock
+     * the receive bin below. */
+    GST_LOG_OBJECT (webrtc, "marking stream %p as active", stream);
+    receive_state = RECEIVE_STATE_PASS;
+    stream->active = TRUE;
+  }
+
   if (new_dir != prev_dir) {
-    ReceiveState receive_state = 0;
+    gchar *prev_dir_s, *new_dir_s;
+
+    prev_dir_s =
+        _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+        prev_dir);
+    new_dir_s =
+        _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+        new_dir);
+
+    GST_DEBUG_OBJECT (webrtc, "transceiver %" GST_PTR_FORMAT
+        " direction change from %s to %s", rtp_trans, prev_dir_s, new_dir_s);
+
+    g_free (prev_dir_s);
+    prev_dir_s = NULL;
+    g_free (new_dir_s);
+    new_dir_s = NULL;
+
+    if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE) {
+      GstWebRTCBinPad *pad;
+
+      pad = _find_pad_for_mline (webrtc, GST_PAD_SRC, media_idx);
+      if (pad) {
+        GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+        if (target) {
+          GstPad *peer = gst_pad_get_peer (target);
+          if (peer) {
+            gst_pad_send_event (peer, gst_event_new_eos ());
+            gst_object_unref (peer);
+          }
+          gst_object_unref (target);
+        }
+        gst_object_unref (pad);
+      }
 
-    GST_TRACE_OBJECT (webrtc, "transceiver direction change");
+      /* XXX: send eos event up the sink pad as well? */
+    }
 
     if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY ||
         new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) {
@@ -3199,35 +3960,35 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
           webrtc_transceiver_set_transport (trans, item);
         }
 
-        if (!bundled)
-          _connect_output_stream (webrtc, trans->stream, media_idx);
-        else
-          *should_connect_bundle_stream = TRUE;
+        _connect_output_stream (webrtc, trans->stream,
+            bundled ? bundle_idx : media_idx);
         /* delay adding the pad until rtpbin creates the recv output pad
          * to ghost to so queries/events travel through the pipeline correctly
          * as soon as the pad is added */
         _add_pad_to_list (webrtc, pad);
       }
 
-      receive_state = RECEIVE_STATE_PASS;
-    } else if (!bundled) {
-      receive_state = RECEIVE_STATE_DROP;
     }
 
-    if (!bundled || bundle_idx == media_idx)
-      g_object_set (stream, "dtls-client",
-          new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
-
-    /* Must be after setting the "dtls-client" so that data is not pushed into
-     * the dtlssrtp elements before the ssl direction has been set which will
-     * throw SSL errors */
-    if (receive_state > 0)
-      transport_receive_bin_set_receive_state (stream->receive_bin,
-          receive_state);
-
     rtp_trans->mline = media_idx;
     rtp_trans->current_direction = new_dir;
   }
+
+  if (!bundled || bundle_idx == media_idx) {
+    if (stream->rtxsend || stream->rtxreceive) {
+      _set_rtx_ptmap_from_stream (webrtc, stream);
+    }
+
+    g_object_set (stream, "dtls-client",
+        new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
+  }
+
+  /* Must be after setting the "dtls-client" so that data is not pushed into
+   * the dtlssrtp elements before the ssl direction has been set which will
+   * throw SSL errors */
+  if (receive_state != RECEIVE_STATE_UNSET)
+    transport_receive_bin_set_receive_state (stream->receive_bin,
+        receive_state);
 }
 
 /* must be called with the pc lock held */
@@ -3250,7 +4011,7 @@ _generate_data_channel_id (GstWebRTCBin * webrtc)
 
   /* TODO: a better search algorithm */
   do {
-    GstWebRTCDataChannel *channel;
+    WebRTCDataChannel *channel;
 
     new_id++;
 
@@ -3317,30 +4078,44 @@ _update_data_channel_from_sdp_media (GstWebRTCBin * webrtc,
 
   webrtc->priv->sctp_transport->max_message_size = max_size;
 
-  g_object_set (webrtc->priv->sctp_transport->sctpdec, "local-sctp-port",
-      local_port, NULL);
-  g_object_set (webrtc->priv->sctp_transport->sctpenc, "remote-sctp-port",
-      remote_port, NULL);
+  {
+    guint orig_local_port, orig_remote_port;
+
+    /* XXX: sctpassociation warns if we are in the wrong state */
+    g_object_get (webrtc->priv->sctp_transport->sctpdec, "local-sctp-port",
+        &orig_local_port, NULL);
+
+    if (orig_local_port != local_port)
+      g_object_set (webrtc->priv->sctp_transport->sctpdec, "local-sctp-port",
+          local_port, NULL);
+
+    g_object_get (webrtc->priv->sctp_transport->sctpenc, "remote-sctp-port",
+        &orig_remote_port, NULL);
+    if (orig_remote_port != remote_port)
+      g_object_set (webrtc->priv->sctp_transport->sctpenc, "remote-sctp-port",
+          remote_port, NULL);
+  }
 
   for (i = 0; i < webrtc->priv->data_channels->len; i++) {
-    GstWebRTCDataChannel *channel;
+    WebRTCDataChannel *channel;
 
-    channel =
-        g_array_index (webrtc->priv->data_channels, GstWebRTCDataChannel *, i);
+    channel = g_ptr_array_index (webrtc->priv->data_channels, i);
 
-    if (channel->id == -1)
-      channel->id = _generate_data_channel_id (webrtc);
-    if (channel->id == -1)
+    if (channel->parent.id == -1)
+      channel->parent.id = _generate_data_channel_id (webrtc);
+    if (channel->parent.id == -1)
       GST_ELEMENT_WARNING (webrtc, RESOURCE, NOT_FOUND,
           ("%s", "Failed to generate an identifier for a data channel"), NULL);
 
     if (webrtc->priv->sctp_transport->association_established
-        && !channel->negotiated && !channel->opened) {
-      _link_data_channel_to_sctp (webrtc, channel);
-      gst_webrtc_data_channel_start_negotiation (channel);
+        && !channel->parent.negotiated && !channel->opened) {
+      webrtc_data_channel_link_to_sctp (channel, webrtc->priv->sctp_transport);
+      webrtc_data_channel_start_negotiation (channel);
     }
   }
 
+  stream->active = TRUE;
+
   receive = TRANSPORT_RECEIVE_BIN (stream->receive_bin);
   transport_receive_bin_set_receive_state (receive, RECEIVE_STATE_PASS);
 }
@@ -3353,6 +4128,8 @@ _find_compatible_unassociated_transceiver (GstWebRTCRTPTransceiver * p1,
     return FALSE;
   if (p1->mline != -1)
     return FALSE;
+  if (p1->stopped)
+    return FALSE;
 
   return TRUE;
 }
@@ -3408,11 +4185,13 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
   gboolean ret = FALSE;
   GStrv bundled = NULL;
   guint bundle_idx = 0;
-  gboolean should_connect_bundle_stream = FALSE;
   TransportStream *bundle_stream = NULL;
 
-  if (!_parse_bundle (sdp->sdp, &bundled))
-    goto done;
+  /* FIXME: With some peers, it's possible we could have
+   * multiple bundles to deal with, although I've never seen one yet */
+  if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE)
+    if (!_parse_bundle (sdp->sdp, &bundled))
+      goto done;
 
   if (bundled) {
 
@@ -3424,13 +4203,19 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
 
     bundle_stream = _get_or_create_transport_stream (webrtc, bundle_idx,
         _message_media_is_datachannel (sdp->sdp, bundle_idx));
-
-    _connect_rtpfunnel (webrtc, bundle_idx);
+    /* Mark the bundle stream as inactive to start. It will be set to TRUE
+     * by any bundled mline that is active, and at the end we set the
+     * receivebin to BLOCK if all mlines were inactive. */
+    bundle_stream->active = FALSE;
 
     g_array_set_size (bundle_stream->ptmap, 0);
     for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
+      /* When bundling, we need to do this up front, or else RTX
+       * parameters aren't set up properly for the bundled streams */
       _update_transport_ptmap_from_media (webrtc, bundle_stream, sdp->sdp, i);
     }
+
+    _connect_rtpfunnel (webrtc, bundle_idx);
   }
 
   for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
@@ -3453,6 +4238,8 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
     stream = _get_or_create_transport_stream (webrtc, transport_idx,
         _message_media_is_datachannel (sdp->sdp, transport_idx));
     if (!bundled) {
+      /* When bundling, these were all set up above, but when not
+       * bundling we need to do it now */
       g_array_set_size (stream->ptmap, 0);
       _update_transport_ptmap_from_media (webrtc, stream, sdp->sdp, i);
     }
@@ -3466,24 +4253,25 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
     } else {
       if (g_strcmp0 (gst_sdp_media_get_media (media), "audio") == 0 ||
           g_strcmp0 (gst_sdp_media_get_media (media), "video") == 0) {
-        if (trans) {
-          _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
-              trans, bundled, bundle_idx, &should_connect_bundle_stream);
-        } else {
+        /* No existing transceiver, find an unused one */
+        if (!trans) {
           trans = _find_transceiver (webrtc, NULL,
               (FindTransceiverFunc) _find_compatible_unassociated_transceiver);
-          /* XXX: default to the advertised direction in the sdp for new
-           * transceviers.  The spec doesn't actually say what happens here, only
-           * that calls to setDirection will change the value.  Nothing about
-           * a default value when the transceiver is created internally */
-          if (!trans) {
-            trans =
-                GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc,
-                    _get_direction_from_media (media), i));
-          }
-          _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
-              trans, bundled, bundle_idx, &should_connect_bundle_stream);
         }
+
+        /* Still no transceiver? Create one */
+        /* XXX: default to the advertised direction in the sdp for new
+         * transceivers.  The spec doesn't actually say what happens here, only
+         * that calls to setDirection will change the value.  Nothing about
+         * a default value when the transceiver is created internally */
+        if (!trans) {
+          trans =
+              GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc,
+                  _get_direction_from_media (media), i));
+        }
+
+        _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
+            trans, bundled, bundle_idx);
       } else if (_message_media_is_datachannel (sdp->sdp, i)) {
         _update_data_channel_from_sdp_media (webrtc, sdp->sdp, i, stream);
       } else {
@@ -3492,16 +4280,20 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
     }
   }
 
-  if (should_connect_bundle_stream) {
-    g_assert (bundle_stream);
-    _connect_output_stream (webrtc, bundle_stream, bundle_idx);
+  if (bundle_stream && bundle_stream->active == FALSE) {
+    /* No bundled mline marked the bundle as active, so block the receive bin, as
+     * this bundle is completely inactive */
+    GST_LOG_OBJECT (webrtc,
+        "All mlines in bundle %u are inactive. Blocking receiver", bundle_idx);
+    transport_receive_bin_set_receive_state (bundle_stream->receive_bin,
+        RECEIVE_STATE_BLOCK);
   }
 
   ret = TRUE;
 
 done:
-  if (bundled)
-    g_strfreev (bundled);
+  g_strfreev (bundled);
+
   return ret;
 }
 
@@ -3517,6 +4309,7 @@ static void
 _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
 {
   GstWebRTCSignalingState new_signaling_state = webrtc->signaling_state;
+  gboolean signalling_state_changed = FALSE;
   GError *error = NULL;
   GStrv bundled = NULL;
   guint bundle_idx = 0;
@@ -3530,7 +4323,7 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
     gchar *sdp_text = gst_sdp_message_as_text (sd->sdp->sdp);
     GST_INFO_OBJECT (webrtc, "Attempting to set %s %s in the %s state",
         _sdp_source_to_string (sd->source), type_str, state);
-    GST_INFO_OBJECT (webrtc, "SDP contents\n%s", sdp_text);
+    GST_TRACE_OBJECT (webrtc, "SDP contents\n%s", sdp_text);
     g_free (sdp_text);
     g_free (state);
     g_free (type_str);
@@ -3547,8 +4340,9 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
     goto out;
   }
 
-  if (!_parse_bundle (sd->sdp->sdp, &bundled))
-    goto out;
+  if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE)
+    if (!_parse_bundle (sd->sdp->sdp, &bundled))
+      goto out;
 
   if (bundled) {
     if (!_get_bundle_index (sd->sdp->sdp, bundled, &bundle_idx)) {
@@ -3656,25 +4450,9 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
     }
   }
 
-  if (new_signaling_state != webrtc->signaling_state) {
-    gchar *from = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
-        webrtc->signaling_state);
-    gchar *to = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
-        new_signaling_state);
-    GST_TRACE_OBJECT (webrtc, "notify signaling-state from %s "
-        "to %s", from, to);
-    webrtc->signaling_state = new_signaling_state;
-    PC_UNLOCK (webrtc);
-    g_object_notify (G_OBJECT (webrtc), "signaling-state");
-    PC_LOCK (webrtc);
-
-    g_free (from);
-    g_free (to);
-  }
-
   if (sd->sdp->type == GST_WEBRTC_SDP_TYPE_ROLLBACK) {
     /* FIXME:
-     * If the mid value of an RTCRtpTransceiver was set to a non-null value 
+     * If the mid value of an RTCRtpTransceiver was set to a non-null value
      * by the RTCSessionDescription that is being rolled back, set the mid
      * value of that transceiver to null, as described by [JSEP]
      * (section 4.1.7.2.).
@@ -3688,44 +4466,98 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
      */
   }
 
-  if (webrtc->signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) {
-    gboolean prev_need_negotiation = webrtc->priv->need_negotiation;
+  if (webrtc->signaling_state != new_signaling_state) {
+    webrtc->signaling_state = new_signaling_state;
+    signalling_state_changed = TRUE;
+  }
+
+  {
+    gboolean ice_controller = FALSE;
+
+    /* get the current value so we don't change ice controller from TRUE to
+     * FALSE on renegotiation or once set to TRUE for the initial local offer */
+    ice_controller = gst_webrtc_ice_get_is_controller (webrtc->priv->ice);
+
+    /* we control ice negotiation if we send the initial offer */
+    ice_controller |=
+        new_signaling_state == GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER
+        && webrtc->current_remote_description == NULL;
+    /* or, if the remote is an ice-lite peer */
+    ice_controller |= new_signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE
+        && webrtc->current_remote_description
+        && _message_has_attribute_key (webrtc->current_remote_description->sdp,
+        "ice-lite");
+
+    GST_DEBUG_OBJECT (webrtc, "we are in ice controlling mode: %s",
+        ice_controller ? "true" : "false");
+    gst_webrtc_ice_set_is_controller (webrtc->priv->ice, ice_controller);
+  }
+
+  if (new_signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) {
     GList *tmp;
 
     /* media modifications */
     _update_transceivers_from_sdp (webrtc, sd->source, sd->sdp);
 
-    for (tmp = webrtc->priv->pending_sink_transceivers; tmp; tmp = tmp->next) {
+    for (tmp = webrtc->priv->pending_sink_transceivers; tmp;) {
       GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (tmp->data);
+      GstWebRTCRTPTransceiverDirection new_dir;
+      GList *old = tmp;
       const GstSDPMedia *media;
 
+      if (!pad->received_caps) {
+        GST_LOG_OBJECT (pad, "has not received any caps yet. Skipping.");
+        tmp = tmp->next;
+        continue;
+      }
+
+      if (pad->mlineindex >= gst_sdp_message_medias_len (sd->sdp->sdp)) {
+        GST_DEBUG_OBJECT (pad, "not mentioned in this description. Skipping");
+        tmp = tmp->next;
+        continue;
+      }
+
       media = gst_sdp_message_get_media (sd->sdp->sdp, pad->mlineindex);
       /* skip rejected media */
-      /* FIXME: arrange for an appropriate flow return */
-      if (gst_sdp_media_get_port (media) == 0)
+      if (gst_sdp_media_get_port (media) == 0) {
+        /* FIXME: arrange for an appropriate flow return */
+        GST_FIXME_OBJECT (pad, "Media has been rejected.  Need to arrange for "
+            "a more correct flow return.");
+        tmp = tmp->next;
+        continue;
+      }
+
+      if (!pad->trans) {
+        GST_LOG_OBJECT (pad, "doesn't have a transceiver");
+        tmp = tmp->next;
+        continue;
+      }
+
+      new_dir = pad->trans->direction;
+      if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY &&
+          new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) {
+        GST_LOG_OBJECT (pad, "transceiver %" GST_PTR_FORMAT " is not sending "
+            "data at the moment. Not connecting input stream yet", pad->trans);
+        tmp = tmp->next;
         continue;
+      }
 
+      GST_LOG_OBJECT (pad, "Connecting input stream to rtpbin with "
+          "transceiver %" GST_PTR_FORMAT " and caps %" GST_PTR_FORMAT,
+          pad->trans, pad->received_caps);
       _connect_input_stream (webrtc, pad);
       gst_pad_remove_probe (GST_PAD (pad), pad->block_id);
       pad->block_id = 0;
-    }
-
-    g_list_free_full (webrtc->priv->pending_sink_transceivers,
-        (GDestroyNotify) gst_object_unref);
-    webrtc->priv->pending_sink_transceivers = NULL;
 
-    /* If connection's signaling state is now stable, update the
-     * negotiation-needed flag. If connection's [[ needNegotiation]] slot
-     * was true both before and after this update, queue a task to check
-     * connection's [[needNegotiation]] slot and, if still true, fire a
-     * simple event named negotiationneeded at connection.*/
-    _update_need_negotiation (webrtc);
-    if (prev_need_negotiation && webrtc->priv->need_negotiation) {
-      _check_need_negotiation_task (webrtc, NULL);
+      tmp = tmp->next;
+      gst_object_unref (old->data);
+      webrtc->priv->pending_sink_transceivers =
+          g_list_delete_link (webrtc->priv->pending_sink_transceivers, old);
     }
   }
 
   for (i = 0; i < gst_sdp_message_medias_len (sd->sdp->sdp); i++) {
+    const GstSDPMedia *media = gst_sdp_message_get_media (sd->sdp->sdp, i);
     gchar *ufrag, *pwd;
     TransportStream *item;
 
@@ -3734,7 +4566,6 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
         _message_media_is_datachannel (sd->sdp->sdp, bundled ? bundle_idx : i));
 
     if (sd->source == SDP_REMOTE) {
-      const GstSDPMedia *media = gst_sdp_message_get_media (sd->sdp->sdp, i);
       guint j;
 
       for (j = 0; j < gst_sdp_media_attributes_len (media); j++) {
@@ -3757,44 +4588,93 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
       }
     }
 
-    if (bundled && bundle_idx != i)
-      continue;
-
-    _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd);
+    if (sd->source == SDP_LOCAL && (!bundled || bundle_idx == i)) {
+      _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd);
 
-    if (sd->source == SDP_LOCAL)
       gst_webrtc_ice_set_local_credentials (webrtc->priv->ice,
           item->stream, ufrag, pwd);
-    else
+      g_free (ufrag);
+      g_free (pwd);
+    } else if (sd->source == SDP_REMOTE && !_media_is_bundle_only (media)) {
+      _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd);
+
       gst_webrtc_ice_set_remote_credentials (webrtc->priv->ice,
           item->stream, ufrag, pwd);
-    g_free (ufrag);
-    g_free (pwd);
+      g_free (ufrag);
+      g_free (pwd);
+    }
   }
 
-  for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) {
-    IceStreamItem *item =
-        &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i);
+  if (sd->source == SDP_LOCAL) {
+    for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) {
+      IceStreamItem *item =
+          &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i);
 
-    gst_webrtc_ice_gather_candidates (webrtc->priv->ice, item->stream);
+      gst_webrtc_ice_gather_candidates (webrtc->priv->ice, item->stream);
+    }
   }
 
+  /* Add any pending trickle ICE candidates if we have both offer and answer */
   if (webrtc->current_local_description && webrtc->current_remote_description) {
     int i;
 
-    for (i = 0; i < webrtc->priv->pending_ice_candidates->len; i++) {
+    GstWebRTCSessionDescription *remote_sdp =
+        webrtc->current_remote_description;
+
+    /* Add any remote ICE candidates from the remote description to
+     * support non-trickle peers first */
+    for (i = 0; i < gst_sdp_message_medias_len (remote_sdp->sdp); i++) {
+      const GstSDPMedia *media = gst_sdp_message_get_media (remote_sdp->sdp, i);
+      _add_ice_candidates_from_sdp (webrtc, i, media);
+    }
+
+    ICE_LOCK (webrtc);
+    for (i = 0; i < webrtc->priv->pending_remote_ice_candidates->len; i++) {
       IceCandidateItem *item =
-          g_array_index (webrtc->priv->pending_ice_candidates,
-          IceCandidateItem *, i);
+          &g_array_index (webrtc->priv->pending_remote_ice_candidates,
+          IceCandidateItem, i);
+
+      _add_ice_candidate (webrtc, item, TRUE);
+    }
+    g_array_set_size (webrtc->priv->pending_remote_ice_candidates, 0);
+    ICE_UNLOCK (webrtc);
+  }
+
+  /*
+   * If connection's signaling state changed above, fire an event named
+   * signalingstatechange at connection.
+   */
+  if (signalling_state_changed) {
+    gchar *from = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
+        webrtc->signaling_state);
+    gchar *to = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
+        new_signaling_state);
+    GST_TRACE_OBJECT (webrtc, "notify signaling-state from %s "
+        "to %s", from, to);
+    PC_UNLOCK (webrtc);
+    g_object_notify (G_OBJECT (webrtc), "signaling-state");
+    PC_LOCK (webrtc);
+
+    g_free (from);
+    g_free (to);
+  }
+
+  if (webrtc->signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) {
+    gboolean prev_need_negotiation = webrtc->priv->need_negotiation;
 
-      _add_ice_candidate (webrtc, item);
+    /* If connection's signaling state is now stable, update the
+     * negotiation-needed flag. If connection's [[ needNegotiation]] slot
+     * was true both before and after this update, queue a task to check
+     * connection's [[needNegotiation]] slot and, if still true, fire a
+     * simple event named negotiationneeded at connection.*/
+    _update_need_negotiation (webrtc);
+    if (prev_need_negotiation && webrtc->priv->need_negotiation) {
+      _check_need_negotiation_task (webrtc, NULL);
     }
-    g_array_set_size (webrtc->priv->pending_ice_candidates, 0);
   }
 
 out:
-  if (bundled)
-    g_strfreev (bundled);
+  g_strfreev (bundled);
 
   PC_UNLOCK (webrtc);
   gst_promise_reply (sd->promise, NULL);
@@ -3828,8 +4708,20 @@ gst_webrtc_bin_set_remote_description (GstWebRTCBin * webrtc,
   sd->source = SDP_REMOTE;
   sd->sdp = gst_webrtc_session_description_copy (remote_sdp);
 
-  gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task,
-      sd, (GDestroyNotify) _free_set_description_data);
+  if (!gst_webrtc_bin_enqueue_task (webrtc,
+          (GstWebRTCBinFunc) _set_description_task, sd,
+          (GDestroyNotify) _free_set_description_data, promise)) {
+    GError *error =
+        g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+        "Could not set remote description. webrtcbin is closed.");
+    GstStructure *s =
+        gst_structure_new ("application/x-gstwebrtcbin-promise-error",
+        "error", G_TYPE_ERROR, error, NULL);
+
+    gst_promise_reply (promise, s);
+
+    g_clear_error (&error);
+  }
 
   return;
 
@@ -3857,8 +4749,20 @@ gst_webrtc_bin_set_local_description (GstWebRTCBin * webrtc,
   sd->source = SDP_LOCAL;
   sd->sdp = gst_webrtc_session_description_copy (local_sdp);
 
-  gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task,
-      sd, (GDestroyNotify) _free_set_description_data);
+  if (!gst_webrtc_bin_enqueue_task (webrtc,
+          (GstWebRTCBinFunc) _set_description_task, sd,
+          (GDestroyNotify) _free_set_description_data, promise)) {
+    GError *error =
+        g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+        "Could not set remote description. webrtcbin is closed");
+    GstStructure *s =
+        gst_structure_new ("application/x-gstwebrtcbin-promise-error",
+        "error", G_TYPE_ERROR, error, NULL);
+
+    gst_promise_reply (promise, s);
+
+    g_clear_error (&error);
+  }
 
   return;
 
@@ -3873,20 +4777,23 @@ static void
 _add_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item)
 {
   if (!webrtc->current_local_description || !webrtc->current_remote_description) {
-    IceCandidateItem *new = g_new0 (IceCandidateItem, 1);
-    new->mlineindex = item->mlineindex;
-    new->candidate = g_strdup (item->candidate);
+    IceCandidateItem new;
+    new.mlineindex = item->mlineindex;
+    new.candidate = g_steal_pointer (&item->candidate);
 
-    g_array_append_val (webrtc->priv->pending_ice_candidates, new);
+    ICE_LOCK (webrtc);
+    g_array_append_val (webrtc->priv->pending_remote_ice_candidates, new);
+    ICE_UNLOCK (webrtc);
   } else {
-    _add_ice_candidate (webrtc, item);
+    _add_ice_candidate (webrtc, item, FALSE);
   }
 }
 
 static void
 _free_ice_candidate_item (IceCandidateItem * item)
 {
-  _clear_ice_candidate_item (&item);
+  _clear_ice_candidate_item (item);
+  g_free (item);
 }
 
 static void
@@ -3903,40 +4810,92 @@ gst_webrtc_bin_add_ice_candidate (GstWebRTCBin * webrtc, guint mline,
     item->candidate = g_strdup_printf ("a=%s", attr);
   gst_webrtc_bin_enqueue_task (webrtc,
       (GstWebRTCBinFunc) _add_ice_candidate_task, item,
-      (GDestroyNotify) _free_ice_candidate_item);
+      (GDestroyNotify) _free_ice_candidate_item, NULL);
 }
 
 static void
-_on_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item)
-{
-  const gchar *cand = item->candidate;
+_on_local_ice_candidate_task (GstWebRTCBin * webrtc)
+{
+  gsize i;
+  GArray *items;
+
+  ICE_LOCK (webrtc);
+  if (webrtc->priv->pending_local_ice_candidates->len == 0) {
+    ICE_UNLOCK (webrtc);
+    GST_LOG_OBJECT (webrtc, "No ICE candidates to process right now");
+    return;                     /* Nothing to process */
+  }
+  /* Take the array so we can process it all and free it later
+   * without holding the lock
+   * FIXME: When we depend on GLib 2.64, we can use g_array_steal()
+   * here */
+  items = webrtc->priv->pending_local_ice_candidates;
+  /* Replace with a new array */
+  webrtc->priv->pending_local_ice_candidates =
+      g_array_new (FALSE, TRUE, sizeof (IceCandidateItem));
+  g_array_set_clear_func (webrtc->priv->pending_local_ice_candidates,
+      (GDestroyNotify) _clear_ice_candidate_item);
+  ICE_UNLOCK (webrtc);
 
-  if (!g_ascii_strncasecmp (cand, "a=candidate:", 12)) {
-    /* stripping away "a=" */
-    cand += 2;
-  }
+  for (i = 0; i < items->len; i++) {
+    IceCandidateItem *item = &g_array_index (items, IceCandidateItem, i);
+    const gchar *cand = item->candidate;
+
+    if (!g_ascii_strncasecmp (cand, "a=candidate:", 12)) {
+      /* stripping away "a=" */
+      cand += 2;
+    }
 
-  GST_TRACE_OBJECT (webrtc, "produced ICE candidate for mline:%u and %s",
-      item->mlineindex, cand);
+    GST_TRACE_OBJECT (webrtc, "produced ICE candidate for mline:%u and %s",
+        item->mlineindex, cand);
 
-  PC_UNLOCK (webrtc);
-  g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL],
-      0, item->mlineindex, cand);
-  PC_LOCK (webrtc);
+    /* First, merge this ice candidate into the appropriate mline
+     * in the local-description SDP.
+     * Second, emit the on-ice-candidate signal for the app.
+     *
+     * FIXME: This ICE candidate should be stored somewhere with
+     * the associated mid and also merged back into any subsequent
+     * local descriptions on renegotiation */
+    if (webrtc->current_local_description)
+      _add_ice_candidate_to_sdp (webrtc, webrtc->current_local_description->sdp,
+          item->mlineindex, cand);
+    if (webrtc->pending_local_description)
+      _add_ice_candidate_to_sdp (webrtc, webrtc->pending_local_description->sdp,
+          item->mlineindex, cand);
+
+    PC_UNLOCK (webrtc);
+    g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL],
+        0, item->mlineindex, cand);
+    PC_LOCK (webrtc);
+
+  }
+  g_array_free (items, TRUE);
 }
 
 static void
-_on_ice_candidate (GstWebRTCICE * ice, guint session_id,
+_on_local_ice_candidate_cb (GstWebRTCICE * ice, guint session_id,
     gchar * candidate, GstWebRTCBin * webrtc)
 {
-  IceCandidateItem *item = g_new0 (IceCandidateItem, 1);
+  IceCandidateItem item;
+  gboolean queue_task = FALSE;
 
-  item->mlineindex = session_id;
-  item->candidate = g_strdup (candidate);
+  item.mlineindex = session_id;
+  item.candidate = g_strdup (candidate);
 
-  gst_webrtc_bin_enqueue_task (webrtc,
-      (GstWebRTCBinFunc) _on_ice_candidate_task, item,
-      (GDestroyNotify) _free_ice_candidate_item);
+  ICE_LOCK (webrtc);
+  g_array_append_val (webrtc->priv->pending_local_ice_candidates, item);
+
+  /* Let the first pending candidate queue a task each time, which will
+   * handle any that arrive between now and when the task runs */
+  if (webrtc->priv->pending_local_ice_candidates->len == 1)
+    queue_task = TRUE;
+  ICE_UNLOCK (webrtc);
+
+  if (queue_task) {
+    GST_TRACE_OBJECT (webrtc, "Queueing on_ice_candidate_task");
+    gst_webrtc_bin_enqueue_task (webrtc,
+        (GstWebRTCBinFunc) _on_local_ice_candidate_task, NULL, NULL, NULL);
+  }
 }
 
 /* https://www.w3.org/TR/webrtc/#dfn-stats-selection-algorithm */
@@ -4005,8 +4964,18 @@ gst_webrtc_bin_get_stats (GstWebRTCBin * webrtc, GstPad * pad,
   if (pad)
     stats->pad = gst_object_ref (pad);
 
-  gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _get_stats_task,
-      stats, (GDestroyNotify) _free_get_stats);
+  if (!gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _get_stats_task,
+          stats, (GDestroyNotify) _free_get_stats, promise)) {
+    GError *error =
+        g_error_new (GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_CLOSED,
+        "Could not retrieve statistics. webrtcbin is closed.");
+    GstStructure *s = gst_structure_new ("application/x-gst-promise-error",
+        "error", G_TYPE_ERROR, error, NULL);
+
+    gst_promise_reply (promise, s);
+
+    g_clear_error (&error);
+  }
 }
 
 static GstWebRTCRTPTransceiver *
@@ -4020,6 +4989,9 @@ gst_webrtc_bin_add_transceiver (GstWebRTCBin * webrtc,
       NULL);
 
   trans = _create_webrtc_transceiver (webrtc, direction, -1);
+  GST_LOG_OBJECT (webrtc,
+      "Created new unassociated transceiver %" GST_PTR_FORMAT, trans);
+
   rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
   if (caps)
     rtp_trans->codec_preferences = gst_caps_ref (caps);
@@ -4030,22 +5002,20 @@ gst_webrtc_bin_add_transceiver (GstWebRTCBin * webrtc,
 static void
 _deref_and_unref (GstObject ** object)
 {
-  if (object)
-    gst_object_unref (*object);
+  gst_clear_object (object);
 }
 
 static GArray *
 gst_webrtc_bin_get_transceivers (GstWebRTCBin * webrtc)
 {
-  GArray *arr = g_array_new (FALSE, TRUE, sizeof (gpointer));
+  GArray *arr = g_array_new (FALSE, TRUE, sizeof (GstWebRTCRTPTransceiver *));
   int i;
 
   g_array_set_clear_func (arr, (GDestroyNotify) _deref_and_unref);
 
   for (i = 0; i < webrtc->priv->transceivers->len; i++) {
     GstWebRTCRTPTransceiver *trans =
-        g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-        i);
+        g_ptr_array_index (webrtc->priv->transceivers, i);
     gst_object_ref (trans);
     g_array_append_val (arr, trans);
   }
@@ -4063,9 +5033,7 @@ gst_webrtc_bin_get_transceiver (GstWebRTCBin * webrtc, guint idx)
     goto done;
   }
 
-  trans =
-      g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
-      idx);
+  trans = g_ptr_array_index (webrtc->priv->transceivers, idx);
   gst_object_ref (trans);
 
 done:
@@ -4094,7 +5062,7 @@ copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
   return TRUE;
 }
 
-static GstWebRTCDataChannel *
+static WebRTCDataChannel *
 gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
     GstStructure * init_params)
 {
@@ -4105,7 +5073,7 @@ gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
   gboolean negotiated;
   gint id;
   GstWebRTCPriorityType priority;
-  GstWebRTCDataChannel *ret;
+  WebRTCDataChannel *ret;
   gint max_channels = 65534;
 
   g_return_val_if_fail (GST_IS_WEBRTC_BIN (webrtc), NULL);
@@ -4172,7 +5140,7 @@ gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
   PC_LOCK (webrtc);
   /* check if the id has been used already */
   if (id != -1) {
-    GstWebRTCDataChannel *channel = _find_data_channel_for_id (webrtc, id);
+    WebRTCDataChannel *channel = _find_data_channel_for_id (webrtc, id);
     if (channel) {
       GST_ELEMENT_WARNING (webrtc, LIBRARY, SETTINGS,
           ("Attempting to add a data channel with a duplicate ID: %i", id),
@@ -4194,7 +5162,7 @@ gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
     }
   }
 
-  ret = g_object_new (GST_TYPE_WEBRTC_DATA_CHANNEL, "label", label,
+  ret = g_object_new (WEBRTC_TYPE_DATA_CHANNEL, "label", label,
       "ordered", ordered, "max-packet-lifetime", max_packet_lifetime,
       "max-retransmits", max_retransmits, "protocol", protocol,
       "negotiated", negotiated, "id", id, "priority", priority, NULL);
@@ -4208,12 +5176,15 @@ gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
 
     ret = gst_object_ref (ret);
     ret->webrtcbin = webrtc;
-    g_array_append_val (webrtc->priv->data_channels, ret);
-    _link_data_channel_to_sctp (webrtc, ret);
+    g_ptr_array_add (webrtc->priv->data_channels, ret);
+    webrtc_data_channel_link_to_sctp (ret, webrtc->priv->sctp_transport);
     if (webrtc->priv->sctp_transport &&
         webrtc->priv->sctp_transport->association_established
-        && !ret->negotiated)
-      gst_webrtc_data_channel_start_negotiation (ret);
+        && !ret->parent.negotiated) {
+      webrtc_data_channel_start_negotiation (ret);
+    } else {
+      _update_need_negotiation (webrtc);
+    }
   }
 
   PC_UNLOCK (webrtc);
@@ -4237,6 +5208,7 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
     TransportStream *stream;
     GstWebRTCBinPad *pad;
     guint media_idx = 0;
+    gboolean found_ssrc = FALSE;
     guint i;
 
     if (sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc,
@@ -4256,10 +5228,15 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
           &g_array_index (stream->remote_ssrcmap, SsrcMapItem, i);
       if (item->ssrc == ssrc) {
         media_idx = item->media_idx;
+        found_ssrc = TRUE;
         break;
       }
     }
 
+    if (!found_ssrc) {
+      GST_WARNING_OBJECT (webrtc, "Could not find ssrc %u", ssrc);
+    }
+
     rtp_trans = _find_transceiver_for_mline (webrtc, media_idx);
     if (!rtp_trans)
       g_warn_if_reached ();
@@ -4320,7 +5297,8 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
     GstWebRTCBin * webrtc)
 {
   TransportStream *stream;
-  GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+  gboolean have_rtx = FALSE;
+  GstStructure *pt_map = NULL;
   GstElement *ret = NULL;
   GstWebRTCRTPTransceiver *trans;
 
@@ -4328,41 +5306,28 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
   trans = _find_transceiver (webrtc, &session_id,
       (FindTransceiverFunc) transceiver_match_for_mline);
 
-  if (stream) {
-    guint i;
-
-    for (i = 0; i < stream->ptmap->len; i++) {
-      PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
-      if (!gst_caps_is_empty (item->caps)) {
-        GstStructure *s = gst_caps_get_structure (item->caps, 0);
-        gint pt;
-        const gchar *apt_str = gst_structure_get_string (s, "apt");
-
-        if (!apt_str)
-          continue;
-
-        if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "RTX") &&
-            gst_structure_get_int (s, "payload", &pt)) {
-          gst_structure_set (pt_map, apt_str, G_TYPE_UINT, pt, NULL);
-        }
-      }
-    }
-  }
+  if (stream)
+    have_rtx = transport_stream_get_pt (stream, "RTX") != 0;
 
   GST_LOG_OBJECT (webrtc, "requesting aux sender for stream %" GST_PTR_FORMAT
       " with transport %" GST_PTR_FORMAT " and pt map %" GST_PTR_FORMAT, stream,
       trans, pt_map);
 
-  if (gst_structure_n_fields (pt_map)) {
+  if (have_rtx) {
     GstElement *rtx;
     GstPad *pad;
     gchar *name;
 
+    if (stream->rtxsend) {
+      GST_WARNING_OBJECT (webrtc, "rtprtxsend already created! rtpbin bug?!");
+      goto out;
+    }
+
     GST_INFO ("creating AUX sender");
     ret = gst_bin_new (NULL);
     rtx = gst_element_factory_make ("rtprtxsend", NULL);
-    g_object_set (rtx, "payload-type-map", pt_map, "max-size-packets", 500,
-        NULL);
+    g_object_set (rtx, "max-size-packets", 500, NULL);
+    _set_rtx_ptmap_from_stream (webrtc, stream);
 
     if (WEBRTC_TRANSCEIVER (trans)->local_rtx_ssrc_map)
       g_object_set (rtx, "ssrc-map",
@@ -4381,9 +5346,13 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
     gst_element_add_pad (ret, gst_ghost_pad_new (name, pad));
     g_free (name);
     gst_object_unref (pad);
+
+    stream->rtxsend = gst_object_ref (rtx);
   }
 
-  gst_structure_free (pt_map);
+out:
+  if (pt_map)
+    gst_structure_free (pt_map);
 
   return ret;
 }
@@ -4406,28 +5375,27 @@ on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
     rtx_pt = transport_stream_get_pt (stream, "RTX");
   }
 
-  GST_LOG_OBJECT (webrtc, "requesting aux receiver for stream %" GST_PTR_FORMAT
-      " with pt red:%u rtx:%u", stream, red_pt, rtx_pt);
+  GST_LOG_OBJECT (webrtc, "requesting aux receiver for stream %" GST_PTR_FORMAT,
+      stream);
 
   if (red_pt || rtx_pt)
     ret = gst_bin_new (NULL);
 
   if (rtx_pt) {
-    GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt);
-    GstElement *rtx = gst_element_factory_make ("rtprtxreceive", NULL);
-    GstStructure *pt_map;
-    const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
+    if (stream->rtxreceive) {
+      GST_WARNING_OBJECT (webrtc,
+          "rtprtxreceive already created! rtpbin bug?!");
+      goto error;
+    }
 
-    gst_bin_add (GST_BIN (ret), rtx);
+    stream->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
+    _set_rtx_ptmap_from_stream (webrtc, stream);
 
-    pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
-    gst_structure_set (pt_map, gst_structure_get_string (s, "apt"), G_TYPE_UINT,
-        rtx_pt, NULL);
-    g_object_set (rtx, "payload-type-map", pt_map, NULL);
+    gst_bin_add (GST_BIN (ret), stream->rtxreceive);
 
-    sinkpad = gst_element_get_static_pad (rtx, "sink");
+    sinkpad = gst_element_get_static_pad (stream->rtxreceive, "sink");
 
-    prev = rtx;
+    prev = gst_object_ref (stream->rtxreceive);
   }
 
   if (red_pt) {
@@ -4465,7 +5433,13 @@ on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
     gst_element_add_pad (ret, ghost);
   }
 
+out:
   return ret;
+
+error:
+  if (ret)
+    gst_object_unref (ret);
+  goto out;
 }
 
 static GstElement *
@@ -4589,9 +5563,83 @@ on_rtpbin_request_fec_encoder (GstElement * rtpbin, guint session_id,
 }
 
 static void
+on_rtpbin_bye_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u received bye", session_id, ssrc);
+}
+
+static void
+on_rtpbin_bye_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u bye timeout", session_id, ssrc);
+}
+
+static void
+on_rtpbin_sender_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u sender timeout", session_id,
+      ssrc);
+}
+
+static void
+on_rtpbin_new_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u new ssrc", session_id, ssrc);
+}
+
+static void
 on_rtpbin_ssrc_active (GstElement * rtpbin, guint session_id, guint ssrc,
     GstWebRTCBin * webrtc)
 {
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u active", session_id, ssrc);
+}
+
+static void
+on_rtpbin_ssrc_collision (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u collision", session_id, ssrc);
+}
+
+static void
+on_rtpbin_ssrc_sdes (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u sdes", session_id, ssrc);
+}
+
+static void
+on_rtpbin_ssrc_validated (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u validated", session_id, ssrc);
+}
+
+static void
+on_rtpbin_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u timeout", session_id, ssrc);
+}
+
+static void
+on_rtpbin_new_sender_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u new sender ssrc", session_id,
+      ssrc);
+}
+
+static void
+on_rtpbin_sender_ssrc_active (GstElement * rtpbin, guint session_id, guint ssrc,
+    GstWebRTCBin * webrtc)
+{
+  GST_INFO_OBJECT (webrtc, "session %u ssrc %u sender ssrc active", session_id,
+      ssrc);
 }
 
 static void
@@ -4616,8 +5664,13 @@ static void
 on_rtpbin_new_storage (GstElement * rtpbin, GstElement * storage,
     guint session_id, GstWebRTCBin * webrtc)
 {
-  /* TODO: when exposing latency, set size-time based on that */
-  g_object_set (storage, "size-time", (guint64) 250 * GST_MSECOND, NULL);
+  guint64 latency = webrtc->priv->jb_latency;
+
+  /* Add an extra 50 ms for safey */
+  latency += RTPSTORAGE_EXTRA_TIME;
+  latency *= GST_MSECOND;
+
+  g_object_set (storage, "size-time", latency, NULL);
 }
 
 static GstElement *
@@ -4647,8 +5700,28 @@ _create_rtpbin (GstWebRTCBin * webrtc)
       G_CALLBACK (on_rtpbin_request_fec_decoder), webrtc);
   g_signal_connect (rtpbin, "request-fec-encoder",
       G_CALLBACK (on_rtpbin_request_fec_encoder), webrtc);
+  g_signal_connect (rtpbin, "on-bye-ssrc",
+      G_CALLBACK (on_rtpbin_bye_ssrc), webrtc);
+  g_signal_connect (rtpbin, "on-bye-timeout",
+      G_CALLBACK (on_rtpbin_bye_timeout), webrtc);
+  g_signal_connect (rtpbin, "on-new-ssrc",
+      G_CALLBACK (on_rtpbin_new_ssrc), webrtc);
+  g_signal_connect (rtpbin, "on-new-sender-ssrc",
+      G_CALLBACK (on_rtpbin_new_sender_ssrc), webrtc);
+  g_signal_connect (rtpbin, "on-sender-ssrc-active",
+      G_CALLBACK (on_rtpbin_sender_ssrc_active), webrtc);
+  g_signal_connect (rtpbin, "on-sender-timeout",
+      G_CALLBACK (on_rtpbin_sender_timeout), webrtc);
   g_signal_connect (rtpbin, "on-ssrc-active",
       G_CALLBACK (on_rtpbin_ssrc_active), webrtc);
+  g_signal_connect (rtpbin, "on-ssrc-collision",
+      G_CALLBACK (on_rtpbin_ssrc_collision), webrtc);
+  g_signal_connect (rtpbin, "on-ssrc-sdes",
+      G_CALLBACK (on_rtpbin_ssrc_sdes), webrtc);
+  g_signal_connect (rtpbin, "on-ssrc-validated",
+      G_CALLBACK (on_rtpbin_ssrc_validated), webrtc);
+  g_signal_connect (rtpbin, "on-timeout",
+      G_CALLBACK (on_rtpbin_timeout), webrtc);
   g_signal_connect (rtpbin, "new-jitterbuffer",
       G_CALLBACK (on_rtpbin_new_jitterbuffer), webrtc);
 
@@ -4670,7 +5743,9 @@ gst_webrtc_bin_change_state (GstElement * element, GstStateChange transition)
       if (!_have_nice_elements (webrtc) || !_have_dtls_elements (webrtc))
         return GST_STATE_CHANGE_FAILURE;
       _start_thread (webrtc);
+      PC_LOCK (webrtc);
       _update_need_negotiation (webrtc);
+      PC_UNLOCK (webrtc);
       break;
     }
     case GST_STATE_CHANGE_READY_TO_PAUSED:
@@ -4706,7 +5781,7 @@ gst_webrtc_bin_change_state (GstElement * element, GstStateChange transition)
 }
 
 static GstPadProbeReturn
-pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
+sink_pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
 {
   GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data);
 
@@ -4742,15 +5817,21 @@ gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
 
     pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, serial);
     trans = _find_transceiver_for_mline (webrtc, serial);
-    if (!trans)
+    if (!trans) {
       trans =
           GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc,
               GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV, serial));
+      GST_LOG_OBJECT (webrtc, "Created new transceiver %" GST_PTR_FORMAT
+          " for mline %u", trans, serial);
+    } else {
+      GST_LOG_OBJECT (webrtc, "Using existing transceiver %" GST_PTR_FORMAT
+          " for mline %u", trans, serial);
+    }
     pad->trans = gst_object_ref (trans);
 
     pad->block_id = gst_pad_add_probe (GST_PAD (pad), GST_PAD_PROBE_TYPE_BLOCK |
         GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST,
-        (GstPadProbeCallback) pad_block, NULL, NULL);
+        (GstPadProbeCallback) sink_pad_block, NULL, NULL);
     webrtc->priv->pending_sink_transceivers =
         g_list_append (webrtc->priv->pending_sink_transceivers,
         gst_object_ref (pad));
@@ -4766,11 +5847,44 @@ gst_webrtc_bin_release_pad (GstElement * element, GstPad * pad)
   GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element);
   GstWebRTCBinPad *webrtc_pad = GST_WEBRTC_BIN_PAD (pad);
 
+  GST_DEBUG_OBJECT (webrtc, "Releasing %" GST_PTR_FORMAT, webrtc_pad);
+
+  /* remove the transceiver from the pad so that subsequent code doesn't use
+   * a possibly dead transceiver */
+  PC_LOCK (webrtc);
   if (webrtc_pad->trans)
     gst_object_unref (webrtc_pad->trans);
   webrtc_pad->trans = NULL;
+  PC_UNLOCK (webrtc);
 
   _remove_pad (webrtc, webrtc_pad);
+
+  PC_LOCK (webrtc);
+  _update_need_negotiation (webrtc);
+  PC_UNLOCK (webrtc);
+}
+
+static void
+_update_rtpstorage_latency (GstWebRTCBin * webrtc)
+{
+  guint i;
+  guint64 latency_ns;
+
+  /* Add an extra 50 ms for safety */
+  latency_ns = webrtc->priv->jb_latency + RTPSTORAGE_EXTRA_TIME;
+  latency_ns *= GST_MSECOND;
+
+  for (i = 0; i < webrtc->priv->transports->len; i++) {
+    TransportStream *stream = g_ptr_array_index (webrtc->priv->transports, i);
+    GObject *storage = NULL;
+
+    g_signal_emit_by_name (webrtc->rtpbin, "get-storage", stream->session_id,
+        &storage);
+
+    g_object_set (storage, "size-time", latency_ns, NULL);
+
+    g_object_unref (storage);
+  }
 }
 
 static void
@@ -4781,8 +5895,12 @@ gst_webrtc_bin_set_property (GObject * object, guint prop_id,
 
   switch (prop_id) {
     case PROP_STUN_SERVER:
+      gst_webrtc_ice_set_stun_server (webrtc->priv->ice,
+          g_value_get_string (value));
+      break;
     case PROP_TURN_SERVER:
-      g_object_set_property (G_OBJECT (webrtc->priv->ice), pspec->name, value);
+      gst_webrtc_ice_set_turn_server (webrtc->priv->ice,
+          g_value_get_string (value));
       break;
     case PROP_BUNDLE_POLICY:
       if (g_value_get_enum (value) == GST_WEBRTC_BUNDLE_POLICY_BALANCED) {
@@ -4793,9 +5911,14 @@ gst_webrtc_bin_set_property (GObject * object, guint prop_id,
       break;
     case PROP_ICE_TRANSPORT_POLICY:
       webrtc->ice_transport_policy = g_value_get_enum (value);
-      g_object_set (webrtc->priv->ice, "force-relay",
+      gst_webrtc_ice_set_force_relay (webrtc->priv->ice,
           webrtc->ice_transport_policy ==
-          GST_WEBRTC_ICE_TRANSPORT_POLICY_RELAY ? TRUE : FALSE, NULL);
+          GST_WEBRTC_ICE_TRANSPORT_POLICY_RELAY ? TRUE : FALSE);
+      break;
+    case PROP_LATENCY:
+      g_object_set_property (G_OBJECT (webrtc->rtpbin), "latency", value);
+      webrtc->priv->jb_latency = g_value_get_uint (value);
+      _update_rtpstorage_latency (webrtc);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -4852,8 +5975,12 @@ gst_webrtc_bin_get_property (GObject * object, guint prop_id,
       g_value_set_boxed (value, webrtc->pending_remote_description);
       break;
     case PROP_STUN_SERVER:
+      g_value_take_string (value,
+          gst_webrtc_ice_get_stun_server (webrtc->priv->ice));
+      break;
     case PROP_TURN_SERVER:
-      g_object_get_property (G_OBJECT (webrtc->priv->ice), pspec->name, value);
+      g_value_take_string (value,
+          gst_webrtc_ice_get_turn_server (webrtc->priv->ice));
       break;
     case PROP_BUNDLE_POLICY:
       g_value_set_enum (value, webrtc->bundle_policy);
@@ -4861,6 +5988,12 @@ gst_webrtc_bin_get_property (GObject * object, guint prop_id,
     case PROP_ICE_TRANSPORT_POLICY:
       g_value_set_enum (value, webrtc->ice_transport_policy);
       break;
+    case PROP_ICE_AGENT:
+      g_value_set_object (value, webrtc->priv->ice);
+      break;
+    case PROP_LATENCY:
+      g_value_set_uint (value, webrtc->priv->jb_latency);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -4869,6 +6002,21 @@ gst_webrtc_bin_get_property (GObject * object, guint prop_id,
 }
 
 static void
+gst_webrtc_bin_constructed (GObject * object)
+{
+  GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
+  gchar *name;
+
+  name = g_strdup_printf ("%s:ice", GST_OBJECT_NAME (webrtc));
+  webrtc->priv->ice = gst_webrtc_ice_new (name);
+
+  gst_webrtc_ice_set_on_ice_candidate (webrtc->priv->ice,
+      (GstWebRTCIceOnCandidateFunc) _on_local_ice_candidate_cb, webrtc, NULL);
+
+  g_free (name);
+}
+
+static void
 _free_pending_pad (GstPad * pad)
 {
   gst_object_unref (pad);
@@ -4898,24 +6046,28 @@ gst_webrtc_bin_finalize (GObject * object)
   GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
 
   if (webrtc->priv->transports)
-    g_array_free (webrtc->priv->transports, TRUE);
+    g_ptr_array_free (webrtc->priv->transports, TRUE);
   webrtc->priv->transports = NULL;
 
   if (webrtc->priv->transceivers)
-    g_array_free (webrtc->priv->transceivers, TRUE);
+    g_ptr_array_free (webrtc->priv->transceivers, TRUE);
   webrtc->priv->transceivers = NULL;
 
   if (webrtc->priv->data_channels)
-    g_array_free (webrtc->priv->data_channels, TRUE);
+    g_ptr_array_free (webrtc->priv->data_channels, TRUE);
   webrtc->priv->data_channels = NULL;
 
   if (webrtc->priv->pending_data_channels)
-    g_array_free (webrtc->priv->pending_data_channels, TRUE);
+    g_ptr_array_free (webrtc->priv->pending_data_channels, TRUE);
   webrtc->priv->pending_data_channels = NULL;
 
-  if (webrtc->priv->pending_ice_candidates)
-    g_array_free (webrtc->priv->pending_ice_candidates, TRUE);
-  webrtc->priv->pending_ice_candidates = NULL;
+  if (webrtc->priv->pending_remote_ice_candidates)
+    g_array_free (webrtc->priv->pending_remote_ice_candidates, TRUE);
+  webrtc->priv->pending_remote_ice_candidates = NULL;
+
+  if (webrtc->priv->pending_local_ice_candidates)
+    g_array_free (webrtc->priv->pending_local_ice_candidates, TRUE);
+  webrtc->priv->pending_local_ice_candidates = NULL;
 
   if (webrtc->priv->session_mid_map)
     g_array_free (webrtc->priv->session_mid_map, TRUE);
@@ -4945,10 +6097,18 @@ gst_webrtc_bin_finalize (GObject * object)
     gst_webrtc_session_description_free (webrtc->pending_remote_description);
   webrtc->pending_remote_description = NULL;
 
+  if (webrtc->priv->last_generated_answer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_answer);
+  webrtc->priv->last_generated_answer = NULL;
+  if (webrtc->priv->last_generated_offer)
+    gst_webrtc_session_description_free (webrtc->priv->last_generated_offer);
+  webrtc->priv->last_generated_offer = NULL;
+
   if (webrtc->priv->stats)
     gst_structure_free (webrtc->priv->stats);
   webrtc->priv->stats = NULL;
 
+  g_mutex_clear (ICE_GET_LOCK (webrtc));
   g_mutex_clear (PC_GET_LOCK (webrtc));
   g_cond_clear (PC_GET_COND (webrtc));
 
@@ -4973,6 +6133,7 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
       "Filter/Network/WebRTC", "A bin for webrtc connections",
       "Matthew Waters <matthew@centricular.com>");
 
+  gobject_class->constructed = gst_webrtc_bin_constructed;
   gobject_class->get_property = gst_webrtc_bin_get_property;
   gobject_class->set_property = gst_webrtc_bin_set_property;
   gobject_class->dispose = gst_webrtc_bin_dispose;
@@ -4981,16 +6142,58 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
   g_object_class_install_property (gobject_class,
       PROP_LOCAL_DESCRIPTION,
       g_param_spec_boxed ("local-description", "Local Description",
-          "The local SDP description to use for this connection",
+          "The local SDP description in use for this connection. "
+          "Favours a pending description over the current description",
           GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_CURRENT_LOCAL_DESCRIPTION,
+      g_param_spec_boxed ("current-local-description",
+          "Current Local Description",
+          "The local description that was successfully negotiated the last time "
+          "the connection transitioned into the stable state",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_PENDING_LOCAL_DESCRIPTION,
+      g_param_spec_boxed ("pending-local-description",
+          "Pending Local Description",
+          "The local description that is in the process of being negotiated plus "
+          "any local candidates that have been generated by the ICE Agent since the "
+          "offer or answer was created",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class,
       PROP_REMOTE_DESCRIPTION,
       g_param_spec_boxed ("remote-description", "Remote Description",
-          "The remote SDP description to use for this connection",
+          "The remote SDP description to use for this connection. "
+          "Favours a pending description over the current description",
           GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_CURRENT_REMOTE_DESCRIPTION,
+      g_param_spec_boxed ("current-remote-description",
+          "Current Remote Description",
+          "The last remote description that was successfully negotiated the last "
+          "time the connection transitioned into the stable state plus any remote "
+          "candidates that have been supplied via addIceCandidate() since the offer "
+          "or answer was created",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_PENDING_REMOTE_DESCRIPTION,
+      g_param_spec_boxed ("pending-remote-description",
+          "Pending Remote Description",
+          "The remote description that is in the process of being negotiated, "
+          "complete with any remote candidates that have been supplied via "
+          "addIceCandidate() since the offer or answer was created",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class,
       PROP_STUN_SERVER,
@@ -5054,73 +6257,89 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
           GST_WEBRTC_ICE_TRANSPORT_POLICY_ALL,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class,
+      PROP_ICE_AGENT,
+      g_param_spec_object ("ice-agent", "WebRTC ICE agent",
+          "The WebRTC ICE agent",
+          GST_TYPE_WEBRTC_ICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstWebRTCBin:latency:
+   *
+   * Default duration to buffer in the jitterbuffers (in ms)
+   *
+   * Since: 1.18
+   */
+
+  g_object_class_install_property (gobject_class,
+      PROP_LATENCY,
+      g_param_spec_uint ("latency", "Latency",
+          "Default duration to buffer in the jitterbuffers (in ms)",
+          0, G_MAXUINT, 200, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   /**
    * GstWebRTCBin::create-offer:
-   * @object: the #GstWebRtcBin
-   * @options: create-offer options
+   * @object: the #webrtcbin
+   * @options: (nullable): create-offer options
    * @promise: a #GstPromise which will contain the offer
    */
   gst_webrtc_bin_signals[CREATE_OFFER_SIGNAL] =
       g_signal_new_class_handler ("create-offer", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_create_offer), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE,
-      GST_TYPE_PROMISE);
+      G_CALLBACK (gst_webrtc_bin_create_offer), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GST_TYPE_STRUCTURE, GST_TYPE_PROMISE);
 
   /**
    * GstWebRTCBin::create-answer:
-   * @object: the #GstWebRtcBin
-   * @options: create-answer options
+   * @object: the #webrtcbin
+   * @options: (nullable): create-answer options
    * @promise: a #GstPromise which will contain the answer
    */
   gst_webrtc_bin_signals[CREATE_ANSWER_SIGNAL] =
       g_signal_new_class_handler ("create-answer", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_create_answer), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE,
-      GST_TYPE_PROMISE);
+      G_CALLBACK (gst_webrtc_bin_create_answer), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GST_TYPE_STRUCTURE, GST_TYPE_PROMISE);
 
   /**
    * GstWebRTCBin::set-local-description:
-   * @object: the #GstWebRtcBin
+   * @object: the #GstWebRTCBin
    * @desc: a #GstWebRTCSessionDescription description
    * @promise: (nullable): a #GstPromise to be notified when it's set
    */
   gst_webrtc_bin_signals[SET_LOCAL_DESCRIPTION_SIGNAL] =
       g_signal_new_class_handler ("set-local-description",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_set_local_description), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2,
-      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
+      G_CALLBACK (gst_webrtc_bin_set_local_description), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
 
   /**
    * GstWebRTCBin::set-remote-description:
-   * @object: the #GstWebRtcBin
+   * @object: the #GstWebRTCBin
    * @desc: a #GstWebRTCSessionDescription description
    * @promise: (nullable): a #GstPromise to be notified when it's set
    */
   gst_webrtc_bin_signals[SET_REMOTE_DESCRIPTION_SIGNAL] =
       g_signal_new_class_handler ("set-remote-description",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_set_remote_description), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2,
-      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
+      G_CALLBACK (gst_webrtc_bin_set_remote_description), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
 
   /**
    * GstWebRTCBin::add-ice-candidate:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    * @mline_index: the index of the media description in the SDP
    * @ice-candidate: an ice candidate
    */
   gst_webrtc_bin_signals[ADD_ICE_CANDIDATE_SIGNAL] =
       g_signal_new_class_handler ("add-ice-candidate",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_add_ice_candidate), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+      G_CALLBACK (gst_webrtc_bin_add_ice_candidate), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
 
   /**
    * GstWebRTCBin::get-stats:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    * @pad: (nullable): A #GstPad to get the stats for, or %NULL for all
    * @promise: a #GstPromise for the result
    *
@@ -5131,7 +6350,7 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
    * and is constantly changing these statistics may be changed to fit with
    * the latest spec.
    *
-   * Each field key is a unique identifer for each RTCStats
+   * Each field key is a unique identifier for each RTCStats
    * (https://www.w3.org/TR/webrtc/#rtcstats-dictionary) value (another
    * GstStructure) in the RTCStatsReport
    * (https://www.w3.org/TR/webrtc/#rtcstatsreport-object).  Each supported
@@ -5191,53 +6410,51 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
   gst_webrtc_bin_signals[GET_STATS_SIGNAL] =
       g_signal_new_class_handler ("get-stats",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_get_stats), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_PAD,
-      GST_TYPE_PROMISE);
+      G_CALLBACK (gst_webrtc_bin_get_stats), NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GST_TYPE_PAD, GST_TYPE_PROMISE);
 
   /**
    * GstWebRTCBin::on-negotiation-needed:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    */
   gst_webrtc_bin_signals[ON_NEGOTIATION_NEEDED_SIGNAL] =
       g_signal_new ("on-negotiation-needed", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 0);
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
 
   /**
    * GstWebRTCBin::on-ice-candidate:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    * @mline_index: the index of the media description in the SDP
    * @candidate: the ICE candidate
    */
   gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL] =
       g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
 
   /**
    * GstWebRTCBin::on-new-transceiver:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    * @candidate: the new #GstWebRTCRTPTransceiver
    */
   gst_webrtc_bin_signals[ON_NEW_TRANSCEIVER_SIGNAL] =
       g_signal_new ("on-new-transceiver", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
 
   /**
    * GstWebRTCBin::on-data-channel:
-   * @object: the #GstWebRtcBin
-   * @candidate: the new #GstWebRTCDataChannel
+   * @object: the #GstWebRTCBin
+   * @candidate: the new `GstWebRTCDataChannel`
    */
   gst_webrtc_bin_signals[ON_DATA_CHANNEL_SIGNAL] =
       g_signal_new ("on-data-channel", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GST_TYPE_WEBRTC_DATA_CHANNEL);
 
   /**
    * GstWebRTCBin::add-transceiver:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    * @direction: the direction of the new transceiver
    * @caps: (allow none): the codec preferences for this transceiver
    *
@@ -5247,39 +6464,38 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
       g_signal_new_class_handler ("add-transceiver", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_CALLBACK (gst_webrtc_bin_add_transceiver), NULL, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 2,
+      NULL, GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 2,
       GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, GST_TYPE_CAPS);
 
   /**
    * GstWebRTCBin::get-transceivers:
-   * @object: the #GstWebRtcBin
+   * @object: the #webrtcbin
    *
    * Returns: a #GArray of #GstWebRTCRTPTransceivers
    */
   gst_webrtc_bin_signals[GET_TRANSCEIVERS_SIGNAL] =
       g_signal_new_class_handler ("get-transceivers", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_get_transceivers), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_ARRAY, 0);
+      G_CALLBACK (gst_webrtc_bin_get_transceivers), NULL, NULL, NULL,
+      G_TYPE_ARRAY, 0);
 
   /**
    * GstWebRTCBin::get-transceiver:
-   * @object: the #GstWebRtcBin
+   * @object: the #GstWebRTCBin
    * @idx: The index of the transceiver
    *
-   * Returns: the #GstWebRTCRTPTransceiver, or %NULL
+   * Returns: (transfer full): the #GstWebRTCRTPTransceiver, or %NULL
    * Since: 1.16
    */
   gst_webrtc_bin_signals[GET_TRANSCEIVER_SIGNAL] =
       g_signal_new_class_handler ("get-transceiver", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_get_transceiver), NULL, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 1,
-      G_TYPE_INT);
+      G_CALLBACK (gst_webrtc_bin_get_transceiver), NULL, NULL, NULL,
+      GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 1, G_TYPE_INT);
 
   /**
    * GstWebRTCBin::add-turn-server:
-   * @object: the #GstWebRtcBin
+   * @object: the #GstWebRTCBin
    * @uri: The uri of the server of the form turn(s)://username:password@host:port
    *
    * Add a turn server to obtain ICE candidates from
@@ -5287,12 +6503,12 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
   gst_webrtc_bin_signals[ADD_TURN_SERVER_SIGNAL] =
       g_signal_new_class_handler ("add-turn-server", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_bin_add_turn_server), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+      G_CALLBACK (gst_webrtc_bin_add_turn_server), NULL, NULL, NULL,
+      G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
 
   /*
    * GstWebRTCBin::create-data-channel:
-   * @object: the #GstWebRtcBin
+   * @object: the #GstWebRTCBin
    * @label: the label for the data channel
    * @options: a #GstStructure of options for creating the data channel
    *
@@ -5300,11 +6516,11 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
    * members outlined https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit and
    * and reproduced below
    *
-   *  ordered               G_TYPE_BOOLEAN        Whether the channal will send data with guarenteed ordering
+   *  ordered               G_TYPE_BOOLEAN        Whether the channal will send data with guaranteed ordering
    *  max-packet-lifetime   G_TYPE_INT            The time in milliseconds to attempt transmitting unacknowledged data. -1 for unset
    *  max-retransmits       G_TYPE_INT            The number of times data will be attempted to be transmitted without acknowledgement before dropping
    *  protocol              G_TYPE_STRING         The subprotocol used by this channel
-   *  negotiated            G_TYPE_BOOLEAN        Whether the created data channel should not perform in-band chnanel announcment.  If %TRUE, then application must negotiate the channel itself and create the corresponding channel on the peer with the same id.
+   *  negotiated            G_TYPE_BOOLEAN        Whether the created data channel should not perform in-band chnanel announcement.  If %TRUE, then application must negotiate the channel itself and create the corresponding channel on the peer with the same id.
    *  id                    G_TYPE_INT            Override the default identifier selection of this channel
    *  priority              GST_TYPE_WEBRTC_PRIORITY_TYPE   The priority to use for this channel
    *
@@ -5314,24 +6530,28 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
       g_signal_new_class_handler ("create-data-channel",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_CALLBACK (gst_webrtc_bin_create_data_channel), NULL, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_WEBRTC_DATA_CHANNEL, 2,
-      G_TYPE_STRING, GST_TYPE_STRUCTURE);
+      NULL, GST_TYPE_WEBRTC_DATA_CHANNEL, 2, G_TYPE_STRING, GST_TYPE_STRUCTURE);
+
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+  gst_type_mark_as_plugin_api (GST_TYPE_WEBRTC_BIN_PAD, 0);
+  gst_type_mark_as_plugin_api (GST_TYPE_WEBRTC_ICE, 0);
+#endif
 }
 
 static void
-_deref_unparent_and_unref (GObject ** object)
+_unparent_and_unref (GObject * object)
 {
-  GstObject *obj = GST_OBJECT (*object);
+  GstObject *obj = GST_OBJECT (object);
 
   GST_OBJECT_PARENT (obj) = NULL;
 
-  gst_object_unref (*object);
+  gst_object_unref (obj);
 }
 
 static void
-_transport_free (GObject ** object)
+_transport_free (GObject * object)
 {
-  TransportStream *stream = (TransportStream *) object;
+  TransportStream *stream = (TransportStream *) object;
   GstWebRTCBin *webrtc;
 
   webrtc = GST_WEBRTC_BIN (GST_OBJECT_PARENT (stream));
@@ -5346,7 +6566,7 @@ _transport_free (GObject ** object)
     g_signal_handlers_disconnect_by_data (stream->rtcp_transport, webrtc);
   }
 
-  gst_object_unref (*object);
+  gst_object_unref (object);
 }
 
 static void
@@ -5356,39 +6576,37 @@ gst_webrtc_bin_init (GstWebRTCBin * webrtc)
   g_mutex_init (PC_GET_LOCK (webrtc));
   g_cond_init (PC_GET_COND (webrtc));
 
+  g_mutex_init (ICE_GET_LOCK (webrtc));
+
   webrtc->rtpbin = _create_rtpbin (webrtc);
   gst_bin_add (GST_BIN (webrtc), webrtc->rtpbin);
 
-  webrtc->priv->transceivers = g_array_new (FALSE, TRUE, sizeof (gpointer));
-  g_array_set_clear_func (webrtc->priv->transceivers,
-      (GDestroyNotify) _deref_unparent_and_unref);
-
-  webrtc->priv->transports = g_array_new (FALSE, TRUE, sizeof (gpointer));
-  g_array_set_clear_func (webrtc->priv->transports,
-      (GDestroyNotify) _transport_free);
+  webrtc->priv->transceivers =
+      g_ptr_array_new_with_free_func ((GDestroyNotify) _unparent_and_unref);
+  webrtc->priv->transports =
+      g_ptr_array_new_with_free_func ((GDestroyNotify) _transport_free);
 
-  webrtc->priv->data_channels = g_array_new (FALSE, TRUE, sizeof (gpointer));
-  g_array_set_clear_func (webrtc->priv->data_channels,
-      (GDestroyNotify) _deref_and_unref);
+  webrtc->priv->data_channels =
+      g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
 
   webrtc->priv->pending_data_channels =
-      g_array_new (FALSE, TRUE, sizeof (gpointer));
-  g_array_set_clear_func (webrtc->priv->pending_data_channels,
-      (GDestroyNotify) _deref_and_unref);
+      g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
 
   webrtc->priv->session_mid_map =
       g_array_new (FALSE, TRUE, sizeof (SessionMidItem));
   g_array_set_clear_func (webrtc->priv->session_mid_map,
       (GDestroyNotify) clear_session_mid_item);
 
-  webrtc->priv->ice = gst_webrtc_ice_new ();
-  g_signal_connect (webrtc->priv->ice, "on-ice-candidate",
-      G_CALLBACK (_on_ice_candidate), webrtc);
   webrtc->priv->ice_stream_map =
       g_array_new (FALSE, TRUE, sizeof (IceStreamItem));
-  webrtc->priv->pending_ice_candidates =
-      g_array_new (FALSE, TRUE, sizeof (IceCandidateItem *));
-  g_array_set_clear_func (webrtc->priv->pending_ice_candidates,
+  webrtc->priv->pending_remote_ice_candidates =
+      g_array_new (FALSE, TRUE, sizeof (IceCandidateItem));
+  g_array_set_clear_func (webrtc->priv->pending_remote_ice_candidates,
+      (GDestroyNotify) _clear_ice_candidate_item);
+
+  webrtc->priv->pending_local_ice_candidates =
+      g_array_new (FALSE, TRUE, sizeof (IceCandidateItem));
+  g_array_set_clear_func (webrtc->priv->pending_local_ice_candidates,
       (GDestroyNotify) _clear_ice_candidate_item);
 
   /* we start off closed until we move to READY */
index 84158e8..e4b462f 100644 (file)
@@ -96,20 +96,24 @@ struct _GstWebRTCBinPrivate
   guint max_sink_pad_serial;
 
   gboolean bundle;
-  GArray *transceivers;
+  GPtrArray *transceivers;
   GArray *session_mid_map;
-  GArray *transports;
-  GArray *data_channels;
+  GPtrArray *transports;
+  GPtrArray *data_channels;
   /* list of data channels we've received a sctp stream for but no data
    * channel protocol for */
-  GArray *pending_data_channels;
+  GPtrArray *pending_data_channels;
+
+  guint jb_latency;
 
   GstWebRTCSCTPTransport *sctp_transport;
   TransportStream *data_channel_transport;
 
   GstWebRTCICE *ice;
   GArray *ice_stream_map;
-  GArray *pending_ice_candidates;
+  GMutex ice_lock;
+  GArray *pending_remote_ice_candidates;
+  GArray *pending_local_ice_candidates;
 
   /* peerconnection variables */
   gboolean is_closed;
@@ -131,6 +135,10 @@ struct _GstWebRTCBinPrivate
   /* count of the number of media streams we've offered for uniqueness */
   /* FIXME: overflow? */
   guint media_counter;
+  /* the number of times create_offer has been called for the version field */
+  guint offer_count;
+  GstWebRTCSessionDescription *last_generated_offer;
+  GstWebRTCSessionDescription *last_generated_answer;
 
   GstStructure *stats;
 };
@@ -143,13 +151,14 @@ typedef struct
   GstWebRTCBinFunc op;
   gpointer data;
   GDestroyNotify notify;
-//  GstPromise *promise;      /* FIXME */
+  GstPromise *promise;
 } GstWebRTCBinTask;
 
-void            gst_webrtc_bin_enqueue_task             (GstWebRTCBin * pc,
+gboolean        gst_webrtc_bin_enqueue_task             (GstWebRTCBin * pc,
                                                          GstWebRTCBinFunc func,
                                                          gpointer data,
-                                                         GDestroyNotify notify);
+                                                         GDestroyNotify notify,
+                                                         GstPromise *promise);
 
 G_END_DECLS
 
index 8887b33..7dd716f 100644 (file)
@@ -46,20 +46,16 @@ gst_webrtc_ice_error_quark (void)
 enum
 {
   SIGNAL_0,
-  ON_ICE_CANDIDATE_SIGNAL,
-  ON_ICE_GATHERING_STATE_CHANGE_SIGNAL,
+  ADD_LOCAL_IP_ADDRESS_SIGNAL,
   LAST_SIGNAL,
 };
 
 enum
 {
   PROP_0,
-  PROP_ICE_GATHERING_STATE,
-  PROP_STUN_SERVER,
-  PROP_TURN_SERVER,
-  PROP_CONTROLLER,
   PROP_AGENT,
-  PROP_FORCE_RELAY,
+  PROP_ICE_TCP,
+  PROP_ICE_UDP,
 };
 
 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
@@ -75,6 +71,10 @@ struct _GstWebRTCICEPrivate
   GMainLoop *loop;
   GMutex lock;
   GCond cond;
+
+  GstWebRTCIceOnCandidateFunc on_candidate;
+  gpointer on_candidate_data;
+  GDestroyNotify on_candidate_notify;
 };
 
 #define gst_webrtc_ice_parent_class parent_class
@@ -118,7 +118,7 @@ static void
 _start_thread (GstWebRTCICE * ice)
 {
   g_mutex_lock (&ice->priv->lock);
-  ice->priv->thread = g_thread_new ("gst-nice-ops",
+  ice->priv->thread = g_thread_new (GST_OBJECT_NAME (ice),
       (GThreadFunc) _gst_nice_thread, ice);
 
   while (!ice->priv->loop)
@@ -286,13 +286,19 @@ _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
 
   colon = g_strstr_len (userinfo, -1, ":");
   if (!colon) {
-    *user = g_strdup (userinfo);
+    *user = g_uri_unescape_string (userinfo, NULL);
     *pass = NULL;
     return;
   }
 
-  *user = g_strndup (userinfo, colon - userinfo);
-  *pass = g_strdup (&colon[1]);
+  /* Check that the first occurence is also the last occurence */
+  if (colon != g_strrstr (userinfo, ":"))
+    GST_WARNING ("userinfo %s contains more than one ':', will assume that the "
+        "first ':' delineates user:pass. You should escape the user and pass "
+        "before adding to the URI.", userinfo);
+
+  *user = g_uri_unescape_segment (userinfo, colon, NULL);
+  *pass = g_uri_unescape_string (&colon[1], NULL);
 }
 
 static gchar *
@@ -366,8 +372,8 @@ _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
   for (i = 0; i < relay_n; i++) {
     ret = nice_agent_set_relay_info (ice->priv->nice_agent,
         item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
-        gst_uri_get_host (turn_server), gst_uri_get_port (turn_server), user,
-        pass, relays[i]);
+        gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
+        user, pass, relays[i]);
     if (!ret) {
       gchar *uri = gst_uri_to_string (turn_server);
       GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
@@ -376,8 +382,8 @@ _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
     }
     ret = nice_agent_set_relay_info (ice->priv->nice_agent,
         item->nice_stream_id, NICE_COMPONENT_TYPE_RTCP,
-        gst_uri_get_host (turn_server), gst_uri_get_port (turn_server), user,
-        pass, relays[i]);
+        gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
+        user, pass, relays[i]);
     if (!ret) {
       gchar *uri = gst_uri_to_string (turn_server);
       GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
@@ -414,9 +420,8 @@ _add_stun_server (GstWebRTCICE * ice, GstUri * stun_server)
   gchar *ip = NULL;
   guint port;
 
-  GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
-
   s = gst_uri_to_string (stun_server);
+  GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
 
   host = gst_uri_get_host (stun_server);
   if (!host) {
@@ -513,8 +518,11 @@ _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
   }
 
   attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
-  g_signal_emit (ice, gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL],
-      0, item->session_id, attr);
+
+  if (ice->priv->on_candidate)
+    ice->priv->on_candidate (ice, item->session_id, attr,
+        ice->priv->on_candidate_data);
+
   g_free (attr);
 }
 
@@ -603,6 +611,47 @@ _candidate_type_to_string (NiceCandidateType type)
 }
 #endif
 
+/* parse the address for possible resolution */
+static gboolean
+get_candidate_address (const gchar * candidate, gchar ** prefix,
+    gchar ** address, gchar ** postfix)
+{
+  char **tokens = NULL;
+
+  if (!g_str_has_prefix (candidate, "a=candidate:")) {
+    GST_ERROR ("candidate \"%s\" does not start with \"a=candidate:\"",
+        candidate);
+    goto failure;
+  }
+
+  if (!(tokens = g_strsplit (candidate, " ", 6))) {
+    GST_ERROR ("candidate \"%s\" could not be tokenized", candidate);
+    goto failure;
+  }
+
+  if (g_strv_length (tokens) < 6) {
+    GST_ERROR ("candidate \"%s\" tokenization resulted in not enough tokens",
+        candidate);
+    goto failure;
+  }
+
+  if (address)
+    *address = g_strdup (tokens[4]);
+  tokens[4] = NULL;
+  if (prefix)
+    *prefix = g_strjoinv (" ", tokens);
+  if (postfix)
+    *postfix = g_strdup (tokens[5]);
+
+  g_strfreev (tokens);
+  return TRUE;
+
+failure:
+  if (tokens)
+    g_strfreev (tokens);
+  return FALSE;
+}
+
 /* must start with "a=candidate:" */
 void
 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
@@ -619,8 +668,58 @@ gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
       nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
       item->nice_stream_id, candidate);
   if (!cand) {
-    GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'", candidate);
-    return;
+    /* might be a .local candidate */
+    char *prefix = NULL, *address = NULL, *postfix = NULL;
+    char *new_addr, *new_candidate;
+    char *new_candv[4] = { NULL, };
+
+    if (!get_candidate_address (candidate, &prefix, &address, &postfix)) {
+      GST_WARNING_OBJECT (ice, "Failed to retrieve address from candidate %s",
+          candidate);
+      goto fail;
+    }
+
+    if (!g_str_has_suffix (address, ".local")) {
+      GST_WARNING_OBJECT (ice, "candidate address \'%s\' does not end "
+          "with \'.local\'", address);
+      goto fail;
+    }
+
+    /* FIXME: async */
+    if (!(new_addr = _resolve_host (ice, address))) {
+      GST_WARNING_OBJECT (ice, "Failed to resolve %s", address);
+      goto fail;
+    }
+
+    new_candv[0] = prefix;
+    new_candv[1] = new_addr;
+    new_candv[2] = postfix;
+    new_candv[3] = NULL;
+    new_candidate = g_strjoinv (" ", new_candv);
+
+    GST_DEBUG_OBJECT (ice, "resolved to candidate %s", new_candidate);
+
+    cand =
+        nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
+        item->nice_stream_id, new_candidate);
+    g_free (new_candidate);
+    if (!cand) {
+      GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'",
+          new_candidate);
+      goto fail;
+    }
+
+    g_free (prefix);
+    g_free (new_addr);
+    g_free (postfix);
+
+    if (0) {
+    fail:
+      g_free (prefix);
+      g_free (address);
+      g_free (postfix);
+      return;
+    }
   }
 
   candidates = g_slist_append (candidates, cand);
@@ -669,6 +768,28 @@ done:
   return ret;
 }
 
+static gboolean
+gst_webrtc_ice_add_local_ip_address (GstWebRTCICE * ice, const gchar * address)
+{
+  gboolean ret = FALSE;
+  NiceAddress nice_addr;
+
+  nice_address_init (&nice_addr);
+
+  ret = nice_address_set_from_string (&nice_addr, address);
+
+  if (ret) {
+    ret = nice_agent_add_local_address (ice->priv->nice_agent, &nice_addr);
+    if (!ret) {
+      GST_ERROR_OBJECT (ice, "Failed to add local address to NiceAgent");
+    }
+  } else {
+    GST_ERROR_OBJECT (ice, "Failed to initialize NiceAddress [%s]", address);
+  }
+
+  return ret;
+}
+
 gboolean
 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
@@ -704,6 +825,42 @@ gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
   return gst_webrtc_ice_stream_gather_candidates (stream);
 }
 
+void
+gst_webrtc_ice_set_is_controller (GstWebRTCICE * ice, gboolean controller)
+{
+  g_object_set (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
+      controller, NULL);
+}
+
+gboolean
+gst_webrtc_ice_get_is_controller (GstWebRTCICE * ice)
+{
+  gboolean ret;
+  g_object_get (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
+      &ret, NULL);
+  return ret;
+}
+
+void
+gst_webrtc_ice_set_force_relay (GstWebRTCICE * ice, gboolean force_relay)
+{
+  g_object_set (G_OBJECT (ice->priv->nice_agent), "force-relay", force_relay,
+      NULL);
+}
+
+void
+gst_webrtc_ice_set_on_ice_candidate (GstWebRTCICE * ice,
+    GstWebRTCIceOnCandidateFunc func, gpointer user_data, GDestroyNotify notify)
+{
+  if (ice->priv->on_candidate_notify)
+    ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
+  ice->priv->on_candidate = NULL;
+
+  ice->priv->on_candidate = func;
+  ice->priv->on_candidate_data = user_data;
+  ice->priv->on_candidate_notify = notify;
+}
+
 static void
 _clear_ice_stream (struct NiceStreamItem *item)
 {
@@ -720,7 +877,7 @@ _clear_ice_stream (struct NiceStreamItem *item)
 static GstUri *
 _validate_turn_server (GstWebRTCICE * ice, const gchar * s)
 {
-  GstUri *uri = gst_uri_from_string (s);
+  GstUri *uri = gst_uri_from_string_escaped (s);
   const gchar *userinfo, *scheme;
   GList *keys = NULL, *l;
   gchar *user = NULL, *pass = NULL;
@@ -792,6 +949,54 @@ out:
   return uri;
 }
 
+void
+gst_webrtc_ice_set_stun_server (GstWebRTCICE * ice, const gchar * uri_s)
+{
+  GstUri *uri = gst_uri_from_string_escaped (uri_s);
+  const gchar *msg = "must be of the form stun://<host>:<port>";
+
+  GST_DEBUG_OBJECT (ice, "setting stun server, %s", uri_s);
+
+  if (!uri) {
+    GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", uri_s, msg);
+    return;
+  }
+
+  if (ice->stun_server)
+    gst_uri_unref (ice->stun_server);
+  ice->stun_server = uri;
+}
+
+gchar *
+gst_webrtc_ice_get_stun_server (GstWebRTCICE * ice)
+{
+  if (ice->stun_server)
+    return gst_uri_to_string (ice->stun_server);
+  else
+    return NULL;
+}
+
+void
+gst_webrtc_ice_set_turn_server (GstWebRTCICE * ice, const gchar * uri_s)
+{
+  GstUri *uri = _validate_turn_server (ice, uri_s);
+
+  if (uri) {
+    if (ice->turn_server)
+      gst_uri_unref (ice->turn_server);
+    ice->turn_server = uri;
+  }
+}
+
+gchar *
+gst_webrtc_ice_get_turn_server (GstWebRTCICE * ice)
+{
+  if (ice->turn_server)
+    return gst_uri_to_string (ice->turn_server);
+  else
+    return NULL;
+}
+
 static void
 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
@@ -799,40 +1004,13 @@ gst_webrtc_ice_set_property (GObject * object, guint prop_id,
   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
 
   switch (prop_id) {
-    case PROP_STUN_SERVER:{
-      const gchar *s = g_value_get_string (value);
-      GstUri *uri = gst_uri_from_string (s);
-      const gchar *msg = "must be of the form stun://<host>:<port>";
-
-      GST_DEBUG_OBJECT (ice, "setting stun server, %s", s);
-
-      if (!uri) {
-        GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", s, msg);
-        return;
-      }
-
-      if (ice->stun_server)
-        gst_uri_unref (ice->stun_server);
-      ice->stun_server = uri;
-      break;
-    }
-    case PROP_TURN_SERVER:{
-      GstUri *uri = _validate_turn_server (ice, g_value_get_string (value));
-
-      if (uri) {
-        if (ice->turn_server)
-          gst_uri_unref (ice->turn_server);
-        ice->turn_server = uri;
-      }
-      break;
-    }
-    case PROP_CONTROLLER:
+    case PROP_ICE_TCP:
       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
-          "controlling-mode", value);
+          "ice-tcp", value);
       break;
-    case PROP_FORCE_RELAY:
+    case PROP_ICE_UDP:
       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
-          "force-relay", value);
+          "ice-udp", value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -847,28 +1025,16 @@ gst_webrtc_ice_get_property (GObject * object, guint prop_id,
   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
 
   switch (prop_id) {
-    case PROP_STUN_SERVER:
-      if (ice->stun_server)
-        g_value_take_string (value, gst_uri_to_string (ice->stun_server));
-      else
-        g_value_take_string (value, NULL);
-      break;
-    case PROP_TURN_SERVER:
-      if (ice->turn_server)
-        g_value_take_string (value, gst_uri_to_string (ice->turn_server));
-      else
-        g_value_take_string (value, NULL);
-      break;
-    case PROP_CONTROLLER:
-      g_object_get_property (G_OBJECT (ice->priv->nice_agent),
-          "controlling-mode", value);
-      break;
     case PROP_AGENT:
       g_value_set_object (value, ice->priv->nice_agent);
       break;
-    case PROP_FORCE_RELAY:
+    case PROP_ICE_TCP:
+      g_object_get_property (G_OBJECT (ice->priv->nice_agent),
+          "ice-tcp", value);
+      break;
+    case PROP_ICE_UDP:
       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
-          "force-relay", value);
+          "ice-udp", value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -885,6 +1051,11 @@ gst_webrtc_ice_finalize (GObject * object)
 
   _stop_thread (ice);
 
+  if (ice->priv->on_candidate_notify)
+    ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
+  ice->priv->on_candidate = NULL;
+  ice->priv->on_candidate_notify = NULL;
+
   if (ice->turn_server)
     gst_uri_unref (ice->turn_server);
   if (ice->stun_server)
@@ -903,54 +1074,66 @@ gst_webrtc_ice_finalize (GObject * object)
 }
 
 static void
+gst_webrtc_ice_constructed (GObject * object)
+{
+  GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
+
+  _start_thread (ice);
+
+  ice->priv->nice_agent = nice_agent_new (ice->priv->main_context,
+      NICE_COMPATIBILITY_RFC5245);
+  g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
+      G_CALLBACK (_on_new_candidate), ice);
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+}
+
+static void
 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
 {
   GObjectClass *gobject_class = (GObjectClass *) klass;
 
+  gobject_class->constructed = gst_webrtc_ice_constructed;
   gobject_class->get_property = gst_webrtc_ice_get_property;
   gobject_class->set_property = gst_webrtc_ice_set_property;
   gobject_class->finalize = gst_webrtc_ice_finalize;
 
   g_object_class_install_property (gobject_class,
-      PROP_STUN_SERVER,
-      g_param_spec_string ("stun-server", "STUN Server",
-          "The STUN server of the form stun://hostname:port",
-          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_TURN_SERVER,
-      g_param_spec_string ("turn-server", "TURN Server",
-          "The TURN server of the form turn(s)://username:password@host:port",
-          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_CONTROLLER,
-      g_param_spec_boolean ("controller", "ICE controller",
-          "Whether the ICE agent is the controller or controlled. "
-          "In WebRTC, the initial offerrer is the ICE controller.", FALSE,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
       PROP_AGENT,
       g_param_spec_object ("agent", "ICE agent",
-          "ICE agent in use by this object", NICE_TYPE_AGENT,
-          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+          "ICE agent in use by this object. WARNING! Accessing this property "
+          "may have disastrous consequences for the operation of webrtcbin. "
+          "Other ICE implementations may not have the same interface.",
+          NICE_TYPE_AGENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class,
-      PROP_FORCE_RELAY,
-      g_param_spec_boolean ("force-relay", "Force Relay",
-          "Force all traffic to go through a relay.", FALSE,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+      PROP_ICE_TCP,
+      g_param_spec_boolean ("ice-tcp", "ICE TCP",
+          "Whether the agent should use ICE-TCP when gathering candidates",
+          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_ICE_UDP,
+      g_param_spec_boolean ("ice-udp", "ICE UDP",
+          "Whether the agent should use ICE-UDP when gathering candidates",
+          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * GstWebRTCICE::on-ice-candidate:
-   * @object: the #GstWebRtcBin
-   * @candidate: the ICE candidate
+   * GstWebRTCICE::add-local-ip-address:
+   * @object: the #GstWebRTCICE
+   * @address: The local IP address
+   *
+   * Add a local IP address to use for ICE candidate gathering.  If none
+   * are supplied, they will be discovered automatically. Calling this signal
+   * stops automatic ICE gathering.
+   *
+   * Returns: whether the address could be added.
    */
-  gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL] =
-      g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+  gst_webrtc_ice_signals[ADD_LOCAL_IP_ADDRESS_SIGNAL] =
+      g_signal_new_class_handler ("add-local-ip-address",
+      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_webrtc_ice_add_local_ip_address), NULL, NULL,
+      g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
 }
 
 static void
@@ -965,13 +1148,6 @@ gst_webrtc_ice_init (GstWebRTCICE * ice)
       g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
       (GDestroyNotify) gst_uri_unref);
 
-  _start_thread (ice);
-
-  ice->priv->nice_agent = nice_agent_new (ice->priv->main_context,
-      NICE_COMPATIBILITY_RFC5245);
-  g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
-      G_CALLBACK (_on_new_candidate), ice);
-
   ice->priv->nice_stream_map =
       g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
   g_array_set_clear_func (ice->priv->nice_stream_map,
@@ -979,7 +1155,7 @@ gst_webrtc_ice_init (GstWebRTCICE * ice)
 }
 
 GstWebRTCICE *
-gst_webrtc_ice_new (void)
+gst_webrtc_ice_new (const gchar * name)
 {
-  return g_object_new (GST_TYPE_WEBRTC_ICE, NULL);
+  return g_object_new (GST_TYPE_WEBRTC_ICE, "name", name, NULL);
 }
index 4b0cfdc..d7c0965 100644 (file)
@@ -58,7 +58,7 @@ struct _GstWebRTCICEClass
   GstObjectClass            parent_class;
 };
 
-GstWebRTCICE *              gst_webrtc_ice_new                      (void);
+GstWebRTCICE *              gst_webrtc_ice_new                      (const gchar * name);
 GstWebRTCICEStream *        gst_webrtc_ice_add_stream               (GstWebRTCICE * ice,
                                                                      guint session_id);
 GstWebRTCICETransport *     gst_webrtc_ice_find_transport           (GstWebRTCICE * ice,
@@ -82,6 +82,25 @@ gboolean                    gst_webrtc_ice_set_remote_credentials   (GstWebRTCIC
 gboolean                    gst_webrtc_ice_add_turn_server          (GstWebRTCICE * ice,
                                                                      const gchar * uri);
 
+void                        gst_webrtc_ice_set_is_controller        (GstWebRTCICE * ice,
+                                                                     gboolean controller);
+gboolean                    gst_webrtc_ice_get_is_controller        (GstWebRTCICE * ice);
+void                        gst_webrtc_ice_set_force_relay          (GstWebRTCICE * ice,
+                                                                     gboolean force_relay);
+void                        gst_webrtc_ice_set_stun_server          (GstWebRTCICE * ice,
+                                                                     const gchar * uri);
+gchar *                     gst_webrtc_ice_get_stun_server          (GstWebRTCICE * ice);
+void                        gst_webrtc_ice_set_turn_server          (GstWebRTCICE * ice,
+                                                                     const gchar * uri);
+gchar *                     gst_webrtc_ice_get_turn_server          (GstWebRTCICE * ice);
+
+typedef void (*GstWebRTCIceOnCandidateFunc) (GstWebRTCICE * ice, guint stream_id, gchar * candidate, gpointer user_data);
+
+void                        gst_webrtc_ice_set_on_ice_candidate     (GstWebRTCICE * ice,
+                                                                     GstWebRTCIceOnCandidateFunc func,
+                                                                     gpointer user_data,
+                                                                     GDestroyNotify notify);
+
 G_END_DECLS
 
 #endif /* __GST_WEBRTC_ICE_H__ */
index bf38304..7ecf9b9 100644 (file)
@@ -82,6 +82,8 @@ _get_peer_connection_stats (GstWebRTCBin * webrtc)
 }
 
 #define CLOCK_RATE_VALUE_TO_SECONDS(v,r) ((double) v / (double) clock_rate)
+#define FIXED_16_16_TO_DOUBLE(v) ((double) ((v & 0xffff0000) >> 16) + ((v & 0xffff) / 65536.0))
+#define FIXED_32_32_TO_DOUBLE(v) ((double) ((v & G_GUINT64_CONSTANT (0xffffffff00000000)) >> 32) + ((v & G_GUINT64_CONSTANT (0xffffffff)) / 4294967296.0))
 
 /* https://www.w3.org/TR/webrtc-stats/#inboundrtpstats-dict*
    https://www.w3.org/TR/webrtc-stats/#outboundrtpstats-dict* */
@@ -90,52 +92,43 @@ _get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc,
     const GstStructure * source_stats, const gchar * codec_id,
     const gchar * transport_id, GstStructure * s)
 {
-  GstStructure *in, *out, *r_in, *r_out;
-  gchar *in_id, *out_id, *r_in_id, *r_out_id;
   guint ssrc, fir, pli, nack, jitter;
   int lost, clock_rate;
   guint64 packets, bytes;
-  gboolean have_rb = FALSE, sent_rb = FALSE;
+  gboolean internal;
   double ts;
 
   gst_structure_get_double (s, "timestamp", &ts);
-  gst_structure_get_uint (source_stats, "ssrc", &ssrc);
-  gst_structure_get (source_stats, "have-rb", G_TYPE_BOOLEAN, &have_rb,
-      "sent_rb", G_TYPE_BOOLEAN, &sent_rb, "clock-rate", G_TYPE_INT,
-      &clock_rate, NULL);
-
-  in_id = g_strdup_printf ("rtp-inbound-stream-stats_%u", ssrc);
-  out_id = g_strdup_printf ("rtp-outbound-stream-stats_%u", ssrc);
-  r_in_id = g_strdup_printf ("rtp-remote-inbound-stream-stats_%u", ssrc);
-  r_out_id = g_strdup_printf ("rtp-remote-outbound-stream-stats_%u", ssrc);
-
-  in = gst_structure_new_empty (in_id);
-  _set_base_stats (in, GST_WEBRTC_STATS_INBOUND_RTP, ts, in_id);
-
-  /* RTCStreamStats */
-  gst_structure_set (in, "ssrc", G_TYPE_UINT, ssrc, NULL);
-  gst_structure_set (in, "codec-id", G_TYPE_STRING, codec_id, NULL);
-  gst_structure_set (in, "transport-id", G_TYPE_STRING, transport_id, NULL);
-  if (gst_structure_get_uint (source_stats, "recv-fir-count", &fir))
-    gst_structure_set (in, "fir-count", G_TYPE_UINT, fir, NULL);
-  if (gst_structure_get_uint (source_stats, "recv-pli-count", &pli))
-    gst_structure_set (in, "pli-count", G_TYPE_UINT, pli, NULL);
-  if (gst_structure_get_uint (source_stats, "recv-nack-count", &nack))
-    gst_structure_set (in, "nack-count", G_TYPE_UINT, nack, NULL);
-  /* XXX: mediaType, trackId, sliCount, qpSum */
-
-  /* RTCReceivedRTPStreamStats */
-  if (gst_structure_get_uint64 (source_stats, "packets-received", &packets))
-    gst_structure_set (in, "packets-received", G_TYPE_UINT64, packets, NULL);
-  if (gst_structure_get_uint64 (source_stats, "octets-received", &bytes))
-    gst_structure_set (in, "bytes-received", G_TYPE_UINT64, bytes, NULL);
-  if (gst_structure_get_int (source_stats, "packets-lost", &lost))
-    gst_structure_set (in, "packets-lost", G_TYPE_INT, lost, NULL);
-  if (gst_structure_get_uint (source_stats, "jitter", &jitter))
-    gst_structure_set (in, "jitter", G_TYPE_DOUBLE,
-        CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL);
-/*
-    RTCReceivedRTPStreamStats
+  gst_structure_get (source_stats, "ssrc", G_TYPE_UINT, &ssrc, "clock-rate",
+      G_TYPE_INT, &clock_rate, "internal", G_TYPE_BOOLEAN, &internal, NULL);
+
+  if (internal) {
+    GstStructure *r_in, *out;
+    gchar *out_id, *r_in_id;
+
+    out_id = g_strdup_printf ("rtp-outbound-stream-stats_%u", ssrc);
+    r_in_id = g_strdup_printf ("rtp-remote-inbound-stream-stats_%u", ssrc);
+
+    r_in = gst_structure_new_empty (r_in_id);
+    _set_base_stats (r_in, GST_WEBRTC_STATS_REMOTE_INBOUND_RTP, ts, r_in_id);
+
+    /* RTCStreamStats */
+    gst_structure_set (r_in, "local-id", G_TYPE_STRING, out_id, NULL);
+    gst_structure_set (r_in, "ssrc", G_TYPE_UINT, ssrc, NULL);
+    gst_structure_set (r_in, "codec-id", G_TYPE_STRING, codec_id, NULL);
+    gst_structure_set (r_in, "transport-id", G_TYPE_STRING, transport_id, NULL);
+    /* XXX: mediaType, trackId, sliCount, qpSum */
+
+    if (gst_structure_get_uint64 (source_stats, "packets-received", &packets))
+      gst_structure_set (r_in, "packets-received", G_TYPE_UINT64, packets,
+          NULL);
+    if (gst_structure_get_int (source_stats, "packets-lost", &lost))
+      gst_structure_set (r_in, "packets-lost", G_TYPE_INT, lost, NULL);
+    if (gst_structure_get_uint (source_stats, "jitter", &jitter))
+      gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE,
+          CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL);
+
+/* XXX: RTCReceivedRTPStreamStats
     double             fractionLost;
     unsigned long      packetsDiscarded;
     unsigned long      packetsFailedDecryption;
@@ -150,33 +143,89 @@ _get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc,
     double             gapDiscardRate;
 */
 
-  /* RTCInboundRTPStreamStats */
-  gst_structure_set (in, "remote-id", G_TYPE_STRING, r_out_id, NULL);
-  /* XXX: framesDecoded, lastPacketReceivedTimestamp */
+    /* RTCRemoteInboundRTPStreamStats */
+    /* XXX: framesDecoded, lastPacketReceivedTimestamp */
 
-  r_in = gst_structure_new_empty (r_in_id);
-  _set_base_stats (r_in, GST_WEBRTC_STATS_REMOTE_INBOUND_RTP, ts, r_in_id);
+    out = gst_structure_new_empty (out_id);
+    _set_base_stats (out, GST_WEBRTC_STATS_OUTBOUND_RTP, ts, out_id);
 
-  /* RTCStreamStats */
-  gst_structure_set (r_in, "ssrc", G_TYPE_UINT, ssrc, NULL);
-  gst_structure_set (r_in, "codec-id", G_TYPE_STRING, codec_id, NULL);
-  gst_structure_set (r_in, "transport-id", G_TYPE_STRING, transport_id, NULL);
-  /* XXX: mediaType, trackId, sliCount, qpSum */
+    /* RTCStreamStats */
+    gst_structure_set (out, "ssrc", G_TYPE_UINT, ssrc, NULL);
+    gst_structure_set (out, "codec-id", G_TYPE_STRING, codec_id, NULL);
+    gst_structure_set (out, "transport-id", G_TYPE_STRING, transport_id, NULL);
+    if (gst_structure_get_uint (source_stats, "sent-fir-count", &fir))
+      gst_structure_set (out, "fir-count", G_TYPE_UINT, fir, NULL);
+    if (gst_structure_get_uint (source_stats, "sent-pli-count", &pli))
+      gst_structure_set (out, "pli-count", G_TYPE_UINT, pli, NULL);
+    if (gst_structure_get_uint (source_stats, "sent-nack-count", &nack))
+      gst_structure_set (out, "nack-count", G_TYPE_UINT, nack, NULL);
+    /* XXX: mediaType, trackId, sliCount, qpSum */
 
-  /* RTCReceivedRTPStreamStats */
-  if (sent_rb) {
-    if (gst_structure_get_uint (source_stats, "sent-rb-jitter", &jitter))
-      gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE,
-          CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL);
-    if (gst_structure_get_int (source_stats, "sent-rb-packetslost", &lost))
-      gst_structure_set (r_in, "packets-lost", G_TYPE_INT, lost, NULL);
-    /* packetsReceived, bytesReceived */
+/* RTCSentRTPStreamStats */
+    if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes))
+      gst_structure_set (out, "bytes-sent", G_TYPE_UINT64, bytes, NULL);
+    if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets))
+      gst_structure_set (out, "packets-sent", G_TYPE_UINT64, packets, NULL);
+/* XXX:
+    unsigned long      packetsDiscardedOnSend;
+    unsigned long long bytesDiscardedOnSend;
+*/
+
+    /* RTCOutboundRTPStreamStats */
+    gst_structure_set (out, "remote-id", G_TYPE_STRING, r_in_id, NULL);
+/* XXX:
+    DOMHighResTimeStamp lastPacketSentTimestamp;
+    double              targetBitrate;
+    unsigned long       framesEncoded;
+    double              totalEncodeTime;
+    double              averageRTCPInterval;
+*/
+    gst_structure_set (s, out_id, GST_TYPE_STRUCTURE, out, NULL);
+    gst_structure_set (s, r_in_id, GST_TYPE_STRUCTURE, r_in, NULL);
+
+    gst_structure_free (out);
+    gst_structure_free (r_in);
+
+    g_free (out_id);
+    g_free (r_in_id);
   } else {
-    /* default values */
-    gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE, 0.0, "packets-lost",
-        G_TYPE_INT, 0, NULL);
-  }
-/* XXX: RTCReceivedRTPStreamStats
+    GstStructure *in, *r_out;
+    gchar *r_out_id, *in_id;
+    gboolean have_rb = FALSE, have_sr = FALSE;
+
+    gst_structure_get (source_stats, "have-rb", G_TYPE_BOOLEAN, &have_rb,
+        "have-sr", G_TYPE_BOOLEAN, &have_sr, NULL);
+
+    in_id = g_strdup_printf ("rtp-inbound-stream-stats_%u", ssrc);
+    r_out_id = g_strdup_printf ("rtp-remote-outbound-stream-stats_%u", ssrc);
+
+    in = gst_structure_new_empty (in_id);
+    _set_base_stats (in, GST_WEBRTC_STATS_INBOUND_RTP, ts, in_id);
+
+    /* RTCStreamStats */
+    gst_structure_set (in, "ssrc", G_TYPE_UINT, ssrc, NULL);
+    gst_structure_set (in, "codec-id", G_TYPE_STRING, codec_id, NULL);
+    gst_structure_set (in, "transport-id", G_TYPE_STRING, transport_id, NULL);
+    if (gst_structure_get_uint (source_stats, "recv-fir-count", &fir))
+      gst_structure_set (in, "fir-count", G_TYPE_UINT, fir, NULL);
+    if (gst_structure_get_uint (source_stats, "recv-pli-count", &pli))
+      gst_structure_set (in, "pli-count", G_TYPE_UINT, pli, NULL);
+    if (gst_structure_get_uint (source_stats, "recv-nack-count", &nack))
+      gst_structure_set (in, "nack-count", G_TYPE_UINT, nack, NULL);
+    /* XXX: mediaType, trackId, sliCount, qpSum */
+
+    /* RTCReceivedRTPStreamStats */
+    if (gst_structure_get_uint64 (source_stats, "packets-received", &packets))
+      gst_structure_set (in, "packets-received", G_TYPE_UINT64, packets, NULL);
+    if (gst_structure_get_uint64 (source_stats, "octets-received", &bytes))
+      gst_structure_set (in, "bytes-received", G_TYPE_UINT64, bytes, NULL);
+    if (gst_structure_get_int (source_stats, "packets-lost", &lost))
+      gst_structure_set (in, "packets-lost", G_TYPE_INT, lost, NULL);
+    if (gst_structure_get_uint (source_stats, "jitter", &jitter))
+      gst_structure_set (in, "jitter", G_TYPE_DOUBLE,
+          CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL);
+/*
+    RTCReceivedRTPStreamStats
     double             fractionLost;
     unsigned long      packetsDiscarded;
     unsigned long      packetsFailedDecryption;
@@ -191,91 +240,65 @@ _get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc,
     double             gapDiscardRate;
 */
 
-  /* RTCRemoteInboundRTPStreamStats */
-  gst_structure_set (r_in, "local-id", G_TYPE_STRING, out_id, NULL);
-  if (have_rb) {
-    guint32 rtt;
-    if (gst_structure_get_uint (source_stats, "rb-round-trip", &rtt)) {
-      /* 16.16 fixed point to double */
-      double val =
-          (double) ((rtt & 0xffff0000) >> 16) + ((rtt & 0xffff) / 65536.0);
-      gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, val, NULL);
+    /* RTCInboundRTPStreamStats */
+    gst_structure_set (in, "remote-id", G_TYPE_STRING, r_out_id, NULL);
+    /* XXX: framesDecoded, lastPacketReceivedTimestamp */
+
+    r_out = gst_structure_new_empty (r_out_id);
+    _set_base_stats (r_out, GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP, ts, r_out_id);
+    /* RTCStreamStats */
+    gst_structure_set (r_out, "ssrc", G_TYPE_UINT, ssrc, NULL);
+    gst_structure_set (r_out, "codec-id", G_TYPE_STRING, codec_id, NULL);
+    gst_structure_set (r_out, "transport-id", G_TYPE_STRING, transport_id,
+        NULL);
+    if (have_rb) {
+      guint32 rtt;
+      if (gst_structure_get_uint (source_stats, "rb-round-trip", &rtt)) {
+        /* 16.16 fixed point to double */
+        double val = FIXED_16_16_TO_DOUBLE (rtt);
+        gst_structure_set (r_out, "round-trip-time", G_TYPE_DOUBLE, val, NULL);
+      }
+    } else {
+      /* default values */
+      gst_structure_set (r_out, "round-trip-time", G_TYPE_DOUBLE, 0.0, NULL);
     }
-  } else {
-    /* default values */
-    gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, 0.0, NULL);
-  }
-  /* XXX: framesDecoded, lastPacketReceivedTimestamp */
-
-  out = gst_structure_new_empty (out_id);
-  _set_base_stats (out, GST_WEBRTC_STATS_OUTBOUND_RTP, ts, out_id);
-
-  /* RTCStreamStats */
-  gst_structure_set (out, "ssrc", G_TYPE_UINT, ssrc, NULL);
-  gst_structure_set (out, "codec-id", G_TYPE_STRING, codec_id, NULL);
-  gst_structure_set (out, "transport-id", G_TYPE_STRING, transport_id, NULL);
-  if (gst_structure_get_uint (source_stats, "sent-fir-count", &fir))
-    gst_structure_set (out, "fir-count", G_TYPE_UINT, fir, NULL);
-  if (gst_structure_get_uint (source_stats, "sent-pli-count", &pli))
-    gst_structure_set (out, "pli-count", G_TYPE_UINT, pli, NULL);
-  if (gst_structure_get_uint (source_stats, "sent-nack-count", &nack))
-    gst_structure_set (out, "nack-count", G_TYPE_UINT, nack, NULL);
-  /* XXX: mediaType, trackId, sliCount, qpSum */
+    /* XXX: mediaType, trackId, sliCount, qpSum */
 
 /* RTCSentRTPStreamStats */
-  if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes))
-    gst_structure_set (out, "bytes-sent", G_TYPE_UINT64, bytes, NULL);
-  if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets))
-    gst_structure_set (out, "packets-sent", G_TYPE_UINT64, packets, NULL);
+    if (have_sr) {
+      if (gst_structure_get_uint64 (source_stats, "sr-octet-count", &bytes))
+        gst_structure_set (r_out, "bytes-sent", G_TYPE_UINT64, bytes, NULL);
+      if (gst_structure_get_uint64 (source_stats, "sr-packet-count", &packets))
+        gst_structure_set (r_out, "packets-sent", G_TYPE_UINT64, packets, NULL);
+    }
 /* XXX:
     unsigned long      packetsDiscardedOnSend;
     unsigned long long bytesDiscardedOnSend;
 */
 
-  /* RTCOutboundRTPStreamStats */
-  gst_structure_set (out, "remote-id", G_TYPE_STRING, r_in_id, NULL);
-/* XXX:
-    DOMHighResTimeStamp lastPacketSentTimestamp;
-    double              targetBitrate;
-    unsigned long       framesEncoded;
-    double              totalEncodeTime;
-    double              averageRTCPInterval;
-*/
-
-  r_out = gst_structure_new_empty (r_out_id);
-  _set_base_stats (r_out, GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP, ts, r_out_id);
-  /* RTCStreamStats */
-  gst_structure_set (r_out, "ssrc", G_TYPE_UINT, ssrc, NULL);
-  gst_structure_set (r_out, "codec-id", G_TYPE_STRING, codec_id, NULL);
-  gst_structure_set (r_out, "transport-id", G_TYPE_STRING, transport_id, NULL);
-  /* XXX: mediaType, trackId, sliCount, qpSum */
-
-/* RTCSentRTPStreamStats */
-/*  if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes))
-    gst_structure_set (r_out, "bytes-sent", G_TYPE_UINT64, bytes, NULL);
-  if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets))
-    gst_structure_set (r_out, "packets-sent", G_TYPE_UINT64, packets, NULL);*/
-/* XXX:
-    unsigned long      packetsDiscardedOnSend;
-    unsigned long long bytesDiscardedOnSend;
-*/
+    if (have_sr) {
+      guint64 ntptime;
+      if (gst_structure_get_uint64 (source_stats, "sr-ntptime", &ntptime)) {
+        /* 16.16 fixed point to double */
+        double val = FIXED_32_32_TO_DOUBLE (ntptime);
+        gst_structure_set (r_out, "remote-timestamp", G_TYPE_DOUBLE, val, NULL);
+      }
+    } else {
+      /* default values */
+      gst_structure_set (r_out, "remote-timestamp", G_TYPE_DOUBLE, 0.0, NULL);
+    }
 
-  gst_structure_set (r_out, "local-id", G_TYPE_STRING, in_id, NULL);
+    gst_structure_set (r_out, "local-id", G_TYPE_STRING, in_id, NULL);
 
-  gst_structure_set (s, in_id, GST_TYPE_STRUCTURE, in, NULL);
-  gst_structure_set (s, out_id, GST_TYPE_STRUCTURE, out, NULL);
-  gst_structure_set (s, r_in_id, GST_TYPE_STRUCTURE, r_in, NULL);
-  gst_structure_set (s, r_out_id, GST_TYPE_STRUCTURE, r_out, NULL);
+    gst_structure_set (s, in_id, GST_TYPE_STRUCTURE, in, NULL);
+    gst_structure_set (s, r_out_id, GST_TYPE_STRUCTURE, r_out, NULL);
 
-  gst_structure_free (in);
-  gst_structure_free (out);
-  gst_structure_free (r_in);
-  gst_structure_free (r_out);
+    gst_structure_free (in);
+    gst_structure_free (r_out);
 
-  g_free (in_id);
-  g_free (out_id);
-  g_free (r_in_id);
-  g_free (r_out_id);
+    g_free (in_id);
+    g_free (r_out_id);
+  }
 }
 
 /* https://www.w3.org/TR/webrtc-stats/#candidatepair-dict* */
@@ -353,6 +376,7 @@ _get_stats_from_dtls_transport (GstWebRTCBin * webrtc,
   GstStructure *stats;
   gchar *id;
   double ts;
+  gchar *ice_id;
 
   gst_structure_get_double (s, "timestamp", &ts);
 
@@ -395,7 +419,8 @@ _get_stats_from_dtls_transport (GstWebRTCBin * webrtc,
   gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL);
   gst_structure_free (stats);
 
-  _get_stats_from_ice_transport (webrtc, transport->transport, s);
+  ice_id = _get_stats_from_ice_transport (webrtc, transport->transport, s);
+  g_free (ice_id);
 
   return id;
 }
@@ -439,16 +464,13 @@ _get_stats_from_transport_channel (GstWebRTCBin * webrtc,
   for (i = 0; i < source_stats->n_values; i++) {
     const GstStructure *stats;
     const GValue *val = g_value_array_get_nth (source_stats, i);
-    gboolean internal;
     guint stats_ssrc = 0;
 
     stats = gst_value_get_structure (val);
 
-    /* skip internal or foreign sources */
-    gst_structure_get (stats,
-        "internal", G_TYPE_BOOLEAN, &internal,
-        "ssrc", G_TYPE_UINT, &stats_ssrc, NULL);
-    if (internal || (ssrc && stats_ssrc && ssrc != stats_ssrc))
+    /* skip foreign sources */
+    gst_structure_get (stats, "ssrc", G_TYPE_UINT, &stats_ssrc, NULL);
+    if (ssrc && stats_ssrc && ssrc != stats_ssrc)
       continue;
 
     _get_stats_from_rtp_source_stats (webrtc, stats, codec_id, transport_id, s);
index e9e7c11..3e7a5d1 100644 (file)
@@ -30,4 +30,5 @@ if libnice_dep.found()
     install_dir : plugins_install_dir,
   )
   pkgconfig.generate(gstwebrtc_plugin, install_dir : plugins_pkgconfig_install_dir)
+  plugins += [gstwebrtc_plugin]
 endif
index 5e7f30e..bbe910f 100644 (file)
@@ -221,9 +221,7 @@ gst_webrtc_nice_transport_constructed (GObject * object)
   if (ice->sink) {
     g_object_set (ice->sink, "agent", agent, "stream", our_stream_id,
         "component", component, "async", FALSE, "enable-last-sample", FALSE,
-        NULL);
-    if (ice->component == GST_WEBRTC_ICE_COMPONENT_RTCP)
-      g_object_set (ice->sink, "sync", FALSE, NULL);
+        "sync", FALSE, NULL);
   }
 
   g_object_unref (agent);
index f5643e9..f5a1e9d 100644 (file)
@@ -91,7 +91,8 @@ _sctp_enqueue_task (GstWebRTCSCTPTransport * sctp, SCTPTask func,
   task->notify = notify;
 
   gst_webrtc_bin_enqueue_task (sctp->webrtcbin,
-      (GstWebRTCBinFunc) _execute_task, task, (GDestroyNotify) _free_task);
+      (GstWebRTCBinFunc) _execute_task, task, (GDestroyNotify) _free_task,
+      NULL);
 }
 
 static void
@@ -254,8 +255,7 @@ gst_webrtc_sctp_transport_class_init (GstWebRTCSCTPTransportClass * klass)
    */
   gst_webrtc_sctp_transport_signals[ON_RESET_STREAM_SIGNAL] =
       g_signal_new ("stream-reset", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 }
 
 static void
index d5327a7..212f15e 100644 (file)
@@ -47,6 +47,7 @@ struct _GstWebRTCSCTPTransport
 
   gboolean                      association_established;
 
+  gulong                        sctpdec_block_id;
   GstElement                   *sctpdec;
   GstElement                   *sctpenc;
 
index d059fa2..6d38a83 100644 (file)
@@ -89,8 +89,6 @@ _receive_state_to_string (ReceiveState state)
   switch (state) {
     case RECEIVE_STATE_BLOCK:
       return "block";
-    case RECEIVE_STATE_DROP:
-      return "drop";
     case RECEIVE_STATE_PASS:
       return "pass";
     default:
@@ -101,46 +99,81 @@ _receive_state_to_string (ReceiveState state)
 static GstPadProbeReturn
 pad_block (GstPad * pad, GstPadProbeInfo * info, TransportReceiveBin * receive)
 {
-  g_mutex_lock (&receive->pad_block_lock);
-  while (receive->receive_state == RECEIVE_STATE_BLOCK) {
-    g_cond_wait (&receive->pad_block_cond, &receive->pad_block_lock);
-    GST_DEBUG_OBJECT (pad, "probe waited. new state %s",
-        _receive_state_to_string (receive->receive_state));
-  }
-
-  g_mutex_unlock (&receive->pad_block_lock);
+  /* Drop all events: we don't care about them and don't want to block on
+   * them. Sticky events would be forwarded again later once we unblock
+   * and we don't want to forward them here already because that might
+   * cause a spurious GST_FLOW_FLUSHING */
+  if (GST_IS_EVENT (info->data))
+    return GST_PAD_PROBE_DROP;
+
+  /* But block on any actual data-flow so we don't accidentally send that
+   * to a pad that is not ready yet, causing GST_FLOW_FLUSHING and everything
+   * to silently stop.
+   */
+  GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data);
 
   return GST_PAD_PROBE_OK;
 }
 
-static GstPadProbeReturn
-src_probe_cb (GstPad * pad, GstPadProbeInfo * info,
-    TransportReceiveBin * receive)
-{
-  GstPadProbeReturn ret;
-
-  g_mutex_lock (&receive->pad_block_lock);
-
-  g_assert (receive->receive_state != RECEIVE_STATE_BLOCK);
-
-  ret =
-      receive->receive_state ==
-      RECEIVE_STATE_DROP ? GST_PAD_PROBE_DROP : GST_PAD_PROBE_OK;
-
-  g_mutex_unlock (&receive->pad_block_lock);
-
-  return ret;
-}
-
 void
 transport_receive_bin_set_receive_state (TransportReceiveBin * receive,
     ReceiveState state)
 {
+
   g_mutex_lock (&receive->pad_block_lock);
+  if (receive->receive_state != state) {
+    GST_DEBUG_OBJECT (receive, "changing receive state to %s",
+        _receive_state_to_string (state));
+  }
+
+  if (state == RECEIVE_STATE_PASS) {
+    if (receive->rtp_block)
+      _free_pad_block (receive->rtp_block);
+    receive->rtp_block = NULL;
+
+    if (receive->rtcp_block)
+      _free_pad_block (receive->rtcp_block);
+    receive->rtcp_block = NULL;
+  } else {
+    g_assert (state == RECEIVE_STATE_BLOCK);
+    if (receive->rtp_block == NULL) {
+      GstWebRTCDTLSTransport *transport;
+      GstElement *dtlssrtpdec;
+      GstPad *pad, *peer_pad;
+
+      if (receive->stream) {
+        transport = receive->stream->transport;
+        dtlssrtpdec = transport->dtlssrtpdec;
+        pad = gst_element_get_static_pad (dtlssrtpdec, "sink");
+        peer_pad = gst_pad_get_peer (pad);
+        receive->rtp_block =
+            _create_pad_block (GST_ELEMENT (receive), peer_pad, 0, NULL, NULL);
+        receive->rtp_block->block_id =
+            gst_pad_add_probe (peer_pad,
+            GST_PAD_PROBE_TYPE_BLOCK |
+            GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+            (GstPadProbeCallback) pad_block, receive, NULL);
+        gst_object_unref (peer_pad);
+        gst_object_unref (pad);
+
+        transport = receive->stream->rtcp_transport;
+        dtlssrtpdec = transport->dtlssrtpdec;
+        pad = gst_element_get_static_pad (dtlssrtpdec, "sink");
+        peer_pad = gst_pad_get_peer (pad);
+        receive->rtcp_block =
+            _create_pad_block (GST_ELEMENT (receive), peer_pad, 0, NULL, NULL);
+        receive->rtcp_block->block_id =
+            gst_pad_add_probe (peer_pad,
+            GST_PAD_PROBE_TYPE_BLOCK |
+            GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+            (GstPadProbeCallback) pad_block, receive, NULL);
+        gst_object_unref (peer_pad);
+        gst_object_unref (pad);
+      }
+    }
+  }
+
   receive->receive_state = state;
-  GST_DEBUG_OBJECT (receive, "changing receive state to %s",
-      _receive_state_to_string (state));
-  g_cond_signal (&receive->pad_block_cond);
   g_mutex_unlock (&receive->pad_block_lock);
 }
 
@@ -187,7 +220,6 @@ transport_receive_bin_finalize (GObject * object)
   TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (object);
 
   g_mutex_clear (&receive->pad_block_lock);
-  g_cond_clear (&receive->pad_block_cond);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -205,37 +237,12 @@ transport_receive_bin_change_state (GstElement * element,
 
   switch (transition) {
     case GST_STATE_CHANGE_NULL_TO_READY:{
-      GstWebRTCDTLSTransport *transport;
-      GstElement *elem, *dtlssrtpdec;
-      GstPad *pad;
-
-      transport = receive->stream->transport;
-      dtlssrtpdec = transport->dtlssrtpdec;
-      pad = gst_element_get_static_pad (dtlssrtpdec, "sink");
-      receive->rtp_block =
-          _create_pad_block (GST_ELEMENT (receive), pad, 0, NULL, NULL);
-      receive->rtp_block->block_id =
-          gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_ALL_BOTH,
-          (GstPadProbeCallback) pad_block, receive, NULL);
-      gst_object_unref (pad);
-
-      receive->rtp_src_probe_id = gst_pad_add_probe (receive->rtp_src,
-          GST_PAD_PROBE_TYPE_ALL_BOTH, (GstPadProbeCallback) src_probe_cb,
-          receive, NULL);
-
-      transport = receive->stream->rtcp_transport;
-      dtlssrtpdec = transport->dtlssrtpdec;
-      pad = gst_element_get_static_pad (dtlssrtpdec, "sink");
-      receive->rtcp_block =
-          _create_pad_block (GST_ELEMENT (receive), pad, 0, NULL, NULL);
-      receive->rtcp_block->block_id =
-          gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_ALL_BOTH,
-          (GstPadProbeCallback) pad_block, receive, NULL);
-      gst_object_unref (pad);
-
-      receive->rtcp_src_probe_id = gst_pad_add_probe (receive->rtcp_src,
-          GST_PAD_PROBE_TYPE_ALL_BOTH, (GstPadProbeCallback) src_probe_cb,
-          receive, NULL);
+      GstElement *elem;
+
+      /* We want to start blocked, unless someone already switched us
+       * to PASS mode. receive_state is set to BLOCKED in _init(),
+       * so set up blocks with whatever the mode is now. */
+      transport_receive_bin_set_receive_state (receive, receive->receive_state);
 
       /* XXX: because nice needs the nicesrc internal main loop running in order
        * correctly STUN... */
@@ -271,18 +278,10 @@ transport_receive_bin_change_state (GstElement * element,
         _free_pad_block (receive->rtp_block);
       receive->rtp_block = NULL;
 
-      if (receive->rtp_src_probe_id)
-        gst_pad_remove_probe (receive->rtp_src, receive->rtp_src_probe_id);
-      receive->rtp_src_probe_id = 0;
-
       if (receive->rtcp_block)
         _free_pad_block (receive->rtcp_block);
       receive->rtcp_block = NULL;
 
-      if (receive->rtcp_src_probe_id)
-        gst_pad_remove_probe (receive->rtcp_src, receive->rtcp_src_probe_id);
-      receive->rtcp_src_probe_id = 0;
-
       break;
     }
     default:
@@ -440,7 +439,7 @@ transport_receive_bin_class_init (TransportReceiveBinClass * klass)
   g_object_class_install_property (gobject_class,
       PROP_STREAM,
       g_param_spec_object ("stream", "Stream",
-          "The TransportStream for this receiveing bin",
+          "The TransportStream for this receiving bin",
           transport_stream_get_type (),
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 }
@@ -448,6 +447,6 @@ transport_receive_bin_class_init (TransportReceiveBinClass * klass)
 static void
 transport_receive_bin_init (TransportReceiveBin * receive)
 {
+  receive->receive_state = RECEIVE_STATE_BLOCK;
   g_mutex_init (&receive->pad_block_lock);
-  g_cond_init (&receive->pad_block_cond);
 }
index a4c1870..50449e3 100644 (file)
@@ -33,8 +33,8 @@ GType transport_receive_bin_get_type(void);
 
 typedef enum
 {
+  RECEIVE_STATE_UNSET = 0,
   RECEIVE_STATE_BLOCK = 1,
-  RECEIVE_STATE_DROP,
   RECEIVE_STATE_PASS,
 } ReceiveState;
 
@@ -43,7 +43,6 @@ struct _TransportReceiveBin
   GstBin                     parent;
 
   TransportStream           *stream;        /* parent transport stream */
-  gboolean                   rtcp_mux;
 
   GstPad                    *rtp_src;
   gulong                     rtp_src_probe_id;
@@ -52,7 +51,6 @@ struct _TransportReceiveBin
   struct pad_block          *rtp_block;
   struct pad_block          *rtcp_block;
   GMutex                     pad_block_lock;
-  GCond                      pad_block_cond;
   ReceiveState               receive_state;
 };
 
index 36522d3..dc5c1ff 100644 (file)
@@ -81,7 +81,6 @@ enum
 #define TSB_UNLOCK(tsb) (g_mutex_unlock (TSB_GET_LOCK(tsb)))
 
 static void cleanup_blocks (TransportSendBin * send);
-static void tsb_remove_probe (struct pad_block *block);
 
 static void
 _set_rtcp_mux (TransportSendBin * send, gboolean rtcp_mux)
@@ -147,6 +146,17 @@ transport_send_bin_get_property (GObject * object, guint prop_id,
 static GstPadProbeReturn
 pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
 {
+  /* Drop all events: we don't care about them and don't want to block on
+   * them. Sticky events would be forwarded again later once we unblock
+   * and we don't want to forward them here already because that might
+   * cause a spurious GST_FLOW_FLUSHING */
+  if (GST_IS_EVENT (info->data))
+    return GST_PAD_PROBE_DROP;
+
+  /* But block on any actual data-flow so we don't accidentally send that
+   * to a pad that is not ready yet, causing GST_FLOW_FLUSHING and everything
+   * to silently stop.
+   */
   GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data);
 
   return GST_PAD_PROBE_OK;
@@ -166,23 +176,13 @@ block_peer_pad (GstElement * elem, const gchar * pad_name)
   peer = gst_pad_get_peer (pad);
   block = _create_pad_block (elem, peer, 0, NULL, NULL);
   block->block_id = gst_pad_add_probe (peer,
-      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER |
-      GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL,
-      NULL);
+      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) pad_block, NULL, NULL);
   gst_object_unref (pad);
   gst_object_unref (peer);
   return block;
 }
 
-static void
-tsb_remove_probe (struct pad_block *block)
-{
-  if (block && block->block_id) {
-    gst_pad_remove_probe (block->pad, block->block_id);
-    block->block_id = 0;
-  }
-}
-
 static GstStateChangeReturn
 transport_send_bin_change_state (GstElement * element,
     GstStateChange transition)
@@ -247,12 +247,7 @@ transport_send_bin_change_state (GstElement * element,
        * if they still exist, without accidentally feeding data to the
        * dtlssrtpenc elements */
       TSB_LOCK (send);
-      tsb_remove_probe (send->rtp_ctx.rtp_block);
-      tsb_remove_probe (send->rtp_ctx.rtcp_block);
-      tsb_remove_probe (send->rtp_ctx.nice_block);
-
-      tsb_remove_probe (send->rtcp_ctx.rtcp_block);
-      tsb_remove_probe (send->rtcp_ctx.nice_block);
+      cleanup_blocks (send);
       TSB_UNLOCK (send);
       break;
     }
@@ -516,6 +511,85 @@ transport_send_bin_finalize (GObject * object)
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+static gboolean
+gst_transport_send_bin_element_query (GstElement * element, GstQuery * query)
+{
+  gboolean ret = TRUE;
+  GstClockTime min_latency;
+
+  GST_LOG_OBJECT (element, "got query %s", GST_QUERY_TYPE_NAME (query));
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_LATENCY:
+      /* when latency is queried, use the result to configure our
+       * own latency internally, piggybacking off the global
+       * latency configuration sequence. */
+      GST_DEBUG_OBJECT (element, "handling latency query");
+
+      /* Call the parent query handler to actually get the query
+       * sent upstream */
+      ret =
+          GST_ELEMENT_CLASS (transport_send_bin_parent_class)->query
+          (GST_ELEMENT (element), query);
+      if (!ret)
+        break;
+
+      gst_query_parse_latency (query, NULL, &min_latency, NULL);
+
+      GST_DEBUG_OBJECT (element,
+          "got min latency %" GST_TIME_FORMAT, GST_TIME_ARGS (min_latency));
+
+      /* configure latency on elements */
+      /* Call the parent event handler, because our sub-class handler
+       * will drop the LATENCY event. We also don't need to that
+       * the latency configuration is valid (min < max), because
+       * the pipeline will do it when checking the query results */
+      if (GST_ELEMENT_CLASS (transport_send_bin_parent_class)->send_event
+          (GST_ELEMENT (element), gst_event_new_latency (min_latency))) {
+        GST_INFO_OBJECT (element, "configured latency of %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (min_latency));
+      } else {
+        GST_WARNING_OBJECT (element,
+            "did not really configure latency of %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (min_latency));
+      }
+
+      break;
+    default:
+      ret =
+          GST_ELEMENT_CLASS (transport_send_bin_parent_class)->query
+          (GST_ELEMENT (element), query);
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_transport_send_bin_element_event (GstElement * element, GstEvent * event)
+{
+  gboolean ret = TRUE;
+
+  GST_LOG_OBJECT (element, "got event %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_LATENCY:
+      /* Ignore the pipeline configured latency, we choose our own
+       * instead when the latency query happens, so that sending
+       * isn't affected by other parts of the pipeline */
+      GST_DEBUG_OBJECT (element, "Ignoring latency event from parent");
+      gst_event_unref (event);
+      break;
+    default:
+      ret =
+          GST_ELEMENT_CLASS (transport_send_bin_parent_class)->send_event
+          (GST_ELEMENT (element), event);
+      break;
+  }
+
+  return ret;
+}
+
 static void
 transport_send_bin_class_init (TransportSendBinClass * klass)
 {
@@ -540,6 +614,9 @@ transport_send_bin_class_init (TransportSendBinClass * klass)
   gobject_class->set_property = transport_send_bin_set_property;
   gobject_class->finalize = transport_send_bin_finalize;
 
+  element_class->send_event = gst_transport_send_bin_element_event;
+  element_class->query = gst_transport_send_bin_element_query;
+
   g_object_class_install_property (gobject_class,
       PROP_STREAM,
       g_param_spec_object ("stream", "Stream",
index 01fa2dc..01261ae 100644 (file)
@@ -75,6 +75,36 @@ transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name)
   return ret;
 }
 
+int *
+transport_stream_get_all_pt (TransportStream * stream,
+    const gchar * encoding_name, gsize * pt_len)
+{
+  guint i;
+  gsize ret_i = 0;
+  gsize ret_size = 8;
+  int *ret = NULL;
+
+  for (i = 0; i < stream->ptmap->len; i++) {
+    PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
+    if (!gst_caps_is_empty (item->caps)) {
+      GstStructure *s = gst_caps_get_structure (item->caps, 0);
+      if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
+              encoding_name)) {
+        if (!ret)
+          ret = g_new0 (int, ret_size);
+        if (ret_i >= ret_size) {
+          ret_size *= 2;
+          ret = g_realloc_n (ret, ret_size, sizeof (int));
+        }
+        ret[ret_i++] = item->pt;
+      }
+    }
+  }
+
+  *pt_len = ret_i;
+  return ret;
+}
+
 static void
 transport_stream_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
@@ -152,6 +182,14 @@ transport_stream_dispose (GObject * object)
     gst_object_unref (stream->rtcp_transport);
   stream->rtcp_transport = NULL;
 
+  if (stream->rtxsend)
+    gst_object_unref (stream->rtxsend);
+  stream->rtxsend = NULL;
+
+  if (stream->rtxreceive)
+    gst_object_unref (stream->rtxreceive);
+  stream->rtxreceive = NULL;
+
   GST_OBJECT_PARENT (object) = NULL;
 
   G_OBJECT_CLASS (parent_class)->dispose (object);
index 8b90a94..174d93e 100644 (file)
@@ -52,6 +52,7 @@ struct _TransportStream
   gboolean                  rtcp_mux;
   gboolean                  rtcp_rsize;
   gboolean                  dtls_client;
+  gboolean                  active;                 /* TRUE if any mline in the bundle/transport is active */
   TransportSendBin         *send_bin;               /* bin containing all the sending transport elements */
   TransportReceiveBin      *receive_bin;            /* bin containing all the receiving transport elements */
   GstWebRTCICEStream       *stream;
@@ -61,6 +62,10 @@ struct _TransportStream
 
   GArray                   *ptmap;                  /* array of PtMapItem's */
   GArray                   *remote_ssrcmap;         /* array of SsrcMapItem's */
+  gboolean                  output_connected;       /* whether receive bin is connected to rtpbin */
+
+  GstElement               *rtxsend;
+  GstElement               *rtxreceive;
 };
 
 struct _TransportStreamClass
@@ -72,6 +77,9 @@ TransportStream *       transport_stream_new        (GstWebRTCBin * webrtc,
                                                      guint session_id);
 int                     transport_stream_get_pt     (TransportStream * stream,
                                                      const gchar * encoding_name);
+int *                   transport_stream_get_all_pt (TransportStream * stream,
+                                                     const gchar * encoding_name,
+                                                     gsize * pt_len);
 GstCaps *               transport_stream_get_caps_for_pt    (TransportStream * stream,
                                                              guint pt);
 
index ac050bf..044d583 100644 (file)
@@ -21,6 +21,8 @@
 # include "config.h"
 #endif
 
+#include <stdlib.h>
+
 #include "utils.h"
 #include "gstwebrtcbin.h"
 
@@ -53,28 +55,59 @@ _find_pad_template (GstElement * element, GstPadDirection direction,
 }
 
 GstSDPMessage *
-_get_latest_sdp (GstWebRTCBin * webrtc)
+_get_latest_offer (GstWebRTCBin * webrtc)
 {
   if (webrtc->current_local_description &&
-      webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+      webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
     return webrtc->current_local_description->sdp;
   }
   if (webrtc->current_remote_description &&
-      webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+      webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
     return webrtc->current_remote_description->sdp;
   }
+
+  return NULL;
+}
+
+GstSDPMessage *
+_get_latest_answer (GstWebRTCBin * webrtc)
+{
   if (webrtc->current_local_description &&
-      webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+      webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
     return webrtc->current_local_description->sdp;
   }
   if (webrtc->current_remote_description &&
-      webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+      webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
     return webrtc->current_remote_description->sdp;
   }
 
   return NULL;
 }
 
+GstSDPMessage *
+_get_latest_sdp (GstWebRTCBin * webrtc)
+{
+  GstSDPMessage *ret = NULL;
+
+  if ((ret = _get_latest_answer (webrtc)))
+    return ret;
+  if ((ret = _get_latest_offer (webrtc)))
+    return ret;
+
+  return NULL;
+}
+
+GstSDPMessage *
+_get_latest_self_generated_sdp (GstWebRTCBin * webrtc)
+{
+  if (webrtc->priv->last_generated_answer)
+    return webrtc->priv->last_generated_answer->sdp;
+  if (webrtc->priv->last_generated_offer)
+    return webrtc->priv->last_generated_offer->sdp;
+
+  return NULL;
+}
+
 struct pad_block *
 _create_pad_block (GstElement * element, GstPad * pad, gulong block_id,
     gpointer user_data, GDestroyNotify notify)
@@ -142,3 +175,33 @@ _g_checksum_to_webrtc_string (GChecksumType type)
       return NULL;
   }
 }
+
+GstCaps *
+_rtp_caps_from_media (const GstSDPMedia * media)
+{
+  GstCaps *ret;
+  int i, j;
+
+  ret = gst_caps_new_empty ();
+  for (i = 0; i < gst_sdp_media_formats_len (media); i++) {
+    guint pt = atoi (gst_sdp_media_get_format (media, i));
+    GstCaps *caps;
+
+    caps = gst_sdp_media_get_caps_from_media (media, pt);
+    if (!caps)
+      continue;
+
+    /* gst_sdp_media_get_caps_from_media() produces caps with name
+     * "application/x-unknown" which will fail intersection with
+     * "application/x-rtp" caps so mangle the returns caps to have the
+     * correct name here */
+    for (j = 0; j < gst_caps_get_size (caps); j++) {
+      GstStructure *s = gst_caps_get_structure (caps, j);
+      gst_structure_set_name (s, "application/x-rtp");
+    }
+
+    gst_caps_append (ret, caps);
+  }
+
+  return ret;
+}
index 8472647..ab4d58e 100644 (file)
@@ -39,6 +39,7 @@ typedef enum
   GST_WEBRTC_BIN_ERROR_FINGERPRINT,
   GST_WEBRTC_BIN_ERROR_SCTP_FAILURE,
   GST_WEBRTC_BIN_ERROR_DATA_CHANNEL_FAILURE,
+  GST_WEBRTC_BIN_ERROR_CLOSED,
 } GstWebRTCError;
 
 GstPadTemplate *        _find_pad_template          (GstElement * element,
@@ -47,6 +48,9 @@ GstPadTemplate *        _find_pad_template          (GstElement * element,
                                                      const gchar * name);
 
 GstSDPMessage *         _get_latest_sdp             (GstWebRTCBin * webrtc);
+GstSDPMessage *         _get_latest_offer           (GstWebRTCBin * webrtc);
+GstSDPMessage *         _get_latest_answer          (GstWebRTCBin * webrtc);
+GstSDPMessage *         _get_latest_self_generated_sdp (GstWebRTCBin * webrtc);
 
 GstWebRTCICEStream *    _find_ice_stream_for_session            (GstWebRTCBin * webrtc,
                                                                  guint session_id);
@@ -74,6 +78,8 @@ G_GNUC_INTERNAL
 gchar *                 _enum_value_to_string       (GType type, guint value);
 G_GNUC_INTERNAL
 const gchar *           _g_checksum_to_webrtc_string (GChecksumType type);
+G_GNUC_INTERNAL
+GstCaps *               _rtp_caps_from_media        (const GstSDPMedia * media);
 
 G_END_DECLS
 
index 7aee403..fde1261 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCDataChannel
  * @see_also: #GstWebRTCRTPTransceiver
  *
- * <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcsctptransport">http://w3c.github.io/webrtc-pc/#dom-rtcsctptransport</ulink>
+ * <http://w3c.github.io/webrtc-pc/#dom-rtcsctptransport>
  */
 
 #ifdef HAVE_CONFIG_H
 #include "gstwebrtcbin.h"
 #include "utils.h"
 
-#define GST_CAT_DEFAULT gst_webrtc_data_channel_debug
+#define GST_CAT_DEFAULT webrtc_data_channel_debug
 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
 
-#define gst_webrtc_data_channel_parent_class parent_class
-G_DEFINE_TYPE_WITH_CODE (GstWebRTCDataChannel, gst_webrtc_data_channel,
-    GST_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_webrtc_data_channel_debug,
-        "webrtcdatachannel", 0, "webrtcdatachannel"););
-
-enum
-{
-  SIGNAL_0,
-  SIGNAL_ON_OPEN,
-  SIGNAL_ON_CLOSE,
-  SIGNAL_ON_ERROR,
-  SIGNAL_ON_MESSAGE_DATA,
-  SIGNAL_ON_MESSAGE_STRING,
-  SIGNAL_ON_BUFFERED_AMOUNT_LOW,
-  SIGNAL_SEND_DATA,
-  SIGNAL_SEND_STRING,
-  SIGNAL_CLOSE,
-  LAST_SIGNAL,
-};
-
-enum
-{
-  PROP_0,
-  PROP_LABEL,
-  PROP_ORDERED,
-  PROP_MAX_PACKET_LIFETIME,
-  PROP_MAX_RETRANSMITS,
-  PROP_PROTOCOL,
-  PROP_NEGOTIATED,
-  PROP_ID,
-  PROP_PRIORITY,
-  PROP_READY_STATE,
-  PROP_BUFFERED_AMOUNT,
-  PROP_BUFFERED_AMOUNT_LOW_THRESHOLD,
-};
-
-static guint gst_webrtc_data_channel_signals[LAST_SIGNAL] = { 0 };
+#define webrtc_data_channel_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (WebRTCDataChannel, webrtc_data_channel,
+    GST_TYPE_WEBRTC_DATA_CHANNEL,
+    GST_DEBUG_CATEGORY_INIT (webrtc_data_channel_debug, "webrtcdatachannel", 0,
+        "webrtcdatachannel"););
 
 typedef enum
 {
@@ -139,11 +107,11 @@ priority_uint_to_type (guint16 val)
 }
 
 static GstBuffer *
-construct_open_packet (GstWebRTCDataChannel * channel)
+construct_open_packet (WebRTCDataChannel * channel)
 {
   GstByteWriter w;
-  gsize label_len = strlen (channel->label);
-  gsize proto_len = strlen (channel->protocol);
+  gsize label_len = strlen (channel->parent.label);
+  gsize proto_len = strlen (channel->parent.protocol);
   gsize size = 12 + label_len + proto_len;
   DataChannelReliabilityType reliability = 0;
   guint32 reliability_param = 0;
@@ -175,18 +143,18 @@ construct_open_packet (GstWebRTCDataChannel * channel)
   if (!gst_byte_writer_put_uint8 (&w, (guint8) CHANNEL_MESSAGE_OPEN))
     g_return_val_if_reached (NULL);
 
-  if (!channel->ordered)
+  if (!channel->parent.ordered)
     reliability |= 0x80;
-  if (channel->max_retransmits != -1) {
+  if (channel->parent.max_retransmits != -1) {
     reliability |= 0x01;
-    reliability_param = channel->max_retransmits;
+    reliability_param = channel->parent.max_retransmits;
   }
-  if (channel->max_packet_lifetime != -1) {
+  if (channel->parent.max_packet_lifetime != -1) {
     reliability |= 0x02;
-    reliability_param = channel->max_packet_lifetime;
+    reliability_param = channel->parent.max_packet_lifetime;
   }
 
-  priority = priority_type_to_uint (channel->priority);
+  priority = priority_type_to_uint (channel->parent.priority);
 
   if (!gst_byte_writer_put_uint8 (&w, (guint8) reliability))
     g_return_val_if_reached (NULL);
@@ -198,9 +166,11 @@ construct_open_packet (GstWebRTCDataChannel * channel)
     g_return_val_if_reached (NULL);
   if (!gst_byte_writer_put_uint16_be (&w, (guint16) proto_len))
     g_return_val_if_reached (NULL);
-  if (!gst_byte_writer_put_data (&w, (guint8 *) channel->label, label_len))
+  if (!gst_byte_writer_put_data (&w, (guint8 *) channel->parent.label,
+          label_len))
     g_return_val_if_reached (NULL);
-  if (!gst_byte_writer_put_data (&w, (guint8 *) channel->protocol, proto_len))
+  if (!gst_byte_writer_put_data (&w, (guint8 *) channel->parent.protocol,
+          proto_len))
     g_return_val_if_reached (NULL);
 
   buf = gst_byte_writer_reset_and_get_buffer (&w);
@@ -213,7 +183,7 @@ construct_open_packet (GstWebRTCDataChannel * channel)
 }
 
 static GstBuffer *
-construct_ack_packet (GstWebRTCDataChannel * channel)
+construct_ack_packet (WebRTCDataChannel * channel)
 {
   GstByteWriter w;
   GstBuffer *buf;
@@ -269,7 +239,7 @@ _free_task (struct task *task)
 }
 
 static void
-_channel_enqueue_task (GstWebRTCDataChannel * channel, ChannelTask func,
+_channel_enqueue_task (WebRTCDataChannel * channel, ChannelTask func,
     gpointer user_data, GDestroyNotify notify)
 {
   struct task *task = g_new0 (struct task, 1);
@@ -280,13 +250,14 @@ _channel_enqueue_task (GstWebRTCDataChannel * channel, ChannelTask func,
   task->notify = notify;
 
   gst_webrtc_bin_enqueue_task (channel->webrtcbin,
-      (GstWebRTCBinFunc) _execute_task, task, (GDestroyNotify) _free_task);
+      (GstWebRTCBinFunc) _execute_task, task, (GDestroyNotify) _free_task,
+      NULL);
 }
 
 static void
-_channel_store_error (GstWebRTCDataChannel * channel, GError * error)
+_channel_store_error (WebRTCDataChannel * channel, GError * error)
 {
-  GST_OBJECT_LOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
   if (error) {
     GST_WARNING_OBJECT (channel, "Error: %s",
         error ? error->message : "Unknown");
@@ -295,76 +266,34 @@ _channel_store_error (GstWebRTCDataChannel * channel, GError * error)
     else
       g_clear_error (&error);
   }
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 }
 
 static void
-_maybe_emit_on_error (GstWebRTCDataChannel * channel, GError * error)
+_emit_on_open (WebRTCDataChannel * channel, gpointer user_data)
 {
-  if (error) {
-    GST_WARNING_OBJECT (channel, "error thrown");
-    g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_ERROR], 0,
-        error);
-  }
+  gst_webrtc_data_channel_on_open (GST_WEBRTC_DATA_CHANNEL (channel));
 }
 
 static void
-_emit_on_open (GstWebRTCDataChannel * channel, gpointer user_data)
-{
-  GST_OBJECT_LOCK (channel);
-  if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING ||
-      channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED) {
-    GST_OBJECT_UNLOCK (channel);
-    return;
-  }
-
-  if (channel->ready_state != GST_WEBRTC_DATA_CHANNEL_STATE_OPEN) {
-    channel->ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_OPEN;
-    GST_OBJECT_UNLOCK (channel);
-    g_object_notify (G_OBJECT (channel), "ready-state");
-
-    GST_INFO_OBJECT (channel, "We are open and ready for data!");
-    g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_OPEN], 0,
-        NULL);
-  } else {
-    GST_OBJECT_UNLOCK (channel);
-  }
-}
-
-static void
-_transport_closed_unlocked (GstWebRTCDataChannel * channel)
+_transport_closed (WebRTCDataChannel * channel)
 {
   GError *error;
 
-  if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED)
-    return;
-
-  channel->ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED;
-
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
   error = channel->stored_error;
   channel->stored_error = NULL;
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 
-  g_object_notify (G_OBJECT (channel), "ready-state");
-  GST_INFO_OBJECT (channel, "We are closed for data");
-
-  _maybe_emit_on_error (channel, error);
-
-  g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_CLOSE], 0,
-      NULL);
-  GST_OBJECT_LOCK (channel);
-}
-
-static void
-_transport_closed (GstWebRTCDataChannel * channel, gpointer user_data)
-{
-  GST_OBJECT_LOCK (channel);
-  _transport_closed_unlocked (channel);
-  GST_OBJECT_UNLOCK (channel);
+  if (error) {
+    gst_webrtc_data_channel_on_error (GST_WEBRTC_DATA_CHANNEL (channel), error);
+    g_clear_error (&error);
+  }
+  gst_webrtc_data_channel_on_close (GST_WEBRTC_DATA_CHANNEL (channel));
 }
 
 static void
-_close_sctp_stream (GstWebRTCDataChannel * channel, gpointer user_data)
+_close_sctp_stream (WebRTCDataChannel * channel, gpointer user_data)
 {
   GstPad *pad, *peer;
 
@@ -382,53 +311,55 @@ _close_sctp_stream (GstWebRTCDataChannel * channel, gpointer user_data)
     gst_object_unref (peer);
   }
 
-  _transport_closed (channel, NULL);
+  _transport_closed (channel);
 }
 
 static void
-_close_procedure (GstWebRTCDataChannel * channel, gpointer user_data)
+_close_procedure (WebRTCDataChannel * channel, gpointer user_data)
 {
   /* https://www.w3.org/TR/webrtc/#data-transport-closing-procedure */
-  GST_OBJECT_LOCK (channel);
-  if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED
-      || channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING) {
-    GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  if (channel->parent.ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED
+      || channel->parent.ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING) {
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
     return;
   }
-  channel->ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING;
-  GST_OBJECT_UNLOCK (channel);
+  channel->parent.ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING;
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
   g_object_notify (G_OBJECT (channel), "ready-state");
 
-  GST_OBJECT_LOCK (channel);
-  if (channel->buffered_amount <= 0) {
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  if (channel->parent.buffered_amount <= 0) {
     _channel_enqueue_task (channel, (ChannelTask) _close_sctp_stream,
         NULL, NULL);
   }
 
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 }
 
 static void
 _on_sctp_reset_stream (GstWebRTCSCTPTransport * sctp, guint stream_id,
-    GstWebRTCDataChannel * channel)
+    WebRTCDataChannel * channel)
 {
-  if (channel->id == stream_id)
+  if (channel->parent.id == stream_id)
     _channel_enqueue_task (channel, (ChannelTask) _transport_closed,
         GUINT_TO_POINTER (stream_id), NULL);
 }
 
 static void
-gst_webrtc_data_channel_close (GstWebRTCDataChannel * channel)
+webrtc_data_channel_close (GstWebRTCDataChannel * channel)
 {
-  _close_procedure (channel, NULL);
+  _close_procedure (WEBRTC_DATA_CHANNEL (channel), NULL);
 }
 
 static GstFlowReturn
-_parse_control_packet (GstWebRTCDataChannel * channel, guint8 * data,
+_parse_control_packet (WebRTCDataChannel * channel, guint8 * data,
     gsize size, GError ** error)
 {
   GstByteReader r;
   guint8 message_type;
+  gchar *label = NULL;
+  gchar *proto = NULL;
 
   if (!data)
     g_return_val_if_reached (GST_FLOW_ERROR);
@@ -449,13 +380,12 @@ _parse_control_packet (GstWebRTCDataChannel * channel, guint8 * data,
     guint32 reliability_param;
     guint16 priority, label_len, proto_len;
     const guint8 *src;
-    gchar *label, *proto;
     GstBuffer *buffer;
     GstFlowReturn ret;
 
     GST_INFO_OBJECT (channel, "Received channel open");
 
-    if (channel->negotiated) {
+    if (channel->parent.negotiated) {
       g_set_error (error, GST_WEBRTC_BIN_ERROR,
           GST_WEBRTC_BIN_ERROR_DATA_CHANNEL_FAILURE,
           "Data channel was signalled as negotiated already");
@@ -488,41 +418,46 @@ _parse_control_packet (GstWebRTCDataChannel * channel, guint8 * data,
     memcpy (proto, src, proto_len);
     proto[proto_len] = '\0';
 
-    channel->label = label;
-    channel->protocol = proto;
-    channel->priority = priority_uint_to_type (priority);
-    channel->ordered = !(reliability & 0x80);
+    g_free (channel->parent.label);
+    channel->parent.label = label;
+    g_free (channel->parent.protocol);
+    channel->parent.protocol = proto;
+    channel->parent.priority = priority_uint_to_type (priority);
+    channel->parent.ordered = !(reliability & 0x80);
     if (reliability & 0x01) {
-      channel->max_retransmits = reliability_param;
-      channel->max_packet_lifetime = -1;
+      channel->parent.max_retransmits = reliability_param;
+      channel->parent.max_packet_lifetime = -1;
     } else if (reliability & 0x02) {
-      channel->max_retransmits = -1;
-      channel->max_packet_lifetime = reliability_param;
+      channel->parent.max_retransmits = -1;
+      channel->parent.max_packet_lifetime = reliability_param;
     } else {
-      channel->max_retransmits = -1;
-      channel->max_packet_lifetime = -1;
+      channel->parent.max_retransmits = -1;
+      channel->parent.max_packet_lifetime = -1;
     }
     channel->opened = TRUE;
 
     GST_INFO_OBJECT (channel, "Received channel open for SCTP stream %i "
-        "label %s protocol %s ordered %s", channel->id, channel->label,
-        channel->protocol, channel->ordered ? "true" : "false");
+        "label %s protocol %s ordered %s", channel->parent.id,
+        channel->parent.label, channel->parent.protocol,
+        channel->parent.ordered ? "true" : "false");
 
     _channel_enqueue_task (channel, (ChannelTask) _emit_on_open, NULL, NULL);
 
     GST_INFO_OBJECT (channel, "Sending channel ack");
     buffer = construct_ack_packet (channel);
 
-    GST_OBJECT_LOCK (channel);
-    channel->buffered_amount += gst_buffer_get_size (buffer);
-    GST_OBJECT_UNLOCK (channel);
+    GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+    channel->parent.buffered_amount += gst_buffer_get_size (buffer);
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 
     ret = gst_app_src_push_buffer (GST_APP_SRC (channel->appsrc), buffer);
     if (ret != GST_FLOW_OK) {
       g_set_error (error, GST_WEBRTC_BIN_ERROR,
           GST_WEBRTC_BIN_ERROR_DATA_CHANNEL_FAILURE,
           "Could not send ack packet");
+      return ret;
     }
+
     return ret;
   } else {
     g_set_error (error, GST_WEBRTC_BIN_ERROR,
@@ -533,6 +468,8 @@ _parse_control_packet (GstWebRTCDataChannel * channel, guint8 * data,
 
 parse_error:
   {
+    g_free (label);
+    g_free (proto);
     g_set_error (error, GST_WEBRTC_BIN_ERROR,
         GST_WEBRTC_BIN_ERROR_DATA_CHANNEL_FAILURE, "Failed to parse packet");
     g_return_val_if_reached (GST_FLOW_ERROR);
@@ -559,23 +496,21 @@ buffer_unmap_and_unref (struct map_info *info)
 }
 
 static void
-_emit_have_data (GstWebRTCDataChannel * channel, GBytes * data)
+_emit_have_data (WebRTCDataChannel * channel, GBytes * data)
 {
-  GST_LOG_OBJECT (channel, "Have data %p", data);
-  g_signal_emit (channel,
-      gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_DATA], 0, data);
+  gst_webrtc_data_channel_on_message_data (GST_WEBRTC_DATA_CHANNEL (channel),
+      data);
 }
 
 static void
 _emit_have_string (GstWebRTCDataChannel * channel, gchar * str)
 {
-  GST_LOG_OBJECT (channel, "Have string %p", str);
-  g_signal_emit (channel,
-      gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_STRING], 0, str);
+  gst_webrtc_data_channel_on_message_string (GST_WEBRTC_DATA_CHANNEL (channel),
+      str);
 }
 
 static GstFlowReturn
-_data_channel_have_sample (GstWebRTCDataChannel * channel, GstSample * sample,
+_data_channel_have_sample (WebRTCDataChannel * channel, GstSample * sample,
     GError ** error)
 {
   GstSctpReceiveMeta *receive;
@@ -610,6 +545,7 @@ _data_channel_have_sample (GstWebRTCDataChannel * channel, GstSample * sample,
         ret = GST_FLOW_ERROR;
       } else {
         ret = _parse_control_packet (channel, info.data, info.size, error);
+        gst_buffer_unmap (buffer, &info);
       }
       break;
     }
@@ -625,6 +561,7 @@ _data_channel_have_sample (GstWebRTCDataChannel * channel, GstSample * sample,
         gchar *str = g_strndup ((gchar *) info.data, info.size);
         _channel_enqueue_task (channel, (ChannelTask) _emit_have_string, str,
             g_free);
+        gst_buffer_unmap (buffer, &info);
       }
       break;
     }
@@ -667,7 +604,7 @@ _data_channel_have_sample (GstWebRTCDataChannel * channel, GstSample * sample,
 static GstFlowReturn
 on_sink_preroll (GstAppSink * sink, gpointer user_data)
 {
-  GstWebRTCDataChannel *channel = user_data;
+  WebRTCDataChannel *channel = user_data;
   GstSample *sample = gst_app_sink_pull_preroll (sink);
   GstFlowReturn ret;
 
@@ -692,7 +629,7 @@ on_sink_preroll (GstAppSink * sink, gpointer user_data)
 static GstFlowReturn
 on_sink_sample (GstAppSink * sink, gpointer user_data)
 {
-  GstWebRTCDataChannel *channel = user_data;
+  WebRTCDataChannel *channel = user_data;
   GstSample *sample = gst_app_sink_pull_sample (sink);
   GstFlowReturn ret;
   GError *error = NULL;
@@ -723,23 +660,24 @@ static GstAppSinkCallbacks sink_callbacks = {
 };
 
 void
-gst_webrtc_data_channel_start_negotiation (GstWebRTCDataChannel * channel)
+webrtc_data_channel_start_negotiation (WebRTCDataChannel * channel)
 {
   GstBuffer *buffer;
 
-  g_return_if_fail (!channel->negotiated);
-  g_return_if_fail (channel->id != -1);
+  g_return_if_fail (!channel->parent.negotiated);
+  g_return_if_fail (channel->parent.id != -1);
   g_return_if_fail (channel->sctp_transport != NULL);
 
   buffer = construct_open_packet (channel);
 
   GST_INFO_OBJECT (channel, "Sending channel open for SCTP stream %i "
-      "label %s protocol %s ordered %s", channel->id, channel->label,
-      channel->protocol, channel->ordered ? "true" : "false");
+      "label %s protocol %s ordered %s", channel->parent.id,
+      channel->parent.label, channel->parent.protocol,
+      channel->parent.ordered ? "true" : "false");
 
-  GST_OBJECT_LOCK (channel);
-  channel->buffered_amount += gst_buffer_get_size (buffer);
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  channel->parent.buffered_amount += gst_buffer_get_size (buffer);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 
   if (gst_app_src_push_buffer (GST_APP_SRC (channel->appsrc),
           buffer) == GST_FLOW_OK) {
@@ -756,15 +694,15 @@ gst_webrtc_data_channel_start_negotiation (GstWebRTCDataChannel * channel)
 }
 
 static void
-_get_sctp_reliability (GstWebRTCDataChannel * channel,
+_get_sctp_reliability (WebRTCDataChannel * channel,
     GstSctpSendMetaPartiallyReliability * reliability, guint * rel_param)
 {
-  if (channel->max_retransmits != -1) {
+  if (channel->parent.max_retransmits != -1) {
     *reliability = GST_SCTP_SEND_META_PARTIAL_RELIABILITY_RTX;
-    *rel_param = channel->max_retransmits;
-  } else if (channel->max_packet_lifetime != -1) {
+    *rel_param = channel->parent.max_retransmits;
+  } else if (channel->parent.max_packet_lifetime != -1) {
     *reliability = GST_SCTP_SEND_META_PARTIAL_RELIABILITY_TTL;
-    *rel_param = channel->max_packet_lifetime;
+    *rel_param = channel->parent.max_packet_lifetime;
   } else {
     *reliability = GST_SCTP_SEND_META_PARTIAL_RELIABILITY_NONE;
     *rel_param = 0;
@@ -772,15 +710,16 @@ _get_sctp_reliability (GstWebRTCDataChannel * channel,
 }
 
 static gboolean
-_is_within_max_message_size (GstWebRTCDataChannel * channel, gsize size)
+_is_within_max_message_size (WebRTCDataChannel * channel, gsize size)
 {
   return size <= channel->sctp_transport->max_message_size;
 }
 
 static void
-gst_webrtc_data_channel_send_data (GstWebRTCDataChannel * channel,
+webrtc_data_channel_send_data (GstWebRTCDataChannel * base_channel,
     GBytes * bytes)
 {
+  WebRTCDataChannel *channel = WEBRTC_DATA_CHANNEL (base_channel);
   GstSctpSendMetaPartiallyReliability reliability;
   guint rel_param;
   guint32 ppid;
@@ -813,15 +752,15 @@ gst_webrtc_data_channel_send_data (GstWebRTCDataChannel * channel,
   }
 
   _get_sctp_reliability (channel, &reliability, &rel_param);
-  gst_sctp_buffer_add_send_meta (buffer, ppid, channel->ordered, reliability,
-      rel_param);
+  gst_sctp_buffer_add_send_meta (buffer, ppid, channel->parent.ordered,
+      reliability, rel_param);
 
   GST_LOG_OBJECT (channel, "Sending data using buffer %" GST_PTR_FORMAT,
       buffer);
 
-  GST_OBJECT_LOCK (channel);
-  channel->buffered_amount += gst_buffer_get_size (buffer);
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  channel->parent.buffered_amount += gst_buffer_get_size (buffer);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 
   ret = gst_app_src_push_buffer (GST_APP_SRC (channel->appsrc), buffer);
 
@@ -835,16 +774,17 @@ gst_webrtc_data_channel_send_data (GstWebRTCDataChannel * channel,
 }
 
 static void
-gst_webrtc_data_channel_send_string (GstWebRTCDataChannel * channel,
-    gchar * str)
+webrtc_data_channel_send_string (GstWebRTCDataChannel * base_channel,
+    const gchar * str)
 {
+  WebRTCDataChannel *channel = WEBRTC_DATA_CHANNEL (base_channel);
   GstSctpSendMetaPartiallyReliability reliability;
   guint rel_param;
   guint32 ppid;
   GstBuffer *buffer;
   GstFlowReturn ret;
 
-  if (!channel->negotiated)
+  if (!channel->parent.negotiated)
     g_return_if_fail (channel->opened);
   g_return_if_fail (channel->sctp_transport != NULL);
 
@@ -873,15 +813,15 @@ gst_webrtc_data_channel_send_string (GstWebRTCDataChannel * channel,
   }
 
   _get_sctp_reliability (channel, &reliability, &rel_param);
-  gst_sctp_buffer_add_send_meta (buffer, ppid, channel->ordered, reliability,
-      rel_param);
+  gst_sctp_buffer_add_send_meta (buffer, ppid, channel->parent.ordered,
+      reliability, rel_param);
 
   GST_TRACE_OBJECT (channel, "Sending string using buffer %" GST_PTR_FORMAT,
       buffer);
 
-  GST_OBJECT_LOCK (channel);
-  channel->buffered_amount += gst_buffer_get_size (buffer);
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  channel->parent.buffered_amount += gst_buffer_get_size (buffer);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 
   ret = gst_app_src_push_buffer (GST_APP_SRC (channel->appsrc), buffer);
 
@@ -896,152 +836,37 @@ gst_webrtc_data_channel_send_string (GstWebRTCDataChannel * channel,
 
 static void
 _on_sctp_notify_state_unlocked (GObject * sctp_transport,
-    GstWebRTCDataChannel * channel)
+    WebRTCDataChannel * channel)
 {
   GstWebRTCSCTPTransportState state;
 
   g_object_get (sctp_transport, "state", &state, NULL);
   if (state == GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTED) {
-    if (channel->negotiated)
+    if (channel->parent.negotiated)
       _channel_enqueue_task (channel, (ChannelTask) _emit_on_open, NULL, NULL);
   }
 }
 
 static void
 _on_sctp_notify_state (GObject * sctp_transport, GParamSpec * pspec,
-    GstWebRTCDataChannel * channel)
+    WebRTCDataChannel * channel)
 {
-  GST_OBJECT_LOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
   _on_sctp_notify_state_unlocked (sctp_transport, channel);
-  GST_OBJECT_UNLOCK (channel);
-}
-
-void
-gst_webrtc_data_channel_set_sctp_transport (GstWebRTCDataChannel * channel,
-    GstWebRTCSCTPTransport * sctp)
-{
-  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
-  g_return_if_fail (GST_IS_WEBRTC_SCTP_TRANSPORT (sctp));
-
-  GST_OBJECT_LOCK (channel);
-  if (channel->sctp_transport)
-    g_signal_handlers_disconnect_by_data (channel->sctp_transport, channel);
-
-  gst_object_replace ((GstObject **) & channel->sctp_transport,
-      GST_OBJECT (sctp));
-
-  if (sctp) {
-    g_signal_connect (sctp, "stream-reset", G_CALLBACK (_on_sctp_reset_stream),
-        channel);
-    g_signal_connect (sctp, "notify::state", G_CALLBACK (_on_sctp_notify_state),
-        channel);
-    _on_sctp_notify_state_unlocked (G_OBJECT (sctp), channel);
-  }
-  GST_OBJECT_UNLOCK (channel);
-}
-
-static void
-gst_webrtc_data_channel_set_property (GObject * object, guint prop_id,
-    const GValue * value, GParamSpec * pspec)
-{
-  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
-
-  GST_OBJECT_LOCK (channel);
-  switch (prop_id) {
-    case PROP_LABEL:
-      channel->label = g_value_dup_string (value);
-      break;
-    case PROP_ORDERED:
-      channel->ordered = g_value_get_boolean (value);
-      break;
-    case PROP_MAX_PACKET_LIFETIME:
-      channel->max_packet_lifetime = g_value_get_int (value);
-      break;
-    case PROP_MAX_RETRANSMITS:
-      channel->max_retransmits = g_value_get_int (value);
-      break;
-    case PROP_PROTOCOL:
-      channel->protocol = g_value_dup_string (value);
-      break;
-    case PROP_NEGOTIATED:
-      channel->negotiated = g_value_get_boolean (value);
-      break;
-    case PROP_ID:
-      channel->id = g_value_get_int (value);
-      break;
-    case PROP_PRIORITY:
-      channel->priority = g_value_get_enum (value);
-      break;
-    case PROP_BUFFERED_AMOUNT_LOW_THRESHOLD:
-      channel->buffered_amount_low_threshold = g_value_get_uint64 (value);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-  }
-  GST_OBJECT_UNLOCK (channel);
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
 }
 
 static void
-gst_webrtc_data_channel_get_property (GObject * object, guint prop_id,
-    GValue * value, GParamSpec * pspec)
+_emit_low_threshold (WebRTCDataChannel * channel, gpointer user_data)
 {
-  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
-
-  GST_OBJECT_LOCK (channel);
-  switch (prop_id) {
-    case PROP_LABEL:
-      g_value_set_string (value, channel->label);
-      break;
-    case PROP_ORDERED:
-      g_value_set_boolean (value, channel->ordered);
-      break;
-    case PROP_MAX_PACKET_LIFETIME:
-      g_value_set_int (value, channel->max_packet_lifetime);
-      break;
-    case PROP_MAX_RETRANSMITS:
-      g_value_set_int (value, channel->max_retransmits);
-      break;
-    case PROP_PROTOCOL:
-      g_value_set_string (value, channel->protocol);
-      break;
-    case PROP_NEGOTIATED:
-      g_value_set_boolean (value, channel->negotiated);
-      break;
-    case PROP_ID:
-      g_value_set_int (value, channel->id);
-      break;
-    case PROP_PRIORITY:
-      g_value_set_enum (value, channel->priority);
-      break;
-    case PROP_READY_STATE:
-      g_value_set_enum (value, channel->ready_state);
-      break;
-    case PROP_BUFFERED_AMOUNT:
-      g_value_set_uint64 (value, channel->buffered_amount);
-      break;
-    case PROP_BUFFERED_AMOUNT_LOW_THRESHOLD:
-      g_value_set_uint64 (value, channel->buffered_amount_low_threshold);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-  }
-  GST_OBJECT_UNLOCK (channel);
-}
-
-static void
-_emit_low_threshold (GstWebRTCDataChannel * channel, gpointer user_data)
-{
-  GST_LOG_OBJECT (channel, "Low threshold reached");
-  g_signal_emit (channel,
-      gst_webrtc_data_channel_signals[SIGNAL_ON_BUFFERED_AMOUNT_LOW], 0);
+  gst_webrtc_data_channel_on_buffered_amount_low (GST_WEBRTC_DATA_CHANNEL
+      (channel));
 }
 
 static GstPadProbeReturn
 on_appsrc_data (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
 {
-  GstWebRTCDataChannel *channel = user_data;
+  WebRTCDataChannel *channel = user_data;
   guint64 prev_amount;
   guint64 size = 0;
 
@@ -1054,21 +879,27 @@ on_appsrc_data (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
   }
 
   if (size > 0) {
-    GST_OBJECT_LOCK (channel);
-    prev_amount = channel->buffered_amount;
-    channel->buffered_amount -= size;
-    if (prev_amount > channel->buffered_amount_low_threshold &&
-        channel->buffered_amount < channel->buffered_amount_low_threshold) {
-      _channel_enqueue_task (channel, (ChannelTask) _emit_low_threshold,
-          NULL, NULL);
+    GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+    prev_amount = channel->parent.buffered_amount;
+    channel->parent.buffered_amount -= size;
+    GST_TRACE_OBJECT (channel, "checking low-threshold: prev %"
+        G_GUINT64_FORMAT " low-threshold %" G_GUINT64_FORMAT " buffered %"
+        G_GUINT64_FORMAT, prev_amount,
+        channel->parent.buffered_amount_low_threshold,
+        channel->parent.buffered_amount);
+    if (prev_amount >= channel->parent.buffered_amount_low_threshold
+        && channel->parent.buffered_amount <
+        channel->parent.buffered_amount_low_threshold) {
+      _channel_enqueue_task (channel, (ChannelTask) _emit_low_threshold, NULL,
+          NULL);
     }
 
-    if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING
-        && channel->buffered_amount <= 0) {
+    if (channel->parent.ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING
+        && channel->parent.buffered_amount <= 0) {
       _channel_enqueue_task (channel, (ChannelTask) _close_sctp_stream, NULL,
           NULL);
     }
-    GST_OBJECT_UNLOCK (channel);
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
   }
 
   return GST_PAD_PROBE_OK;
@@ -1077,7 +908,7 @@ on_appsrc_data (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
 static void
 gst_webrtc_data_channel_constructed (GObject * object)
 {
-  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
+  WebRTCDataChannel *channel = WEBRTC_DATA_CHANNEL (object);
   GstPad *pad;
   GstCaps *caps;
 
@@ -1104,7 +935,7 @@ gst_webrtc_data_channel_constructed (GObject * object)
 static void
 gst_webrtc_data_channel_finalize (GObject * object)
 {
-  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
+  WebRTCDataChannel *channel = WEBRTC_DATA_CHANNEL (object);
 
   if (channel->src_probe) {
     GstPad *pad = gst_element_get_static_pad (channel->appsrc, "src");
@@ -1113,12 +944,6 @@ gst_webrtc_data_channel_finalize (GObject * object)
     channel->src_probe = 0;
   }
 
-  g_free (channel->label);
-  channel->label = NULL;
-
-  g_free (channel->protocol);
-  channel->protocol = NULL;
-
   if (channel->sctp_transport)
     g_signal_handlers_disconnect_by_data (channel->sctp_transport, channel);
   g_clear_object (&channel->sctp_transport);
@@ -1130,194 +955,67 @@ gst_webrtc_data_channel_finalize (GObject * object)
 }
 
 static void
-gst_webrtc_data_channel_class_init (GstWebRTCDataChannelClass * klass)
+webrtc_data_channel_class_init (WebRTCDataChannelClass * klass)
 {
   GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstWebRTCDataChannelClass *channel_class =
+      (GstWebRTCDataChannelClass *) klass;
 
   gobject_class->constructed = gst_webrtc_data_channel_constructed;
-  gobject_class->get_property = gst_webrtc_data_channel_get_property;
-  gobject_class->set_property = gst_webrtc_data_channel_set_property;
   gobject_class->finalize = gst_webrtc_data_channel_finalize;
 
-  g_object_class_install_property (gobject_class,
-      PROP_LABEL,
-      g_param_spec_string ("label",
-          "Label", "Data channel label",
-          NULL,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_ORDERED,
-      g_param_spec_boolean ("ordered",
-          "Ordered", "Using ordered transmission mode",
-          FALSE,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_MAX_PACKET_LIFETIME,
-      g_param_spec_int ("max-packet-lifetime",
-          "Maximum Packet Lifetime",
-          "Maximum number of milliseconds that transmissions and "
-          "retransmissions may occur in unreliable mode (-1 = unset)",
-          -1, G_MAXUINT16, -1,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_MAX_RETRANSMITS,
-      g_param_spec_int ("max-retransmits",
-          "Maximum Retransmits",
-          "Maximum number of retransmissions attempted in unreliable mode",
-          -1, G_MAXUINT16, 0,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_PROTOCOL,
-      g_param_spec_string ("protocol",
-          "Protocol", "Data channel protocol",
-          "",
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_NEGOTIATED,
-      g_param_spec_boolean ("negotiated",
-          "Negotiated",
-          "Whether this data channel was negotiated by the application", FALSE,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_ID,
-      g_param_spec_int ("id",
-          "ID",
-          "ID negotiated by this data channel (-1 = unset)",
-          -1, G_MAXUINT16, -1,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_PRIORITY,
-      g_param_spec_enum ("priority",
-          "Priority",
-          "The priority of data sent using this data channel",
-          GST_TYPE_WEBRTC_PRIORITY_TYPE,
-          GST_WEBRTC_PRIORITY_TYPE_LOW,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_READY_STATE,
-      g_param_spec_enum ("ready-state",
-          "Ready State",
-          "The Ready state of this data channel",
-          GST_TYPE_WEBRTC_DATA_CHANNEL_STATE,
-          GST_WEBRTC_DATA_CHANNEL_STATE_NEW,
-          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_BUFFERED_AMOUNT,
-      g_param_spec_uint64 ("buffered-amount",
-          "Buffered Amount",
-          "The amount of data in bytes currently buffered",
-          0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class,
-      PROP_BUFFERED_AMOUNT_LOW_THRESHOLD,
-      g_param_spec_uint64 ("buffered-amount-low-threshold",
-          "Buffered Amount Low Threshold",
-          "The threshold at which the buffered amount is considered low and "
-          "the buffered-amount-low signal is emitted",
-          0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  /**
-   * GstWebRTCDataChannel::on-open:
-   * @object: the #GstWebRTCDataChannel
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_OPEN] =
-      g_signal_new ("on-open", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 0);
-
-  /**
-   * GstWebRTCDataChannel::on-close:
-   * @object: the #GstWebRTCDataChannel
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_CLOSE] =
-      g_signal_new ("on-close", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 0);
-
-  /**
-   * GstWebRTCDataChannel::on-error:
-   * @object: the #GstWebRTCDataChannel
-   * @error: the #GError thrown
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_ERROR] =
-      g_signal_new ("on-error", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 1, G_TYPE_ERROR);
-
-  /**
-   * GstWebRTCDataChannel::on-message-data:
-   * @object: the #GstWebRTCDataChannel
-   * @data: (nullable): a #GBytes of the data received
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_DATA] =
-      g_signal_new ("on-message-data", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 1, G_TYPE_BYTES);
-
-  /**
-   * GstWebRTCDataChannel::on-message-string:
-   * @object: the #GstWebRTCDataChannel
-   * @data: (nullable): the data received as a string
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_STRING] =
-      g_signal_new ("on-message-string", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 1, G_TYPE_STRING);
-
-  /**
-   * GstWebRTCDataChannel::on-buffered-amount-low:
-   * @object: the #GstWebRTCDataChannel
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_ON_BUFFERED_AMOUNT_LOW] =
-      g_signal_new ("on-buffered-amount-low", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 0);
-
-  /**
-   * GstWebRTCDataChannel::send-data:
-   * @object: the #GstWebRTCDataChannel
-   * @data: (nullable): a #GBytes with the data
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_SEND_DATA] =
-      g_signal_new_class_handler ("send-data", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_data_channel_send_data), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BYTES);
-
-  /**
-   * GstWebRTCDataChannel::send-string:
-   * @object: the #GstWebRTCDataChannel
-   * @data: (nullable): a #GBytes with the data
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_SEND_STRING] =
-      g_signal_new_class_handler ("send-string", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_data_channel_send_string), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_STRING);
-
-  /**
-   * GstWebRTCDataChannel::close:
-   * @object: the #GstWebRTCDataChannel
-   *
-   * Close the data channel
-   */
-  gst_webrtc_data_channel_signals[SIGNAL_CLOSE] =
-      g_signal_new_class_handler ("close", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-      G_CALLBACK (gst_webrtc_data_channel_close), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 0);
+  channel_class->send_data = webrtc_data_channel_send_data;
+  channel_class->send_string = webrtc_data_channel_send_string;
+  channel_class->close = webrtc_data_channel_close;
+}
+
+static void
+webrtc_data_channel_init (WebRTCDataChannel * channel)
+{
 }
 
 static void
-gst_webrtc_data_channel_init (GstWebRTCDataChannel * channel)
+_data_channel_set_sctp_transport (WebRTCDataChannel * channel,
+    GstWebRTCSCTPTransport * sctp)
 {
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+  g_return_if_fail (GST_IS_WEBRTC_SCTP_TRANSPORT (sctp));
+
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  if (channel->sctp_transport)
+    g_signal_handlers_disconnect_by_data (channel->sctp_transport, channel);
+
+  gst_object_replace ((GstObject **) & channel->sctp_transport,
+      GST_OBJECT (sctp));
+
+  if (sctp) {
+    g_signal_connect (sctp, "stream-reset", G_CALLBACK (_on_sctp_reset_stream),
+        channel);
+    g_signal_connect (sctp, "notify::state", G_CALLBACK (_on_sctp_notify_state),
+        channel);
+    _on_sctp_notify_state_unlocked (G_OBJECT (sctp), channel);
+  }
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+}
+
+void
+webrtc_data_channel_link_to_sctp (WebRTCDataChannel * channel,
+    GstWebRTCSCTPTransport * sctp_transport)
+{
+  if (sctp_transport && !channel->sctp_transport) {
+    gint id;
+
+    g_object_get (channel, "id", &id, NULL);
+
+    if (sctp_transport->association_established && id != -1) {
+      gchar *pad_name;
+
+      _data_channel_set_sctp_transport (channel, sctp_transport);
+      pad_name = g_strdup_printf ("sink_%u", id);
+      if (!gst_element_link_pads (channel->appsrc, "src",
+              channel->sctp_transport->sctpenc, pad_name))
+        g_warn_if_reached ();
+      g_free (pad_name);
+    }
+  }
 }
index 7698441..7ca3c0d 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GST_WEBRTC_DATA_CHANNEL_H__
-#define __GST_WEBRTC_DATA_CHANNEL_H__
+#ifndef __WEBRTC_DATA_CHANNEL_H__
+#define __WEBRTC_DATA_CHANNEL_H__
 
 #include <gst/gst.h>
 #include <gst/webrtc/webrtc_fwd.h>
 #include <gst/webrtc/dtlstransport.h>
+#include <gst/webrtc/datachannel.h>
 #include "sctptransport.h"
 
 G_BEGIN_DECLS
 
-GST_WEBRTC_API
-GType gst_webrtc_data_channel_get_type(void);
-#define GST_TYPE_WEBRTC_DATA_CHANNEL            (gst_webrtc_data_channel_get_type())
-#define GST_WEBRTC_DATA_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannel))
-#define GST_IS_WEBRTC_DATA_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_DATA_CHANNEL))
-#define GST_WEBRTC_DATA_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannelClass))
-#define GST_IS_WEBRTC_DATA_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_DATA_CHANNEL))
-#define GST_WEBRTC_DATA_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannelClass))
+GType webrtc_data_channel_get_type(void);
+#define WEBRTC_TYPE_DATA_CHANNEL            (webrtc_data_channel_get_type())
+#define WEBRTC_DATA_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),WEBRTC_TYPE_DATA_CHANNEL,WebRTCDataChannel))
+#define WEBRTC_IS_DATA_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),WEBRTC_TYPE_DATA_CHANNEL))
+#define WEBRTC_DATA_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass) ,WEBRTC_TYPE_DATA_CHANNEL,WebRTCDataChannelClass))
+#define WEBRTC_IS_DATA_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,WEBRTC_TYPE_DATA_CHANNEL))
+#define WEBRTC_DATA_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,WEBRTC_TYPE_DATA_CHANNEL,WebRTCDataChannelClass))
 
-typedef struct _GstWebRTCDataChannel GstWebRTCDataChannel;
-typedef struct _GstWebRTCDataChannelClass GstWebRTCDataChannelClass;
+typedef struct _WebRTCDataChannel WebRTCDataChannel;
+typedef struct _WebRTCDataChannelClass WebRTCDataChannelClass;
 
-struct _GstWebRTCDataChannel
+struct _WebRTCDataChannel
 {
-  GstObject                         parent;
+  GstWebRTCDataChannel              parent;
 
   GstWebRTCSCTPTransport           *sctp_transport;
   GstElement                       *appsrc;
   GstElement                       *appsink;
 
-  gchar                            *label;
-  gboolean                          ordered;
-  guint                             max_packet_lifetime;
-  guint                             max_retransmits;
-  gchar                            *protocol;
-  gboolean                          negotiated;
-  gint                              id;
-  GstWebRTCPriorityType             priority;
-  GstWebRTCDataChannelState         ready_state;
-  guint64                           buffered_amount;
-  guint64                           buffered_amount_low_threshold;
-
   GstWebRTCBin                     *webrtcbin;
   gboolean                          opened;
   gulong                            src_probe;
@@ -67,17 +55,18 @@ struct _GstWebRTCDataChannel
   gpointer                          _padding[GST_PADDING];
 };
 
-struct _GstWebRTCDataChannelClass
+struct _WebRTCDataChannelClass
 {
-  GstObjectClass            parent_class;
+  GstWebRTCDataChannelClass  parent_class;
 
   gpointer                  _padding[GST_PADDING];
 };
 
-void    gst_webrtc_data_channel_start_negotiation   (GstWebRTCDataChannel       *channel);
-void    gst_webrtc_data_channel_set_sctp_transport  (GstWebRTCDataChannel       *channel,
-                                                     GstWebRTCSCTPTransport     *sctp);
+void    webrtc_data_channel_start_negotiation   (WebRTCDataChannel       *channel);
+G_GNUC_INTERNAL
+void    webrtc_data_channel_link_to_sctp (WebRTCDataChannel                 *channel,
+                                          GstWebRTCSCTPTransport            *sctp_transport);
 
 G_END_DECLS
 
-#endif /* __GST_WEBRTC_DATA_CHANNEL_H__ */
+#endif /* __WEBRTC_DATA_CHANNEL_H__ */
index 5a08c82..8164b7a 100644 (file)
@@ -144,9 +144,8 @@ _check_sdp_crypto (SDPSource source, GstWebRTCSessionDescription * sdp,
   return TRUE;
 }
 
-#if 0
-static gboolean
-_session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
+gboolean
+_message_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
 {
   int i;
   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
@@ -159,6 +158,7 @@ _session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
   return FALSE;
 }
 
+#if 0
 static gboolean
 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
     const gchar * value)
@@ -212,7 +212,7 @@ _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
   return TRUE;
 }
 
-static const gchar *
+const gchar *
 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
 {
   const gchar *ice_ufrag;
@@ -227,7 +227,7 @@ _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
   return ice_ufrag;
 }
 
-static const gchar *
+const gchar *
 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
 {
   const gchar *ice_pwd;
@@ -319,7 +319,7 @@ validate_sdp (GstWebRTCSignalingState state, SDPSource source,
     }
     if (!_media_has_setup (media, i, error))
       goto fail;
-    /* check paramaters in bundle are the same */
+    /* check parameters in bundle are the same */
     if (media_in_bundle) {
       const gchar *ice_ufrag =
           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
@@ -397,6 +397,8 @@ GstWebRTCRTPTransceiverDirection
 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
     GstWebRTCRTPTransceiverDirection answer)
 {
+  if (offer == DIR (INACTIVE) || answer == DIR (INACTIVE))
+    return DIR (INACTIVE);
   if (offer == DIR (SENDONLY) && answer == DIR (SENDRECV))
     return DIR (RECVONLY);
   if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY))
@@ -411,6 +413,10 @@ _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
     return DIR (SENDONLY);
   if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY))
     return DIR (RECVONLY);
+  if (offer == DIR (RECVONLY) && answer == DIR (RECVONLY))
+    return DIR (INACTIVE);
+  if (offer == DIR (SENDONLY) && answer == DIR (SENDONLY))
+    return DIR (INACTIVE);
 
   return DIR (NONE);
 }
@@ -431,14 +437,13 @@ _media_replace_direction (GstSDPMedia * media,
 
     if (g_strcmp0 (attr->key, "sendonly") == 0
         || g_strcmp0 (attr->key, "sendrecv") == 0
-        || g_strcmp0 (attr->key, "recvonly") == 0) {
+        || g_strcmp0 (attr->key, "recvonly") == 0
+        || g_strcmp0 (attr->key, "inactive") == 0) {
       GstSDPAttribute new_attr = { 0, };
       GST_TRACE ("replace %s with %s", attr->key, dir_str);
       gst_sdp_attribute_set (&new_attr, dir_str, "");
       gst_sdp_media_replace_attribute (media, i, &new_attr);
-#ifdef __TIZEN__
       g_free (dir_str);
-#endif
       return;
     }
   }
@@ -710,21 +715,56 @@ _generate_ice_credentials (gchar ** ufrag, gchar ** password)
 int
 _get_sctp_port_from_media (const GstSDPMedia * media)
 {
-  int sctpmap = -1, i;
+  int i;
+  const gchar *format;
+  gchar *endptr;
 
-  for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
-    const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+  if (gst_sdp_media_formats_len (media) != 1) {
+    /* only exactly one format is supported */
+    return -1;
+  }
 
-    if (g_strcmp0 (attr->key, "sctp-port") == 0) {
-      return atoi (attr->value);
-    } else if (g_strcmp0 (attr->key, "sctpmap") == 0) {
-      sctpmap = atoi (attr->value);
+  format = gst_sdp_media_get_format (media, 0);
+
+  if (g_strcmp0 (format, "webrtc-datachannel") == 0) {
+    /* draft-ietf-mmusic-sctp-sdp-21, e.g. Firefox 63 and later */
+
+    for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
+      const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+
+      if (g_strcmp0 (attr->key, "sctp-port") == 0) {
+        gint64 port = g_ascii_strtoll (attr->value, &endptr, 10);
+        if (endptr == attr->value) {
+          /* conversion error */
+          return -1;
+        }
+        return port;
+      }
+    }
+  } else {
+    /* draft-ietf-mmusic-sctp-sdp-05, e.g. Chrome as recent as 75 */
+    gint64 port = g_ascii_strtoll (format, &endptr, 10);
+    if (endptr == format) {
+      /* conversion error */
+      return -1;
+    }
+
+    for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
+      const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+
+      if (g_strcmp0 (attr->key, "sctpmap") == 0 && atoi (attr->value) == port) {
+        /* a=sctpmap:5000 webrtc-datachannel 256 */
+        gchar **parts = g_strsplit (attr->value, " ", 3);
+        if (!parts[1] || g_strcmp0 (parts[1], "webrtc-datachannel") != 0) {
+          port = -1;
+        }
+        g_strfreev (parts);
+        return port;
+      }
     }
   }
 
-  if (sctpmap >= 0)
-    GST_LOG ("no sctp-port attribute in media");
-  return sctpmap;
+  return -1;
 }
 
 guint64
@@ -768,6 +808,21 @@ _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
   return TRUE;
 }
 
+guint
+_message_get_datachannel_index (const GstSDPMessage * msg)
+{
+  guint i;
+
+  for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
+    if (_message_media_is_datachannel (msg, i)) {
+      g_assert (i < G_MAXUINT);
+      return i;
+    }
+  }
+
+  return G_MAXUINT;
+}
+
 void
 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
     gchar ** ufrag, gchar ** pwd)
@@ -833,6 +888,8 @@ _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
     if (!(*bundled)[0]) {
       GST_ERROR ("Invalid format for BUNDLE group, expected at least "
           "one mid (%s)", group);
+      g_strfreev (*bundled);
+      *bundled = NULL;
       goto done;
     }
   } else {
@@ -865,3 +922,19 @@ _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
 
   return ret;
 }
+
+gboolean
+_media_is_bundle_only (const GstSDPMedia * media)
+{
+  int i;
+
+  for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
+    const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+
+    if (g_strcmp0 (attr->key, "bundle-only") == 0) {
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
index a9a86fc..1501cbc 100644 (file)
@@ -89,6 +89,11 @@ void                                _get_ice_credentials_from_sdp_media     (con
 G_GNUC_INTERNAL
 gboolean                            _message_media_is_datachannel           (const GstSDPMessage * msg,
                                                                              guint media_id);
+G_GNUC_INTERNAL
+guint                               _message_get_datachannel_index          (const GstSDPMessage * msg);
+G_GNUC_INTERNAL
+gboolean                            _message_has_attribute_key              (const GstSDPMessage * msg,
+                                                                             const gchar * key);
 
 G_GNUC_INTERNAL
 gboolean                            _get_bundle_index                       (GstSDPMessage * sdp,
@@ -98,4 +103,14 @@ G_GNUC_INTERNAL
 gboolean                            _parse_bundle                           (GstSDPMessage * sdp,
                                                                              GStrv * bundled);
 
+G_GNUC_INTERNAL
+const gchar *                       _media_get_ice_pwd                  (const GstSDPMessage * msg,
+                                                                             guint media_idx);
+G_GNUC_INTERNAL
+const gchar *                       _media_get_ice_ufrag                (const GstSDPMessage * msg,
+                                                                             guint media_idx);
+
+G_GNUC_INTERNAL
+gboolean                            _media_is_bundle_only               (const GstSDPMedia * sdp);
+
 #endif /* __WEBRTC_UTILS_H__ */
index c1a3faa..f265367 100644 (file)
 #include "utils.h"
 #include "webrtctransceiver.h"
 
+#define GST_CAT_DEFAULT webrtc_transceiver_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
 #define webrtc_transceiver_parent_class parent_class
-G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver,
-    GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
+G_DEFINE_TYPE_WITH_CODE (WebRTCTransceiver, webrtc_transceiver,
+    GST_TYPE_WEBRTC_RTP_TRANSCEIVER,
+    GST_DEBUG_CATEGORY_INIT (webrtc_transceiver_debug,
+        "webrtctransceiver", 0, "webrtctransceiver"););
 
 #define DEFAULT_FEC_TYPE GST_WEBRTC_FEC_TYPE_NONE
 #define DEFAULT_DO_NACK FALSE
@@ -166,6 +171,8 @@ webrtc_transceiver_finalize (GObject * object)
     gst_structure_free (trans->local_rtx_ssrc_map);
   trans->local_rtx_ssrc_map = NULL;
 
+  gst_caps_replace (&trans->last_configured_caps, NULL);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
index f1e338f..c037304 100644 (file)
@@ -44,6 +44,8 @@ struct _WebRTCTransceiver
   GstWebRTCFECType         fec_type;
   guint                    fec_percentage;
   gboolean                 do_nack;
+
+  GstCaps                  *last_configured_caps;
 };
 
 struct _WebRTCTransceiverClass
index edc9de0..c95b230 100644 (file)
@@ -18,7 +18,8 @@ libgstwebrtc_@GST_API_VERSION@_la_SOURCES = \
        rtcsessiondescription.c \
        rtpreceiver.c \
        rtpsender.c \
-       rtptransceiver.c
+       rtptransceiver.c \
+       datachannel.c
 
 nodist_libgstwebrtc_@GST_API_VERSION@_la_SOURCES = $(built_sources)
 
@@ -31,7 +32,8 @@ libgstwebrtc_@GST_API_VERSION@include_HEADERS = \
        rtpsender.h \
        rtptransceiver.h \
        webrtc_fwd.h \
-       webrtc.h
+       webrtc.h \
+       datachannel.h
 
 nodist_libgstwebrtc_@GST_API_VERSION@include_HEADERS = $(built_headers)
 
diff --git a/gst-libs/gst/webrtc/datachannel.c b/gst-libs/gst/webrtc/datachannel.c
new file mode 100644 (file)
index 0000000..ee0be60
--- /dev/null
@@ -0,0 +1,559 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-datachannel
+ * @short_description: RTCDataChannel object
+ * @title: GstWebRTCDataChannel
+ *
+ * <https://www.w3.org/TR/webrtc/#rtcdatachannel>
+ *
+ * Since: 1.18
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "datachannel.h"
+
+#define GST_CAT_DEFAULT gst_webrtc_data_channel_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define gst_webrtc_data_channel_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCDataChannel, gst_webrtc_data_channel,
+    G_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_webrtc_data_channel_debug,
+        "webrtcdatachannel", 0, "webrtcdatachannel"););
+
+enum
+{
+  SIGNAL_0,
+  SIGNAL_ON_OPEN,
+  SIGNAL_ON_CLOSE,
+  SIGNAL_ON_ERROR,
+  SIGNAL_ON_MESSAGE_DATA,
+  SIGNAL_ON_MESSAGE_STRING,
+  SIGNAL_ON_BUFFERED_AMOUNT_LOW,
+  SIGNAL_SEND_DATA,
+  SIGNAL_SEND_STRING,
+  SIGNAL_CLOSE,
+  LAST_SIGNAL,
+};
+
+enum
+{
+  PROP_0,
+  PROP_LABEL,
+  PROP_ORDERED,
+  PROP_MAX_PACKET_LIFETIME,
+  PROP_MAX_RETRANSMITS,
+  PROP_PROTOCOL,
+  PROP_NEGOTIATED,
+  PROP_ID,
+  PROP_PRIORITY,
+  PROP_READY_STATE,
+  PROP_BUFFERED_AMOUNT,
+  PROP_BUFFERED_AMOUNT_LOW_THRESHOLD,
+};
+
+static guint gst_webrtc_data_channel_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gst_webrtc_data_channel_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
+
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  switch (prop_id) {
+    case PROP_LABEL:
+      g_free (channel->label);
+      channel->label = g_value_dup_string (value);
+      break;
+    case PROP_ORDERED:
+      channel->ordered = g_value_get_boolean (value);
+      break;
+    case PROP_MAX_PACKET_LIFETIME:
+      channel->max_packet_lifetime = g_value_get_int (value);
+      break;
+    case PROP_MAX_RETRANSMITS:
+      channel->max_retransmits = g_value_get_int (value);
+      break;
+    case PROP_PROTOCOL:
+      g_free (channel->protocol);
+      channel->protocol = g_value_dup_string (value);
+      break;
+    case PROP_NEGOTIATED:
+      channel->negotiated = g_value_get_boolean (value);
+      break;
+    case PROP_ID:
+      channel->id = g_value_get_int (value);
+      break;
+    case PROP_PRIORITY:
+      channel->priority = g_value_get_enum (value);
+      break;
+    case PROP_BUFFERED_AMOUNT_LOW_THRESHOLD:
+      channel->buffered_amount_low_threshold = g_value_get_uint64 (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+}
+
+static void
+gst_webrtc_data_channel_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
+
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  switch (prop_id) {
+    case PROP_LABEL:
+      g_value_set_string (value, channel->label);
+      break;
+    case PROP_ORDERED:
+      g_value_set_boolean (value, channel->ordered);
+      break;
+    case PROP_MAX_PACKET_LIFETIME:
+      g_value_set_int (value, channel->max_packet_lifetime);
+      break;
+    case PROP_MAX_RETRANSMITS:
+      g_value_set_int (value, channel->max_retransmits);
+      break;
+    case PROP_PROTOCOL:
+      g_value_set_string (value, channel->protocol);
+      break;
+    case PROP_NEGOTIATED:
+      g_value_set_boolean (value, channel->negotiated);
+      break;
+    case PROP_ID:
+      g_value_set_int (value, channel->id);
+      break;
+    case PROP_PRIORITY:
+      g_value_set_enum (value, channel->priority);
+      break;
+    case PROP_READY_STATE:
+      g_value_set_enum (value, channel->ready_state);
+      break;
+    case PROP_BUFFERED_AMOUNT:
+      g_value_set_uint64 (value, channel->buffered_amount);
+      break;
+    case PROP_BUFFERED_AMOUNT_LOW_THRESHOLD:
+      g_value_set_uint64 (value, channel->buffered_amount_low_threshold);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+}
+
+static void
+gst_webrtc_data_channel_finalize (GObject * object)
+{
+  GstWebRTCDataChannel *channel = GST_WEBRTC_DATA_CHANNEL (object);
+
+  g_free (channel->label);
+  channel->label = NULL;
+
+  g_free (channel->protocol);
+  channel->protocol = NULL;
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_webrtc_data_channel_class_init (GstWebRTCDataChannelClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->get_property = gst_webrtc_data_channel_get_property;
+  gobject_class->set_property = gst_webrtc_data_channel_set_property;
+  gobject_class->finalize = gst_webrtc_data_channel_finalize;
+
+  g_object_class_install_property (gobject_class,
+      PROP_LABEL,
+      g_param_spec_string ("label",
+          "Label", "Data channel label",
+          NULL,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_ORDERED,
+      g_param_spec_boolean ("ordered",
+          "Ordered", "Using ordered transmission mode",
+          FALSE,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_MAX_PACKET_LIFETIME,
+      g_param_spec_int ("max-packet-lifetime",
+          "Maximum Packet Lifetime",
+          "Maximum number of milliseconds that transmissions and "
+          "retransmissions may occur in unreliable mode (-1 = unset)",
+          -1, G_MAXUINT16, -1,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_MAX_RETRANSMITS,
+      g_param_spec_int ("max-retransmits",
+          "Maximum Retransmits",
+          "Maximum number of retransmissions attempted in unreliable mode",
+          -1, G_MAXUINT16, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_PROTOCOL,
+      g_param_spec_string ("protocol",
+          "Protocol", "Data channel protocol",
+          "",
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_NEGOTIATED,
+      g_param_spec_boolean ("negotiated",
+          "Negotiated",
+          "Whether this data channel was negotiated by the application", FALSE,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_ID,
+      g_param_spec_int ("id",
+          "ID",
+          "ID negotiated by this data channel (-1 = unset)",
+          -1, G_MAXUINT16, -1,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_PRIORITY,
+      g_param_spec_enum ("priority",
+          "Priority",
+          "The priority of data sent using this data channel",
+          GST_TYPE_WEBRTC_PRIORITY_TYPE,
+          GST_WEBRTC_PRIORITY_TYPE_LOW,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_READY_STATE,
+      g_param_spec_enum ("ready-state",
+          "Ready State",
+          "The Ready state of this data channel",
+          GST_TYPE_WEBRTC_DATA_CHANNEL_STATE,
+          GST_WEBRTC_DATA_CHANNEL_STATE_NEW,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_BUFFERED_AMOUNT,
+      g_param_spec_uint64 ("buffered-amount",
+          "Buffered Amount",
+          "The amount of data in bytes currently buffered",
+          0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_BUFFERED_AMOUNT_LOW_THRESHOLD,
+      g_param_spec_uint64 ("buffered-amount-low-threshold",
+          "Buffered Amount Low Threshold",
+          "The threshold at which the buffered amount is considered low and "
+          "the buffered-amount-low signal is emitted",
+          0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstWebRTCDataChannel::on-open:
+   * @object: the #GstWebRTCDataChannel
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_OPEN] =
+      g_signal_new ("on-open", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * GstWebRTCDataChannel::on-close:
+   * @object: the #GstWebRTCDataChannel
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_CLOSE] =
+      g_signal_new ("on-close", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * GstWebRTCDataChannel::on-error:
+   * @object: the #GstWebRTCDataChannel
+   * @error: the #GError thrown
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_ERROR] =
+      g_signal_new ("on-error", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR);
+
+  /**
+   * GstWebRTCDataChannel::on-message-data:
+   * @object: the #GstWebRTCDataChannel
+   * @data: (nullable): a #GBytes of the data received
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_DATA] =
+      g_signal_new ("on-message-data", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BYTES);
+
+  /**
+   * GstWebRTCDataChannel::on-message-string:
+   * @object: the #GstWebRTCDataChannel
+   * @data: (nullable): the data received as a string
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_STRING] =
+      g_signal_new ("on-message-string", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+  /**
+   * GstWebRTCDataChannel::on-buffered-amount-low:
+   * @object: the #GstWebRTCDataChannel
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_ON_BUFFERED_AMOUNT_LOW] =
+      g_signal_new ("on-buffered-amount-low", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * GstWebRTCDataChannel::send-data:
+   * @object: the #GstWebRTCDataChannel
+   * @data: (nullable): a #GBytes with the data
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_SEND_DATA] =
+      g_signal_new_class_handler ("send-data", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_webrtc_data_channel_send_data), NULL, NULL, NULL,
+      G_TYPE_NONE, 1, G_TYPE_BYTES);
+
+  /**
+   * GstWebRTCDataChannel::send-string:
+   * @object: the #GstWebRTCDataChannel
+   * @data: (nullable): the data to send as a string
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_SEND_STRING] =
+      g_signal_new_class_handler ("send-string", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_webrtc_data_channel_send_string), NULL, NULL, NULL,
+      G_TYPE_NONE, 1, G_TYPE_STRING);
+
+  /**
+   * GstWebRTCDataChannel::close:
+   * @object: the #GstWebRTCDataChannel
+   *
+   * Close the data channel
+   */
+  gst_webrtc_data_channel_signals[SIGNAL_CLOSE] =
+      g_signal_new_class_handler ("close", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_CALLBACK (gst_webrtc_data_channel_close), NULL, NULL, NULL,
+      G_TYPE_NONE, 0);
+}
+
+static void
+gst_webrtc_data_channel_init (GstWebRTCDataChannel * channel)
+{
+  g_mutex_init (&channel->lock);
+}
+
+/**
+ * gst_webrtc_data_channel_on_open:
+ * @channel: a #GstWebRTCDataChannel
+ *
+ * Signal that the data channel was opened. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_open (GstWebRTCDataChannel * channel)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING ||
+      channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED) {
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+    return;
+  }
+
+  if (channel->ready_state != GST_WEBRTC_DATA_CHANNEL_STATE_OPEN) {
+    channel->ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_OPEN;
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+    g_object_notify (G_OBJECT (channel), "ready-state");
+
+    GST_INFO_OBJECT (channel, "We are open and ready for data!");
+  } else {
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+  }
+
+  GST_INFO_OBJECT (channel, "Opened");
+
+  g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_OPEN], 0,
+      NULL);
+}
+
+/**
+ * gst_webrtc_data_channel_on_close:
+ * @channel: a #GstWebRTCDataChannel
+ *
+ * Signal that the data channel was closed. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_close (GstWebRTCDataChannel * channel)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  GST_INFO_OBJECT (channel, "Closed");
+
+  GST_WEBRTC_DATA_CHANNEL_LOCK (channel);
+  if (channel->ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED) {
+    GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+    return;
+  }
+
+  channel->ready_state = GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED;
+  GST_WEBRTC_DATA_CHANNEL_UNLOCK (channel);
+
+  g_object_notify (G_OBJECT (channel), "ready-state");
+  GST_INFO_OBJECT (channel, "We are closed for data");
+
+  g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_CLOSE], 0,
+      NULL);
+}
+
+/**
+ * gst_webrtc_data_channel_on_error:
+ * @channel: a #GstWebRTCDataChannel
+ * @error: (transfer full): a #GError
+ *
+ * Signal that the data channel had an error. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_error (GstWebRTCDataChannel * channel,
+    GError * error)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+  g_return_if_fail (error != NULL);
+
+  GST_WARNING_OBJECT (channel, "Error: %s", GST_STR_NULL (error->message));
+
+  g_signal_emit (channel, gst_webrtc_data_channel_signals[SIGNAL_ON_ERROR], 0,
+      error);
+}
+
+/**
+ * gst_webrtc_data_channel_on_message_data:
+ * @channel: a #GstWebRTCDataChannel
+ * @data: (nullable): a #GBytes or %NULL
+ *
+ * Signal that the data channel received a data message. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_message_data (GstWebRTCDataChannel * channel,
+    GBytes * data)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  GST_LOG_OBJECT (channel, "Have data %p", data);
+  g_signal_emit (channel,
+      gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_DATA], 0, data);
+}
+
+/**
+ * gst_webrtc_data_channel_on_message_string:
+ * @channel: a #GstWebRTCDataChannel
+ * @str: (nullable): a string or %NULL
+ *
+ * Signal that the data channel received a string message. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_message_string (GstWebRTCDataChannel * channel,
+    const gchar * str)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  GST_LOG_OBJECT (channel, "Have string %p", str);
+  g_signal_emit (channel,
+      gst_webrtc_data_channel_signals[SIGNAL_ON_MESSAGE_STRING], 0, str);
+}
+
+/**
+ * gst_webrtc_data_channel_on_buffered_amount_low:
+ * @channel: a #GstWebRTCDataChannel
+ *
+ * Signal that the data channel reached a low buffered amount. Should only be used by subclasses.
+ */
+void
+gst_webrtc_data_channel_on_buffered_amount_low (GstWebRTCDataChannel * channel)
+{
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  GST_LOG_OBJECT (channel, "Low threshold reached");
+  g_signal_emit (channel,
+      gst_webrtc_data_channel_signals[SIGNAL_ON_BUFFERED_AMOUNT_LOW], 0);
+}
+
+/**
+ * gst_webrtc_data_channel_send_data:
+ * @channel: a #GstWebRTCDataChannel
+ * @data: (nullable): a #GBytes or %NULL
+ *
+ * Send @data as a data message over @channel.
+ */
+void
+gst_webrtc_data_channel_send_data (GstWebRTCDataChannel * channel,
+    GBytes * data)
+{
+  GstWebRTCDataChannelClass *klass;
+
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  klass = GST_WEBRTC_DATA_CHANNEL_GET_CLASS (channel);
+  klass->send_data (channel, data);
+}
+
+/**
+ * gst_webrtc_data_channel_send_string:
+ * @channel: a #GstWebRTCDataChannel
+ * @str: (nullable): a string or %NULL
+ *
+ * Send @str as a string message over @channel.
+ */
+void
+gst_webrtc_data_channel_send_string (GstWebRTCDataChannel * channel,
+    const gchar * str)
+{
+  GstWebRTCDataChannelClass *klass;
+
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  klass = GST_WEBRTC_DATA_CHANNEL_GET_CLASS (channel);
+  klass->send_string (channel, str);
+}
+
+/**
+ * gst_webrtc_data_channel_close:
+ * @channel: a #GstWebRTCDataChannel
+ *
+ * Close the @channel.
+ */
+void
+gst_webrtc_data_channel_close (GstWebRTCDataChannel * channel)
+{
+  GstWebRTCDataChannelClass *klass;
+
+  g_return_if_fail (GST_IS_WEBRTC_DATA_CHANNEL (channel));
+
+  klass = GST_WEBRTC_DATA_CHANNEL_GET_CLASS (channel);
+  klass->close (channel);
+}
diff --git a/gst-libs/gst/webrtc/datachannel.h b/gst-libs/gst/webrtc/datachannel.h
new file mode 100644 (file)
index 0000000..79b536f
--- /dev/null
@@ -0,0 +1,115 @@
+/* GStreamer
+ * Copyright (C) 2018 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_DATA_CHANNEL_H__
+#define __GST_WEBRTC_DATA_CHANNEL_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+
+G_BEGIN_DECLS
+
+GST_WEBRTC_API
+GType gst_webrtc_data_channel_get_type(void);
+
+#define GST_TYPE_WEBRTC_DATA_CHANNEL            (gst_webrtc_data_channel_get_type())
+#define GST_WEBRTC_DATA_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannel))
+#define GST_IS_WEBRTC_DATA_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_DATA_CHANNEL))
+#define GST_WEBRTC_DATA_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannelClass))
+#define GST_IS_WEBRTC_DATA_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_DATA_CHANNEL))
+#define GST_WEBRTC_DATA_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_DATA_CHANNEL,GstWebRTCDataChannelClass))
+
+#define GST_WEBRTC_DATA_CHANNEL_LOCK(channel) g_mutex_lock(&((GstWebRTCDataChannel *)(channel))->lock)
+#define GST_WEBRTC_DATA_CHANNEL_UNLOCK(channel) g_mutex_unlock(&((GstWebRTCDataChannel *)(channel))->lock)
+
+/**
+ * GstWebRTCDataChannel:
+ *
+ * Since: 1.18
+ */
+struct _GstWebRTCDataChannel
+{
+  GObject                           parent;
+
+  GMutex                            lock;
+
+  gchar                            *label;
+  gboolean                          ordered;
+  guint                             max_packet_lifetime;
+  guint                             max_retransmits;
+  gchar                            *protocol;
+  gboolean                          negotiated;
+  gint                              id;
+  GstWebRTCPriorityType             priority;
+  GstWebRTCDataChannelState         ready_state;
+  guint64                           buffered_amount;
+  guint64                           buffered_amount_low_threshold;
+
+  gpointer                         _padding[GST_PADDING];
+};
+
+/**
+ * GstWebRTCDataChannelClass:
+ *
+ * Since: 1.18
+ */
+struct _GstWebRTCDataChannelClass
+{
+  GObjectClass        parent_class;
+
+  void              (*send_data)   (GstWebRTCDataChannel * channel, GBytes *data);
+  void              (*send_string) (GstWebRTCDataChannel * channel, const gchar *str);
+  void              (*close)       (GstWebRTCDataChannel * channel);
+
+  gpointer           _padding[GST_PADDING];
+};
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_open (GstWebRTCDataChannel * channel);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_close (GstWebRTCDataChannel * channel);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_error (GstWebRTCDataChannel * channel, GError * error);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_message_data (GstWebRTCDataChannel * channel, GBytes * data);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_message_string (GstWebRTCDataChannel * channel, const gchar * str);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_on_buffered_amount_low (GstWebRTCDataChannel * channel);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_send_data (GstWebRTCDataChannel * channel, GBytes * data);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_send_string (GstWebRTCDataChannel * channel, const gchar * str);
+
+GST_WEBRTC_API
+void gst_webrtc_data_channel_close (GstWebRTCDataChannel * channel);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCDataChannel, g_object_unref)
+
+G_END_DECLS
+
+#endif /* __GST_WEBRTC_DATA_CHANNEL_H__ */
index c3b2d51..2c7135b 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCDTLSTransport
  * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCICETransport
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcdtlstransport">https://www.w3.org/TR/webrtc/#rtcdtlstransport</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcdtlstransport>
  */
 
 #ifdef HAVE_CONFIG_H
@@ -146,6 +146,36 @@ gst_webrtc_dtls_transport_finalize (GObject * object)
 }
 
 static void
+on_connection_state_changed (GObject * obj, GParamSpec * pspec,
+    gpointer user_data)
+{
+  GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (user_data);
+  gint state;
+
+  g_object_get (obj, "connection-state", &state, NULL);
+  switch (state) {
+    case 0:
+      webrtc->state = GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW;
+      break;
+    case 1:
+      webrtc->state = GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED;
+      break;
+    default:
+    case 2:
+      webrtc->state = GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED;
+      break;
+    case 3:
+      webrtc->state = GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING;
+      break;
+    case 4:
+      webrtc->state = GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED;
+      break;
+  }
+
+  g_object_notify (G_OBJECT (webrtc), "state");
+}
+
+static void
 gst_webrtc_dtls_transport_constructed (GObject * object)
 {
   GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (object);
@@ -159,12 +189,15 @@ gst_webrtc_dtls_transport_constructed (GObject * object)
 
   webrtc->dtlssrtpenc = gst_element_factory_make ("dtlssrtpenc", NULL);
   g_object_set (webrtc->dtlssrtpenc, "connection-id", connection_id,
-      "is-client", webrtc->client, NULL);
+      "is-client", webrtc->client, "rtp-sync", TRUE, NULL);
 
   webrtc->dtlssrtpdec = gst_element_factory_make ("dtlssrtpdec", NULL);
   g_object_set (webrtc->dtlssrtpdec, "connection-id", connection_id, NULL);
   g_free (connection_id);
 
+  g_signal_connect (webrtc->dtlssrtpenc, "notify::connection-state",
+      G_CALLBACK (on_connection_state_changed), webrtc);
+
   G_OBJECT_CLASS (parent_class)->constructed (object);
 }
 
@@ -191,7 +224,6 @@ gst_webrtc_dtls_transport_class_init (GstWebRTCDTLSTransportClass * klass)
           GST_TYPE_WEBRTC_ICE_TRANSPORT,
           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
-  /* FIXME: implement */
   g_object_class_install_property (gobject_class,
       PROP_STATE,
       g_param_spec_enum ("state", "DTLS state",
index 2af1975..feb3944 100644 (file)
@@ -35,6 +35,9 @@ GType gst_webrtc_dtls_transport_get_type(void);
 #define GST_IS_WEBRTC_DTLS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_DTLS_TRANSPORT))
 #define GST_WEBRTC_DTLS_TRANSPORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_DTLS_TRANSPORT,GstWebRTCDTLSTransportClass))
 
+/**
+ * GstWebRTCDTLSTransport:
+ */
 struct _GstWebRTCDTLSTransport
 {
   GstObject                          parent;
@@ -65,9 +68,7 @@ GST_WEBRTC_API
 void                        gst_webrtc_dtls_transport_set_transport     (GstWebRTCDTLSTransport * transport,
                                                                          GstWebRTCICETransport * ice);
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCDTLSTransport, gst_object_unref)
-#endif
 
 G_END_DECLS
 
index e6f4437..21e2cbe 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCICETransport
  * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCDTLSTransport
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcicetransport">https://www.w3.org/TR/webrtc/#rtcicetransport</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcicetransport>
  */
 
 #ifdef HAVE_CONFIG_H
@@ -189,8 +189,8 @@ gst_webrtc_ice_transport_class_init (GstWebRTCICETransportClass * klass)
    */
   gst_webrtc_ice_transport_signals[ON_SELECTED_CANDIDATE_PAIR_CHANGE_SIGNAL] =
       g_signal_new ("on-selected-candidate-pair-change",
-      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 0);
+      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+      G_TYPE_NONE, 0);
 
   /**
    * GstWebRTC::on-new-candidate:
@@ -198,8 +198,8 @@ gst_webrtc_ice_transport_class_init (GstWebRTCICETransportClass * klass)
    */
   gst_webrtc_ice_transport_signals[ON_NEW_CANDIDATE_SIGNAL] =
       g_signal_new ("on-new-candidate",
-      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_STRING);
+      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+      G_TYPE_NONE, 1, G_TYPE_STRING);
 }
 
 static void
index d18c44f..c1e56d4 100644 (file)
@@ -34,6 +34,9 @@ GType gst_webrtc_ice_transport_get_type(void);
 #define GST_IS_WEBRTC_ICE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_ICE_TRANSPORT))
 #define GST_WEBRTC_ICE_TRANSPORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_ICE_TRANSPORT,GstWebRTCICETransportClass))
 
+/**
+ * GstWebRTCICETransport:
+ */
 struct _GstWebRTCICETransport
 {
   GstObject                          parent;
@@ -71,9 +74,7 @@ void            gst_webrtc_ice_transport_selected_pair_change       (GstWebRTCIC
 GST_WEBRTC_API
 void            gst_webrtc_ice_transport_new_candidate              (GstWebRTCICETransport * ice, guint stream_id, GstWebRTCICEComponent component, gchar * attr);
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCICETransport, gst_object_unref)
-#endif
 
 G_END_DECLS
 
index a9f11dc..981083c 100644 (file)
@@ -5,6 +5,7 @@ webrtc_sources = [
   'rtpreceiver.c',
   'rtpsender.c',
   'rtptransceiver.c',
+  'datachannel.c',
 ]
 
 webrtc_headers = [
@@ -14,6 +15,7 @@ webrtc_headers = [
   'rtpreceiver.h',
   'rtpsender.h',
   'rtptransceiver.h',
+  'datachannel.h',
   'webrtc_fwd.h',
   'webrtc.h',
 ]
index af5cd1c..abdf5ca 100644 (file)
@@ -22,7 +22,7 @@
  * @short_description: RTCSessionDescription object
  * @title: GstWebRTCSessionDescription
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcsessiondescription-class">https://www.w3.org/TR/webrtc/#rtcsessiondescription-class</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcsessiondescription-class>
  */
 
 #ifdef HAVE_CONFIG_H
index ae6863b..5308c54 100644 (file)
@@ -38,7 +38,7 @@ GType gst_webrtc_session_description_get_type (void);
  * @type: the #GstWebRTCSDPType of the description
  * @sdp: the #GstSDPMessage of the description
  *
- * See <ulink url="https://www.w3.org/TR/webrtc/#rtcsessiondescription-class">https://www.w3.org/TR/webrtc/#rtcsessiondescription-class</ulink>
+ * See <https://www.w3.org/TR/webrtc/#rtcsessiondescription-class>
  */
 struct _GstWebRTCSessionDescription
 {
@@ -54,9 +54,7 @@ GST_WEBRTC_API
 void                                gst_webrtc_session_description_free     (GstWebRTCSessionDescription * desc);
 
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCSessionDescription, gst_webrtc_session_description_free)
-#endif
 
 G_END_DECLS
 
index f21d77e..768e987 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCRTPReceiver
  * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPTransceiver
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface">https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface>
  */
 
 #ifdef HAVE_CONFIG_H
index 9502c5c..55a9a86 100644 (file)
@@ -35,6 +35,9 @@ GType gst_webrtc_rtp_receiver_get_type(void);
 #define GST_IS_WEBRTC_RTP_RECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_RECEIVER))
 #define GST_WEBRTC_RTP_RECEIVER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_RECEIVER,GstWebRTCRTPReceiverClass))
 
+/**
+ * GstWebRTCRTPReceiver:
+ */
 struct _GstWebRTCRTPReceiver
 {
   GstObject                          parent;
@@ -62,9 +65,7 @@ GST_WEBRTC_API
 void                        gst_webrtc_rtp_receiver_set_rtcp_transport  (GstWebRTCRTPReceiver * receiver,
                                                                          GstWebRTCDTLSTransport * transport);
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCRTPReceiver, gst_object_unref)
-#endif
 
 G_END_DECLS
 
index da743f3..3a8a904 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCRTPSender
  * @see_also: #GstWebRTCRTPReceiver, #GstWebRTCRTPTransceiver
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtpsender-interface">https://www.w3.org/TR/webrtc/#rtcrtpsender-interface</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcrtpsender-interface>
  */
 
 #ifdef HAVE_CONFIG_H
index a23551e..bcaf93c 100644 (file)
@@ -35,6 +35,9 @@ GType gst_webrtc_rtp_sender_get_type(void);
 #define GST_IS_WEBRTC_RTP_SENDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_SENDER))
 #define GST_WEBRTC_RTP_SENDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_SENDER,GstWebRTCRTPSenderClass))
 
+/**
+ * GstWebRTCRTPSender:
+ */
 struct _GstWebRTCRTPSender
 {
   GstObject                          parent;
@@ -66,9 +69,7 @@ void                        gst_webrtc_rtp_sender_set_rtcp_transport    (GstWebR
                                                                          GstWebRTCDTLSTransport * transport);
 
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCRTPSender, gst_object_unref)
-#endif
 
 G_END_DECLS
 
index d0d9628..0801946 100644 (file)
@@ -23,7 +23,7 @@
  * @title: GstWebRTCRTPTransceiver
  * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver
  *
- * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface">https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface</ulink>
+ * <https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface>
  */
 
 #ifdef HAVE_CONFIG_H
@@ -39,7 +39,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCRTPTransceiver,
     gst_webrtc_rtp_transceiver, GST_TYPE_OBJECT,
     GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_transceiver_debug,
-        "webrtctransceiver", 0, "webrtctransceiver");
+        "webrtcrtptransceiver", 0, "webrtcrtptransceiver");
     );
 
 enum
@@ -54,9 +54,9 @@ enum
   PROP_MID,
   PROP_SENDER,
   PROP_RECEIVER,
-  PROP_STOPPED,                 // FIXME
-  PROP_DIRECTION,               // FIXME
+  PROP_DIRECTION,
   PROP_MLINE,
+  PROP_STOPPED,                 // FIXME
 };
 
 //static guint gst_webrtc_rtp_transceiver_signals[LAST_SIGNAL] = { 0 };
@@ -77,6 +77,9 @@ gst_webrtc_rtp_transceiver_set_property (GObject * object, guint prop_id,
     case PROP_MLINE:
       webrtc->mline = g_value_get_uint (value);
       break;
+    case PROP_DIRECTION:
+      webrtc->direction = g_value_get_enum (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -99,6 +102,9 @@ gst_webrtc_rtp_transceiver_get_property (GObject * object, guint prop_id,
     case PROP_MLINE:
       g_value_set_uint (value, webrtc->mline);
       break;
+    case PROP_DIRECTION:
+      g_value_set_enum (value, webrtc->direction);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -178,9 +184,25 @@ gst_webrtc_rtp_transceiver_class_init (GstWebRTCRTPTransceiverClass * klass)
           "Index in the SDP of the Media",
           0, G_MAXUINT, 0,
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstWebRTCRTPTransceiver:direction:
+   *
+   * Direction of the transceiver.
+   *
+   * Since: 1.18
+   **/
+  g_object_class_install_property (gobject_class,
+      PROP_DIRECTION,
+      g_param_spec_enum ("direction", "Direction",
+          "Transceiver direction",
+          GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+          GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
 gst_webrtc_rtp_transceiver_init (GstWebRTCRTPTransceiver * webrtc)
 {
+  webrtc->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
 }
index 3c7c92a..4b2e6e3 100644 (file)
@@ -36,6 +36,9 @@ GType gst_webrtc_rtp_transceiver_get_type(void);
 #define GST_IS_WEBRTC_RTP_TRANSCEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_TRANSCEIVER))
 #define GST_WEBRTC_RTP_TRANSCEIVER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_TRANSCEIVER,GstWebRTCRTPTransceiverClass))
 
+/**
+ * GstWebRTCRTPTransceiver:
+ */
 struct _GstWebRTCRTPTransceiver
 {
   GstObject                         parent;
@@ -58,12 +61,11 @@ struct _GstWebRTCRTPTransceiverClass
 {
   GstObjectClass        parent_class;
 
+  /* FIXME; reset */
   gpointer              _padding[GST_PADDING];
 };
 
-#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstWebRTCRTPTransceiver, gst_object_unref)
-#endif
 
 G_END_DECLS
 
index 354c15c..e68a9db 100644 (file)
@@ -29,5 +29,6 @@
 #include <gst/webrtc/rtpreceiver.h>
 #include <gst/webrtc/rtpsender.h>
 #include <gst/webrtc/rtptransceiver.h>
+#include <gst/webrtc/datachannel.h>
 
 #endif /* __GST_WEBRTC_WEBRTC_H__ */
index 0d00e2b..5c727d2 100644 (file)
 
 #include <gst/gst.h>
 
+/**
+ * SECTION:webrtc_fwd.h
+ * @title: GstWebRTC Enumerations
+ */
+
 #ifndef GST_WEBRTC_API
 # ifdef BUILDING_GST_WEBRTC
 #  define GST_WEBRTC_API GST_API_EXPORT         /* from config.h */
@@ -54,13 +59,16 @@ typedef struct _GstWebRTCSessionDescription GstWebRTCSessionDescription;
 typedef struct _GstWebRTCRTPTransceiver GstWebRTCRTPTransceiver;
 typedef struct _GstWebRTCRTPTransceiverClass GstWebRTCRTPTransceiverClass;
 
+typedef struct _GstWebRTCDataChannel GstWebRTCDataChannel;
+typedef struct _GstWebRTCDataChannelClass GstWebRTCDataChannelClass;
+
 /**
  * GstWebRTCDTLSTransportState:
- * GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW: new
- * GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED: closed
- * GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED: failed
- * GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING: connecting
- * GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED: connected
+ * @GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW: new
+ * @GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED: closed
+ * @GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED: failed
+ * @GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING: connecting
+ * @GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED: connected
  */
 typedef enum /*< underscore_name=gst_webrtc_dtls_transport_state >*/
 {
@@ -73,11 +81,11 @@ typedef enum /*< underscore_name=gst_webrtc_dtls_transport_state >*/
 
 /**
  * GstWebRTCICEGatheringState:
- * GST_WEBRTC_ICE_GATHERING_STATE_NEW: new
- * GST_WEBRTC_ICE_GATHERING_STATE_GATHERING: gathering
- * GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE: complete
+ * @GST_WEBRTC_ICE_GATHERING_STATE_NEW: new
+ * @GST_WEBRTC_ICE_GATHERING_STATE_GATHERING: gathering
+ * @GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE: complete
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate">http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate>
  */
 typedef enum /*< underscore_name=gst_webrtc_ice_gathering_state >*/
 {
@@ -88,15 +96,15 @@ typedef enum /*< underscore_name=gst_webrtc_ice_gathering_state >*/
 
 /**
  * GstWebRTCICEConnectionState:
- * GST_WEBRTC_ICE_CONNECTION_STATE_NEW: new
- * GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: checking
- * GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED: connected
- * GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED: completed
- * GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: failed
- * GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED: disconnected
- * GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED: closed
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_NEW: new
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: checking
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED: connected
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED: completed
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: failed
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED: disconnected
+ * @GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED: closed
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate">http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate>
  */
 typedef enum /*< underscore_name=gst_webrtc_ice_connection_state >*/
 {
@@ -111,14 +119,14 @@ typedef enum /*< underscore_name=gst_webrtc_ice_connection_state >*/
 
 /**
  * GstWebRTCSignalingState:
- * GST_WEBRTC_SIGNALING_STATE_STABLE: stable
- * GST_WEBRTC_SIGNALING_STATE_CLOSED: closed
- * GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER: have-local-offer
- * GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER: have-remote-offer
- * GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER: have-local-pranswer
- * GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER: have-remote-pranswer
+ * @GST_WEBRTC_SIGNALING_STATE_STABLE: stable
+ * @GST_WEBRTC_SIGNALING_STATE_CLOSED: closed
+ * @GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER: have-local-offer
+ * @GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER: have-remote-offer
+ * @GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER: have-local-pranswer
+ * @GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER: have-remote-pranswer
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate">http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate>
  */
 typedef enum /*< underscore_name=gst_webrtc_signaling_state >*/
 {
@@ -132,14 +140,14 @@ typedef enum /*< underscore_name=gst_webrtc_signaling_state >*/
 
 /**
  * GstWebRTCPeerConnectionState:
- * GST_WEBRTC_PEER_CONNECTION_STATE_NEW: new
- * GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING: connecting
- * GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED: connected
- * GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED: disconnected
- * GST_WEBRTC_PEER_CONNECTION_STATE_FAILED: failed
- * GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED: closed
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_NEW: new
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING: connecting
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED: connected
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED: disconnected
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_FAILED: failed
+ * @GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED: closed
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate">http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate>
  */
 typedef enum /*< underscore_name=gst_webrtc_peer_connection_state >*/
 {
@@ -153,8 +161,8 @@ typedef enum /*< underscore_name=gst_webrtc_peer_connection_state >*/
 
 /**
  * GstWebRTCICERole:
- * GST_WEBRTC_ICE_ROLE_CONTROLLED: controlled
- * GST_WEBRTC_ICE_ROLE_CONTROLLING: controlling
+ * @GST_WEBRTC_ICE_ROLE_CONTROLLED: controlled
+ * @GST_WEBRTC_ICE_ROLE_CONTROLLING: controlling
  */
 typedef enum /*< underscore_name=gst_webrtc_ice_role >*/
 {
@@ -164,8 +172,8 @@ typedef enum /*< underscore_name=gst_webrtc_ice_role >*/
 
 /**
  * GstWebRTCICEComponent:
- * GST_WEBRTC_ICE_COMPONENT_RTP,
- * GST_WEBRTC_ICE_COMPONENT_RTCP,
+ * @GST_WEBRTC_ICE_COMPONENT_RTP: RTP component
+ * @GST_WEBRTC_ICE_COMPONENT_RTCP: RTCP component
  */
 typedef enum /*< underscore_name=gst_webrtc_ice_component >*/
 {
@@ -175,12 +183,12 @@ typedef enum /*< underscore_name=gst_webrtc_ice_component >*/
 
 /**
  * GstWebRTCSDPType:
- * GST_WEBRTC_SDP_TYPE_OFFER: offer
- * GST_WEBRTC_SDP_TYPE_PRANSWER: pranswer
- * GST_WEBRTC_SDP_TYPE_ANSWER: answer
- * GST_WEBRTC_SDP_TYPE_ROLLBACK: rollback
+ * @GST_WEBRTC_SDP_TYPE_OFFER: offer
+ * @GST_WEBRTC_SDP_TYPE_PRANSWER: pranswer
+ * @GST_WEBRTC_SDP_TYPE_ANSWER: answer
+ * @GST_WEBRTC_SDP_TYPE_ROLLBACK: rollback
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#rtcsdptype">http://w3c.github.io/webrtc-pc/#rtcsdptype</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#rtcsdptype>
  */
 typedef enum /*< underscore_name=gst_webrtc_sdp_type >*/
 {
@@ -191,12 +199,12 @@ typedef enum /*< underscore_name=gst_webrtc_sdp_type >*/
 } GstWebRTCSDPType;
 
 /**
- * GstWebRTCRtpTransceiverDirection:
- * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE: none
- * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE: inactive
- * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY: sendonly
- * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY: recvonly
- * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV: sendrecv
+ * GstWebRTCRTPTransceiverDirection:
+ * @GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE: none
+ * @GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE: inactive
+ * @GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY: sendonly
+ * @GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY: recvonly
+ * @GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV: sendrecv
  */
 typedef enum /*< underscore_name=gst_webrtc_rtp_transceiver_direction >*/
 {
@@ -209,10 +217,10 @@ typedef enum /*< underscore_name=gst_webrtc_rtp_transceiver_direction >*/
 
 /**
  * GstWebRTCDTLSSetup:
- * GST_WEBRTC_DTLS_SETUP_NONE: none
- * GST_WEBRTC_DTLS_SETUP_ACTPASS: actpass
- * GST_WEBRTC_DTLS_SETUP_ACTIVE: sendonly
- * GST_WEBRTC_DTLS_SETUP_PASSIVE: recvonly
+ * @GST_WEBRTC_DTLS_SETUP_NONE: none
+ * @GST_WEBRTC_DTLS_SETUP_ACTPASS: actpass
+ * @GST_WEBRTC_DTLS_SETUP_ACTIVE: sendonly
+ * @GST_WEBRTC_DTLS_SETUP_PASSIVE: recvonly
  */
 typedef enum /*< underscore_name=gst_webrtc_dtls_setup >*/
 {
@@ -224,20 +232,20 @@ typedef enum /*< underscore_name=gst_webrtc_dtls_setup >*/
 
 /**
  * GstWebRTCStatsType:
- * GST_WEBRTC_STATS_CODEC: codec
- * GST_WEBRTC_STATS_INBOUND_RTP: inbound-rtp
- * GST_WEBRTC_STATS_OUTBOUND_RTP: outbound-rtp
- * GST_WEBRTC_STATS_REMOTE_INBOUND_RTP: remote-inbound-rtp
- * GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP: remote-outbound-rtp
- * GST_WEBRTC_STATS_CSRC: csrc
- * GST_WEBRTC_STATS_PEER_CONNECTION: peer-connectiion
- * GST_WEBRTC_STATS_DATA_CHANNEL: data-channel
- * GST_WEBRTC_STATS_STREAM: stream
- * GST_WEBRTC_STATS_TRANSPORT: transport
- * GST_WEBRTC_STATS_CANDIDATE_PAIR: candidate-pair
- * GST_WEBRTC_STATS_LOCAL_CANDIDATE: local-candidate
- * GST_WEBRTC_STATS_REMOTE_CANDIDATE: remote-candidate
- * GST_WEBRTC_STATS_CERTIFICATE: certificate
+ * @GST_WEBRTC_STATS_CODEC: codec
+ * @GST_WEBRTC_STATS_INBOUND_RTP: inbound-rtp
+ * @GST_WEBRTC_STATS_OUTBOUND_RTP: outbound-rtp
+ * @GST_WEBRTC_STATS_REMOTE_INBOUND_RTP: remote-inbound-rtp
+ * @GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP: remote-outbound-rtp
+ * @GST_WEBRTC_STATS_CSRC: csrc
+ * @GST_WEBRTC_STATS_PEER_CONNECTION: peer-connectiion
+ * @GST_WEBRTC_STATS_DATA_CHANNEL: data-channel
+ * @GST_WEBRTC_STATS_STREAM: stream
+ * @GST_WEBRTC_STATS_TRANSPORT: transport
+ * @GST_WEBRTC_STATS_CANDIDATE_PAIR: candidate-pair
+ * @GST_WEBRTC_STATS_LOCAL_CANDIDATE: local-candidate
+ * @GST_WEBRTC_STATS_REMOTE_CANDIDATE: remote-candidate
+ * @GST_WEBRTC_STATS_CERTIFICATE: certificate
  */
 typedef enum /*< underscore_name=gst_webrtc_stats_type >*/
 {
@@ -277,7 +285,7 @@ typedef enum /*< underscore_name=gst_webrtc_fec_type >*/
  * GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTED: connected
  * GST_WEBRTC_SCTP_TRANSPORT_STATE_CLOSED: closed
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcsctptransportstate">http://w3c.github.io/webrtc-pc/#dom-rtcsctptransportstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcsctptransportstate>
  *
  * Since: 1.16
  */
@@ -296,7 +304,7 @@ typedef enum /*< underscore_name=gst_webrtc_sctp_transport_state >*/
  * GST_WEBRTC_PRIORITY_TYPE_MEDIUM: medium
  * GST_WEBRTC_PRIORITY_TYPE_HIGH: high
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcprioritytype">http://w3c.github.io/webrtc-pc/#dom-rtcprioritytype</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcprioritytype>
  *
  * Since: 1.16
  */
@@ -316,7 +324,7 @@ typedef enum /*< underscore_name=gst_webrtc_priority_type >*/
  * GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING: closing
  * GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED: closed
  *
- * See <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcdatachannelstate">http://w3c.github.io/webrtc-pc/#dom-rtcdatachannelstate</ulink>
+ * See <http://w3c.github.io/webrtc-pc/#dom-rtcdatachannelstate>
  *
  * Since: 1.16
  */
index 035ff02..f4ae56a 100644 (file)
@@ -87,6 +87,8 @@ export CFLAGS+=" -Wall -g -fPIC\
   -DTIZEN_FEATURE_H264PARSE_MODIFICATION\
   -DTIZEN_FEATURE_AD\
   -DTIZEN_FEATURE_HLSDEMUX_LANG_TAG\
+  -DTIZEN_FEATURE_UPSTREAM\
+  -DTIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK\
   -D__TIZEN__\
   -fstack-protector-strong\
   -Wl,-z,relro\