From 70841f17aa5f85b83929666b27771fc19a258497 Mon Sep 17 00:00:00 2001 From: Wouter Cloetens Date: Wed, 30 Jan 2008 13:06:01 +0000 Subject: docs/plugins/: Add souphttpsrc to the docs. Original commit message from CVS: Patch by: Wouter Cloetens * docs/plugins/Makefile.am: * docs/plugins/gst-plugins-bad-plugins-docs.sgml: * docs/plugins/gst-plugins-bad-plugins-sections.txt: Add souphttpsrc to the docs. * configure.ac: * ext/soup/gstsouphttpsrc.c: (gst_souphttp_src_class_init), (gst_souphttp_src_init), (gst_souphttp_src_dispose), (gst_souphttp_src_set_property), (gst_souphttp_src_get_property), (gst_souphttp_src_cancel_message), (gst_souphttp_src_queue_message), (gst_souphttp_src_add_range_header), (gst_souphttp_src_session_unpause_message), (gst_souphttp_src_session_pause_message), (gst_souphttp_src_session_close), (gst_souphttp_src_got_headers_cb), (gst_souphttp_src_got_body_cb), (gst_souphttp_src_finished_cb), (gst_souphttp_src_got_chunk_cb), (gst_souphttp_src_response_cb), (gst_souphttp_src_parse_status), (gst_souphttp_src_create), (gst_souphttp_src_start), (gst_souphttp_src_stop), (gst_souphttp_src_unlock), (gst_souphttp_src_unlock_stop), (gst_souphttp_src_get_size), (gst_souphttp_src_is_seekable), (gst_souphttp_src_do_seek), (gst_souphttp_src_set_location), (gst_souphttp_src_set_proxy), (plugin_init): * ext/soup/gstsouphttpsrc.h: Add support for libsoup2.4 and require it. Also implement redirection and manual proxy specification. Fixes bug #510708. * tests/check/Makefile.am: * tests/check/elements/.cvsignore: * tests/check/elements/souphttpsrc.c: Add unit test for souphttpsrc. --- ext/soup/gstsouphttpsrc.c | 735 +++++++++++++++++++++++++++++----------------- ext/soup/gstsouphttpsrc.h | 11 + 2 files changed, 482 insertions(+), 264 deletions(-) (limited to 'ext/soup') diff --git a/ext/soup/gstsouphttpsrc.c b/ext/soup/gstsouphttpsrc.c index 8e850519..dff897c6 100644 --- a/ext/soup/gstsouphttpsrc.c +++ b/ext/soup/gstsouphttpsrc.c @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <2007> Wouter Cloetens + * Copyright (C) 2007-2008 Wouter Cloetens * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -12,6 +12,72 @@ * Library General Public License for more */ +/** + * SECTION:element-souphttpsrc + * @short_description: Read from an HTTP/HTTPS/WebDAV/Icecast/Shoutcast + * location. + * + * + * + * This plugin reads data from a remote location specified by a URI. + * Supported protocols are 'http', 'https', 'dav', or 'davs'. + * + * + * In case the element-souphttpsrc::iradio-mode property is set and the + * location is a http resource, souphttpsrc will send special Icecast HTTP + * headers to the server to request additional Icecast meta-information. If + * the server is not an Icecast server, it will behave as if the + * element-souphttpsrc::iradio-mode property were not set. If it is, + * souphttpsrc will output data with a media type of application/x-icy, + * in which case you will need to use the #ICYDemux element as follow-up + * element to extract the Icecast metadata and to determine the underlying + * media type. + * + * + * Example pipeline: + * + * gst-launch -v souphttpsrc location=https://some.server.org/index.html + * ! filesink location=/home/joe/server.html + * + * The above pipeline reads a web page from a server using the HTTPS protocol + * and writes it to a local file. + * + * + * Another example pipeline: + * + * gst-launch -v souphttpsrc user-agent="FooPlayer 0.99 beta" + * automatic-redirect=false proxy=http://proxy.intranet.local:8080 + * location=http://music.foobar.com/demo.mp3 ! mad ! audioconvert + * ! audioresample ! alsasink + * + * The above pipeline will read and decode and play an mp3 file from a + * web server using the HTTP protocol. If the server sends redirects, + * the request fails instead of following the redirect. The specified + * HTTP proxy server is used. The User-Agent HTTP request header + * is set to a custom string instead of "GStreamer souphttpsrc." + * + * + * Yet another example pipeline: + * + * gst-launch -v souphttpsrc location=http://10.11.12.13/mjpeg + * do-timestamp=true ! multipartdemux + * ! image/jpeg,width=640,height=480 ! matroskamux + * ! filesink location=mjpeg.mkv + * + * The above pipeline reads a motion JPEG stream from an IP camera + * using the HTTP protocol, encoded as mime/multipart image/jpeg + * parts, and writes a Matroska motion JPEG file. The width and + * height properties are set in the caps to provide the Matroska + * multiplexer with the information to set this in the header. + * Timestamps are set on the buffers as they arrive from the camera. + * These are used by the mime/multipart demultiplexer to emit timestamps + * on the JPEG-encoded video frame buffers. This allows the Matroska + * multiplexer to timestamp the frames in the resulting file. + * + * + * + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -43,6 +109,8 @@ enum PROP_0, PROP_LOCATION, PROP_USER_AGENT, + PROP_AUTOMATIC_REDIRECT, + PROP_PROXY, PROP_IRADIO_MODE, PROP_IRADIO_NAME, PROP_IRADIO_GENRE, @@ -75,18 +143,32 @@ static gboolean gst_souphttp_src_unlock_stop (GstBaseSrc * bsrc); static gboolean gst_souphttp_src_set_location (GstSouphttpSrc * src, const gchar * uri); -static gboolean soup_add_range_header (GstSouphttpSrc * src, guint64 offset); - -static void soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src); -static void soup_finished (SoupMessage * msg, GstSouphttpSrc * src); -static void soup_got_body (SoupMessage * msg, GstSouphttpSrc * src); -static void soup_got_chunk (SoupMessage * msg, GstSouphttpSrc * src); -static void soup_response (SoupMessage * msg, gpointer user_data); -static void soup_parse_status (SoupMessage * msg, GstSouphttpSrc * src); -static void soup_session_close (GstSouphttpSrc * src); +static gboolean gst_souphttp_src_set_proxy (GstSouphttpSrc * src, + const gchar * uri); static char *gst_souphttp_src_unicodify (const char *str); +static void gst_souphttp_src_cancel_message (GstSouphttpSrc * src); +static void gst_souphttp_src_queue_message (GstSouphttpSrc * src); +static gboolean gst_souphttp_src_add_range_header (GstSouphttpSrc * src, + guint64 offset); +static void gst_souphttp_src_session_unpause_message (GstSouphttpSrc * src); +static void gst_souphttp_src_session_pause_message (GstSouphttpSrc * src); +static void gst_souphttp_src_session_close (GstSouphttpSrc * src); +static void gst_souphttp_src_parse_status (SoupMessage * msg, + GstSouphttpSrc * src); +static void gst_souphttp_src_got_chunk_cb (SoupMessage * msg, + SoupBuffer * chunk, GstSouphttpSrc * src); +static void gst_souphttp_src_response_cb (SoupSession * session, + SoupMessage * msg, GstSouphttpSrc * src); +static void gst_souphttp_src_got_headers_cb (SoupMessage * msg, + GstSouphttpSrc * src); +static void gst_souphttp_src_got_body_cb (SoupMessage * msg, + GstSouphttpSrc * src); +static void gst_souphttp_src_finished_cb (SoupMessage * msg, + GstSouphttpSrc * src); + + static void _do_init (GType type) { @@ -140,6 +222,15 @@ gst_souphttp_src_class_init (GstSouphttpSrcClass * klass) g_param_spec_string ("user-agent", "User-Agent", "Value of the User-Agent HTTP request header field", DEFAULT_USER_AGENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_AUTOMATIC_REDIRECT, + g_param_spec_boolean ("automatic-redirect", "automatic-redirect", + "Automatically follow HTTP redirects (HTTP Status Code 3xx)", + TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_PROXY, + g_param_spec_string ("proxy", "Proxy", + "HTTP proxy server URI", "", G_PARAM_READWRITE)); /* icecast stuff */ g_object_class_install_property (gobject_class, @@ -187,6 +278,8 @@ static void gst_souphttp_src_init (GstSouphttpSrc * src, GstSouphttpSrcClass * g_class) { src->location = NULL; + src->proxy = NULL; + src->automatic_redirect = TRUE; src->user_agent = g_strdup (DEFAULT_USER_AGENT); src->icy_caps = NULL; src->iradio_mode = FALSE; @@ -215,6 +308,10 @@ gst_souphttp_src_dispose (GObject * gobject) src->location = NULL; g_free (src->user_agent); src->user_agent = NULL; + if (src->proxy != NULL) { + soup_uri_free (src->proxy); + src->proxy = NULL; + } g_free (src->iradio_name); src->iradio_name = NULL; g_free (src->iradio_genre); @@ -265,6 +362,25 @@ gst_souphttp_src_set_property (GObject * object, guint prop_id, default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; + case PROP_AUTOMATIC_REDIRECT: + src->automatic_redirect = g_value_get_boolean (value); + break; + case PROP_PROXY: + { + const gchar *proxy; + + proxy = g_value_get_string (value); + + if (proxy == NULL) { + GST_WARNING ("proxy property cannot be NULL"); + goto done; + } + if (!gst_souphttp_src_set_proxy (src, proxy)) { + GST_WARNING ("badly formatted proxy URI"); + goto done; + } + break; + } } done: return; @@ -283,6 +399,19 @@ gst_souphttp_src_get_property (GObject * object, guint prop_id, case PROP_USER_AGENT: g_value_set_string (value, src->user_agent); break; + case PROP_AUTOMATIC_REDIRECT: + g_value_set_boolean (value, src->automatic_redirect); + break; + case PROP_PROXY: + if (src->proxy == NULL) + g_value_set_string (value, ""); + else { + char *proxy = soup_uri_to_string (src->proxy, FALSE); + + g_value_set_string (value, proxy); + free (proxy); + } + break; case PROP_IRADIO_MODE: g_value_set_boolean (value, src->iradio_mode); break; @@ -314,247 +443,64 @@ gst_souphttp_src_unicodify (const gchar * str) return gst_tag_freeform_string_to_utf8 (str, -1, env_vars); } -static GstFlowReturn -gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf) -{ - GstSouphttpSrc *src; - - src = GST_SOUPHTTP_SRC (psrc); - - if (src->msg && (src->request_position != src->read_position)) { - if (src->msg->status == SOUP_MESSAGE_STATUS_IDLE) { - soup_add_range_header (src, src->request_position); - } else { - GST_DEBUG_OBJECT (src, "Seek from position %" G_GUINT64_FORMAT - " to %" G_GUINT64_FORMAT ": requeueing connection request", - src->read_position, src->request_position); - soup_session_cancel_message (src->session, src->msg); - src->msg = NULL; - } - } - if (!src->msg) { - src->msg = soup_message_new (SOUP_METHOD_GET, src->location); - if (!src->msg) { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, - (NULL), ("Error parsing URL \"%s\"", src->location)); - return GST_FLOW_ERROR; - } - soup_message_add_header (src->msg->request_headers, "Connection", "close"); - if (src->user_agent) { - soup_message_add_header (src->msg->request_headers, "User-Agent", - src->user_agent); - } - if (src->iradio_mode) { - soup_message_add_header (src->msg->request_headers, "icy-metadata", "1"); - } - - g_signal_connect (src->msg, "got_headers", - G_CALLBACK (soup_got_headers), src); - g_signal_connect (src->msg, "got_body", G_CALLBACK (soup_got_body), src); - g_signal_connect (src->msg, "finished", G_CALLBACK (soup_finished), src); - g_signal_connect (src->msg, "got_chunk", G_CALLBACK (soup_got_chunk), src); - soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS); - soup_add_range_header (src, src->request_position); - } - - src->ret = GST_FLOW_CUSTOM_ERROR; - src->outbuf = outbuf; - do { - if (src->interrupted) { - soup_session_cancel_message (src->session, src->msg); - src->msg = NULL; - break; - } - if (!src->msg) { - GST_DEBUG_OBJECT (src, "EOS reached"); - break; - } - - switch (src->msg->status) { - case SOUP_MESSAGE_STATUS_IDLE: - GST_DEBUG_OBJECT (src, "Queueing connection request"); - soup_session_queue_message (src->session, src->msg, soup_response, src); - break; - case SOUP_MESSAGE_STATUS_FINISHED: - GST_DEBUG_OBJECT (src, "Connection closed"); - soup_session_cancel_message (src->session, src->msg); - src->msg = NULL; - break; - case SOUP_MESSAGE_STATUS_QUEUED: - break; - case SOUP_MESSAGE_STATUS_CONNECTING: - case SOUP_MESSAGE_STATUS_RUNNING: - default: - soup_message_io_unpause (src->msg); - break; - } - - if (src->ret == GST_FLOW_CUSTOM_ERROR) - g_main_loop_run (src->loop); - } while (src->ret == GST_FLOW_CUSTOM_ERROR); - - if (src->ret == GST_FLOW_CUSTOM_ERROR) - src->ret = GST_FLOW_UNEXPECTED; - return src->ret; -} - -static gboolean -gst_souphttp_src_start (GstBaseSrc * bsrc) -{ - GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); - - GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location); - - if (!src->location) { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, - (NULL), ("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; - } - - src->session = - soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT, - src->context, NULL); - if (!src->session) { - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - (NULL), ("Failed to create async session")); - return FALSE; - } - - return TRUE; -} - -/* Close the socket and associated resources - * used both to recover from errors and go to NULL state. */ -static gboolean -gst_souphttp_src_stop (GstBaseSrc * bsrc) -{ - GstSouphttpSrc *src; - - src = GST_SOUPHTTP_SRC (bsrc); - GST_DEBUG_OBJECT (src, "stop()"); - soup_session_close (src); - if (src->loop) { - g_main_loop_unref (src->loop); - g_main_context_unref (src->context); - src->loop = NULL; - src->context = NULL; - } - - return TRUE; -} - -/* Interrupt a blocking request. */ -static gboolean -gst_souphttp_src_unlock (GstBaseSrc * bsrc) -{ - GstSouphttpSrc *src; - - src = GST_SOUPHTTP_SRC (bsrc); - GST_DEBUG_OBJECT (src, "unlock()"); - - src->interrupted = TRUE; - if (src->loop) - g_main_loop_quit (src->loop); - return TRUE; -} - -/* Interrupt interrupt. */ -static gboolean -gst_souphttp_src_unlock_stop (GstBaseSrc * bsrc) -{ - GstSouphttpSrc *src; - - src = GST_SOUPHTTP_SRC (bsrc); - GST_DEBUG_OBJECT (src, "unlock_stop()"); - - src->interrupted = FALSE; - return TRUE; -} - -static gboolean -gst_souphttp_src_get_size (GstBaseSrc * bsrc, guint64 * size) -{ - GstSouphttpSrc *src; - - src = GST_SOUPHTTP_SRC (bsrc); - - if (src->have_size) { - GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT, - src->content_size); - *size = src->content_size; - return TRUE; - } - GST_DEBUG_OBJECT (src, "get_size() = FALSE"); - return FALSE; -} - -static gboolean -gst_souphttp_src_is_seekable (GstBaseSrc * bsrc) +static void +gst_souphttp_src_cancel_message (GstSouphttpSrc * src) { - GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); - - return src->seekable; + soup_session_cancel_message (src->session, src->msg, SOUP_STATUS_CANCELLED); + src->session_io_status = GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE; + src->msg = NULL; } -static gboolean -gst_souphttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) +static void +gst_souphttp_src_queue_message (GstSouphttpSrc * src) { - GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); - - GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT ")", segment->start); - - if (src->read_position == segment->start) - return TRUE; - - if (!src->seekable) - return FALSE; - - /* Wait for create() to handle the jump in offset. */ - src->request_position = segment->start; - return TRUE; + soup_session_queue_message (src->session, src->msg, + (SoupSessionCallback) gst_souphttp_src_response_cb, src); + src->session_io_status = GST_SOUPHTTP_SRC_SESSION_IO_STATUS_QUEUED; } static gboolean -soup_add_range_header (GstSouphttpSrc * src, guint64 offset) +gst_souphttp_src_add_range_header (GstSouphttpSrc * src, guint64 offset) { gchar buf[64]; gint rc; - soup_message_remove_header (src->msg->request_headers, "Range"); + soup_message_headers_remove (src->msg->request_headers, "Range"); if (offset) { rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-", offset); if (rc > sizeof (buf) || rc < 0) return FALSE; - soup_message_add_header (src->msg->request_headers, "Range", buf); + soup_message_headers_append (src->msg->request_headers, "Range", buf); } src->read_position = offset; return TRUE; } -static gboolean -gst_souphttp_src_set_location (GstSouphttpSrc * src, const gchar * uri) +static void +gst_souphttp_src_session_unpause_message (GstSouphttpSrc * src) { - if (src->location) { - g_free (src->location); - src->location = NULL; - } - src->location = g_strdup (uri); + soup_session_unpause_message (src->session, src->msg); +} - return TRUE; +static void +gst_souphttp_src_session_pause_message (GstSouphttpSrc * src) +{ + soup_session_pause_message (src->session, src->msg); } static void -soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) +gst_souphttp_src_session_close (GstSouphttpSrc * src) +{ + if (src->session) { + soup_session_abort (src->session); /* This unrefs the message. */ + g_object_unref (src->session); + src->session = NULL; + src->msg = NULL; + } +} + +static void +gst_souphttp_src_got_headers_cb (SoupMessage * msg, GstSouphttpSrc * src) { const char *value; GstTagList *tag_list; @@ -563,14 +509,23 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) GST_DEBUG_OBJECT (src, "got headers"); + if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { + GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code, + soup_message_headers_get (msg->response_headers, "Location")); + return; + } + + src->session_io_status = GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING; + /* Parse Content-Length. */ - value = soup_message_get_header (msg->response_headers, "Content-Length"); - if (value != NULL) { - newsize = src->request_position + g_ascii_strtoull (value, NULL, 10); + if (soup_message_headers_get_encoding (msg->response_headers) == + SOUP_ENCODING_CONTENT_LENGTH) { + newsize = src->request_position + + soup_message_headers_get_content_length (msg->response_headers); if (!src->have_size || (src->content_size != newsize)) { src->content_size = newsize; src->have_size = TRUE; - GST_DEBUG_OBJECT (src, "size = %llu", src->content_size); + GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size); basesrc = GST_BASE_SRC_CAST (src); gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES, @@ -585,7 +540,7 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) tag_list = gst_tag_list_new (); if ((value = - soup_message_get_header (msg->response_headers, + soup_message_headers_get (msg->response_headers, "icy-metaint")) != NULL) { gint icy_metaint = atoi (value); @@ -596,7 +551,7 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) } if ((value = - soup_message_get_header (msg->response_headers, + soup_message_headers_get (msg->response_headers, "icy-name")) != NULL) { g_free (src->iradio_name); src->iradio_name = gst_souphttp_src_unicodify (value); @@ -607,7 +562,7 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) } } if ((value = - soup_message_get_header (msg->response_headers, + soup_message_headers_get (msg->response_headers, "icy-genre")) != NULL) { g_free (src->iradio_genre); src->iradio_genre = gst_souphttp_src_unicodify (value); @@ -617,8 +572,8 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) src->iradio_genre, NULL); } } - if ((value = - soup_message_get_header (msg->response_headers, "icy-url")) != NULL) { + if ((value = soup_message_headers_get (msg->response_headers, "icy-url")) + != NULL) { g_free (src->iradio_url); src->iradio_url = gst_souphttp_src_unicodify (value); if (src->iradio_url) { @@ -636,7 +591,7 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) } /* Handle HTTP errors. */ - soup_parse_status (msg, src); + gst_souphttp_src_parse_status (msg, src); /* Check if Range header was respected. */ if (src->ret == GST_FLOW_CUSTOM_ERROR && @@ -651,27 +606,37 @@ soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src) /* Have body. Signal EOS. */ static void -soup_got_body (SoupMessage * msg, GstSouphttpSrc * src) +gst_souphttp_src_got_body_cb (SoupMessage * msg, GstSouphttpSrc * src) { - if (msg != src->msg) { + if (G_UNLIKELY (msg != src->msg)) { GST_DEBUG_OBJECT (src, "got body, but not for current message"); return; } + if (G_UNLIKELY (src->session_io_status != + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING)) { + /* Probably a redirect. */ + return; + } GST_DEBUG_OBJECT (src, "got body"); src->ret = GST_FLOW_UNEXPECTED; if (src->loop) g_main_loop_quit (src->loop); - soup_message_io_pause (msg); + gst_souphttp_src_session_pause_message (src); } /* Finished. Signal EOS. */ static void -soup_finished (SoupMessage * msg, GstSouphttpSrc * src) +gst_souphttp_src_finished_cb (SoupMessage * msg, GstSouphttpSrc * src) { - if (msg != src->msg) { + if (G_UNLIKELY (msg != src->msg)) { GST_DEBUG_OBJECT (src, "finished, but not for current message"); return; } + if (G_UNLIKELY (src->session_io_status != + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING)) { + /* Probably a redirect. */ + return; + } GST_DEBUG_OBJECT (src, "finished"); src->ret = GST_FLOW_UNEXPECTED; if (src->loop) @@ -679,53 +644,66 @@ soup_finished (SoupMessage * msg, GstSouphttpSrc * src) } static void -soup_got_chunk (SoupMessage * msg, GstSouphttpSrc * src) +gst_souphttp_src_got_chunk_cb (SoupMessage * msg, SoupBuffer * chunk, + GstSouphttpSrc * src) { GstBaseSrc *basesrc; guint64 new_position; + const char *data; + gsize length; if (G_UNLIKELY (msg != src->msg)) { GST_DEBUG_OBJECT (src, "got chunk, but not for current message"); return; } + if (G_UNLIKELY (src->session_io_status != + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING)) { + /* Probably a redirect. */ + return; + } basesrc = GST_BASE_SRC_CAST (src); - GST_DEBUG_OBJECT (src, "got chunk of %d bytes", msg->response.length); + data = chunk->data; + length = chunk->length; + GST_DEBUG_OBJECT (src, "got chunk of %d bytes", length); /* Create the buffer. */ src->ret = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc), - basesrc->segment.last_stop, msg->response.length, + basesrc->segment.last_stop, length, GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)), src->outbuf); if (G_LIKELY (src->ret == GST_FLOW_OK)) { - memcpy (GST_BUFFER_DATA (*src->outbuf), msg->response.body, - msg->response.length); - new_position = src->read_position + msg->response.length; + memcpy (GST_BUFFER_DATA (*src->outbuf), data, length); + new_position = src->read_position + length; if (G_LIKELY (src->request_position == src->read_position)) src->request_position = new_position; src->read_position = new_position; } g_main_loop_quit (src->loop); - soup_message_io_pause (msg); + gst_souphttp_src_session_pause_message (src); } static void -soup_response (SoupMessage * msg, gpointer user_data) +gst_souphttp_src_response_cb (SoupSession * session, SoupMessage * msg, + GstSouphttpSrc * src) { - GstSouphttpSrc *src = (GstSouphttpSrc *) user_data; - - if (msg != src->msg) { + if (G_UNLIKELY (msg != src->msg)) { GST_DEBUG_OBJECT (src, "got response %d: %s, but not for current message", msg->status_code, msg->reason_phrase); return; } + if (G_UNLIKELY (src->session_io_status != + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING)) { + /* Probably a redirect. */ + return; + } GST_DEBUG_OBJECT (src, "got response %d: %s", msg->status_code, msg->reason_phrase); - soup_parse_status (msg, src); + gst_souphttp_src_parse_status (msg, src); g_main_loop_quit (src->loop); } static void -soup_parse_status (SoupMessage * msg, GstSouphttpSrc * src) +gst_souphttp_src_parse_status (SoupMessage * msg, GstSouphttpSrc * src) { if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { switch (msg->status_code) { @@ -761,6 +739,7 @@ soup_parse_status (SoupMessage * msg, GstSouphttpSrc * src) break; } } else if (SOUP_STATUS_IS_CLIENT_ERROR (msg->status_code) || + SOUP_STATUS_IS_REDIRECTION (msg->status_code) || SOUP_STATUS_IS_SERVER_ERROR (msg->status_code)) { /* Report HTTP error. */ GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, @@ -770,15 +749,245 @@ soup_parse_status (SoupMessage * msg, GstSouphttpSrc * src) } } -static void -soup_session_close (GstSouphttpSrc * src) +static GstFlowReturn +gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf) { - if (src->session) { - soup_session_abort (src->session); /* This unrefs the message. */ - g_object_unref (src->session); - src->session = NULL; - src->msg = NULL; + GstSouphttpSrc *src; + + src = GST_SOUPHTTP_SRC (psrc); + + if (src->msg && (src->request_position != src->read_position)) { + if (src->session_io_status == GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE) { + gst_souphttp_src_add_range_header (src, src->request_position); + } else { + GST_DEBUG_OBJECT (src, "Seek from position %" G_GUINT64_FORMAT + " to %" G_GUINT64_FORMAT ": requeueing connection request", + src->read_position, src->request_position); + gst_souphttp_src_cancel_message (src); + } + } + if (!src->msg) { + src->msg = soup_message_new (SOUP_METHOD_GET, src->location); + if (!src->msg) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (NULL), ("Error parsing URL \"%s\"", src->location)); + return GST_FLOW_ERROR; + } + src->session_io_status = GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE; + soup_message_headers_append (src->msg->request_headers, "Connection", + "close"); + if (src->user_agent) { + soup_message_headers_append (src->msg->request_headers, "User-Agent", + src->user_agent); + } + if (src->iradio_mode) { + soup_message_headers_append (src->msg->request_headers, "icy-metadata", + "1"); + } + + g_signal_connect (src->msg, "got_headers", + G_CALLBACK (gst_souphttp_src_got_headers_cb), src); + g_signal_connect (src->msg, "got_body", + G_CALLBACK (gst_souphttp_src_got_body_cb), src); + g_signal_connect (src->msg, "finished", + G_CALLBACK (gst_souphttp_src_finished_cb), src); + g_signal_connect (src->msg, "got_chunk", + G_CALLBACK (gst_souphttp_src_got_chunk_cb), src); + soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS | + src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT); + gst_souphttp_src_add_range_header (src, src->request_position); + } + + src->ret = GST_FLOW_CUSTOM_ERROR; + src->outbuf = outbuf; + do { + if (src->interrupted) { + gst_souphttp_src_cancel_message (src); + break; + } + if (!src->msg) { + GST_DEBUG_OBJECT (src, "EOS reached"); + break; + } + + switch (src->session_io_status) { + case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE: + GST_DEBUG_OBJECT (src, "Queueing connection request"); + gst_souphttp_src_queue_message (src); + break; + case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_FINISHED: + GST_DEBUG_OBJECT (src, "Connection closed"); + gst_souphttp_src_cancel_message (src); + break; + case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_QUEUED: + break; + case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING: + gst_souphttp_src_session_unpause_message (src); + break; + } + + if (src->ret == GST_FLOW_CUSTOM_ERROR) + g_main_loop_run (src->loop); + } while (src->ret == GST_FLOW_CUSTOM_ERROR); + + if (src->ret == GST_FLOW_CUSTOM_ERROR) + src->ret = GST_FLOW_UNEXPECTED; + return src->ret; +} + +static gboolean +gst_souphttp_src_start (GstBaseSrc * bsrc) +{ + GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); + + GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location); + + if (!src->location) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (NULL), ("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, NULL); + else + src->session = + soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT, + src->context, SOUP_SESSION_PROXY_URI, src->proxy, NULL); + if (!src->session) { + GST_ELEMENT_ERROR (src, LIBRARY, INIT, + (NULL), ("Failed to create async session")); + return FALSE; + } + + return TRUE; +} + +/* Close the socket and associated resources + * used both to recover from errors and go to NULL state. */ +static gboolean +gst_souphttp_src_stop (GstBaseSrc * bsrc) +{ + GstSouphttpSrc *src; + + src = GST_SOUPHTTP_SRC (bsrc); + GST_DEBUG_OBJECT (src, "stop()"); + gst_souphttp_src_session_close (src); + if (src->loop) { + g_main_loop_unref (src->loop); + g_main_context_unref (src->context); + src->loop = NULL; + src->context = NULL; + } + + return TRUE; +} + +/* Interrupt a blocking request. */ +static gboolean +gst_souphttp_src_unlock (GstBaseSrc * bsrc) +{ + GstSouphttpSrc *src; + + src = GST_SOUPHTTP_SRC (bsrc); + GST_DEBUG_OBJECT (src, "unlock()"); + + src->interrupted = TRUE; + if (src->loop) + g_main_loop_quit (src->loop); + return TRUE; +} + +/* Interrupt interrupt. */ +static gboolean +gst_souphttp_src_unlock_stop (GstBaseSrc * bsrc) +{ + GstSouphttpSrc *src; + + src = GST_SOUPHTTP_SRC (bsrc); + GST_DEBUG_OBJECT (src, "unlock_stop()"); + + src->interrupted = FALSE; + return TRUE; +} + +static gboolean +gst_souphttp_src_get_size (GstBaseSrc * bsrc, guint64 * size) +{ + GstSouphttpSrc *src; + + src = GST_SOUPHTTP_SRC (bsrc); + + if (src->have_size) { + GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT, + src->content_size); + *size = src->content_size; + return TRUE; } + GST_DEBUG_OBJECT (src, "get_size() = FALSE"); + return FALSE; +} + +static gboolean +gst_souphttp_src_is_seekable (GstBaseSrc * bsrc) +{ + GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); + + return src->seekable; +} + +static gboolean +gst_souphttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) +{ + GstSouphttpSrc *src = GST_SOUPHTTP_SRC (bsrc); + + GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT ")", segment->start); + + if (src->read_position == segment->start) + return TRUE; + + if (!src->seekable) + return FALSE; + + /* Wait for create() to handle the jump in offset. */ + src->request_position = segment->start; + return TRUE; +} + +static gboolean +gst_souphttp_src_set_location (GstSouphttpSrc * src, const gchar * uri) +{ + if (src->location) { + g_free (src->location); + src->location = NULL; + } + src->location = g_strdup (uri); + + return TRUE; +} + +static gboolean +gst_souphttp_src_set_proxy (GstSouphttpSrc * src, const gchar * uri) +{ + if (src->proxy) { + soup_uri_free (src->proxy); + src->proxy = NULL; + } + src->proxy = soup_uri_new (uri); + + return TRUE; } static guint @@ -824,9 +1033,7 @@ gst_souphttp_src_uri_handler_init (gpointer g_iface, gpointer iface_data) static gboolean plugin_init (GstPlugin * plugin) { - /* note: do not upgrade rank before we depend on a libsoup version where - * icecast is supported properly out of the box */ - return gst_element_register (plugin, "souphttpsrc", GST_RANK_NONE, + return gst_element_register (plugin, "souphttpsrc", GST_RANK_MARGINAL, GST_TYPE_SOUPHTTP_SRC); } diff --git a/ext/soup/gstsouphttpsrc.h b/ext/soup/gstsouphttpsrc.h index 21aff958..9c24382a 100644 --- a/ext/soup/gstsouphttpsrc.h +++ b/ext/soup/gstsouphttpsrc.h @@ -37,14 +37,25 @@ G_BEGIN_DECLS typedef struct _GstSouphttpSrc GstSouphttpSrc; typedef struct _GstSouphttpSrcClass GstSouphttpSrcClass; +typedef enum { + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE, + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_QUEUED, + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING, + GST_SOUPHTTP_SRC_SESSION_IO_STATUS_FINISHED, +} GstSouphttpSrcSessionIOStatus; + struct _GstSouphttpSrc { GstPushSrc element; gchar * location; /* Full URI. */ gchar * user_agent; /* User-Agent HTTP header. */ + gboolean automatic_redirect; /* Follow redirects. */ + SoupURI * proxy; /* HTTP proxy URI. */ GMainContext * context; /* I/O context. */ GMainLoop * loop; /* Event loop. */ SoupSession * session; /* Async context. */ + GstSouphttpSrcSessionIOStatus session_io_status; + /* Async I/O status. */ SoupMessage * msg; /* Request message. */ GstFlowReturn ret; /* Return code from callback. */ GstBuffer ** outbuf; /* Return buffer allocated by callback. */ -- cgit