Start support for RTSP 2.0
authorThibault Saunier <thibault.saunier@osg.samsung.com>
Sat, 22 Apr 2017 12:26:07 +0000 (09:26 -0300)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Thu, 5 Oct 2017 16:23:48 +0000 (13:23 -0300)
This adds basic support for new 2.0 features, though the protocol is
subposdely backward incompatible, most semantics are the sames.

This commit adds:

- features:
 * version negotiation
 * pipelined requests support
 * Media-Properties support
 * Accept-Ranges support

- APIs:
  * gst_rtsp_media_seekable

The RTSP methods that have been removed when using 2.0 now return
BAD_REQUEST.

https://bugzilla.gnome.org/show_bug.cgi?id=781446

gst/rtsp-server/rtsp-client.c
gst/rtsp-server/rtsp-media.c
gst/rtsp-server/rtsp-media.h

index f007bd5..2890407 100644 (file)
@@ -93,6 +93,11 @@ struct _GstRTSPClientPrivate
 
   guint rtsp_ctrl_timeout_id;
   guint rtsp_ctrl_timeout_cnt;
+
+  /* The version currently being used */
+  GstRTSPVersion version;
+
+  GHashTable *pipelined_requests;       /* pipelined_request_id -> session_id */
 };
 
 static GMutex tunnels_lock;
@@ -528,6 +533,8 @@ gst_rtsp_client_init (GstRTSPClient * client)
   priv->transports =
       g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
       g_object_unref);
+  priv->pipelined_requests = g_hash_table_new_full (g_str_hash,
+      g_str_equal, g_free, g_free);
 }
 
 static GstRTSPFilterResult
@@ -800,6 +807,10 @@ send_message (GstRTSPClient * client, GstRTSPContext * ctx,
   if (close)
     gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close");
 
+  if (ctx->request)
+    message->type_data.response.version =
+        ctx->request->type_data.request.version;
+
   g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE],
       0, ctx, message);
 
@@ -1251,6 +1262,12 @@ handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
     goto bad_request;
 
   if (size == 0 || !data || strlen ((char *) data) == 0) {
+    if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+      GST_ERROR_OBJECT (client, "Using PLAY request for keep-alive is forbidden"
+          " in RTSP 2.0");
+      goto bad_request;
+    }
+
     /* no body (or only '\0'), keep-alive request */
     send_generic_response (client, GST_RTSP_STS_OK, ctx);
   } else {
@@ -1498,6 +1515,7 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
   GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT;
   gchar *path, *rtpinfo;
   gint matched;
+  gchar *seek_style = NULL;
   GstRTSPStatusCode sig_result;
 
   if (!(session = ctx->session))
@@ -1546,10 +1564,26 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
   if (res == GST_RTSP_OK) {
     if (gst_rtsp_range_parse (str, &range) == GST_RTSP_OK) {
       GstRTSPMediaStatus media_status;
+      GstSeekFlags flags = 0;
+
+      if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_SEEK_STYLE,
+              &seek_style, 0)) {
+        if (g_strcmp0 (seek_style, "RAP") == 0)
+          flags = GST_SEEK_FLAG_ACCURATE;
+        else if (g_strcmp0 (seek_style, "CoRAP") == 0)
+          flags = GST_SEEK_FLAG_KEY_UNIT;
+        else if (g_strcmp0 (seek_style, "First-Prior") == 0)
+          flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_BEFORE;
+        else if (g_strcmp0 (seek_style, "Next") == 0)
+          flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_AFTER;
+        else
+          GST_FIXME_OBJECT (client, "Add support for seek style %s",
+              seek_style);
+      }
 
       /* we have a range, seek to the position */
       unit = range->unit;
-      gst_rtsp_media_seek (media, range);
+      gst_rtsp_media_seek_full (media, range, flags);
       gst_rtsp_range_free (range);
 
       media_status = gst_rtsp_media_get_status (media);
@@ -1570,6 +1604,9 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
   if (rtpinfo)
     gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO,
         rtpinfo);
+  if (seek_style)
+    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_SEEK_STYLE,
+        seek_style);
 
   /* add the range */
   str = gst_rtsp_media_get_range_string (media, TRUE, unit);
@@ -2208,6 +2245,7 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
   gint matched;
   gboolean new_session = FALSE;
   GstRTSPStatusCode sig_result;
+  gchar *pipelined_request_id = NULL, *accept_range = NULL;
 
   if (!ctx->uri)
     goto no_uri;
@@ -2223,6 +2261,11 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
   if (res != GST_RTSP_OK)
     goto no_transport;
 
+  /* Handle Pipelined-requests if using >= 2.0 */
+  if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0)
+    gst_rtsp_message_get_header (ctx->request,
+        GST_RTSP_HDR_PIPELINED_REQUESTS, &pipelined_request_id, 0);
+
   /* we create the session after parsing stuff so that we don't make
    * a session for malformed requests */
   if (priv->session_pool == NULL)
@@ -2292,6 +2335,9 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
     if (!(session = gst_rtsp_session_pool_create (priv->session_pool)))
       goto service_unavailable;
 
+    /* Pipelined requests should be cleared between sessions */
+    g_hash_table_remove_all (priv->pipelined_requests);
+
     /* make sure this client is closed when the session is closed */
     client_watch_session (client, session);
 
@@ -2303,6 +2349,11 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
     ctx->session = session;
   }
 
+  if (pipelined_request_id) {
+    g_hash_table_insert (client->priv->pipelined_requests,
+        g_strdup (pipelined_request_id),
+        g_strdup (gst_rtsp_session_get_sessionid (session)));
+  }
   rtsp_ctrl_timeout_remove (priv);
 
   if (!klass->configure_client_media (client, media, stream, ctx))
@@ -2328,6 +2379,33 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
       goto keymgmt_error;
   }
 
+  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+          &accept_range, 0) == GST_RTSP_OK) {
+    GEnumValue *runit = NULL;
+    gint i;
+    gchar **valid_ranges;
+    GEnumClass *runit_class = g_type_class_ref (GST_TYPE_RTSP_RANGE_UNIT);
+
+    gst_rtsp_message_dump (ctx->request);
+    valid_ranges = g_strsplit (accept_range, ",", -1);
+
+    for (i = 0; valid_ranges[i]; i++) {
+      gchar *range = valid_ranges[i];
+
+      while (*range == ' ')
+        range++;
+
+      runit = g_enum_get_value_by_nick (runit_class, range);
+      if (runit)
+        break;
+    }
+    g_strfreev (valid_ranges);
+    g_type_class_unref (runit_class);
+
+    if (!runit)
+      goto unsupported_range_unit;
+  }
+
   if (sessmedia == NULL) {
     /* manage the media in our session now, if not done already  */
     sessmedia =
@@ -2386,6 +2464,33 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
       trans_str);
   g_free (trans_str);
 
+  if (pipelined_request_id)
+    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PIPELINED_REQUESTS,
+        pipelined_request_id);
+
+  if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+    GstClockTimeDiff seekable = gst_rtsp_media_seekable (media);
+    GString *media_properties = g_string_new (NULL);
+
+    if (seekable == -1)
+      g_string_append (media_properties,
+          "No-Seeking,Time-Progressing,Time-Duration=0.0");
+    else if (seekable == 0)
+      g_string_append (media_properties, "Beginning-Only");
+    else if (seekable == G_MAXINT64)
+      g_string_append (media_properties, "Random-Access");
+    else
+      g_string_append_printf (media_properties,
+          "Random-Access=%f, Unlimited, Immutable",
+          (gdouble) seekable / GST_SECOND);
+
+    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_MEDIA_PROPERTIES,
+        g_string_free (media_properties, FALSE));
+    /* TODO Check how Accept-Ranges should be filled */
+    gst_rtsp_message_add_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+        "npt, clock, smpte, clock");
+  }
+
   send_message (client, ctx, ctx->response, FALSE);
 
   /* update the state */
@@ -2505,6 +2610,13 @@ unsupported_mode:
     send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
     goto cleanup_transport;
   }
+unsupported_range_unit:
+  {
+    GST_ERROR ("Client %p: does not support any range format we support",
+        client);
+    send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
+    goto cleanup_transport;
+  }
 keymgmt_error:
   {
     GST_ERROR ("client %p: keymgmt error", client);
@@ -3036,7 +3148,8 @@ unsuspend_failed:
 }
 
 static gboolean
-handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
+handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
+    GstRTSPVersion version)
 {
   GstRTSPMethod options;
   gchar *str;
@@ -3046,10 +3159,14 @@ handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
       GST_RTSP_OPTIONS |
       GST_RTSP_PAUSE |
       GST_RTSP_PLAY |
-      GST_RTSP_RECORD | GST_RTSP_ANNOUNCE |
       GST_RTSP_SETUP |
       GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN;
 
+  if (version < GST_RTSP_VERSION_2_0) {
+    options |= GST_RTSP_RECORD;
+    options |= GST_RTSP_ANNOUNCE;
+  }
+
   str = gst_rtsp_options_as_text (options);
 
   gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
@@ -3209,7 +3326,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
   GstRTSPContext sctx = { NULL }, *ctx;
   GstRTSPMessage response = { 0 };
   gchar *unsupported_reqs = NULL;
-  gchar *sessid;
+  gchar *sessid = NULL, *pipelined_request_id = NULL;
 
   if (!(ctx = gst_rtsp_context_get_current ())) {
     ctx = &sctx;
@@ -3233,7 +3350,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
       gst_rtsp_version_as_text (version));
 
   /* we can only handle 1.0 requests */
-  if (version != GST_RTSP_VERSION_1_0)
+  if (version != GST_RTSP_VERSION_1_0 && version != GST_RTSP_VERSION_2_0)
     goto not_supported;
 
   ctx->method = method;
@@ -3272,7 +3389,20 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
   }
 
   /* get the session if there is any */
-  res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);
+  res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_PIPELINED_REQUESTS,
+      &pipelined_request_id, 0);
+  if (res == GST_RTSP_OK) {
+    sessid = g_hash_table_lookup (client->priv->pipelined_requests,
+        pipelined_request_id);
+
+    if (!sessid)
+      res = GST_RTSP_ERROR;
+  }
+
+  if (res != GST_RTSP_OK)
+    res =
+        gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);
+
   if (res == GST_RTSP_OK) {
     if (priv->session_pool == NULL)
       goto no_pool;
@@ -3334,7 +3464,8 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
   /* now see what is asked and dispatch to a dedicated handler */
   switch (method) {
     case GST_RTSP_OPTIONS:
-      handle_options_request (client, ctx);
+      priv->version = version;
+      handle_options_request (client, ctx, version);
       break;
     case GST_RTSP_DESCRIBE:
       handle_describe_request (client, ctx);
@@ -3358,9 +3489,13 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
       handle_get_param_request (client, ctx);
       break;
     case GST_RTSP_ANNOUNCE:
+      if (version >= GST_RTSP_VERSION_2_0)
+        goto invalid_command_for_version;
       handle_announce_request (client, ctx);
       break;
     case GST_RTSP_RECORD:
+      if (version >= GST_RTSP_VERSION_2_0)
+        goto invalid_command_for_version;
       handle_record_request (client, ctx);
       break;
     case GST_RTSP_REDIRECT:
@@ -3394,6 +3529,15 @@ not_supported:
         ctx);
     goto done;
   }
+invalid_command_for_version:
+  {
+    if (priv->watch != NULL)
+      gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
+
+    GST_ERROR ("client %p: invalid command for version", client);
+    send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+    goto done;
+  }
 bad_request:
   {
     GST_ERROR ("client %p: bad request", client);
index 996f453..e4df4ea 100644 (file)
@@ -125,7 +125,7 @@ struct _GstRTSPMediaPrivate
   GstNetTimeProvider *nettime;
 
   gboolean is_live;
-  gboolean seekable;
+  GstClockTimeDiff seekable;
   gboolean buffering;
   GstState target_state;
 
@@ -663,6 +663,45 @@ 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 ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) {
+    /* TODO: Seeking for RECORD? */
+    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.0;
+  }
+
+  gst_query_unref (query);
+}
+
+
+/* must be called with state lock */
+static void
 collect_media_stats (GstRTSPMedia * media)
 {
   GstRTSPMediaPrivate *priv = media->priv;
@@ -730,6 +769,8 @@ collect_media_stats (GstRTSPMedia * media)
       priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
       priv->range_stop = stop;
     }
+
+    check_seekable (media);
   }
 }
 
@@ -2114,9 +2155,10 @@ gst_rtsp_media_get_status (GstRTSPMedia * media)
 }
 
 /**
- * gst_rtsp_media_seek:
+ * 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. @media must be prepared with
  * gst_rtsp_media_prepare().
@@ -2124,14 +2166,14 @@ gst_rtsp_media_get_status (GstRTSPMedia * media)
  * Returns: %TRUE on success.
  */
 gboolean
-gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range,
+    GstSeekFlags flags)
 {
   GstRTSPMediaClass *klass;
   GstRTSPMediaPrivate *priv;
   gboolean res;
   GstClockTime start, stop;
   GstSeekType start_type, stop_type;
-  GstQuery *query;
   gint64 current_position;
 
   klass = GST_RTSP_MEDIA_GET_CLASS (media);
@@ -2147,37 +2189,16 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
     goto not_prepared;
 
   /* 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 {
-    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 = FALSE;
-        goto not_seekable;
-      }
-    }
-
-    query = gst_query_new_seeking (GST_FORMAT_TIME);
-    if (gst_element_query (priv->pipeline, query)) {
-      GstFormat format;
-      gboolean seekable;
-      gint64 start, end;
+  check_seekable (media);
 
-      gst_query_parse_seeking (query, &format, &seekable, &start, &end);
-      priv->seekable = seekable;
-    }
-
-    gst_query_unref (query);
-  }
+  if (priv->seekable == 0) {
+    GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
+        " not seekable streams.");
 
-  if (!priv->seekable)
     goto not_seekable;
+  } else if (priv->seekable < 0) {
+    goto not_seekable;
+  }
 
   start_type = stop_type = GST_SEEK_TYPE_NONE;
 
@@ -2205,14 +2226,18 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
     stop_type = GST_SEEK_TYPE_SET;
 
   if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) {
-    GstSeekFlags flags;
+    gboolean had_flags = flags != 0;
 
     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;
+    if (had_flags)
+      flags |= GST_SEEK_FLAG_FLUSH;
+    else
+      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
@@ -2225,12 +2250,14 @@ 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;
+        if (!had_flags)
+          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 (!had_flags)
+          flags |= GST_SEEK_FLAG_KEY_UNIT;
     }
 
     if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) {
@@ -2300,6 +2327,24 @@ preroll_failed:
   }
 }
 
+
+/**
+ * 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_full (media, range, 0);
+}
+
+
 static void
 stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked)
 {
@@ -2675,18 +2720,16 @@ start_preroll (GstRTSPMedia * media)
   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 */
@@ -2953,7 +2996,7 @@ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
 
   /* reset some variables */
   priv->is_live = FALSE;
-  priv->seekable = FALSE;
+  priv->seekable = -1;
   priv->buffering = FALSE;
 
   /* we're preparing now */
@@ -3937,3 +3980,24 @@ gst_rtsp_media_get_transport_mode (GstRTSPMedia * media)
 
   return res;
 }
+
+/**
+ * gst_rtsp_media_get_seekbale:
+ * @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.
+ */
+GstClockTimeDiff
+gst_rtsp_media_seekable (GstRTSPMedia * media)
+{
+  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+  /* Currently we are not able to seek on live streams,
+   * and no stream is seekable only to the beginning */
+  return media->priv->seekable;
+}
index 9f95e5b..9c63d8f 100644 (file)
@@ -362,6 +362,12 @@ GstRTSPStream *       gst_rtsp_media_find_stream      (GstRTSPMedia *media, cons
 
 GST_EXPORT
 gboolean              gst_rtsp_media_seek             (GstRTSPMedia *media, GstRTSPTimeRange *range);
+gboolean              gst_rtsp_media_seek_full        (GstRTSPMedia *media,
+                                                       GstRTSPTimeRange *range,
+                                                       GstSeekFlags flags);
+
+GST_EXPORT
+GstClockTimeDiff      gst_rtsp_media_seekable         (GstRTSPMedia *media);
 
 GST_EXPORT
 gchar *               gst_rtsp_media_get_range_string (GstRTSPMedia *media,