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.
This commit is contained in:
Wouter Cloetens 2008-01-30 13:06:01 +00:00 committed by Sebastian Dröge
parent 1159638102
commit c5058eae62
10 changed files with 976 additions and 285 deletions

View file

@ -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:

View file

@ -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)
])

View file

@ -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 \

View file

@ -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" />

View file

@ -266,6 +266,20 @@ GST_TYPE_GIO_STREAM_SRC
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>

View file

@ -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,6 +443,312 @@ gst_souphttp_src_unicodify (const gchar * str)
return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
}
static void
gst_souphttp_src_cancel_message (GstSouphttpSrc * src)
{
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 void
gst_souphttp_src_queue_message (GstSouphttpSrc * src)
{
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
gst_souphttp_src_add_range_header (GstSouphttpSrc * src, guint64 offset)
{
gchar buf[64];
gint rc;
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_headers_append (src->msg->request_headers, "Range", buf);
}
src->read_position = offset;
return TRUE;
}
static void
gst_souphttp_src_session_unpause_message (GstSouphttpSrc * src)
{
soup_session_unpause_message (src->session, src->msg);
}
static void
gst_souphttp_src_session_pause_message (GstSouphttpSrc * src)
{
soup_session_pause_message (src->session, src->msg);
}
static void
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;
GstBaseSrc *basesrc;
guint64 newsize;
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. */
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 = %" G_GUINT64_FORMAT, src->content_size);
basesrc = GST_BASE_SRC_CAST (src);
gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES,
src->content_size);
gst_element_post_message (GST_ELEMENT (src),
gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_BYTES,
src->content_size));
}
}
/* Icecast stuff */
tag_list = gst_tag_list_new ();
if ((value =
soup_message_headers_get (msg->response_headers,
"icy-metaint")) != NULL) {
gint icy_metaint = atoi (value);
GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
if (icy_metaint > 0)
src->icy_caps = gst_caps_new_simple ("application/x-icy",
"metadata-interval", G_TYPE_INT, icy_metaint, NULL);
}
if ((value =
soup_message_headers_get (msg->response_headers,
"icy-name")) != NULL) {
g_free (src->iradio_name);
src->iradio_name = gst_souphttp_src_unicodify (value);
if (src->iradio_name) {
g_object_notify (G_OBJECT (src), "iradio-name");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
src->iradio_name, NULL);
}
}
if ((value =
soup_message_headers_get (msg->response_headers,
"icy-genre")) != NULL) {
g_free (src->iradio_genre);
src->iradio_genre = gst_souphttp_src_unicodify (value);
if (src->iradio_genre) {
g_object_notify (G_OBJECT (src), "iradio-genre");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
src->iradio_genre, 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) {
g_object_notify (G_OBJECT (src), "iradio-url");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
src->iradio_url, NULL);
}
}
if (!gst_tag_list_is_empty (tag_list)) {
GST_DEBUG_OBJECT (src,
"calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
gst_element_found_tags (GST_ELEMENT_CAST (src), tag_list);
} else {
gst_tag_list_free (tag_list);
}
/* Handle HTTP errors. */
gst_souphttp_src_parse_status (msg, src);
/* Check if Range header was respected. */
if (src->ret == GST_FLOW_CUSTOM_ERROR &&
src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
src->seekable = FALSE;
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("\"%s\": failed to seek; server does not accept Range HTTP header",
src->location), (NULL));
src->ret = GST_FLOW_ERROR;
}
}
/* Have body. Signal EOS. */
static void
gst_souphttp_src_got_body_cb (SoupMessage * msg, GstSouphttpSrc * src)
{
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);
gst_souphttp_src_session_pause_message (src);
}
/* Finished. Signal EOS. */
static void
gst_souphttp_src_finished_cb (SoupMessage * msg, GstSouphttpSrc * src)
{
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)
g_main_loop_quit (src->loop);
}
static void
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);
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, 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), 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);
gst_souphttp_src_session_pause_message (src);
}
static void
gst_souphttp_src_response_cb (SoupSession * session, SoupMessage * msg,
GstSouphttpSrc * src)
{
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);
gst_souphttp_src_parse_status (msg, src);
g_main_loop_quit (src->loop);
}
static void
gst_souphttp_src_parse_status (SoupMessage * msg, GstSouphttpSrc * src)
{
if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
switch (msg->status_code) {
case SOUP_STATUS_CANT_RESOLVE:
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANT_RESOLVE_PROXY:
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("%s", msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANT_CONNECT:
case SOUP_STATUS_CANT_CONNECT_PROXY:
case SOUP_STATUS_SSL_FAILED:
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_IO_ERROR:
case SOUP_STATUS_MALFORMED:
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANCELLED:
/* No error message when interrupted by program. */
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,
("\"%s\": %s", src->location, msg->reason_phrase),
("%d %s", msg->status_code, msg->reason_phrase));
src->ret = GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
@ -322,14 +757,13 @@ gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
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);
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);
soup_session_cancel_message (src->session, src->msg);
src->msg = NULL;
gst_souphttp_src_cancel_message (src);
}
}
if (!src->msg) {
@ -339,30 +773,36 @@ gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
(NULL), ("Error parsing URL \"%s\"", src->location));
return GST_FLOW_ERROR;
}
soup_message_add_header (src->msg->request_headers, "Connection", "close");
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_add_header (src->msg->request_headers, "User-Agent",
soup_message_headers_append (src->msg->request_headers, "User-Agent",
src->user_agent);
}
if (src->iradio_mode) {
soup_message_add_header (src->msg->request_headers, "icy-metadata", "1");
soup_message_headers_append (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);
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) {
soup_session_cancel_message (src->session, src->msg);
src->msg = NULL;
gst_souphttp_src_cancel_message (src);
break;
}
if (!src->msg) {
@ -370,22 +810,19 @@ gst_souphttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
break;
}
switch (src->msg->status) {
case SOUP_MESSAGE_STATUS_IDLE:
switch (src->session_io_status) {
case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_IDLE:
GST_DEBUG_OBJECT (src, "Queueing connection request");
soup_session_queue_message (src->session, src->msg, soup_response, src);
gst_souphttp_src_queue_message (src);
break;
case SOUP_MESSAGE_STATUS_FINISHED:
case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_FINISHED:
GST_DEBUG_OBJECT (src, "Connection closed");
soup_session_cancel_message (src->session, src->msg);
src->msg = NULL;
gst_souphttp_src_cancel_message (src);
break;
case SOUP_MESSAGE_STATUS_QUEUED:
case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_QUEUED:
break;
case SOUP_MESSAGE_STATUS_CONNECTING:
case SOUP_MESSAGE_STATUS_RUNNING:
default:
soup_message_io_unpause (src->msg);
case GST_SOUPHTTP_SRC_SESSION_IO_STATUS_RUNNING:
gst_souphttp_src_session_unpause_message (src);
break;
}
@ -421,9 +858,14 @@ gst_souphttp_src_start (GstBaseSrc * bsrc)
return FALSE;
}
src->session =
soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
src->context, NULL);
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"));
@ -442,7 +884,7 @@ gst_souphttp_src_stop (GstBaseSrc * bsrc)
src = GST_SOUPHTTP_SRC (bsrc);
GST_DEBUG_OBJECT (src, "stop()");
soup_session_close (src);
gst_souphttp_src_session_close (src);
if (src->loop) {
g_main_loop_unref (src->loop);
g_main_context_unref (src->context);
@ -524,23 +966,6 @@ gst_souphttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
return TRUE;
}
static gboolean
soup_add_range_header (GstSouphttpSrc * src, guint64 offset)
{
gchar buf[64];
gint rc;
soup_message_remove_header (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);
}
src->read_position = offset;
return TRUE;
}
static gboolean
gst_souphttp_src_set_location (GstSouphttpSrc * src, const gchar * uri)
{
@ -553,232 +978,16 @@ gst_souphttp_src_set_location (GstSouphttpSrc * src, const gchar * uri)
return TRUE;
}
static void
soup_got_headers (SoupMessage * msg, GstSouphttpSrc * src)
static gboolean
gst_souphttp_src_set_proxy (GstSouphttpSrc * src, const gchar * uri)
{
const char *value;
GstTagList *tag_list;
GstBaseSrc *basesrc;
guint64 newsize;
GST_DEBUG_OBJECT (src, "got headers");
/* 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 (!src->have_size || (src->content_size != newsize)) {
src->content_size = newsize;
src->have_size = TRUE;
GST_DEBUG_OBJECT (src, "size = %llu", src->content_size);
basesrc = GST_BASE_SRC_CAST (src);
gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES,
src->content_size);
gst_element_post_message (GST_ELEMENT (src),
gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_BYTES,
src->content_size));
}
if (src->proxy) {
soup_uri_free (src->proxy);
src->proxy = NULL;
}
src->proxy = soup_uri_new (uri);
/* Icecast stuff */
tag_list = gst_tag_list_new ();
if ((value =
soup_message_get_header (msg->response_headers,
"icy-metaint")) != NULL) {
gint icy_metaint = atoi (value);
GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
if (icy_metaint > 0)
src->icy_caps = gst_caps_new_simple ("application/x-icy",
"metadata-interval", G_TYPE_INT, icy_metaint, NULL);
}
if ((value =
soup_message_get_header (msg->response_headers,
"icy-name")) != NULL) {
g_free (src->iradio_name);
src->iradio_name = gst_souphttp_src_unicodify (value);
if (src->iradio_name) {
g_object_notify (G_OBJECT (src), "iradio-name");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
src->iradio_name, NULL);
}
}
if ((value =
soup_message_get_header (msg->response_headers,
"icy-genre")) != NULL) {
g_free (src->iradio_genre);
src->iradio_genre = gst_souphttp_src_unicodify (value);
if (src->iradio_genre) {
g_object_notify (G_OBJECT (src), "iradio-genre");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
src->iradio_genre, NULL);
}
}
if ((value =
soup_message_get_header (msg->response_headers, "icy-url")) != NULL) {
g_free (src->iradio_url);
src->iradio_url = gst_souphttp_src_unicodify (value);
if (src->iradio_url) {
g_object_notify (G_OBJECT (src), "iradio-url");
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
src->iradio_url, NULL);
}
}
if (!gst_tag_list_is_empty (tag_list)) {
GST_DEBUG_OBJECT (src,
"calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
gst_element_found_tags (GST_ELEMENT_CAST (src), tag_list);
} else {
gst_tag_list_free (tag_list);
}
/* Handle HTTP errors. */
soup_parse_status (msg, src);
/* Check if Range header was respected. */
if (src->ret == GST_FLOW_CUSTOM_ERROR &&
src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
src->seekable = FALSE;
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("\"%s\": failed to seek; server does not accept Range HTTP header",
src->location), (NULL));
src->ret = GST_FLOW_ERROR;
}
}
/* Have body. Signal EOS. */
static void
soup_got_body (SoupMessage * msg, GstSouphttpSrc * src)
{
if (msg != src->msg) {
GST_DEBUG_OBJECT (src, "got body, but not for current message");
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);
}
/* Finished. Signal EOS. */
static void
soup_finished (SoupMessage * msg, GstSouphttpSrc * src)
{
if (msg != src->msg) {
GST_DEBUG_OBJECT (src, "finished, but not for current message");
return;
}
GST_DEBUG_OBJECT (src, "finished");
src->ret = GST_FLOW_UNEXPECTED;
if (src->loop)
g_main_loop_quit (src->loop);
}
static void
soup_got_chunk (SoupMessage * msg, GstSouphttpSrc * src)
{
GstBaseSrc *basesrc;
guint64 new_position;
if (G_UNLIKELY (msg != src->msg)) {
GST_DEBUG_OBJECT (src, "got chunk, but not for current message");
return;
}
basesrc = GST_BASE_SRC_CAST (src);
GST_DEBUG_OBJECT (src, "got chunk of %d bytes", msg->response.length);
/* Create the buffer. */
src->ret = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc),
basesrc->segment.last_stop, msg->response.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;
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);
}
static void
soup_response (SoupMessage * msg, gpointer user_data)
{
GstSouphttpSrc *src = (GstSouphttpSrc *) user_data;
if (msg != src->msg) {
GST_DEBUG_OBJECT (src, "got response %d: %s, but not for current message",
msg->status_code, msg->reason_phrase);
return;
}
GST_DEBUG_OBJECT (src, "got response %d: %s", msg->status_code,
msg->reason_phrase);
soup_parse_status (msg, src);
g_main_loop_quit (src->loop);
}
static void
soup_parse_status (SoupMessage * msg, GstSouphttpSrc * src)
{
if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
switch (msg->status_code) {
case SOUP_STATUS_CANT_RESOLVE:
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANT_RESOLVE_PROXY:
GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
("%s", msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANT_CONNECT:
case SOUP_STATUS_CANT_CONNECT_PROXY:
case SOUP_STATUS_SSL_FAILED:
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_IO_ERROR:
case SOUP_STATUS_MALFORMED:
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("\"%s\": %s", src->location, msg->reason_phrase),
("libsoup status code %d", msg->status_code));
src->ret = GST_FLOW_ERROR;
break;
case SOUP_STATUS_CANCELLED:
/* No error message when interrupted by program. */
break;
}
} else if (SOUP_STATUS_IS_CLIENT_ERROR (msg->status_code) ||
SOUP_STATUS_IS_SERVER_ERROR (msg->status_code)) {
/* Report HTTP error. */
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
("\"%s\": %s", src->location, msg->reason_phrase),
("%d %s", msg->status_code, msg->reason_phrase));
src->ret = GST_FLOW_ERROR;
}
}
static void
soup_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;
}
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);
}

View file

@ -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. */

View file

@ -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)

View file

@ -8,6 +8,7 @@ interleave
lpwsinc
mpeg2enc
neonhttpsrc
souphttpsrc
rganalysis
rglimiter
rgvolume

View file

@ -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;
}