rtsp-media: Unblock all streams
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-media.c
index 2483504..345559d 100644 (file)
@@ -50,7 +50,8 @@
  * #GstRTSPSession and #GstRTSPSessionMedia.
  *
  * The state of the media can be controlled with gst_rtsp_media_set_state ().
- * Seeking can be done with gst_rtsp_media_seek().
+ * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full()
+ * or gst_rtsp_media_seek_trickmode() for finer control of the seek.
  *
  * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When
  * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to
@@ -62,6 +63,9 @@
  *
  * Last reviewed on 2013-07-11 (1.0.0)
  */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
 
 #include <stdio.h>
 #include <string.h>
@@ -81,9 +85,6 @@
 
 #include "rtsp-media.h"
 
-#define GST_RTSP_MEDIA_GET_PRIVATE(obj)  \
-     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaPrivate))
-
 struct _GstRTSPMediaPrivate
 {
   GMutex lock;
@@ -100,8 +101,12 @@ struct _GstRTSPMediaPrivate
   gboolean eos_shutdown;
   guint buffer_size;
   GstRTSPAddressPool *pool;
+  gchar *multicast_iface;
+  guint max_mcast_ttl;
+  gboolean bind_mcast_address;
   gboolean blocked;
   GstRTSPTransportMode transport_mode;
+  gboolean stop_on_disconnect;
 
   GstElement *element;
   GRecMutex state_lock;         /* locking order: state lock, lock */
@@ -110,20 +115,21 @@ struct _GstRTSPMediaPrivate
   GstRTSPMediaStatus status;    /* protected by lock */
   gint prepare_count;
   gint n_active;
-  gboolean adding;
+  gboolean complete;
+  gboolean finishing_unprepare;
 
   /* the pipeline for the media */
   GstElement *pipeline;
-  GstElement *fakesink;         /* protected by lock */
   GSource *source;
   guint id;
   GstRTSPThread *thread;
+  GList *pending_pipeline_elements;
 
   gboolean time_provider;
   GstNetTimeProvider *nettime;
 
   gboolean is_live;
-  gboolean seekable;
+  GstClockTimeDiff seekable;
   gboolean buffering;
   GstState target_state;
 
@@ -137,7 +143,16 @@ struct _GstRTSPMediaPrivate
 
   GList *payloads;              /* protected by lock */
   GstClockTime rtx_time;        /* protected by lock */
+  gboolean do_retransmission;   /* protected by lock */
   guint latency;                /* protected by lock */
+  GstClock *clock;              /* protected by lock */
+  gboolean do_rate_control;     /* protected by lock */
+  GstRTSPPublishClockMode publish_clock_mode;
+
+  /* Dynamic element handling */
+  guint nb_dynamic_elements;
+  guint no_more_pads_pending;
+  gboolean expected_async_done;
 };
 
 #define DEFAULT_SHARED          FALSE
@@ -151,6 +166,12 @@ struct _GstRTSPMediaPrivate
 #define DEFAULT_TIME_PROVIDER   FALSE
 #define DEFAULT_LATENCY         200
 #define DEFAULT_TRANSPORT_MODE  GST_RTSP_TRANSPORT_MODE_PLAY
+#define DEFAULT_STOP_ON_DISCONNECT TRUE
+#define DEFAULT_MAX_MCAST_TTL   255
+#define DEFAULT_BIND_MCAST_ADDRESS FALSE
+#define DEFAULT_DO_RATE_CONTROL TRUE
+
+#define DEFAULT_DO_RETRANSMISSION FALSE
 
 /* define to dump received RTCP packets */
 #undef DUMP_STATS
@@ -169,6 +190,10 @@ enum
   PROP_TIME_PROVIDER,
   PROP_LATENCY,
   PROP_TRANSPORT_MODE,
+  PROP_STOP_ON_DISCONNECT,
+  PROP_CLOCK,
+  PROP_MAX_MCAST_TTL,
+  PROP_BIND_MCAST_ADDRESS,
   PROP_LAST
 };
 
@@ -211,12 +236,16 @@ static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
 
 static gboolean wait_preroll (GstRTSPMedia * media);
 
-static GstElement *find_payload_element (GstElement * payloader);
+static GstElement *find_payload_element (GstElement * payloader, GstPad * pad);
 
 static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 };
 
+static gboolean check_complete (GstRTSPMedia * media);
+
 #define C_ENUM(v) ((gint) v)
 
+#define TRICKMODE_FLAGS (GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED)
+
 GType
 gst_rtsp_suspend_mode_get_type (void)
 {
@@ -258,15 +287,36 @@ gst_rtsp_transport_mode_get_type (void)
   return (GType) id;
 }
 
-G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
+GType
+gst_rtsp_publish_clock_mode_get_type (void)
+{
+  static gsize id = 0;
+  static const GEnumValue values[] = {
+    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE),
+        "GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"},
+    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK),
+          "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK",
+        "clock"},
+    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET),
+          "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET",
+        "clock-and-offset"},
+    {0, NULL, NULL}
+  };
+
+  if (g_once_init_enter (&id)) {
+    GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values);
+    g_once_init_leave (&id, tmp);
+  }
+  return (GType) id;
+}
+
+G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
 
 static void
 gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
 {
   GObjectClass *gobject_class;
 
-  g_type_class_add_private (klass, sizeof (GstRTSPMediaPrivate));
-
   gobject_class = G_OBJECT_CLASS (klass);
 
   gobject_class->get_property = gst_rtsp_media_get_property;
@@ -329,6 +379,31 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
           GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT,
+      g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect",
+          "If this media pipeline should be stopped "
+          "when a client disconnects without TEARDOWN",
+          DEFAULT_STOP_ON_DISCONNECT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_CLOCK,
+      g_param_spec_object ("clock", "Clock",
+          "Clock to be used by the media pipeline",
+          GST_TYPE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_MAX_MCAST_TTL,
+      g_param_spec_uint ("max-mcast-ttl", "Maximum multicast ttl",
+          "The maximum time-to-live value of outgoing multicast packets", 1,
+          255, DEFAULT_MAX_MCAST_TTL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_BIND_MCAST_ADDRESS,
+      g_param_spec_boolean ("bind-mcast-address", "Bind mcast address",
+          "Whether the multicast sockets should be bound to multicast addresses "
+          "or INADDR_ANY",
+          DEFAULT_BIND_MCAST_ADDRESS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
       g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
       G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL,
@@ -378,7 +453,7 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
 static void
 gst_rtsp_media_init (GstRTSPMedia * media)
 {
-  GstRTSPMediaPrivate *priv = GST_RTSP_MEDIA_GET_PRIVATE (media);
+  GstRTSPMediaPrivate *priv = gst_rtsp_media_get_instance_private (media);
 
   media->priv = priv;
 
@@ -396,6 +471,13 @@ gst_rtsp_media_init (GstRTSPMedia * media)
   priv->buffer_size = DEFAULT_BUFFER_SIZE;
   priv->time_provider = DEFAULT_TIME_PROVIDER;
   priv->transport_mode = DEFAULT_TRANSPORT_MODE;
+  priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
+  priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
+  priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
+  priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
+  priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
+  priv->do_rate_control = DEFAULT_DO_RATE_CONTROL;
+  priv->expected_async_done = FALSE;
 }
 
 static void
@@ -415,6 +497,7 @@ gst_rtsp_media_finalize (GObject * obj)
   g_ptr_array_unref (priv->streams);
 
   g_list_free_full (priv->dynamic, gst_object_unref);
+  g_list_free_full (priv->pending_pipeline_elements, gst_object_unref);
 
   if (priv->pipeline)
     gst_object_unref (priv->pipeline);
@@ -425,6 +508,9 @@ gst_rtsp_media_finalize (GObject * obj)
     g_object_unref (priv->pool);
   if (priv->payloads)
     g_list_free (priv->payloads);
+  if (priv->clock)
+    gst_object_unref (priv->clock);
+  g_free (priv->multicast_iface);
   g_mutex_clear (&priv->lock);
   g_cond_clear (&priv->cond);
   g_rec_mutex_clear (&priv->state_lock);
@@ -472,6 +558,18 @@ gst_rtsp_media_get_property (GObject * object, guint propid,
     case PROP_TRANSPORT_MODE:
       g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media));
       break;
+    case PROP_STOP_ON_DISCONNECT:
+      g_value_set_boolean (value, gst_rtsp_media_is_stop_on_disconnect (media));
+      break;
+    case PROP_CLOCK:
+      g_value_take_object (value, gst_rtsp_media_get_clock (media));
+      break;
+    case PROP_MAX_MCAST_TTL:
+      g_value_set_uint (value, gst_rtsp_media_get_max_mcast_ttl (media));
+      break;
+    case PROP_BIND_MCAST_ADDRESS:
+      g_value_set_boolean (value, gst_rtsp_media_is_bind_mcast_address (media));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
   }
@@ -518,6 +616,20 @@ gst_rtsp_media_set_property (GObject * object, guint propid,
     case PROP_TRANSPORT_MODE:
       gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value));
       break;
+    case PROP_STOP_ON_DISCONNECT:
+      gst_rtsp_media_set_stop_on_disconnect (media,
+          g_value_get_boolean (value));
+      break;
+    case PROP_CLOCK:
+      gst_rtsp_media_set_clock (media, g_value_get_object (value));
+      break;
+    case PROP_MAX_MCAST_TTL:
+      gst_rtsp_media_set_max_mcast_ttl (media, g_value_get_uint (value));
+      break;
+    case PROP_BIND_MCAST_ADDRESS:
+      gst_rtsp_media_set_bind_mcast_address (media,
+          g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
   }
@@ -526,6 +638,7 @@ gst_rtsp_media_set_property (GObject * object, guint propid,
 typedef struct
 {
   gint64 position;
+  gboolean complete_streams_only;
   gboolean ret;
 } DoQueryPositionData;
 
@@ -534,10 +647,21 @@ do_query_position (GstRTSPStream * stream, DoQueryPositionData * data)
 {
   gint64 tmp;
 
+  if (!gst_rtsp_stream_is_sender (stream))
+    return;
+
+  if (data->complete_streams_only && !gst_rtsp_stream_is_complete (stream)) {
+    GST_DEBUG_OBJECT (stream, "stream not complete, do not query position");
+    return;
+  }
+
   if (gst_rtsp_stream_query_position (stream, &tmp)) {
-    data->position = MAX (data->position, tmp);
+    data->position = MIN (data->position, tmp);
     data->ret = TRUE;
   }
+
+  GST_INFO_OBJECT (stream, "media position: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (data->position));
 }
 
 static gboolean
@@ -548,12 +672,24 @@ default_query_position (GstRTSPMedia * media, gint64 * position)
 
   priv = media->priv;
 
-  data.position = -1;
+  data.position = G_MAXINT64;
   data.ret = FALSE;
 
+  /* if the media is complete, i.e. one or more streams have been configured
+   * with sinks, then we want to query the position on those streams only.
+   * a query on an incmplete stream may return a position that originates from
+   * an earlier preroll */
+  if (check_complete (media))
+    data.complete_streams_only = TRUE;
+  else
+    data.complete_streams_only = FALSE;
+
   g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data);
 
-  *position = data.position;
+  if (!data.ret)
+    *position = GST_CLOCK_TIME_NONE;
+  else
+    *position = data.position;
 
   return data.ret;
 }
@@ -567,7 +703,7 @@ typedef struct
 static void
 do_query_stop (GstRTSPStream * stream, DoQueryStopData * data)
 {
-  gint64 tmp;
+  gint64 tmp = 0;
 
   if (gst_rtsp_stream_query_stop (stream, &tmp)) {
     data->stop = MAX (data->stop, tmp);
@@ -605,6 +741,74 @@ default_create_rtpbin (GstRTSPMedia * media)
 
 /* must be called with state lock */
 static void
+check_seekable (GstRTSPMedia * media)
+{
+  GstQuery *query;
+  GstRTSPMediaPrivate *priv = media->priv;
+
+  /* Update the seekable state of the pipeline in case it changed */
+  if (gst_rtsp_media_is_receive_only (media)) {
+    /* TODO: Seeking for "receive-only"? */
+    priv->seekable = -1;
+  } else {
+    guint i, n = priv->streams->len;
+
+    for (i = 0; i < n; i++) {
+      GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+      if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
+          GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
+        priv->seekable = -1;
+        return;
+      }
+    }
+  }
+
+  query = gst_query_new_seeking (GST_FORMAT_TIME);
+  if (gst_element_query (priv->pipeline, query)) {
+    GstFormat format;
+    gboolean seekable;
+    gint64 start, end;
+
+    gst_query_parse_seeking (query, &format, &seekable, &start, &end);
+    priv->seekable = seekable ? G_MAXINT64 : 0;
+  } else if (priv->streams->len) {
+    gboolean seekable = TRUE;
+    guint i, n = priv->streams->len;
+
+    GST_DEBUG_OBJECT (media, "Checking %d streams", n);
+    for (i = 0; i < n; i++) {
+      GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+      seekable &= gst_rtsp_stream_seekable (stream);
+    }
+    priv->seekable = seekable ? G_MAXINT64 : -1;
+  }
+
+  GST_DEBUG_OBJECT (media, "seekable:%" G_GINT64_FORMAT, priv->seekable);
+
+  gst_query_unref (query);
+}
+
+/* must be called with state lock */
+static gboolean
+check_complete (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+
+  guint i, n = priv->streams->len;
+
+  for (i = 0; i < n; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+    if (gst_rtsp_stream_is_complete (stream))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+/* must be called with state lock */
+static void
 collect_media_stats (GstRTSPMedia * media)
 {
   GstRTSPMediaPrivate *priv = media->priv;
@@ -672,6 +876,8 @@ collect_media_stats (GstRTSPMedia * media)
       priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
       priv->range_stop = stop;
     }
+
+    check_seekable (media);
   }
 }
 
@@ -730,6 +936,7 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline)
   GstRTSPMediaPrivate *priv;
   GstElement *old;
   GstNetTimeProvider *nettime;
+  GList *l;
 
   g_return_if_fail (GST_IS_RTSP_MEDIA (media));
   g_return_if_fail (GST_IS_PIPELINE (pipeline));
@@ -750,12 +957,18 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline)
     gst_object_unref (nettime);
 
   gst_bin_add (GST_BIN_CAST (pipeline), priv->element);
+
+  for (l = priv->pending_pipeline_elements; l; l = l->next) {
+    gst_bin_add (GST_BIN_CAST (pipeline), l->data);
+  }
+  g_list_free (priv->pending_pipeline_elements);
+  priv->pending_pipeline_elements = NULL;
 }
 
 /**
  * gst_rtsp_media_set_permissions:
  * @media: a #GstRTSPMedia
- * @permissions: (transfer none): a #GstRTSPPermissions
+ * @permissions: (transfer none) (nullable): a #GstRTSPPermissions
  *
  * Set @permissions on @media.
  */
@@ -783,7 +996,7 @@ gst_rtsp_media_set_permissions (GstRTSPMedia * media,
  *
  * Get the permissions object from @media.
  *
- * Returns: (transfer full): a #GstRTSPPermissions object, unref after usage.
+ * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage.
  */
 GstRTSPPermissions *
 gst_rtsp_media_get_permissions (GstRTSPMedia * media)
@@ -1168,6 +1381,56 @@ gst_rtsp_media_get_buffer_size (GstRTSPMedia * media)
 }
 
 /**
+ * gst_rtsp_media_set_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ * @stop_on_disconnect: the new value
+ *
+ * Set or unset if the pipeline for @media should be stopped when a
+ * client disconnects without sending TEARDOWN.
+ */
+void
+gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia * media,
+    gboolean stop_on_disconnect)
+{
+  GstRTSPMediaPrivate *priv;
+
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  priv->stop_on_disconnect = stop_on_disconnect;
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_is_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media will be stopped when a client disconnects
+ * without sending TEARDOWN.
+ *
+ * Returns: %TRUE if the media will be stopped when a client disconnects
+ *     without sending TEARDOWN.
+ */
+gboolean
+gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  gboolean res;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), TRUE);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  res = priv->stop_on_disconnect;
+  g_mutex_unlock (&priv->lock);
+
+  return res;
+}
+
+/**
  * gst_rtsp_media_set_retransmission_time:
  * @media: a #GstRTSPMedia
  * @time: the new value
@@ -1193,9 +1456,6 @@ gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time)
 
     gst_rtsp_stream_set_retransmission_time (stream, time);
   }
-
-  if (priv->rtpbin)
-    g_object_set (priv->rtpbin, "do-retransmission", time > 0, NULL);
   g_mutex_unlock (&priv->lock);
 }
 
@@ -1217,7 +1477,7 @@ gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media)
 
   priv = media->priv;
 
-  g_mutex_unlock (&priv->lock);
+  g_mutex_lock (&priv->lock);
   res = priv->rtx_time;
   g_mutex_unlock (&priv->lock);
 
@@ -1225,7 +1485,56 @@ gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media)
 }
 
 /**
- * gst_rtsp_media_set_latncy:
+ * gst_rtsp_media_set_do_retransmission:
+ *
+ * Set whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+void
+gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
+    gboolean do_retransmission)
+{
+  GstRTSPMediaPrivate *priv;
+
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  priv->do_retransmission = do_retransmission;
+
+  if (priv->rtpbin)
+    g_object_set (priv->rtpbin, "do-retransmission", do_retransmission, NULL);
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_get_do_retransmission:
+ *
+ * Returns: Whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+gboolean
+gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  gboolean res;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  res = priv->do_retransmission;
+  g_mutex_unlock (&priv->lock);
+
+  return res;
+}
+
+/**
+ * gst_rtsp_media_set_latency:
  * @media: a #GstRTSPMedia
  * @latency: latency in milliseconds
  *
@@ -1235,6 +1544,7 @@ void
 gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
 {
   GstRTSPMediaPrivate *priv;
+  guint i;
 
   g_return_if_fail (GST_IS_RTSP_MEDIA (media));
 
@@ -1244,8 +1554,20 @@ gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
 
   g_mutex_lock (&priv->lock);
   priv->latency = latency;
-  if (priv->rtpbin)
+  if (priv->rtpbin) {
     g_object_set (priv->rtpbin, "latency", latency, NULL);
+
+    for (i = 0; i < media->priv->streams->len; i++) {
+      GObject *storage = NULL;
+
+      g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage",
+          i, &storage);
+      if (storage)
+        g_object_set (storage, "size-time",
+            (media->priv->latency + 50) * GST_MSECOND, NULL);
+    }
+  }
+
   g_mutex_unlock (&priv->lock);
 }
 
@@ -1267,7 +1589,7 @@ gst_rtsp_media_get_latency (GstRTSPMedia * media)
 
   priv = media->priv;
 
-  g_mutex_unlock (&priv->lock);
+  g_mutex_lock (&priv->lock);
   res = priv->latency;
   g_mutex_unlock (&priv->lock);
 
@@ -1315,7 +1637,7 @@ gst_rtsp_media_is_time_provider (GstRTSPMedia * media)
 
   priv = media->priv;
 
-  g_mutex_unlock (&priv->lock);
+  g_mutex_lock (&priv->lock);
   res = priv->time_provider;
   g_mutex_unlock (&priv->lock);
 
@@ -1323,9 +1645,95 @@ gst_rtsp_media_is_time_provider (GstRTSPMedia * media)
 }
 
 /**
+ * gst_rtsp_media_set_clock:
+ * @media: a #GstRTSPMedia
+ * @clock: (nullable): #GstClock to be used
+ *
+ * Configure the clock used for the media.
+ */
+void
+gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock)
+{
+  GstRTSPMediaPrivate *priv;
+
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+  g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL);
+
+  GST_LOG_OBJECT (media, "setting clock %" GST_PTR_FORMAT, clock);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  if (priv->clock)
+    gst_object_unref (priv->clock);
+  priv->clock = clock ? gst_object_ref (clock) : NULL;
+  if (priv->pipeline) {
+    if (clock)
+      gst_pipeline_use_clock (GST_PIPELINE_CAST (priv->pipeline), clock);
+    else
+      gst_pipeline_auto_clock (GST_PIPELINE_CAST (priv->pipeline));
+  }
+
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_set_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ * @mode: the clock publish mode
+ *
+ * Sets if and how the media clock should be published according to RFC7273.
+ *
+ * Since: 1.8
+ */
+void
+gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media,
+    GstRTSPPublishClockMode mode)
+{
+  GstRTSPMediaPrivate *priv;
+  guint i, n;
+
+  priv = media->priv;
+  g_mutex_lock (&priv->lock);
+  priv->publish_clock_mode = mode;
+
+  n = priv->streams->len;
+  for (i = 0; i < n; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+    gst_rtsp_stream_set_publish_clock_mode (stream, mode);
+  }
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_get_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ *
+ * Gets if and how the media clock should be published according to RFC7273.
+ *
+ * Returns: The GstRTSPPublishClockMode
+ *
+ * Since: 1.8
+ */
+GstRTSPPublishClockMode
+gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  GstRTSPPublishClockMode ret;
+
+  priv = media->priv;
+  g_mutex_lock (&priv->lock);
+  ret = priv->publish_clock_mode;
+  g_mutex_unlock (&priv->lock);
+
+  return ret;
+}
+
+/**
  * gst_rtsp_media_set_address_pool:
  * @media: a #GstRTSPMedia
- * @pool: (transfer none): a #GstRTSPAddressPool
+ * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
  *
  * configure @pool to be used as the address pool of @media.
  */
@@ -1361,8 +1769,8 @@ gst_rtsp_media_set_address_pool (GstRTSPMedia * media,
  *
  * Get the #GstRTSPAddressPool used as the address pool of @media.
  *
- * Returns: (transfer full): the #GstRTSPAddressPool of @media. g_object_unref() after
- * usage.
+ * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @media.
+ * g_object_unref() after usage.
  */
 GstRTSPAddressPool *
 gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
@@ -1382,29 +1790,214 @@ gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
   return result;
 }
 
-static GList *
-_find_payload_types (GstRTSPMedia * media)
+/**
+ * gst_rtsp_media_set_multicast_iface:
+ * @media: a #GstRTSPMedia
+ * @multicast_iface: (transfer none) (nullable): a multicast interface name
+ *
+ * configure @multicast_iface to be used for @media.
+ */
+void
+gst_rtsp_media_set_multicast_iface (GstRTSPMedia * media,
+    const gchar * multicast_iface)
 {
-  gint i, n;
-  GQueue queue = G_QUEUE_INIT;
+  GstRTSPMediaPrivate *priv;
+  gchar *old;
 
-  n = media->priv->streams->len;
-  for (i = 0; i < n; i++) {
-    GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
-    guint pt = gst_rtsp_stream_get_pt (stream);
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
 
-    g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
-  }
+  priv = media->priv;
 
-  return queue.head;
-}
+  GST_LOG_OBJECT (media, "set multicast interface %s", multicast_iface);
 
-static guint
-_next_available_pt (GList * payloads)
-{
-  guint i;
+  g_mutex_lock (&priv->lock);
+  if ((old = priv->multicast_iface) != multicast_iface)
+    priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
+  else
+    old = NULL;
+  g_ptr_array_foreach (priv->streams,
+      (GFunc) gst_rtsp_stream_set_multicast_iface, (gchar *) multicast_iface);
+  g_mutex_unlock (&priv->lock);
 
-  for (i = 96; i <= 127; i++) {
+  if (old)
+    g_free (old);
+}
+
+/**
+ * gst_rtsp_media_get_multicast_iface:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the multicast interface used for @media.
+ *
+ * Returns: (transfer full) (nullable): the multicast interface for @media.
+ * g_free() after usage.
+ */
+gchar *
+gst_rtsp_media_get_multicast_iface (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  gchar *result;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  if ((result = priv->multicast_iface))
+    result = g_strdup (result);
+  g_mutex_unlock (&priv->lock);
+
+  return result;
+}
+
+/**
+ * gst_rtsp_media_set_max_mcast_ttl:
+ * @media: a #GstRTSPMedia
+ * @ttl: the new multicast ttl value
+ *
+ * Set the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: %TRUE if the requested ttl has been set successfully.
+ *
+ * Since: 1.16
+ */
+gboolean
+gst_rtsp_media_set_max_mcast_ttl (GstRTSPMedia * media, guint ttl)
+{
+  GstRTSPMediaPrivate *priv;
+  guint i;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  GST_LOG_OBJECT (media, "set max mcast ttl %u", ttl);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+
+  if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
+    GST_WARNING_OBJECT (media, "The reqested mcast TTL value is not valid.");
+    g_mutex_unlock (&priv->lock);
+    return FALSE;
+  }
+  priv->max_mcast_ttl = ttl;
+
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+    gst_rtsp_stream_set_max_mcast_ttl (stream, ttl);
+  }
+  g_mutex_unlock (&priv->lock);
+
+  return TRUE;
+}
+
+/**
+ * gst_rtsp_media_get_max_mcast_ttl:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Since: 1.16
+ */
+guint
+gst_rtsp_media_get_max_mcast_ttl (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  guint res;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  res = priv->max_mcast_ttl;
+  g_mutex_unlock (&priv->lock);
+
+  return res;
+}
+
+/**
+ * gst_rtsp_media_set_bind_mcast_address:
+ * @media: a #GstRTSPMedia
+ * @bind_mcast_addr: the new value
+ *
+ * Decide whether the multicast socket should be bound to a multicast address or
+ * INADDR_ANY.
+ *
+ * Since: 1.16
+ */
+void
+gst_rtsp_media_set_bind_mcast_address (GstRTSPMedia * media,
+    gboolean bind_mcast_addr)
+{
+  GstRTSPMediaPrivate *priv;
+  guint i;
+
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  priv->bind_mcast_address = bind_mcast_addr;
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+    gst_rtsp_stream_set_bind_mcast_address (stream, bind_mcast_addr);
+  }
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_is_bind_mcast_address:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Since: 1.16
+ */
+gboolean
+gst_rtsp_media_is_bind_mcast_address (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  gboolean result;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  result = priv->bind_mcast_address;
+  g_mutex_unlock (&priv->lock);
+
+  return result;
+}
+
+static GList *
+_find_payload_types (GstRTSPMedia * media)
+{
+  gint i, n;
+  GQueue queue = G_QUEUE_INIT;
+
+  n = media->priv->streams->len;
+  for (i = 0; i < n; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
+    guint pt = gst_rtsp_stream_get_pt (stream);
+
+    g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
+  }
+
+  return queue.head;
+}
+
+static guint
+_next_available_pt (GList * payloads)
+{
+  guint i;
+
+  for (i = 96; i <= 127; i++) {
     GList *iter = g_list_find (payloads, GINT_TO_POINTER (i));
     if (!iter)
       return GPOINTER_TO_UINT (i);
@@ -1457,7 +2050,7 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
       pad = gst_element_get_static_pad (elem, "src");
 
       /* find the real payload element in case elem is a GstBin */
-      pay = find_payload_element (elem);
+      pay = find_payload_element (elem, pad);
 
       /* create the stream */
       if (pay == NULL) {
@@ -1486,6 +2079,8 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
       priv->dynamic = g_list_prepend (priv->dynamic, elem);
       g_mutex_unlock (&priv->lock);
 
+      priv->nb_dynamic_elements++;
+
       have_elem = TRUE;
       more_elem_remaining = TRUE;
       mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
@@ -1517,6 +2112,79 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
   }
 }
 
+typedef struct
+{
+  GstElement *appsink, *appsrc;
+  GstRTSPStream *stream;
+} AppSinkSrcData;
+
+static GstFlowReturn
+appsink_new_sample (GstAppSink * appsink, gpointer user_data)
+{
+  AppSinkSrcData *data = user_data;
+  GstSample *sample;
+  GstFlowReturn ret;
+
+  sample = gst_app_sink_pull_sample (appsink);
+  if (!sample)
+    return GST_FLOW_FLUSHING;
+
+
+  ret = gst_app_src_push_sample (GST_APP_SRC (data->appsrc), sample);
+  gst_sample_unref (sample);
+  return ret;
+}
+
+static GstAppSinkCallbacks appsink_callbacks = {
+  NULL,
+  NULL,
+  appsink_new_sample,
+};
+
+static GstPadProbeReturn
+appsink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+  AppSinkSrcData *data = user_data;
+
+  if (GST_IS_EVENT (info->data)
+      && GST_EVENT_TYPE (info->data) == GST_EVENT_LATENCY) {
+    GstClockTime min, max;
+
+    if (gst_base_sink_query_latency (GST_BASE_SINK (data->appsink), NULL, NULL,
+            &min, &max)) {
+      g_object_set (data->appsrc, "min-latency", min, "max-latency", max, NULL);
+      GST_DEBUG ("setting latency to min %" GST_TIME_FORMAT " max %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+    }
+  } else if (GST_IS_QUERY (info->data)) {
+    GstPad *srcpad = gst_element_get_static_pad (data->appsrc, "src");
+    if (gst_pad_peer_query (srcpad, GST_QUERY_CAST (info->data))) {
+      gst_object_unref (srcpad);
+      return GST_PAD_PROBE_HANDLED;
+    }
+    gst_object_unref (srcpad);
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+appsrc_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+  AppSinkSrcData *data = user_data;
+
+  if (GST_IS_QUERY (info->data)) {
+    GstPad *sinkpad = gst_element_get_static_pad (data->appsink, "sink");
+    if (gst_pad_peer_query (sinkpad, GST_QUERY_CAST (info->data))) {
+      gst_object_unref (sinkpad);
+      return GST_PAD_PROBE_HANDLED;
+    }
+    gst_object_unref (sinkpad);
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
 /**
  * gst_rtsp_media_create_stream:
  * @media: a #GstRTSPMedia
@@ -1535,9 +2203,10 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
 {
   GstRTSPMediaPrivate *priv;
   GstRTSPStream *stream;
-  GstPad *ghostpad;
+  GstPad *streampad;
   gchar *name;
   gint idx;
+  AppSinkSrcData *data = NULL;
 
   g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
   g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
@@ -1548,25 +2217,91 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
   g_mutex_lock (&priv->lock);
   idx = priv->streams->len;
 
-  GST_DEBUG ("media %p: creating stream with index %d", media, idx);
+  GST_DEBUG ("media %p: creating stream with index %d and payloader %"
+      GST_PTR_FORMAT, media, idx, payloader);
 
   if (GST_PAD_IS_SRC (pad))
     name = g_strdup_printf ("src_%u", idx);
   else
     name = g_strdup_printf ("sink_%u", idx);
 
-  ghostpad = gst_ghost_pad_new (name, pad);
-  gst_pad_set_active (ghostpad, TRUE);
-  gst_element_add_pad (priv->element, ghostpad);
+  if ((GST_PAD_IS_SRC (pad) && priv->element->numsinkpads > 0) ||
+      (GST_PAD_IS_SINK (pad) && priv->element->numsrcpads > 0)) {
+    GstElement *appsink, *appsrc;
+    GstPad *sinkpad, *srcpad;
+
+    appsink = gst_element_factory_make ("appsink", NULL);
+    appsrc = gst_element_factory_make ("appsrc", NULL);
+
+    if (GST_PAD_IS_SINK (pad)) {
+      srcpad = gst_element_get_static_pad (appsrc, "src");
+
+      gst_bin_add (GST_BIN (priv->element), appsrc);
+
+      gst_pad_link (srcpad, pad);
+      gst_object_unref (srcpad);
+
+      streampad = gst_element_get_static_pad (appsink, "sink");
+
+      priv->pending_pipeline_elements =
+          g_list_prepend (priv->pending_pipeline_elements, appsink);
+    } else {
+      sinkpad = gst_element_get_static_pad (appsink, "sink");
+
+      gst_pad_link (pad, sinkpad);
+      gst_object_unref (sinkpad);
+
+      streampad = gst_element_get_static_pad (appsrc, "src");
+
+      priv->pending_pipeline_elements =
+          g_list_prepend (priv->pending_pipeline_elements, appsrc);
+    }
+
+    g_object_set (appsrc, "block", TRUE, "format", GST_FORMAT_TIME, "is-live",
+        TRUE, "emit-signals", FALSE, NULL);
+    g_object_set (appsink, "sync", FALSE, "async", FALSE, "emit-signals",
+        FALSE, "buffer-list", TRUE, NULL);
+
+    data = g_new0 (AppSinkSrcData, 1);
+    data->appsink = appsink;
+    data->appsrc = appsrc;
+
+    sinkpad = gst_element_get_static_pad (appsink, "sink");
+    gst_pad_add_probe (sinkpad,
+        GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+        appsink_pad_probe, data, NULL);
+    gst_object_unref (sinkpad);
+
+    srcpad = gst_element_get_static_pad (appsrc, "src");
+    gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
+        appsrc_pad_probe, data, NULL);
+    gst_object_unref (srcpad);
+
+    gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &appsink_callbacks,
+        data, NULL);
+    g_object_set_data_full (G_OBJECT (streampad), "media-appsink-appsrc", data,
+        g_free);
+  } else {
+    streampad = gst_ghost_pad_new (name, pad);
+    gst_pad_set_active (streampad, TRUE);
+    gst_element_add_pad (priv->element, streampad);
+  }
   g_free (name);
 
-  stream = gst_rtsp_stream_new (idx, payloader, ghostpad);
+  stream = gst_rtsp_stream_new (idx, payloader, streampad);
+  if (data)
+    data->stream = stream;
   if (priv->pool)
     gst_rtsp_stream_set_address_pool (stream, priv->pool);
+  gst_rtsp_stream_set_multicast_iface (stream, priv->multicast_iface);
+  gst_rtsp_stream_set_max_mcast_ttl (stream, priv->max_mcast_ttl);
+  gst_rtsp_stream_set_bind_mcast_address (stream, priv->bind_mcast_address);
   gst_rtsp_stream_set_profiles (stream, priv->profiles);
   gst_rtsp_stream_set_protocols (stream, priv->protocols);
   gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time);
   gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size);
+  gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode);
+  gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control);
 
   g_ptr_array_add (priv->streams, stream);
 
@@ -1606,13 +2341,28 @@ gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream)
 {
   GstRTSPMediaPrivate *priv;
   GstPad *srcpad;
+  AppSinkSrcData *data;
 
   priv = media->priv;
 
   g_mutex_lock (&priv->lock);
   /* remove the ghostpad */
   srcpad = gst_rtsp_stream_get_srcpad (stream);
-  gst_element_remove_pad (priv->element, srcpad);
+  data = g_object_get_data (G_OBJECT (srcpad), "media-appsink-appsrc");
+  if (data) {
+    if (GST_OBJECT_PARENT (data->appsrc) == GST_OBJECT_CAST (priv->pipeline))
+      gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsrc);
+    else if (GST_OBJECT_PARENT (data->appsrc) ==
+        GST_OBJECT_CAST (priv->element))
+      gst_bin_remove (GST_BIN_CAST (priv->element), data->appsrc);
+    if (GST_OBJECT_PARENT (data->appsink) == GST_OBJECT_CAST (priv->pipeline))
+      gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsink);
+    else if (GST_OBJECT_PARENT (data->appsink) ==
+        GST_OBJECT_CAST (priv->element))
+      gst_bin_remove (GST_BIN_CAST (priv->element), data->appsink);
+  } else {
+    gst_element_remove_pad (priv->element, srcpad);
+  }
   gst_object_unref (srcpad);
   /* now remove the stream */
   g_object_ref (stream);
@@ -1737,7 +2487,7 @@ default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range,
  * Get the current range as a string. @media must be prepared with
  * gst_rtsp_media_prepare ().
  *
- * Returns: (transfer full): The range as a string, g_free() after usage.
+ * Returns: (transfer full) (nullable): The range as a string, g_free() after usage.
  */
 gchar *
 gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play,
@@ -1795,6 +2545,77 @@ conversion_failed:
   }
 }
 
+/**
+ * gst_rtsp_media_get_rates:
+ * @media: a #GstRTSPMedia
+ * @rate (allow-none): the rate of the current segment
+ * @applied_rate (allow-none): the applied_rate of the current segment
+ *
+ * Get the rate and applied_rate of the current segment.
+ *
+ * Returns: %FALSE if looking up the rate and applied rate failed. Otherwise
+ * %TRUE is returned and @rate and @applied_rate are set to the rate and
+ * applied_rate of the current segment.
+ * Since: 1.18
+ */
+gboolean
+gst_rtsp_media_get_rates (GstRTSPMedia * media, gdouble * rate,
+    gdouble * applied_rate)
+{
+  GstRTSPMediaPrivate *priv;
+  GstRTSPStream *stream;
+  gdouble save_rate, save_applied_rate;
+  gboolean result = TRUE;
+  gboolean first_stream = TRUE;
+  gint i;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  if (!rate && !applied_rate) {
+    GST_WARNING_OBJECT (media, "rate and applied_rate are both NULL");
+    return FALSE;
+  }
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+
+  g_assert (priv->streams->len > 0);
+  for (i = 0; i < priv->streams->len; i++) {
+    stream = g_ptr_array_index (priv->streams, i);
+    if (gst_rtsp_stream_is_complete (stream)) {
+      if (gst_rtsp_stream_get_rates (stream, rate, applied_rate)) {
+        if (first_stream) {
+          save_rate = *rate;
+          save_applied_rate = *applied_rate;
+          first_stream = FALSE;
+        } else {
+          if (save_rate != *rate || save_applied_rate != *applied_rate) {
+            /* diffrent rate or applied_rate, weird */
+            g_assert (FALSE);
+            result = FALSE;
+            break;
+          }
+        }
+      } else {
+        /* complete stream withot rate and applied_rate, weird */
+        g_assert (FALSE);
+        result = FALSE;
+        break;
+      }
+    }
+  }
+
+  if (!result) {
+    GST_WARNING_OBJECT (media,
+        "failed to obtain consistent rate and applied_rate");
+  }
+
+  g_mutex_unlock (&priv->lock);
+
+  return result;
+}
+
 static void
 stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media)
 {
@@ -1812,6 +2633,24 @@ media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked)
 }
 
 static void
+stream_unblock (GstRTSPStream * stream, GstRTSPMedia * media)
+{
+  gst_rtsp_stream_set_blocked (stream, FALSE);
+}
+
+static void
+media_unblock (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+
+  GST_DEBUG ("media %p unblocking streams", media);
+  /* media is not blocked any longer, as it contains active streams,
+   * streams that are complete */
+  priv->blocked = FALSE;
+  g_ptr_array_foreach (priv->streams, (GFunc) stream_unblock, media);
+}
+
+static void
 gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status)
 {
   GstRTSPMediaPrivate *priv = media->priv;
@@ -1858,31 +2697,43 @@ gst_rtsp_media_get_status (GstRTSPMedia * media)
 }
 
 /**
- * gst_rtsp_media_seek:
+ * gst_rtsp_media_seek_trickmode:
  * @media: a #GstRTSPMedia
  * @range: (transfer none): a #GstRTSPTimeRange
+ * @flags: The minimal set of #GstSeekFlags to use
+ * @rate: the rate to use in the seek
+ * @trickmode_interval: The trickmode interval to use for KEY_UNITS trick mode
  *
- * Seek the pipeline of @media to @range. @media must be prepared with
- * gst_rtsp_media_prepare().
+ * Seek the pipeline of @media to @range with the given @flags and @rate,
+ * and @trickmode_interval.
+ * @media must be prepared with gst_rtsp_media_prepare().
+ * In order to perform the seek operation, the pipeline must contain all
+ * needed transport parts (transport sinks).
  *
  * Returns: %TRUE on success.
+ *
+ * Since: 1.18
  */
 gboolean
-gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+gst_rtsp_media_seek_trickmode (GstRTSPMedia * media,
+    GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate,
+    GstClockTime trickmode_interval)
 {
   GstRTSPMediaClass *klass;
   GstRTSPMediaPrivate *priv;
   gboolean res;
   GstClockTime start, stop;
   GstSeekType start_type, stop_type;
-  GstQuery *query;
   gint64 current_position;
+  gboolean force_seek;
 
   klass = GST_RTSP_MEDIA_GET_CLASS (media);
 
   g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
-  g_return_val_if_fail (range != NULL, FALSE);
-  g_return_val_if_fail (klass->convert_range != NULL, FALSE);
+  /* if there's a range then klass->convert_range must be set */
+  g_return_val_if_fail (range == NULL || klass->convert_range != NULL, FALSE);
+
+  GST_DEBUG ("flags=%x  rate=%f", flags, rate);
 
   priv = media->priv;
 
@@ -1890,36 +2741,39 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
   if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
     goto not_prepared;
 
+  /* check if the media pipeline is complete in order to perform a
+   * seek operation on it */
+  if (!check_complete (media))
+    goto not_complete;
+
   /* Update the seekable state of the pipeline in case it changed */
-  if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) {
-    /* TODO: Seeking for RECORD? */
-    priv->seekable = FALSE;
-  } else {
-    query = gst_query_new_seeking (GST_FORMAT_TIME);
-    if (gst_element_query (priv->pipeline, query)) {
-      GstFormat format;
-      gboolean seekable;
-      gint64 start, end;
-
-      gst_query_parse_seeking (query, &format, &seekable, &start, &end);
-      priv->seekable = seekable;
-    }
-    gst_query_unref (query);
-  }
+  check_seekable (media);
 
-  if (!priv->seekable)
+  if (priv->seekable == 0) {
+    GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
+        " not seekable streams.");
+
+    goto not_seekable;
+  } else if (priv->seekable < 0) {
     goto not_seekable;
+  }
 
   start_type = stop_type = GST_SEEK_TYPE_NONE;
+  start = stop = GST_CLOCK_TIME_NONE;
 
-  if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT))
-    goto not_supported;
-  gst_rtsp_range_get_times (range, &start, &stop);
+  /* if caller provided a range convert it to NPT format
+   * if no range provided the seek is assumed to be the same position but with
+   * e.g. the rate changed */
+  if (range != NULL) {
+    if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT))
+      goto not_supported;
+    gst_rtsp_range_get_times (range, &start, &stop);
 
-  GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
-  GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop));
+    GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+    GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop));
+  }
 
   current_position = -1;
   if (klass->query_position)
@@ -1930,25 +2784,25 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
   if (start != GST_CLOCK_TIME_NONE)
     start_type = GST_SEEK_TYPE_SET;
 
-  if (priv->range_stop == stop)
-    stop = GST_CLOCK_TIME_NONE;
-  else if (stop != GST_CLOCK_TIME_NONE)
+  if (stop != GST_CLOCK_TIME_NONE)
     stop_type = GST_SEEK_TYPE_SET;
 
-  if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) {
-    GstSeekFlags flags;
+  /* we force a seek if any trickmode flag is set, or if the rate
+   * is non-standard, i.e. not 1.0 */
+  force_seek = (flags & TRICKMODE_FLAGS) || rate != 1.0;
 
+  if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE || force_seek) {
     GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
         GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
 
     /* depends on the current playing state of the pipeline. We might need to
      * queue this until we get EOS. */
-    flags = GST_SEEK_FLAG_FLUSH;
+    flags |= GST_SEEK_FLAG_FLUSH;
 
     /* if range start was not supplied we must continue from current position.
      * but since we're doing a flushing seek, let us query the current position
      * so we end up at exactly the same position after the seek. */
-    if (range->min.type == GST_RTSP_TIME_END) { /* Yepp, that's right! */
+    if (range == NULL || range->min.type == GST_RTSP_TIME_END) {
       if (current_position == -1) {
         GST_WARNING ("current position unknown");
       } else {
@@ -1956,25 +2810,72 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
             GST_TIME_ARGS (current_position));
         start = current_position;
         start_type = GST_SEEK_TYPE_SET;
-        flags |= GST_SEEK_FLAG_ACCURATE;
       }
-    } else {
-      /* only set keyframe flag when modifying start */
-      if (start_type != GST_SEEK_TYPE_NONE)
-        flags |= GST_SEEK_FLAG_KEY_UNIT;
     }
 
-    if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) {
-      GST_DEBUG ("not seeking because no position change");
+    if (start == current_position && stop_type == GST_SEEK_TYPE_NONE &&
+        !force_seek) {
+      GST_DEBUG ("no position change, no flags set by caller, so not seeking");
       res = TRUE;
     } else {
+      GstEvent *seek_event;
+      gboolean unblock = FALSE;
+
+      /* Handle expected async-done before waiting on next async-done.
+       * 
+       * Since the seek further down in code will cause a preroll and
+       * a async-done will be generated it's important to wait on async-done
+       * if that is expected. Otherwise there is the risk that the waiting
+       * for async-done after the seek is detecting the expected async-done
+       * instead of the one that corresponds to the seek. Then execution
+       * continue and act as if the pipeline is prerolled, but it's not.
+       * 
+       * During wait_preroll message GST_MESSAGE_ASYNC_DONE will come
+       * and then the state will change from preparing to prepared */
+      if (priv->expected_async_done) {
+        GST_DEBUG (" expected to get async-done, waiting ");
+        gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+        g_rec_mutex_unlock (&priv->state_lock);
+
+        /* wait until pipeline is prerolled  */
+        if (!wait_preroll (media))
+          goto preroll_failed_expected_async_done;
+
+        g_rec_mutex_lock (&priv->state_lock);
+        GST_DEBUG (" got expected async-done");
+      }
+
       gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
-      if (priv->blocked)
+
+      if (rate < 0.0) {
+        GstClockTime temp_time = start;
+        GstSeekType temp_type = start_type;
+
+        start = stop;
+        start_type = stop_type;
+        stop = temp_time;
+        stop_type = temp_type;
+      }
+
+      seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
+          start, stop_type, stop);
+
+      gst_event_set_seek_trickmode_interval (seek_event, trickmode_interval);
+
+      if (!media->priv->blocked) {
+        /* Prevent a race condition with multiple streams,
+         * where one stream may have time to preroll before others
+         * have even started flushing, causing async-done to be
+         * posted too early.
+         */
         media_streams_set_blocked (media, TRUE);
+        unblock = TRUE;
+      }
 
-      /* FIXME, we only do forwards playback, no trick modes yet */
-      res = gst_element_seek (priv->pipeline, 1.0, GST_FORMAT_TIME,
-          flags, start_type, start, stop_type, stop);
+      res = gst_element_send_event (priv->pipeline, seek_event);
+
+      if (unblock)
+        media_streams_set_blocked (media, FALSE);
 
       /* and block for the seek to complete */
       GST_INFO ("done seeking %d", res);
@@ -2005,6 +2906,12 @@ not_prepared:
     GST_INFO ("media %p is not prepared", media);
     return FALSE;
   }
+not_complete:
+  {
+    g_rec_mutex_unlock (&priv->state_lock);
+    GST_INFO ("pipeline is not complete");
+    return FALSE;
+  }
 not_seekable:
   {
     g_rec_mutex_unlock (&priv->state_lock);
@@ -2029,6 +2936,47 @@ preroll_failed:
     GST_WARNING ("failed to preroll after seek");
     return FALSE;
   }
+preroll_failed_expected_async_done:
+  {
+    GST_WARNING ("failed to preroll");
+    return FALSE;
+  }
+}
+
+/**
+ * gst_rtsp_media_seek_full:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ * @flags: The minimal set of #GstSeekFlags to use
+ *
+ * Seek the pipeline of @media to @range with the given @flags.
+ * @media must be prepared with gst_rtsp_media_prepare().
+ *
+ * Returns: %TRUE on success.
+ * Since: 1.18
+ */
+gboolean
+gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range,
+    GstSeekFlags flags)
+{
+  return gst_rtsp_media_seek_trickmode (media, range, flags, 1.0, 0);
+}
+
+/**
+ * gst_rtsp_media_seek:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ *
+ * Seek the pipeline of @media to @range. @media must be prepared with
+ * gst_rtsp_media_prepare().
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+{
+  return gst_rtsp_media_seek_trickmode (media, range, GST_SEEK_FLAG_NONE,
+      1.0, 0);
 }
 
 static void
@@ -2104,8 +3052,9 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message)
       GST_DEBUG ("%p: went from %s to %s (pending %s)", media,
           gst_element_state_get_name (old), gst_element_state_get_name (new),
           gst_element_state_get_name (pending));
-      if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)
-          && old == GST_STATE_READY && new == GST_STATE_PAUSED) {
+      if (priv->no_more_pads_pending == 0
+          && gst_rtsp_media_is_receive_only (media) && old == GST_STATE_READY
+          && new == GST_STATE_PAUSED) {
         GST_INFO ("%p: went to PAUSED, prepared now", media);
         collect_media_stats (media);
 
@@ -2186,8 +3135,9 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message)
       s = gst_message_get_structure (message);
       if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
         GST_DEBUG ("media received blocking message");
-        if (priv->blocked && media_streams_blocking (media)) {
-          GST_DEBUG ("media is blocking");
+        if (priv->blocked && media_streams_blocking (media) &&
+            priv->no_more_pads_pending == 0) {
+          GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "media is blocking");
           collect_media_stats (media);
 
           if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
@@ -2199,15 +3149,13 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message)
     case GST_MESSAGE_STREAM_STATUS:
       break;
     case GST_MESSAGE_ASYNC_DONE:
-      if (priv->adding) {
-        /* when we are dynamically adding pads, the addition of the udpsrc will
-         * temporarily produce ASYNC_DONE messages. We have to ignore them and
-         * wait for the final ASYNC_DONE after everything prerolled */
-        GST_INFO ("%p: ignoring ASYNC_DONE", media);
-      } else {
-        GST_INFO ("%p: got ASYNC_DONE", media);
-        collect_media_stats (media);
-
+      if (priv->expected_async_done)
+        priv->expected_async_done = FALSE;
+      if (priv->complete) {
+        /* receive the final ASYNC_DONE, that is posted by the media pipeline
+         * after all the transport parts have been successfully added to
+         * the media streams. */
+        GST_DEBUG_OBJECT (media, "got async-done");
         if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
           gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
       }
@@ -2254,27 +3202,57 @@ watch_destroyed (GstRTSPMedia * media)
   g_object_unref (media);
 }
 
+static gboolean
+is_payloader (GstElement * element)
+{
+  GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element);
+  const gchar *klass;
+
+  klass = gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS);
+  if (klass == NULL)
+    return FALSE;
+
+  if (strstr (klass, "Payloader") && strstr (klass, "RTP")) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
 static GstElement *
-find_payload_element (GstElement * payloader)
+find_payload_element (GstElement * payloader, GstPad * pad)
 {
   GstElement *pay = NULL;
 
   if (GST_IS_BIN (payloader)) {
     GstIterator *iter;
     GValue item = { 0 };
+    gchar *pad_name, *payloader_name;
+    GstElement *element;
+
+    if ((element = gst_bin_get_by_name (GST_BIN (payloader), "pay"))) {
+      if (is_payloader (element))
+        return element;
+      gst_object_unref (element);
+    }
+
+    pad_name = gst_object_get_name (GST_OBJECT (pad));
+    payloader_name = g_strdup_printf ("pay_%s", pad_name);
+    g_free (pad_name);
+    if ((element = gst_bin_get_by_name (GST_BIN (payloader), payloader_name))) {
+      g_free (payloader_name);
+      if (is_payloader (element))
+        return element;
+      gst_object_unref (element);
+    } else {
+      g_free (payloader_name);
+    }
 
     iter = gst_bin_iterate_recurse (GST_BIN (payloader));
     while (gst_iterator_next (iter, &item) == GST_ITERATOR_OK) {
-      GstElement *element = (GstElement *) g_value_get_object (&item);
-      GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element);
-      const gchar *klass;
-
-      klass =
-          gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS);
-      if (klass == NULL)
-        continue;
+      element = (GstElement *) g_value_get_object (&item);
 
-      if (strstr (klass, "Payloader") && strstr (klass, "RTP")) {
+      if (is_payloader (element)) {
         pay = gst_object_ref (element);
         g_value_unset (&item);
         break;
@@ -2298,7 +3276,7 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
   GstElement *pay;
 
   /* find the real payload element */
-  pay = find_payload_element (element);
+  pay = find_payload_element (element, pad);
   stream = gst_rtsp_media_create_stream (media, pay, pad);
   gst_object_unref (pay);
 
@@ -2310,11 +3288,6 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
 
   g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream);
 
-  /* we will be adding elements below that will cause ASYNC_DONE to be
-   * posted in the bus. We want to ignore those messages until the
-   * pipeline really prerolled. */
-  priv->adding = TRUE;
-
   /* join the element in the PAUSED state because this callback is
    * called from the streaming thread and it is PAUSED */
   if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
@@ -2322,7 +3295,9 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
     GST_WARNING ("failed to join bin element");
   }
 
-  priv->adding = FALSE;
+  if (priv->blocked)
+    gst_rtsp_stream_set_blocked (stream, TRUE);
+
   g_rec_mutex_unlock (&priv->state_lock);
 
   return;
@@ -2353,26 +3328,7 @@ pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
   gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
   g_rec_mutex_unlock (&priv->state_lock);
 
-  gst_rtsp_media_remove_stream (media, stream);
-}
-
-static void
-remove_fakesink (GstRTSPMediaPrivate * priv)
-{
-  GstElement *fakesink;
-
-  g_mutex_lock (&priv->lock);
-  if ((fakesink = priv->fakesink))
-    gst_object_ref (fakesink);
-  priv->fakesink = NULL;
-  g_mutex_unlock (&priv->lock);
-
-  if (fakesink) {
-    gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
-    gst_element_set_state (fakesink, GST_STATE_NULL);
-    gst_object_unref (fakesink);
-    GST_INFO ("removed fakesink");
-  }
+  gst_rtsp_media_remove_stream (media, stream);
 }
 
 static void
@@ -2380,8 +3336,10 @@ no_more_pads_cb (GstElement * element, GstRTSPMedia * media)
 {
   GstRTSPMediaPrivate *priv = media->priv;
 
-  GST_INFO ("no more pads");
-  remove_fakesink (priv);
+  GST_INFO_OBJECT (element, "no more pads");
+  g_mutex_lock (&priv->lock);
+  priv->no_more_pads_pending--;
+  g_mutex_unlock (&priv->lock);
 }
 
 typedef struct _DynPaySignalHandlers DynPaySignalHandlers;
@@ -2400,29 +3358,26 @@ start_preroll (GstRTSPMedia * media)
   GstStateChangeReturn ret;
 
   GST_INFO ("setting pipeline to PAUSED for media %p", media);
-  /* first go to PAUSED */
+
+  /* start blocked since it is possible that there are no sink elements yet */
+  media_streams_set_blocked (media, TRUE);
   ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
 
   switch (ret) {
     case GST_STATE_CHANGE_SUCCESS:
       GST_INFO ("SUCCESS state change for media %p", media);
-      priv->seekable = TRUE;
       break;
     case GST_STATE_CHANGE_ASYNC:
       GST_INFO ("ASYNC state change for media %p", media);
-      priv->seekable = TRUE;
       break;
     case GST_STATE_CHANGE_NO_PREROLL:
       /* we need to go to PLAYING */
       GST_INFO ("NO_PREROLL state change: live media %p", media);
       /* FIXME we disable seeking for live streams for now. We should perform a
        * seeking query in preroll instead */
-      priv->seekable = FALSE;
+      priv->seekable = -1;
       priv->is_live = TRUE;
-      if (!(priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) {
-        /* start blocked  to make sure nothing goes to the sink */
-        media_streams_set_blocked (media, TRUE);
-      }
+
       ret = set_state (media, GST_STATE_PLAYING);
       if (ret == GST_STATE_CHANGE_FAILURE)
         goto state_failed;
@@ -2467,6 +3422,57 @@ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
   GstRTSPMediaPrivate *priv = media->priv;
   GstRTSPStream *stream = NULL;
   guint i;
+  GstElement *res = NULL;
+
+  g_mutex_lock (&priv->lock);
+  for (i = 0; i < priv->streams->len; i++) {
+    stream = g_ptr_array_index (priv->streams, i);
+
+    if (sessid == gst_rtsp_stream_get_index (stream))
+      break;
+
+    stream = NULL;
+  }
+  g_mutex_unlock (&priv->lock);
+
+  if (stream)
+    res = gst_rtsp_stream_request_aux_sender (stream, sessid);
+
+  return res;
+}
+
+static GstElement *
+request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+  GstRTSPStream *stream = NULL;
+  guint i;
+  GstElement *res = NULL;
+
+  g_mutex_lock (&priv->lock);
+  for (i = 0; i < priv->streams->len; i++) {
+    stream = g_ptr_array_index (priv->streams, i);
+
+    if (sessid == gst_rtsp_stream_get_index (stream))
+      break;
+
+    stream = NULL;
+  }
+  g_mutex_unlock (&priv->lock);
+
+  if (stream)
+    res = gst_rtsp_stream_request_aux_receiver (stream, sessid);
+
+  return res;
+}
+
+static GstElement *
+request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+  GstRTSPStream *stream = NULL;
+  guint i;
+  GstElement *res = NULL;
 
   g_mutex_lock (&priv->lock);
   for (i = 0; i < priv->streams->len; i++) {
@@ -2474,10 +3480,24 @@ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
 
     if (sessid == gst_rtsp_stream_get_index (stream))
       break;
+
+    stream = NULL;
   }
   g_mutex_unlock (&priv->lock);
 
-  return gst_rtsp_stream_request_aux_sender (stream, sessid);
+  if (stream) {
+    res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid);
+  }
+
+  return res;
+}
+
+static void
+new_storage_cb (GstElement * rtpbin, GObject * storage, guint sessid,
+    GstRTSPMedia * media)
+{
+  g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND,
+      NULL);
 }
 
 static gboolean
@@ -2487,6 +3507,15 @@ start_prepare (GstRTSPMedia * media)
   guint i;
   GList *walk;
 
+  g_rec_mutex_lock (&priv->state_lock);
+  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
+    goto no_longer_preparing;
+
+  g_signal_connect (priv->rtpbin, "new-storage", G_CALLBACK (new_storage_cb),
+      media);
+  g_signal_connect (priv->rtpbin, "request-fec-decoder",
+      G_CALLBACK (request_fec_decoder), media);
+
   /* link streams we already have, other streams might appear when we have
    * dynamic elements */
   for (i = 0; i < priv->streams->len; i++) {
@@ -2500,6 +3529,11 @@ start_prepare (GstRTSPMedia * media)
           (GCallback) request_aux_sender, media);
     }
 
+    if (priv->do_retransmission) {
+      g_signal_connect (priv->rtpbin, "request-aux-receiver",
+          (GCallback) request_aux_receiver, media);
+    }
+
     if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
             priv->rtpbin, GST_STATE_NULL)) {
       goto join_bin_failed;
@@ -2507,7 +3541,8 @@ start_prepare (GstRTSPMedia * media)
   }
 
   if (priv->rtpbin)
-    g_object_set (priv->rtpbin, "do-retransmission", priv->rtx_time > 0, NULL);
+    g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission,
+        "do-lost", TRUE, NULL);
 
   for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
     GstElement *elem = walk->data;
@@ -2523,30 +3558,39 @@ start_prepare (GstRTSPMedia * media)
         (GCallback) no_more_pads_cb, media);
 
     g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers);
-
-    if (!priv->fakesink) {
-      /* we add a fakesink here in order to make the state change async. We remove
-       * the fakesink again in the no-more-pads callback. */
-      priv->fakesink = gst_element_factory_make ("fakesink", "fakesink");
-      gst_bin_add (GST_BIN (priv->pipeline), priv->fakesink);
-    }
   }
 
-  if (!start_preroll (media))
+  if (priv->nb_dynamic_elements == 0 && gst_rtsp_media_is_receive_only (media)) {
+    /* If we are receive_only (RECORD), do not try to preroll, to avoid
+     * a second ASYNC state change failing */
+    priv->is_live = TRUE;
+    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+  } else if (!start_preroll (media)) {
     goto preroll_failed;
+  }
+
+  g_rec_mutex_unlock (&priv->state_lock);
 
   return FALSE;
 
+no_longer_preparing:
+  {
+    GST_INFO ("media is no longer preparing");
+    g_rec_mutex_unlock (&priv->state_lock);
+    return FALSE;
+  }
 join_bin_failed:
   {
     GST_WARNING ("failed to join bin element");
     gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+    g_rec_mutex_unlock (&priv->state_lock);
     return FALSE;
   }
 preroll_failed:
   {
     GST_WARNING ("failed to preroll pipeline");
     gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+    g_rec_mutex_unlock (&priv->state_lock);
     return FALSE;
   }
 }
@@ -2603,7 +3647,8 @@ default_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
 
   /* do remainder in context */
   source = g_idle_source_new ();
-  g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL);
+  g_source_set_callback (source, (GSourceFunc) start_prepare,
+      g_object_ref (media), (GDestroyNotify) g_object_unref);
   g_source_attach (source, context);
   g_source_unref (source);
 
@@ -2669,8 +3714,9 @@ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
 
   /* reset some variables */
   priv->is_live = FALSE;
-  priv->seekable = FALSE;
+  priv->seekable = -1;
   priv->buffering = FALSE;
+  priv->no_more_pads_pending = priv->nb_dynamic_elements;
 
   /* we're preparing now */
   gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
@@ -2759,6 +3805,10 @@ finish_unprepare (GstRTSPMedia * media)
   gint i;
   GList *walk;
 
+  if (priv->finishing_unprepare)
+    return;
+  priv->finishing_unprepare = TRUE;
+
   GST_DEBUG ("shutting down");
 
   /* release the lock on shutdown, otherwise pad_added_cb might try to
@@ -2766,7 +3816,8 @@ finish_unprepare (GstRTSPMedia * media)
   g_rec_mutex_unlock (&priv->state_lock);
   set_state (media, GST_STATE_NULL);
   g_rec_mutex_lock (&priv->state_lock);
-  remove_fakesink (priv);
+
+  media_streams_set_blocked (media, FALSE);
 
   for (i = 0; i < priv->streams->len; i++) {
     GstRTSPStream *stream;
@@ -2820,6 +3871,8 @@ finish_unprepare (GstRTSPMedia * media)
     GST_DEBUG ("stop thread");
     gst_rtsp_thread_stop (priv->thread);
   }
+
+  priv->finishing_unprepare = FALSE;
 }
 
 /* called with state-lock */
@@ -2872,8 +3925,6 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media)
     goto is_busy;
 
   GST_INFO ("unprepare media %p", media);
-  if (priv->blocked)
-    media_streams_set_blocked (media, FALSE);
   set_target_state (media, GST_STATE_NULL, FALSE);
   success = TRUE;
 
@@ -2884,6 +3935,7 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media)
     if (klass->unprepare)
       success = klass->unprepare (media);
   } else {
+    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
     finish_unprepare (media);
   }
   g_rec_mutex_unlock (&priv->state_lock);
@@ -2923,7 +3975,7 @@ get_clock_unlocked (GstRTSPMedia * media)
  *
  * @media must be prepared before this method returns a valid clock object.
  *
- * Returns: (transfer full): the #GstClock used by @media. unref after usage.
+ * Returns: (transfer full) (nullable): the #GstClock used by @media. unref after usage.
  */
 GstClock *
 gst_rtsp_media_get_clock (GstRTSPMedia * media)
@@ -2972,582 +4024,108 @@ gst_rtsp_media_get_base_time (GstRTSPMedia * media)
   return result;
 
   /* ERRORS */
-not_prepared:
-  {
-    g_rec_mutex_unlock (&priv->state_lock);
-    GST_DEBUG_OBJECT (media, "media was not prepared");
-    return GST_CLOCK_TIME_NONE;
-  }
-}
-
-/**
- * gst_rtsp_media_get_time_provider:
- * @media: a #GstRTSPMedia
- * @address: (allow-none): an address or %NULL
- * @port: a port or 0
- *
- * Get the #GstNetTimeProvider for the clock used by @media. The time provider
- * will listen on @address and @port for client time requests.
- *
- * Returns: (transfer full): the #GstNetTimeProvider of @media.
- */
-GstNetTimeProvider *
-gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address,
-    guint16 port)
-{
-  GstRTSPMediaPrivate *priv;
-  GstNetTimeProvider *provider = NULL;
-
-  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
-
-  priv = media->priv;
-
-  g_rec_mutex_lock (&priv->state_lock);
-  if (priv->time_provider) {
-    if ((provider = priv->nettime) == NULL) {
-      GstClock *clock;
-
-      if (priv->time_provider && (clock = get_clock_unlocked (media))) {
-        provider = gst_net_time_provider_new (clock, address, port);
-        gst_object_unref (clock);
-
-        priv->nettime = provider;
-      }
-    }
-  }
-  g_rec_mutex_unlock (&priv->state_lock);
-
-  if (provider)
-    gst_object_ref (provider);
-
-  return provider;
-}
-
-static gboolean
-default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info)
-{
-  return gst_rtsp_sdp_from_media (sdp, info, media);
-}
-
-/**
- * gst_rtsp_media_setup_sdp:
- * @media: a #GstRTSPMedia
- * @sdp: (transfer none): a #GstSDPMessage
- * @info: (transfer none): a #GstSDPInfo
- *
- * Add @media specific info to @sdp. @info is used to configure the connection
- * information in the SDP.
- *
- * Returns: TRUE on success.
- */
-gboolean
-gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
-    GstSDPInfo * info)
-{
-  GstRTSPMediaPrivate *priv;
-  GstRTSPMediaClass *klass;
-  gboolean res;
-
-  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
-  g_return_val_if_fail (sdp != NULL, FALSE);
-  g_return_val_if_fail (info != NULL, FALSE);
-
-  priv = media->priv;
-
-  g_rec_mutex_lock (&priv->state_lock);
-
-  klass = GST_RTSP_MEDIA_GET_CLASS (media);
-
-  if (!klass->setup_sdp)
-    goto no_setup_sdp;
-
-  res = klass->setup_sdp (media, sdp, info);
-
-  g_rec_mutex_unlock (&priv->state_lock);
-
-  return res;
-
-  /* ERRORS */
-no_setup_sdp:
-  {
-    g_rec_mutex_unlock (&priv->state_lock);
-    GST_ERROR ("no setup_sdp function");
-    g_critical ("no setup_sdp vmethod function set");
-    return FALSE;
-  }
-}
-
-static const gchar *
-rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name,
-    gint pt)
-{
-  guint i;
-
-  for (i = 0;; i++) {
-    const gchar *attr;
-    gint val;
-
-    if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL)
-      break;
-
-    if (sscanf (attr, "%d ", &val) != 1)
-      continue;
-
-    if (val == pt)
-      return attr;
-  }
-  return NULL;
-}
-
-#define PARSE_INT(p, del, res)          \
-G_STMT_START {                          \
-  gchar *t = p;                         \
-  p = strstr (p, del);                  \
-  if (p == NULL)                        \
-    res = -1;                           \
-  else {                                \
-    *p = '\0';                          \
-    p++;                                \
-    res = atoi (t);                     \
-  }                                     \
-} G_STMT_END
-
-#define PARSE_STRING(p, del, res)       \
-G_STMT_START {                          \
-  gchar *t = p;                         \
-  p = strstr (p, del);                  \
-  if (p == NULL) {                      \
-    res = NULL;                         \
-    p = t;                              \
-  }                                     \
-  else {                                \
-    *p = '\0';                          \
-    p++;                                \
-    res = t;                            \
-  }                                     \
-} G_STMT_END
-
-#define SKIP_SPACES(p)                  \
-  while (*p && g_ascii_isspace (*p))    \
-    p++;
-
-/* rtpmap contains:
- *
- *  <payload> <encoding_name>/<clock_rate>[/<encoding_params>]
- */
-static gboolean
-parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name,
-    gint * rate, gchar ** params)
-{
-  gchar *p, *t;
-
-  p = (gchar *) rtpmap;
-
-  PARSE_INT (p, " ", *payload);
-  if (*payload == -1)
-    return FALSE;
-
-  SKIP_SPACES (p);
-  if (*p == '\0')
-    return FALSE;
-
-  PARSE_STRING (p, "/", *name);
-  if (*name == NULL) {
-    GST_DEBUG ("no rate, name %s", p);
-    /* no rate, assume -1 then, this is not supposed to happen but RealMedia
-     * streams seem to omit the rate. */
-    *name = p;
-    *rate = -1;
-    return TRUE;
-  }
-
-  t = p;
-  p = strstr (p, "/");
-  if (p == NULL) {
-    *rate = atoi (t);
-    return TRUE;
-  }
-  *p = '\0';
-  p++;
-  *rate = atoi (t);
-
-  t = p;
-  if (*p == '\0')
-    return TRUE;
-  *params = t;
-
-  return TRUE;
-}
-
-/*
- *  Mapping of caps to and from SDP fields:
- *
- *   a=rtpmap:<payload> <encoding_name>/<clock_rate>[/<encoding_params>]
- *   a=framesize:<payload> <width>-<height>
- *   a=fmtp:<payload> <param>[=<value>];...
- */
-static GstCaps *
-media_to_caps (gint pt, const GstSDPMedia * media)
-{
-  GstCaps *caps;
-  const gchar *rtpmap;
-  const gchar *fmtp;
-  const gchar *framesize;
-  gchar *name = NULL;
-  gint rate = -1;
-  gchar *params = NULL;
-  gchar *tmp;
-  GstStructure *s;
-  gint payload = 0;
-  gboolean ret;
-
-  /* get and parse rtpmap */
-  rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt);
-
-  if (rtpmap) {
-    ret = parse_rtpmap (rtpmap, &payload, &name, &rate, &params);
-    if (!ret) {
-      g_warning ("error parsing rtpmap, ignoring");
-      rtpmap = NULL;
-    }
-  }
-  /* dynamic payloads need rtpmap or we fail */
-  if (rtpmap == NULL && pt >= 96)
-    goto no_rtpmap;
-
-  /* check if we have a rate, if not, we need to look up the rate from the
-   * default rates based on the payload types. */
-  if (rate == -1) {
-    const GstRTPPayloadInfo *info;
-
-    if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) {
-      /* dynamic types, use media and encoding_name */
-      tmp = g_ascii_strdown (media->media, -1);
-      info = gst_rtp_payload_info_for_name (tmp, name);
-      g_free (tmp);
-    } else {
-      /* static types, use payload type */
-      info = gst_rtp_payload_info_for_pt (pt);
-    }
-
-    if (info) {
-      if ((rate = info->clock_rate) == 0)
-        rate = -1;
-    }
-    /* we fail if we cannot find one */
-    if (rate == -1)
-      goto no_rate;
-  }
-
-  tmp = g_ascii_strdown (media->media, -1);
-  caps = gst_caps_new_simple ("application/x-unknown",
-      "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL);
-  g_free (tmp);
-  s = gst_caps_get_structure (caps, 0);
-
-  gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL);
-
-  /* encoding name must be upper case */
-  if (name != NULL) {
-    tmp = g_ascii_strup (name, -1);
-    gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL);
-    g_free (tmp);
-  }
-
-  /* params must be lower case */
-  if (params != NULL) {
-    tmp = g_ascii_strdown (params, -1);
-    gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL);
-    g_free (tmp);
-  }
-
-  /* parse optional fmtp: field */
-  if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) {
-    gchar *p;
-    gint payload = 0;
-
-    p = (gchar *) fmtp;
-
-    /* p is now of the format <payload> <param>[=<value>];... */
-    PARSE_INT (p, " ", payload);
-    if (payload != -1 && payload == pt) {
-      gchar **pairs;
-      gint i;
-
-      /* <param>[=<value>] are separated with ';' */
-      pairs = g_strsplit (p, ";", 0);
-      for (i = 0; pairs[i]; i++) {
-        gchar *valpos;
-        const gchar *val, *key;
-        gint j;
-        const gchar *reserved_keys[] =
-            { "media", "payload", "clock-rate", "encoding-name",
-          "encoding-params"
-        };
-
-        /* the key may not have a '=', the value can have other '='s */
-        valpos = strstr (pairs[i], "=");
-        if (valpos) {
-          /* we have a '=' and thus a value, remove the '=' with \0 */
-          *valpos = '\0';
-          /* value is everything between '=' and ';'. We split the pairs at ;
-           * boundaries so we can take the remainder of the value. Some servers
-           * put spaces around the value which we strip off here. Alternatively
-           * we could strip those spaces in the depayloaders should these spaces
-           * actually carry any meaning in the future. */
-          val = g_strstrip (valpos + 1);
-        } else {
-          /* simple <param>;.. is translated into <param>=1;... */
-          val = "1";
-        }
-        /* strip the key of spaces, convert key to lowercase but not the value. */
-        key = g_strstrip (pairs[i]);
-
-        /* skip keys from the fmtp, which we already use ourselves for the
-         * caps. Some software is adding random things like clock-rate into
-         * the fmtp, and we would otherwise here set a string-typed clock-rate
-         * in the caps... and thus fail to create valid RTP caps
-         */
-        for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) {
-          if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) {
-            key = "";
-            break;
-          }
-        }
-
-        if (strlen (key) > 1) {
-          tmp = g_ascii_strdown (key, -1);
-          gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL);
-          g_free (tmp);
-        }
-      }
-      g_strfreev (pairs);
-    }
-  }
-
-  /* parse framesize: field */
-  if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) {
-    gchar *p;
-
-    /* p is now of the format <payload> <width>-<height> */
-    p = (gchar *) framesize;
-
-    PARSE_INT (p, " ", payload);
-    if (payload != -1 && payload == pt) {
-      gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL);
-    }
-  }
-  return caps;
-
-  /* ERRORS */
-no_rtpmap:
-  {
-    g_warning ("rtpmap type not given for dynamic payload %d", pt);
-    return NULL;
-  }
-no_rate:
+not_prepared:
   {
-    g_warning ("rate unknown for payload type %d", pt);
-    return NULL;
+    g_rec_mutex_unlock (&priv->state_lock);
+    GST_DEBUG_OBJECT (media, "media was not prepared");
+    return GST_CLOCK_TIME_NONE;
   }
 }
 
-static gboolean
-parse_keymgmt (const gchar * keymgmt, GstCaps * caps)
+/**
+ * gst_rtsp_media_get_time_provider:
+ * @media: a #GstRTSPMedia
+ * @address: (allow-none): an address or %NULL
+ * @port: a port or 0
+ *
+ * Get the #GstNetTimeProvider for the clock used by @media. The time provider
+ * will listen on @address and @port for client time requests.
+ *
+ * Returns: (transfer full): the #GstNetTimeProvider of @media.
+ */
+GstNetTimeProvider *
+gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address,
+    guint16 port)
 {
-  gboolean res = FALSE;
-  gchar *p, *kmpid;
-  gsize size;
-  guchar *data;
-  GstMIKEYMessage *msg;
-  const GstMIKEYPayload *payload;
-  const gchar *srtp_cipher;
-  const gchar *srtp_auth;
-
-  p = (gchar *) keymgmt;
+  GstRTSPMediaPrivate *priv;
+  GstNetTimeProvider *provider = NULL;
 
-  SKIP_SPACES (p);
-  if (*p == '\0')
-    return FALSE;
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
 
-  PARSE_STRING (p, " ", kmpid);
-  if (!g_str_equal (kmpid, "mikey"))
-    return FALSE;
+  priv = media->priv;
 
-  data = g_base64_decode (p, &size);
-  if (data == NULL)
-    return FALSE;
+  g_rec_mutex_lock (&priv->state_lock);
+  if (priv->time_provider) {
+    if ((provider = priv->nettime) == NULL) {
+      GstClock *clock;
 
-  msg = gst_mikey_message_new_from_data (data, size, NULL, NULL);
-  g_free (data);
-  if (msg == NULL)
-    return FALSE;
+      if (priv->time_provider && (clock = get_clock_unlocked (media))) {
+        provider = gst_net_time_provider_new (clock, address, port);
+        gst_object_unref (clock);
 
-  srtp_cipher = "aes-128-icm";
-  srtp_auth = "hmac-sha1-80";
-
-  /* check the Security policy if any */
-  if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) {
-    GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload;
-    guint len, i;
-
-    if (p->proto != GST_MIKEY_SEC_PROTO_SRTP)
-      goto done;
-
-    len = gst_mikey_payload_sp_get_n_params (payload);
-    for (i = 0; i < len; i++) {
-      const GstMIKEYPayloadSPParam *param =
-          gst_mikey_payload_sp_get_param (payload, i);
-
-      switch (param->type) {
-        case GST_MIKEY_SP_SRTP_ENC_ALG:
-          switch (param->val[0]) {
-            case 0:
-              srtp_cipher = "null";
-              break;
-            case 2:
-            case 1:
-              srtp_cipher = "aes-128-icm";
-              break;
-            default:
-              break;
-          }
-          break;
-        case GST_MIKEY_SP_SRTP_ENC_KEY_LEN:
-          switch (param->val[0]) {
-            case AES_128_KEY_LEN:
-              srtp_cipher = "aes-128-icm";
-              break;
-            case AES_256_KEY_LEN:
-              srtp_cipher = "aes-256-icm";
-              break;
-            default:
-              break;
-          }
-          break;
-        case GST_MIKEY_SP_SRTP_AUTH_ALG:
-          switch (param->val[0]) {
-            case 0:
-              srtp_auth = "null";
-              break;
-            case 2:
-            case 1:
-              srtp_auth = "hmac-sha1-80";
-              break;
-            default:
-              break;
-          }
-          break;
-        case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN:
-          switch (param->val[0]) {
-            case HMAC_32_KEY_LEN:
-              srtp_auth = "hmac-sha1-32";
-              break;
-            case HMAC_80_KEY_LEN:
-              srtp_auth = "hmac-sha1-80";
-              break;
-            default:
-              break;
-          }
-          break;
-        case GST_MIKEY_SP_SRTP_SRTP_ENC:
-          break;
-        case GST_MIKEY_SP_SRTP_SRTCP_ENC:
-          break;
-        default:
-          break;
+        priv->nettime = provider;
       }
     }
   }
+  g_rec_mutex_unlock (&priv->state_lock);
 
-  if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0)))
-    goto done;
-  else {
-    GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload;
-    const GstMIKEYPayload *sub;
-    GstMIKEYPayloadKeyData *pkd;
-    GstBuffer *buf;
-
-    if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL)
-      goto done;
-
-    if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0)))
-      goto done;
-
-    if (sub->type != GST_MIKEY_PT_KEY_DATA)
-      goto done;
-
-    pkd = (GstMIKEYPayloadKeyData *) sub;
-    buf =
-        gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len),
-        pkd->key_len);
-    gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL);
-  }
-
-  gst_caps_set_simple (caps,
-      "srtp-cipher", G_TYPE_STRING, srtp_cipher,
-      "srtp-auth", G_TYPE_STRING, srtp_auth,
-      "srtcp-cipher", G_TYPE_STRING, srtp_cipher,
-      "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL);
+  if (provider)
+    gst_object_ref (provider);
 
-  res = TRUE;
-done:
-  gst_mikey_message_unref (msg);
+  return provider;
+}
 
-  return res;
+static gboolean
+default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info)
+{
+  return gst_rtsp_sdp_from_media (sdp, info, media);
 }
 
-/*
- * Mapping SDP attributes to caps
+/**
+ * gst_rtsp_media_setup_sdp:
+ * @media: a #GstRTSPMedia
+ * @sdp: (transfer none): a #GstSDPMessage
+ * @info: (transfer none): a #GstSDPInfo
  *
- * prepend 'a-' to IANA registered sdp attributes names
- * (ie: not prefixed with 'x-') in order to avoid
- * collision with gstreamer standard caps properties names
+ * Add @media specific info to @sdp. @info is used to configure the connection
+ * information in the SDP.
+ *
+ * Returns: TRUE on success.
  */
-static void
-sdp_attributes_to_caps (GArray * attributes, GstCaps * caps)
+gboolean
+gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
+    GstSDPInfo * info)
 {
-  if (attributes->len > 0) {
-    GstStructure *s;
-    guint i;
+  GstRTSPMediaPrivate *priv;
+  GstRTSPMediaClass *klass;
+  gboolean res;
 
-    s = gst_caps_get_structure (caps, 0);
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+  g_return_val_if_fail (sdp != NULL, FALSE);
+  g_return_val_if_fail (info != NULL, FALSE);
 
-    for (i = 0; i < attributes->len; i++) {
-      GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i);
-      gchar *tofree, *key;
+  priv = media->priv;
 
-      key = attr->key;
+  g_rec_mutex_lock (&priv->state_lock);
 
-      /* skip some of the attribute we already handle */
-      if (!strcmp (key, "fmtp"))
-        continue;
-      if (!strcmp (key, "rtpmap"))
-        continue;
-      if (!strcmp (key, "control"))
-        continue;
-      if (!strcmp (key, "range"))
-        continue;
-      if (!strcmp (key, "framesize"))
-        continue;
-      if (g_str_equal (key, "key-mgmt")) {
-        parse_keymgmt (attr->value, caps);
-        continue;
-      }
+  klass = GST_RTSP_MEDIA_GET_CLASS (media);
 
-      /* string must be valid UTF8 */
-      if (!g_utf8_validate (attr->value, -1, NULL))
-        continue;
+  if (!klass->setup_sdp)
+    goto no_setup_sdp;
+
+  res = klass->setup_sdp (media, sdp, info);
 
-      if (!g_str_has_prefix (key, "x-"))
-        tofree = key = g_strdup_printf ("a-%s", key);
-      else
-        tofree = NULL;
+  g_rec_mutex_unlock (&priv->state_lock);
 
-      GST_DEBUG ("adding caps: %s=%s", key, attr->value);
-      gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL);
-      g_free (tofree);
-    }
+  return res;
+
+  /* ERRORS */
+no_setup_sdp:
+  {
+    g_rec_mutex_unlock (&priv->state_lock);
+    GST_ERROR ("no setup_sdp function");
+    g_critical ("no setup_sdp vmethod function set");
+    return FALSE;
   }
 }
 
@@ -3565,7 +4143,7 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
   }
 
   for (i = 0; i < medias_len; i++) {
-    const gchar *proto, *media_type;
+    const gchar *proto;
     const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
     GstRTSPStream *stream;
     gint j, formats_len;
@@ -3584,16 +4162,12 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
     }
 
     if (g_str_equal (proto, "RTP/AVP")) {
-      media_type = "application/x-rtp";
       profile = GST_RTSP_PROFILE_AVP;
     } else if (g_str_equal (proto, "RTP/SAVP")) {
-      media_type = "application/x-srtp";
       profile = GST_RTSP_PROFILE_SAVP;
     } else if (g_str_equal (proto, "RTP/AVPF")) {
-      media_type = "application/x-rtp";
       profile = GST_RTSP_PROFILE_AVPF;
     } else if (g_str_equal (proto, "RTP/SAVPF")) {
-      media_type = "application/x-srtp";
       profile = GST_RTSP_PROFILE_SAVPF;
     } else {
       GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
@@ -3617,7 +4191,7 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
       GST_DEBUG (" looking at %d pt: %d", j, pt);
 
       /* convert caps */
-      caps = media_to_caps (pt, sdp_media);
+      caps = gst_sdp_media_get_caps_from_media (sdp_media, pt);
       if (caps == NULL) {
         GST_WARNING (" skipping pt %d without caps", pt);
         continue;
@@ -3625,12 +4199,15 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
 
       /* do some tweaks */
       GST_DEBUG ("mapping sdp session level attributes to caps");
-      sdp_attributes_to_caps (sdp->attributes, caps);
+      gst_sdp_message_attributes_to_caps (sdp, caps);
       GST_DEBUG ("mapping sdp media level attributes to caps");
-      sdp_attributes_to_caps (sdp_media->attributes, caps);
+      gst_sdp_media_attributes_to_caps (sdp_media, caps);
 
       s = gst_caps_get_structure (caps, 0);
-      gst_structure_set_name (s, media_type);
+      gst_structure_set_name (s, "application/x-rtp");
+
+      if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+        gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);
 
       gst_rtsp_stream_set_pt_map (stream, pt, caps);
       gst_caps_unref (caps);
@@ -3693,8 +4270,11 @@ static void
 do_set_seqnum (GstRTSPStream * stream)
 {
   guint16 seq_num;
-  seq_num = gst_rtsp_stream_get_current_seqnum (stream);
-  gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
+
+  if (gst_rtsp_stream_is_sender (stream)) {
+    seq_num = gst_rtsp_stream_get_current_seqnum (stream);
+    gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
+  }
 }
 
 /* call with state_lock */
@@ -3703,7 +4283,6 @@ default_suspend (GstRTSPMedia * media)
 {
   GstRTSPMediaPrivate *priv = media->priv;
   GstStateChangeReturn ret;
-  gboolean unblock = FALSE;
 
   switch (priv->suspend_mode) {
     case GST_RTSP_SUSPEND_MODE_NONE:
@@ -3714,7 +4293,6 @@ default_suspend (GstRTSPMedia * media)
       ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
       if (ret == GST_STATE_CHANGE_FAILURE)
         goto state_failed;
-      unblock = TRUE;
       break;
     case GST_RTSP_SUSPEND_MODE_RESET:
       GST_DEBUG ("media %p suspend to NULL", media);
@@ -3727,16 +4305,11 @@ default_suspend (GstRTSPMedia * media)
        * is actually from NULL to PLAY will create a new sequence
        * number. */
       g_ptr_array_foreach (priv->streams, (GFunc) do_set_seqnum, NULL);
-      unblock = TRUE;
       break;
     default:
       break;
   }
 
-  /* let the streams do the state changes freely, if any */
-  if (unblock)
-    media_streams_set_blocked (media, FALSE);
-
   return TRUE;
 
   /* ERRORS */
@@ -3810,10 +4383,28 @@ static gboolean
 default_unsuspend (GstRTSPMedia * media)
 {
   GstRTSPMediaPrivate *priv = media->priv;
+  gboolean preroll_ok;
 
   switch (priv->suspend_mode) {
     case GST_RTSP_SUSPEND_MODE_NONE:
-      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+      if (gst_rtsp_media_is_receive_only (media))
+        break;
+      if (media_streams_blocking (media)) {
+        gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+        /* at this point the media pipeline has been updated and contain all
+         * specific transport parts: all active streams contain at least one sink
+         * element and it's safe to unblock all blocked streams */
+        media_unblock (media);
+      } else {
+        /* streams are not blocked and media is suspended from PAUSED */
+        gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+      }
+      g_rec_mutex_unlock (&priv->state_lock);
+      if (gst_rtsp_media_get_status (media) == GST_RTSP_MEDIA_STATUS_ERROR) {
+        g_rec_mutex_lock (&priv->state_lock);
+        goto preroll_failed;
+      }
+      g_rec_mutex_lock (&priv->state_lock);
       break;
     case GST_RTSP_SUSPEND_MODE_PAUSE:
       gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
@@ -3821,14 +4412,19 @@ default_unsuspend (GstRTSPMedia * media)
     case GST_RTSP_SUSPEND_MODE_RESET:
     {
       gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+      /* at this point the media pipeline has been updated and contain all
+       * specific transport parts: all active streams contain at least one sink
+       * element and it's safe to unblock all blocked streams */
+      media_unblock (media);
       if (!start_preroll (media))
         goto start_failed;
+
       g_rec_mutex_unlock (&priv->state_lock);
+      preroll_ok = wait_preroll (media);
+      g_rec_mutex_lock (&priv->state_lock);
 
-      if (!wait_preroll (media))
+      if (!preroll_ok)
         goto preroll_failed;
-
-      g_rec_mutex_lock (&priv->state_lock);
     }
     default:
       break;
@@ -3896,6 +4492,8 @@ static void
 media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state)
 {
   GstRTSPMediaPrivate *priv = media->priv;
+  GstStateChangeReturn set_state_ret;
+  priv->expected_async_done = FALSE;
 
   if (state == GST_STATE_NULL) {
     gst_rtsp_media_unprepare (media);
@@ -3909,13 +4507,17 @@ media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state)
     } else {
       if (state == GST_STATE_PLAYING)
         /* make sure pads are not blocking anymore when going to PLAYING */
-        media_streams_set_blocked (media, FALSE);
+        media_unblock (media);
 
-      set_state (media, state);
-
-      /* and suspend after pause */
-      if (state == GST_STATE_PAUSED)
+      if (state == GST_STATE_PAUSED) {
+        set_state_ret = set_state (media, state);
+        if (set_state_ret == GST_STATE_CHANGE_ASYNC)
+          priv->expected_async_done = TRUE;
+        /* and suspend after pause */
         gst_rtsp_media_suspend (media);
+      } else {
+        set_state (media, state);
+      }
     }
   }
 }
@@ -3965,6 +4567,13 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
   priv = media->priv;
 
   g_rec_mutex_lock (&priv->state_lock);
+
+  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING
+      && gst_rtsp_media_is_shared (media)) {
+    g_rec_mutex_unlock (&priv->state_lock);
+    gst_rtsp_media_get_status (media);
+    g_rec_mutex_lock (&priv->state_lock);
+  }
   if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR)
     goto error_status;
   if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
@@ -4023,8 +4632,10 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
   /* we just activated the first media, do the playing state change */
   if (old_active == 0 && activate)
     do_state = TRUE;
-  /* if we have no more active media, do the downward state changes */
-  else if (priv->n_active == 0)
+  /* if we have no more active media and prepare count is not indicate 
+   * that there are new session/sessions ongoing,
+   * do the downward state changes */
+  else if (priv->n_active == 0 && priv->prepare_count <= 1)
     do_state = TRUE;
   else
     do_state = FALSE;
@@ -4033,11 +4644,11 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
       media, do_state);
 
   if (priv->target_state != state) {
-    if (do_state)
+    if (do_state) {
       media_set_pipeline_state_locked (media, state);
-
-    g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
-        NULL);
+      g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
+          NULL);
+    }
   }
 
   /* remember where we are */
@@ -4124,3 +4735,199 @@ gst_rtsp_media_get_transport_mode (GstRTSPMedia * media)
 
   return res;
 }
+
+/**
+ * gst_rtsp_media_seekable:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media seek and up to what point in time,
+ * it can seek.
+ *
+ * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning
+ * and > 0 to indicate the longest duration between any two random access points.
+ * %G_MAXINT64 means any value is possible.
+ *
+ * Since: 1.14
+ */
+GstClockTimeDiff
+gst_rtsp_media_seekable (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  GstClockTimeDiff res;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  priv = media->priv;
+
+  /* Currently we are not able to seek on live streams,
+   * and no stream is seekable only to the beginning */
+  g_mutex_lock (&priv->lock);
+  res = priv->seekable;
+  g_mutex_unlock (&priv->lock);
+
+  return res;
+}
+
+/**
+ * gst_rtsp_media_complete_pipeline:
+ * @media: a #GstRTSPMedia
+ * @transports: (element-type GstRTSPTransport): a list of #GstRTSPTransport
+ *
+ * Add a receiver and sender parts to the pipeline based on the transport from
+ * SETUP.
+ *
+ * Returns: %TRUE if the media pipeline has been sucessfully updated.
+ *
+ * Since: 1.14
+ */
+gboolean
+gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports)
+{
+  GstRTSPMediaPrivate *priv;
+  guint i;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+  g_return_val_if_fail (transports, FALSE);
+
+  GST_DEBUG_OBJECT (media, "complete pipeline");
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStreamTransport *transport;
+    GstRTSPStream *stream;
+    const GstRTSPTransport *rtsp_transport;
+
+    transport = g_ptr_array_index (transports, i);
+    if (!transport)
+      continue;
+
+    stream = gst_rtsp_stream_transport_get_stream (transport);
+    if (!stream)
+      continue;
+
+    rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
+
+    if (!gst_rtsp_stream_complete_stream (stream, rtsp_transport)) {
+      g_mutex_unlock (&priv->lock);
+      return FALSE;
+    }
+  }
+
+  priv->complete = TRUE;
+  g_mutex_unlock (&priv->lock);
+
+  return TRUE;
+}
+
+/**
+ * gst_rtsp_media_is_receive_only:
+ *
+ * Returns: %TRUE if @media is receive-only, %FALSE otherwise.
+ * Since: 1.18
+ */
+gboolean
+gst_rtsp_media_is_receive_only (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+  gboolean receive_only = TRUE;
+  guint i;
+
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+    if (gst_rtsp_stream_is_sender (stream) ||
+        !gst_rtsp_stream_is_receiver (stream)) {
+      receive_only = FALSE;
+      break;
+    }
+  }
+
+  return receive_only;
+}
+
+/**
+ * gst_rtsp_media_has_completed_sender:
+ *
+ * See gst_rtsp_stream_is_complete(), gst_rtsp_stream_is_sender().
+ *    
+ * Returns: whether @media has at least one complete sender stream.
+ * Since: 1.18
+ */
+gboolean
+gst_rtsp_media_has_completed_sender (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+  gboolean sender = FALSE;
+  guint i;
+
+  g_mutex_lock (&priv->lock);
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+    if (gst_rtsp_stream_is_complete (stream))
+      if (gst_rtsp_stream_is_sender (stream) ||
+          !gst_rtsp_stream_is_receiver (stream)) {
+        sender = TRUE;
+        break;
+      }
+  }
+  g_mutex_unlock (&priv->lock);
+
+  return sender;
+}
+
+/**
+ * gst_rtsp_media_set_rate_control:
+ *
+ * Define whether @media will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+void
+gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled)
+{
+  GstRTSPMediaPrivate *priv;
+  guint i;
+
+  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+  GST_LOG_OBJECT (media, "%s rate control", enabled ? "Enabling" : "Disabling");
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  priv->do_rate_control = enabled;
+  for (i = 0; i < priv->streams->len; i++) {
+    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+    gst_rtsp_stream_set_rate_control (stream, enabled);
+
+  }
+  g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_get_rate_control:
+ *
+ * Returns: whether @media will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+gboolean
+gst_rtsp_media_get_rate_control (GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv;
+  gboolean res;
+
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  priv = media->priv;
+
+  g_mutex_lock (&priv->lock);
+  res = priv->do_rate_control;
+  g_mutex_unlock (&priv->lock);
+
+  return res;
+}