summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog37
-rw-r--r--configure.ac4
-rw-r--r--docs/plugins/Makefile.am1
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-docs.sgml1
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-sections.txt14
-rw-r--r--ext/soup/gstsouphttpsrc.c735
-rw-r--r--ext/soup/gstsouphttpsrc.h11
-rw-r--r--tests/check/Makefile.am11
-rw-r--r--tests/check/elements/.gitignore1
-rw-r--r--tests/check/elements/souphttpsrc.c408
10 files changed, 957 insertions, 266 deletions
diff --git a/ChangeLog b/ChangeLog
index d527bfb4..62edc7dc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,40 @@
+2008-01-30 Sebastian Dröge <slomo@circular-chaos.org>
+
+ 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.
+
2008-01-30 Edgard Lima <edgard.lima@indt.org.br>
* ext/Makefile.am:
diff --git a/configure.ac b/configure.ac
index 2876ecd1..20b96a6f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -822,8 +822,8 @@ AG_GST_CHECK_FEATURE(NEON, [neon http client plugins], neonhttpsrc, [
dnl *** soup ***
translit(dnm, m, l) AM_CONDITIONAL(USE_SOUP, true)
-AG_GST_CHECK_FEATURE(SOUP, [soup http client plugins], souphttpsrc, [
- PKG_CHECK_MODULES(SOUP, libsoup-2.2 >= 2.2.104, HAVE_SOUP="yes", [
+AG_GST_CHECK_FEATURE(SOUP, [soup http client plugin (2.4)], souphttpsrc, [
+ PKG_CHECK_MODULES(SOUP, libsoup-2.4, HAVE_SOUP="yes", [
HAVE_SOUP="no"
AC_MSG_RESULT(no)
])
diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am
index 29b3300b..2f49f2af 100644
--- a/docs/plugins/Makefile.am
+++ b/docs/plugins/Makefile.am
@@ -103,6 +103,7 @@ EXTRA_HFILES = \
$(top_srcdir)/ext/gio/gstgiosrc.h \
$(top_srcdir)/ext/gio/gstgiostreamsink.h \
$(top_srcdir)/ext/gio/gstgiostreamsrc.h \
+ $(top_srcdir)/ext/soup/gstsouphttpsrc.h \
$(top_srcdir)/ext/ivorbis/vorbisdec.h \
$(top_srcdir)/ext/jack/gstjackaudiosink.h \
$(top_srcdir)/ext/musicbrainz/gsttrm.h \
diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
index d2f00cca..3d0e50e5 100644
--- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml
+++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
@@ -53,6 +53,7 @@
<xi:include href="xml/element-sdlaudiosink.xml" />
<xi:include href="xml/element-sdlvideosink.xml" />
<xi:include href="xml/element-sdpdemux.xml" />
+ <xi:include href="xml/element-souphttpsrc.xml" />
<xi:include href="xml/element-spectrum.xml" />
<xi:include href="xml/element-speed.xml" />
<xi:include href="xml/element-speexresample.xml" />
diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt
index e7ca6f3b..295ea113 100644
--- a/docs/plugins/gst-plugins-bad-plugins-sections.txt
+++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt
@@ -267,6 +267,20 @@ gst_gio_stream_src_get_type
</SECTION>
<SECTION>
+<FILE>element-souphttpsrc</FILE>
+<TITLE>souphttpsrc</TITLE>
+GstSouphttpSrc
+<SUBSECTION Standard>
+GstSouphttpSrcClass
+GST_SOUPHTTP_SRC
+GST_SOUPHTTP_SRC_CLASS
+GST_IS_SOUPHTTP_SRC
+GST_IS_SOUPHTTP_SRC_CLASS
+GST_TYPE_SOUPHTTP_SRC
+gst_souphttp_src_get_type
+</SECTION>
+
+<SECTION>
<FILE>element-bpwsinc</FILE>
<TITLE>bpwsinc</TITLE>
GstBPWSinc
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. */
diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am
index 387098c1..ebdbd097 100644
--- a/tests/check/Makefile.am
+++ b/tests/check/Makefile.am
@@ -15,6 +15,8 @@ TESTS_ENVIRONMENT = \
GST_PLUGIN_PATH=$(top_builddir)/gst:$(top_builddir)/sys:$(top_builddir)/ext:$(GSTPB_PLUGINS_DIR):$(GST_PLUGINS_DIR) \
STATE_IGNORE_ELEMENTS="vcdsrc nassink glimagesink"
+EXTRA_DIST = test-cert.pem test-key.pem
+
plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@
# override to _not_ install the test plugins
@@ -45,6 +47,12 @@ else
check_neon =
endif
+if USE_SOUP
+check_soup = elements/souphttpsrc
+else
+check_soup =
+endif
+
if USE_TIMIDITY
check_timidity=elements/timidity
else
@@ -64,6 +72,7 @@ check_PROGRAMS = \
$(check_gio) \
$(check_mpeg2enc) \
$(check_neon) \
+ $(check_soup) \
$(check_timidity) \
elements/bpwsinc \
elements/equalizer \
@@ -90,3 +99,5 @@ elements_timidity_LDADD = $(GST_BASE_LIBS) $(LDADD)
pipelines_gio_CFLAGS = $(GIO_CFLAGS) $(AM_CFLAGS)
pipelines_gio_LDADD = $(GIO_LIBS) $(LDADD)
+elements_souphttpsrc_CFLAGS = $(SOUP_CFLAGS) $(AM_CFLAGS)
+elements_souphttpsrc_LDADD = $(SOUP_LIBS) $(LDADD)
diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore
index bdeb9a37..ac1d683f 100644
--- a/tests/check/elements/.gitignore
+++ b/tests/check/elements/.gitignore
@@ -8,6 +8,7 @@ interleave
lpwsinc
mpeg2enc
neonhttpsrc
+souphttpsrc
rganalysis
rglimiter
rgvolume
diff --git a/tests/check/elements/souphttpsrc.c b/tests/check/elements/souphttpsrc.c
new file mode 100644
index 00000000..8f85c1df
--- /dev/null
+++ b/tests/check/elements/souphttpsrc.c
@@ -0,0 +1,408 @@
+/* GStreamer unit tests for the souphttpsrc element
+ * Copyright (C) 2006-2007 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2008 Wouter Cloetens <wouter@mind.be>
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+#include <gst/check/gstcheck.h>
+
+static int http_port = 0, https_port = 0;
+gboolean redirect = TRUE;
+
+static int run_server (int *http_port, int *https_port);
+
+
+static void
+handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
+ GstBuffer ** p_outbuf)
+{
+ GST_LOG ("handoff, buf = %p", buf);
+ if (*p_outbuf == NULL)
+ *p_outbuf = gst_buffer_ref (buf);
+}
+
+int
+run_test (const char *format, ...)
+{
+ GstStateChangeReturn ret;
+ GstElement *pipe, *src, *sink;
+ GstBuffer *buf = NULL;
+ GstMessage *msg;
+ gchar *url;
+ va_list args;
+ int rc = -1;
+
+ pipe = gst_pipeline_new (NULL);
+
+ src = gst_element_factory_make ("souphttpsrc", NULL);
+ fail_unless (src != NULL);
+
+ sink = gst_element_factory_make ("fakesink", NULL);
+ fail_unless (sink != NULL);
+
+ gst_bin_add (GST_BIN (pipe), src);
+ gst_bin_add (GST_BIN (pipe), sink);
+ fail_unless (gst_element_link (src, sink));
+
+ if (http_port == 0) {
+ GST_DEBUG ("failed to start soup http server");
+ }
+ fail_unless (http_port != 0);
+ va_start (args, format);
+ g_vasprintf (&url, format, args);
+ va_end (args);
+ fail_unless (url != NULL);
+ g_object_set (src, "location", url, NULL);
+ g_free (url);
+
+ g_object_set (src, "automatic-redirect", redirect, NULL);
+ g_object_set (sink, "signal-handoffs", TRUE, NULL);
+ g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
+
+ ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
+ if (ret != GST_STATE_CHANGE_ASYNC) {
+ GST_DEBUG ("failed to start up soup http src, ret = %d", ret);
+ goto done;
+ }
+
+ gst_element_set_state (pipe, GST_STATE_PLAYING);
+ msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
+ GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
+ if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
+ gchar *debug = NULL;
+ GError *err = NULL;
+
+ gst_message_parse_error (msg, &err, &debug);
+ GST_INFO ("error: %s", err->message);
+ if (g_str_has_suffix (err->message, "Not Found"))
+ rc = 404;
+ else if (g_str_has_suffix (err->message, "Forbidden"))
+ rc = 403;
+ else if (g_str_has_suffix (err->message, "Found"))
+ rc = 302;
+ GST_INFO ("debug: %s", debug);
+ g_error_free (err);
+ g_free (debug);
+ gst_message_unref (msg);
+ goto done;
+ }
+ gst_message_unref (msg);
+
+ /* don't wait for more than 10 seconds */
+ ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
+ GST_LOG ("ret = %u", ret);
+
+ if (buf == NULL) {
+ /* we want to test the buffer offset, nothing else; if there's a failure
+ * it might be for lots of reasons (no network connection, whatever), we're
+ * not interested in those */
+ GST_DEBUG ("didn't manage to get data within 10 seconds, skipping test");
+ goto done;
+ }
+
+ GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
+
+ /* first buffer should have a 0 offset */
+ fail_unless (GST_BUFFER_OFFSET (buf) == 0);
+ gst_buffer_unref (buf);
+ rc = 0;
+
+done:
+
+ gst_element_set_state (pipe, GST_STATE_NULL);
+ gst_object_unref (pipe);
+ return rc;
+}
+
+GST_START_TEST (test_first_buffer_has_offset)
+{
+ fail_unless (run_test ("http://127.0.0.1:%d/", http_port) == 0);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_not_found)
+{
+ fail_unless (run_test ("http://127.0.0.1:%d/404", http_port) == 404);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_forbidden)
+{
+ fail_unless (run_test ("http://127.0.0.1:%d/403", http_port) == 403);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_redirect_no)
+{
+ redirect = FALSE;
+ fail_unless (run_test ("http://127.0.0.1:%d/302", http_port) == 302);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_redirect_yes)
+{
+ redirect = TRUE;
+ fail_unless (run_test ("http://127.0.0.1:%d/302", http_port) == 0);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_https)
+{
+ if (!https_port)
+ GST_INFO ("Failed to start an HTTPS server; let's just skip this test.");
+ else
+ fail_unless (run_test ("https://127.0.0.1:%d/", https_port) == 0);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_icy_stream)
+{
+ GstElement *pipe, *src, *sink;
+ GstMessage *msg;
+
+ pipe = gst_pipeline_new (NULL);
+
+ src = gst_element_factory_make ("souphttpsrc", NULL);
+ fail_unless (src != NULL);
+
+ sink = gst_element_factory_make ("fakesink", NULL);
+ fail_unless (sink != NULL);
+
+ gst_bin_add (GST_BIN (pipe), src);
+ gst_bin_add (GST_BIN (pipe), sink);
+ fail_unless (gst_element_link (src, sink));
+
+ /* First try Virgin Radio Ogg stream, to see if there's connectivity and all
+ * (which is an attempt to work around the completely horrid error reporting
+ * and that we can't distinguish different types of failures here). */
+
+ g_object_set (src, "location", "http://ogg2.smgradio.com/vr32.ogg", NULL);
+ g_object_set (src, "num-buffers", 1, NULL);
+ gst_element_set_state (pipe, GST_STATE_PLAYING);
+
+ msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
+ GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
+ if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
+ GST_INFO ("looks like there's no net connectivity or sgmradio.com is "
+ "down. In any case, let's just skip this test");
+ gst_message_unref (msg);
+ goto done;
+ }
+ gst_message_unref (msg);
+ msg = NULL;
+ gst_element_set_state (pipe, GST_STATE_NULL);
+
+ /* Now, if the ogg stream works, the mp3 shoutcast stream should work as
+ * well (time will tell if that's true) */
+
+ /* Virgin Radio 32kbps mp3 shoutcast stream */
+ g_object_set (src, "location", "http://mp3-vr-32.smgradio.com:80/", NULL);
+
+
+ /* EOS after the first buffer */
+ g_object_set (src, "num-buffers", 1, NULL);
+
+ gst_element_set_state (pipe, GST_STATE_PLAYING);
+ msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
+ GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
+
+ if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
+ GST_DEBUG ("success, we're done here");
+ gst_message_unref (msg);
+ goto done;
+ }
+
+ {
+ GError *err = NULL;
+
+ gst_message_parse_error (msg, &err, NULL);
+ gst_message_unref (msg);
+ g_error ("Error with ICY mp3 shoutcast stream: %s", err->message);
+ g_error_free (err);
+ }
+
+done:
+
+ gst_element_set_state (pipe, GST_STATE_NULL);
+ gst_object_unref (pipe);
+}
+
+GST_END_TEST;
+
+static Suite *
+souphttpsrc_suite (void)
+{
+ g_type_init ();
+ g_thread_init (NULL);
+
+ Suite *s = suite_create ("souphttpsrc");
+ TCase *tc_chain = tcase_create ("general");
+
+ suite_add_tcase (s, tc_chain);
+ run_server (&http_port, &https_port);
+ tcase_add_test (tc_chain, test_first_buffer_has_offset);
+ tcase_add_test (tc_chain, test_https);
+ tcase_add_test (tc_chain, test_redirect_yes);
+ tcase_add_test (tc_chain, test_redirect_no);
+ tcase_add_test (tc_chain, test_not_found);
+ tcase_add_test (tc_chain, test_forbidden);
+ tcase_add_test (tc_chain, test_icy_stream);
+
+ return s;
+}
+
+GST_CHECK_MAIN (souphttpsrc);
+
+static void
+do_get (SoupMessage * msg, const char *path)
+{
+ char *uri;
+ int buflen = 4096;
+ SoupKnownStatusCode status = SOUP_STATUS_OK;
+
+ uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
+ GST_DEBUG ("request: \"%s\"", uri);
+
+ if (!strcmp (path, "/301"))
+ status = SOUP_STATUS_MOVED_PERMANENTLY;
+ else if (!strcmp (path, "/302"))
+ status = SOUP_STATUS_MOVED_TEMPORARILY;
+ else if (!strcmp (path, "/307"))
+ status = SOUP_STATUS_TEMPORARY_REDIRECT;
+ else if (!strcmp (path, "/403"))
+ status = SOUP_STATUS_FORBIDDEN;
+ else if (!strcmp (path, "/404"))
+ status = SOUP_STATUS_NOT_FOUND;
+
+ if (SOUP_STATUS_IS_REDIRECTION (status)) {
+ char *redir_uri;
+
+ redir_uri = g_strdup_printf ("%s-redirected", uri);
+ soup_message_headers_append (msg->response_headers, "Location", redir_uri);
+ g_free (redir_uri);
+ }
+ if (status != SOUP_STATUS_OK)
+ goto leave;
+
+ if (msg->method == SOUP_METHOD_GET) {
+ char *buf;
+
+ buf = g_malloc (buflen);
+ memset (buf, 0, buflen);
+ soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE,
+ buf, buflen);
+ } else { /* msg->method == SOUP_METHOD_HEAD */
+
+ char *length;
+
+ /* We could just use the same code for both GET and
+ * HEAD. But we'll optimize and avoid the extra
+ * malloc.
+ */
+ length = g_strdup_printf ("%lu", (gulong) buflen);
+ soup_message_headers_append (msg->response_headers,
+ "Content-Length", length);
+ g_free (length);
+ }
+
+leave:
+ soup_message_set_status (msg, status);
+ g_free (uri);
+}
+
+static void
+print_header (const char *name, const char *value, gpointer data)
+{
+ GST_DEBUG ("header: %s: %s", name, value);
+}
+
+static void
+server_callback (SoupServer * server, SoupMessage * msg,
+ const char *path, GHashTable * query,
+ SoupClientContext * context, gpointer data)
+{
+ GST_DEBUG ("%s %s HTTP/1.%d", msg->method, path,
+ soup_message_get_http_version (msg));
+ soup_message_headers_foreach (msg->request_headers, print_header, NULL);
+ if (msg->request_body->length)
+ GST_DEBUG ("%s", msg->request_body->data);
+
+ if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
+ do_get (msg, path);
+ else
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+
+ GST_DEBUG (" -> %d %s", msg->status_code, msg->reason_phrase);
+}
+
+int
+run_server (int *http_port, int *https_port)
+{
+ SoupServer *server, *ssl_server;
+ int port = SOUP_ADDRESS_ANY_PORT;
+ int ssl_port = SOUP_ADDRESS_ANY_PORT;
+ const char *ssl_cert_file = "test-cert.pem", *ssl_key_file = "test-key.pem";
+ static int server_running = 0;
+
+ if (server_running)
+ return 0;
+ server_running = 1;
+
+ *http_port = *https_port = 0;
+
+ server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
+ if (!server) {
+ GST_DEBUG ("Unable to bind to server port %d", port);
+ return 1;
+ }
+ *http_port = soup_server_get_port (server);
+ GST_INFO ("HTTP server listening on port %d", *http_port);
+ soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
+ soup_server_run_async (server);
+
+ if (ssl_cert_file && ssl_key_file) {
+ ssl_server = soup_server_new (SOUP_SERVER_PORT, ssl_port,
+ SOUP_SERVER_SSL_CERT_FILE, ssl_cert_file,
+ SOUP_SERVER_SSL_KEY_FILE, ssl_key_file, NULL);
+
+ if (!ssl_server) {
+ GST_DEBUG ("Unable to bind to SSL server port %d", ssl_port);
+ return 1;
+ }
+ *https_port = soup_server_get_port (ssl_server);
+ GST_INFO ("HTTPS server listening on port %d", *https_port);
+ soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL);
+ soup_server_run_async (ssl_server);
+ }
+
+ return 0;
+}