souphttpsrc: allow seeks in ready

On is_seekable, check if the server's headers have already been
received. If not, do a HEAD request to get them before responding
to basesrc.

https://bugzilla.gnome.org/show_bug.cgi?id=702206
This commit is contained in:
Thiago Santos 2013-06-19 14:06:40 -03:00
parent 1db7e62060
commit 5bc5f4a0f6
2 changed files with 119 additions and 50 deletions

View file

@ -136,13 +136,15 @@ static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
const gchar * uri);
static char *gst_soup_http_src_unicodify (const char *str);
static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src);
static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src,
const gchar * method);
static void gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src);
static void gst_soup_http_src_queue_message (GstSoupHTTPSrc * src);
static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
guint64 offset);
static void gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src);
static void gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src);
static gboolean gst_soup_http_src_session_open (GstSoupHTTPSrc * src);
static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
static void gst_soup_http_src_parse_status (SoupMessage * msg,
GstSoupHTTPSrc * src);
@ -273,6 +275,7 @@ gst_soup_http_src_reset (GstSoupHTTPSrc * src)
src->interrupted = FALSE;
src->retry = FALSE;
src->have_size = FALSE;
src->got_headers = FALSE;
src->seekable = FALSE;
src->read_position = 0;
src->request_position = 0;
@ -292,6 +295,8 @@ gst_soup_http_src_init (GstSoupHTTPSrc * src)
{
const gchar *proxy;
g_mutex_init (&src->mutex);
g_cond_init (&src->request_finished_cond);
src->location = NULL;
src->automatic_redirect = TRUE;
src->user_agent = g_strdup (DEFAULT_USER_AGENT);
@ -322,6 +327,8 @@ gst_soup_http_src_finalize (GObject * gobject)
GST_DEBUG_OBJECT (src, "finalize");
g_mutex_clear (&src->mutex);
g_cond_clear (&src->request_finished_cond);
g_free (src->location);
g_free (src->user_agent);
if (src->proxy != NULL) {
@ -627,6 +634,57 @@ gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src)
soup_session_pause_message (src->session, src->msg);
}
static gboolean
gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
{
if (src->session) {
GST_DEBUG_OBJECT (src, "Session is already open");
return TRUE;
}
if (!src->location) {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
("Missing location property"));
return FALSE;
}
src->context = g_main_context_new ();
src->loop = g_main_loop_new (src->context, TRUE);
if (!src->loop) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
(NULL), ("Failed to start GMainLoop"));
g_main_context_unref (src->context);
return FALSE;
}
GST_DEBUG_OBJECT (src, "Creating session");
if (src->proxy == NULL) {
src->session =
soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
src->context, SOUP_SESSION_USER_AGENT, src->user_agent,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
NULL);
} else {
src->session =
soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
src->context, SOUP_SESSION_PROXY_URI, src->proxy,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
}
if (!src->session) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
(NULL), ("Failed to create async session"));
return FALSE;
}
g_signal_connect (src->session, "authenticate",
G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
return TRUE;
}
static void
gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
{
@ -687,6 +745,7 @@ gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
return;
src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING;
src->got_headers = TRUE;
/* Parse Content-Length. */
if (soup_message_headers_get_encoding (msg->response_headers) ==
@ -825,6 +884,7 @@ gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
if (src->loop)
g_main_loop_quit (src->loop);
}
g_cond_signal (&src->request_finished_cond);
}
/* Have body. Signal EOS. */
@ -876,6 +936,7 @@ gst_soup_http_src_finished_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
}
if (src->loop)
g_main_loop_quit (src->loop);
g_cond_signal (&src->request_finished_cond);
}
/* Buffer lifecycle management.
@ -1092,9 +1153,9 @@ gst_soup_http_src_parse_status (SoupMessage * msg, GstSoupHTTPSrc * src)
}
static gboolean
gst_soup_http_src_build_message (GstSoupHTTPSrc * src)
gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method)
{
src->msg = soup_message_new (SOUP_METHOD_GET, src->location);
src->msg = soup_message_new (method, src->location);
if (!src->msg) {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
("Error parsing URL."), ("URL: %s", src->location));
@ -1141,12 +1202,10 @@ gst_soup_http_src_build_message (GstSoupHTTPSrc * src)
}
static GstFlowReturn
gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method,
GstBuffer ** outbuf)
{
GstSoupHTTPSrc *src;
src = GST_SOUP_HTTP_SRC (psrc);
GST_LOG_OBJECT (src, "Running request for method: %s", method);
if (src->msg && (src->request_position != src->read_position)) {
if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
gst_soup_http_src_add_range_header (src, src->request_position);
@ -1158,8 +1217,9 @@ gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
}
}
if (!src->msg)
if (!gst_soup_http_src_build_message (src))
if (!gst_soup_http_src_build_message (src, method)) {
return GST_FLOW_ERROR;
}
src->ret = GST_FLOW_CUSTOM_ERROR;
src->outbuf = outbuf;
@ -1170,8 +1230,9 @@ gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
}
if (src->retry) {
GST_DEBUG_OBJECT (src, "Reconnecting");
if (!gst_soup_http_src_build_message (src))
if (!gst_soup_http_src_build_message (src, method)) {
return GST_FLOW_ERROR;
}
src->retry = FALSE;
continue;
}
@ -1201,9 +1262,24 @@ gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
if (src->ret == GST_FLOW_CUSTOM_ERROR)
src->ret = GST_FLOW_EOS;
g_cond_signal (&src->request_finished_cond);
return src->ret;
}
static GstFlowReturn
gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
GstSoupHTTPSrc *src;
GstFlowReturn ret;
src = GST_SOUP_HTTP_SRC (psrc);
g_mutex_lock (&src->mutex);
ret = gst_soup_http_src_do_request (src, SOUP_METHOD_GET, outbuf);
g_mutex_unlock (&src->mutex);
return ret;
}
static gboolean
gst_soup_http_src_start (GstBaseSrc * bsrc)
{
@ -1211,46 +1287,7 @@ gst_soup_http_src_start (GstBaseSrc * bsrc)
GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
if (!src->location) {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
("Missing location property"));
return FALSE;
}
src->context = g_main_context_new ();
src->loop = g_main_loop_new (src->context, TRUE);
if (!src->loop) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
(NULL), ("Failed to start GMainLoop"));
g_main_context_unref (src->context);
return FALSE;
}
if (src->proxy == NULL) {
src->session =
soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
src->context, SOUP_SESSION_USER_AGENT, src->user_agent,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
NULL);
} else {
src->session =
soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
src->context, SOUP_SESSION_PROXY_URI, src->proxy,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
}
if (!src->session) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
(NULL), ("Failed to create async session"));
return FALSE;
}
g_signal_connect (src->session, "authenticate",
G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
return TRUE;
return gst_soup_http_src_session_open (src);
}
static gboolean
@ -1288,6 +1325,7 @@ gst_soup_http_src_unlock (GstBaseSrc * bsrc)
src->interrupted = TRUE;
if (src->loop)
g_main_loop_quit (src->loop);
g_cond_signal (&src->request_finished_cond);
return TRUE;
}
@ -1325,6 +1363,33 @@ static gboolean
gst_soup_http_src_is_seekable (GstBaseSrc * bsrc)
{
GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
GstFlowReturn ret = GST_FLOW_OK;
/* Special case to check if the server allows range requests
* before really starting to get data in the buffer creation
* loops.
*/
if (!src->got_headers && GST_STATE (src) != GST_STATE_NULL) {
g_mutex_lock (&src->mutex);
while (!src->got_headers && !src->interrupted && ret == GST_FLOW_OK) {
if ((src->msg && src->msg->method != SOUP_METHOD_HEAD) &&
src->session_io_status != GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
/* wait for the current request to finish */
g_cond_wait (&src->request_finished_cond, &src->mutex);
} else {
if (gst_soup_http_src_session_open (src)) {
ret = gst_soup_http_src_do_request (src, SOUP_METHOD_HEAD, NULL);
}
}
}
if (src->ret == GST_FLOW_EOS) {
/* A HEAD request shouldn't lead to EOS */
src->ret = GST_FLOW_OK;
}
/* resets status to idle */
gst_soup_http_src_cancel_message (src);
g_mutex_unlock (&src->mutex);
}
return src->seekable;
}

View file

@ -68,6 +68,7 @@ struct _GstSoupHTTPSrc {
gboolean interrupted; /* Signal unlock(). */
gboolean retry; /* Should attempt to reconnect. */
gboolean got_headers; /* Already received headers from the server */
gboolean have_size; /* Received and parsed Content-Length
header. */
guint64 content_size; /* Value of Content-Length header. */
@ -86,6 +87,9 @@ struct _GstSoupHTTPSrc {
GstStructure *extra_headers;
guint timeout;
GMutex mutex;
GCond request_finished_cond;
};
struct _GstSoupHTTPSrcClass {