summaryrefslogtreecommitdiffstats
path: root/ext/soup
diff options
context:
space:
mode:
authorWouter Cloetens <wouter@mind.be>2008-01-30 13:06:01 +0000
committerSebastian Dröge <slomo@circular-chaos.org>2008-01-30 13:06:01 +0000
commit70841f17aa5f85b83929666b27771fc19a258497 (patch)
treee7d10b42c76036dd6d3853af313f11aba758cea2 /ext/soup
parent3ad8e778d7995fcb47f8edbc58b0db21331ec05e (diff)
docs/plugins/: Add souphttpsrc to the docs.
Original commit message from CVS: Patch by: Wouter Cloetens <wouter at mind dot be> * 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.
Diffstat (limited to 'ext/soup')
-rw-r--r--ext/soup/gstsouphttpsrc.c735
-rw-r--r--ext/soup/gstsouphttpsrc.h11
2 files changed, 482 insertions, 264 deletions
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 <wouter@mind.be>
+ * Copyright (C) 2007-2008 Wouter Cloetens <wouter@mind.be>
*
* 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.
+ *
+ * <refsect2>
+ * <para>
+ * This plugin reads data from a remote location specified by a URI.
+ * Supported protocols are 'http', 'https', 'dav', or 'davs'.
+ * </para>
+ * <para>
+ * 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.
+ * </para>
+ * <para>
+ * Example pipeline:
+ * <programlisting>
+ * gst-launch -v souphttpsrc location=https://some.server.org/index.html
+ * ! filesink location=/home/joe/server.html
+ * </programlisting>
+ * The above pipeline reads a web page from a server using the HTTPS protocol
+ * and writes it to a local file.
+ * </para>
+ * <para>
+ * Another example pipeline:
+ * <programlisting>
+ * 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
+ * </programlisting>
+ * 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."
+ * </para>
+ * <para>
+ * Yet another example pipeline:
+ * <programlisting>
+ * 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
+ * </programlisting>
+ * 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.
+ * </para>
+ * </refsect2>
+ *
+ */
+
#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. */