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;
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
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);
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 {
GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT;
gchar *path, *rtpinfo;
gint matched;
+ gchar *seek_style = NULL;
GstRTSPStatusCode sig_result;
if (!(session = ctx->session))
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);
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);
gint matched;
gboolean new_session = FALSE;
GstRTSPStatusCode sig_result;
+ gchar *pipelined_request_id = NULL, *accept_range = NULL;
if (!ctx->uri)
goto no_uri;
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)
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);
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))
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 =
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 */
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);
}
static gboolean
-handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
+handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPVersion version)
{
GstRTSPMethod options;
gchar *str;
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,
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;
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;
}
/* 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;
/* 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);
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:
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);
GstNetTimeProvider *nettime;
gboolean is_live;
- gboolean seekable;
+ GstClockTimeDiff seekable;
gboolean buffering;
GstState target_state;
/* 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;
priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
priv->range_stop = stop;
}
+
+ check_seekable (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().
* 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);
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;
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
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) {
}
}
+
+/**
+ * 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)
{
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 */
/* reset some variables */
priv->is_live = FALSE;
- priv->seekable = FALSE;
+ priv->seekable = -1;
priv->buffering = FALSE;
/* we're preparing now */
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;
+}