2 * Copyright (C) 2007-2008 Wouter Cloetens <wouter@mind.be>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more
16 * SECTION:element-souphttpsrc
17 * @short_description: Read from an HTTP/HTTPS/WebDAV/Icecast/Shoutcast
22 * This plugin reads data from a remote location specified by a URI.
23 * Supported protocols are 'http', 'https'.
26 * An HTTP proxy must be specified by its URL.
27 * If the "http_proxy" environment variable is set, its value is used.
28 * The element-souphttpsrc::proxy property can be used to override the
32 * In case the element-souphttpsrc::iradio-mode property is set and the
33 * location is an HTTP resource, souphttpsrc will send special Icecast HTTP
34 * headers to the server to request additional Icecast meta-information. If
35 * the server is not an Icecast server, it will behave as if the
36 * element-souphttpsrc::iradio-mode property were not set. If it is,
37 * souphttpsrc will output data with a media type of application/x-icy,
38 * in which case you will need to use the #ICYDemux element as follow-up
39 * element to extract the Icecast metadata and to determine the underlying
45 * gst-launch -v souphttpsrc location=https://some.server.org/index.html
46 * ! filesink location=/home/joe/server.html
48 * The above pipeline reads a web page from a server using the HTTPS protocol
49 * and writes it to a local file.
52 * Another example pipeline:
54 * gst-launch -v souphttpsrc user-agent="FooPlayer 0.99 beta"
55 * automatic-redirect=false proxy=http://proxy.intranet.local:8080
56 * location=http://music.foobar.com/demo.mp3 ! mad ! audioconvert
57 * ! audioresample ! alsasink
59 * The above pipeline will read and decode and play an mp3 file from a
60 * web server using the HTTP protocol. If the server sends redirects,
61 * the request fails instead of following the redirect. The specified
62 * HTTP proxy server is used. The User-Agent HTTP request header
63 * is set to a custom string instead of "GStreamer souphttpsrc."
66 * Yet another example pipeline:
68 * gst-launch -v souphttpsrc location=http://10.11.12.13/mjpeg
69 * do-timestamp=true ! multipartdemux
70 * ! image/jpeg,width=640,height=480 ! matroskamux
71 * ! filesink location=mjpeg.mkv
73 * The above pipeline reads a motion JPEG stream from an IP camera
74 * using the HTTP protocol, encoded as mime/multipart image/jpeg
75 * parts, and writes a Matroska motion JPEG file. The width and
76 * height properties are set in the caps to provide the Matroska
77 * multiplexer with the information to set this in the header.
78 * Timestamps are set on the buffers as they arrive from the camera.
79 * These are used by the mime/multipart demultiplexer to emit timestamps
80 * on the JPEG-encoded video frame buffers. This allows the Matroska
81 * multiplexer to timestamp the frames in the resulting file.
93 #include <stdlib.h> /* atoi() */
95 #include <gst/gstelement.h>
96 #include <gst/gst-i18n-plugin.h>
97 #include <libsoup/soup.h>
98 #include "gstsouphttpsrc.h"
100 #include <gst/tag/tag.h>
102 GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
103 #define GST_CAT_DEFAULT souphttpsrc_debug
105 static const GstElementDetails gst_soup_http_src_details =
106 GST_ELEMENT_DETAILS ("HTTP client source",
108 "Receive data as a client over the network via HTTP using SOUP",
109 "Wouter Cloetens <wouter@mind.be>");
111 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
114 GST_STATIC_CAPS_ANY);
122 PROP_AUTOMATIC_REDIRECT,
132 #define DEFAULT_USER_AGENT "GStreamer souphttpsrc "
134 static void gst_soup_http_src_uri_handler_init (gpointer g_iface,
135 gpointer iface_data);
136 static void gst_soup_http_src_init (GstSoupHTTPSrc * src,
137 GstSoupHTTPSrcClass * g_class);
138 static void gst_soup_http_src_dispose (GObject * gobject);
140 static void gst_soup_http_src_set_property (GObject * object, guint prop_id,
141 const GValue * value, GParamSpec * pspec);
142 static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
143 GValue * value, GParamSpec * pspec);
145 static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
146 GstBuffer ** outbuf);
147 static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
149 static gboolean gst_soup_http_src_stop (GstBaseSrc * bsrc);
151 static gboolean gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size);
153 static gboolean gst_soup_http_src_is_seekable (GstBaseSrc * bsrc);
155 static gboolean gst_soup_http_src_do_seek (GstBaseSrc * bsrc,
156 GstSegment * segment);
157 static gboolean gst_soup_http_src_unlock (GstBaseSrc * bsrc);
159 static gboolean gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc);
161 static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
163 static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
166 static char *gst_soup_http_src_unicodify (const char *str);
168 static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src);
170 static void gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src);
172 static void gst_soup_http_src_queue_message (GstSoupHTTPSrc * src);
174 static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
176 static void gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src);
178 static void gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src);
180 static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
182 static void gst_soup_http_src_parse_status (SoupMessage * msg,
183 GstSoupHTTPSrc * src);
184 static void gst_soup_http_src_chunk_free (gpointer gstbuf);
186 static SoupBuffer *gst_soup_http_src_chunk_allocator (SoupMessage * msg,
187 gsize max_len, gpointer user_data);
188 static void gst_soup_http_src_got_chunk_cb (SoupMessage * msg,
189 SoupBuffer * chunk, GstSoupHTTPSrc * src);
190 static void gst_soup_http_src_response_cb (SoupSession * session,
191 SoupMessage * msg, GstSoupHTTPSrc * src);
192 static void gst_soup_http_src_got_headers_cb (SoupMessage * msg,
193 GstSoupHTTPSrc * src);
194 static void gst_soup_http_src_got_body_cb (SoupMessage * msg,
195 GstSoupHTTPSrc * src);
196 static void gst_soup_http_src_finished_cb (SoupMessage * msg,
197 GstSoupHTTPSrc * src);
201 _do_init (GType type)
203 static const GInterfaceInfo urihandler_info = {
204 gst_soup_http_src_uri_handler_init,
209 g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
211 GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
215 GST_BOILERPLATE_FULL (GstSoupHTTPSrc, gst_soup_http_src, GstPushSrc,
216 GST_TYPE_PUSH_SRC, _do_init);
219 gst_soup_http_src_base_init (gpointer g_class)
221 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
223 gst_element_class_add_pad_template (element_class,
224 gst_static_pad_template_get (&srctemplate));
226 gst_element_class_set_details (element_class, &gst_soup_http_src_details);
230 gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
232 GObjectClass *gobject_class;
234 GstBaseSrcClass *gstbasesrc_class;
236 GstPushSrcClass *gstpushsrc_class;
238 gobject_class = (GObjectClass *) klass;
239 gstbasesrc_class = (GstBaseSrcClass *) klass;
240 gstpushsrc_class = (GstPushSrcClass *) klass;
242 gobject_class->set_property = gst_soup_http_src_set_property;
243 gobject_class->get_property = gst_soup_http_src_get_property;
244 gobject_class->dispose = gst_soup_http_src_dispose;
246 g_object_class_install_property (gobject_class,
248 g_param_spec_string ("location", "Location",
249 "Location to read from", "", G_PARAM_READWRITE));
250 g_object_class_install_property (gobject_class,
252 g_param_spec_string ("user-agent", "User-Agent",
253 "Value of the User-Agent HTTP request header field",
254 DEFAULT_USER_AGENT, G_PARAM_READWRITE));
255 g_object_class_install_property (gobject_class,
256 PROP_AUTOMATIC_REDIRECT,
257 g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
258 "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
259 TRUE, G_PARAM_READWRITE));
260 g_object_class_install_property (gobject_class,
262 g_param_spec_string ("proxy", "Proxy",
263 "HTTP proxy server URI", "", G_PARAM_READWRITE));
264 g_object_class_install_property (gobject_class,
265 PROP_COOKIES, g_param_spec_boxed ("cookies", "Cookies",
266 "HTTP request cookies", G_TYPE_STRV, G_PARAM_READWRITE));
267 g_object_class_install_property (gobject_class,
269 g_param_spec_boolean ("is-live", "is-live",
270 "Act like a live source", FALSE, G_PARAM_READWRITE));
273 g_object_class_install_property (gobject_class,
275 g_param_spec_boolean ("iradio-mode",
277 "Enable internet radio mode (extraction of shoutcast/icecast metadata)",
278 FALSE, G_PARAM_READWRITE));
279 g_object_class_install_property (gobject_class,
281 g_param_spec_string ("iradio-name",
282 "iradio-name", "Name of the stream", NULL, G_PARAM_READABLE));
283 g_object_class_install_property (gobject_class,
285 g_param_spec_string ("iradio-genre",
286 "iradio-genre", "Genre of the stream", NULL, G_PARAM_READABLE));
287 g_object_class_install_property (gobject_class,
289 g_param_spec_string ("iradio-url",
291 "Homepage URL for radio stream", NULL, G_PARAM_READABLE));
292 g_object_class_install_property (gobject_class,
294 g_param_spec_string ("iradio-title",
296 "Name of currently playing song", NULL, G_PARAM_READABLE));
298 gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
299 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
300 gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock);
301 gstbasesrc_class->unlock_stop =
302 GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock_stop);
303 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_soup_http_src_get_size);
304 gstbasesrc_class->is_seekable =
305 GST_DEBUG_FUNCPTR (gst_soup_http_src_is_seekable);
306 gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_soup_http_src_do_seek);
308 gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_soup_http_src_create);
310 GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
311 "SOUP HTTP Client Source");
315 gst_soup_http_src_init (GstSoupHTTPSrc * src, GstSoupHTTPSrcClass * g_class)
319 src->location = NULL;
320 src->automatic_redirect = TRUE;
321 src->user_agent = g_strdup (DEFAULT_USER_AGENT);
323 src->icy_caps = NULL;
324 src->iradio_mode = FALSE;
325 src->iradio_name = NULL;
326 src->iradio_genre = NULL;
327 src->iradio_url = NULL;
328 src->iradio_title = NULL;
333 src->interrupted = FALSE;
335 src->have_size = FALSE;
336 src->seekable = TRUE;
337 src->read_position = 0;
338 src->request_position = 0;
339 proxy = g_getenv ("http_proxy");
340 if (proxy && !gst_soup_http_src_set_proxy (src, proxy)) {
341 GST_WARNING_OBJECT (src,
342 "The proxy in the http_proxy env var (\"%s\") cannot be parsed.",
348 gst_soup_http_src_dispose (GObject * gobject)
350 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
352 GST_DEBUG_OBJECT (src, "dispose");
353 g_free (src->location);
354 src->location = NULL;
355 g_free (src->user_agent);
356 src->user_agent = NULL;
357 if (src->proxy != NULL) {
358 soup_uri_free (src->proxy);
361 g_strfreev (src->cookies);
362 g_free (src->iradio_name);
363 src->iradio_name = NULL;
364 g_free (src->iradio_genre);
365 src->iradio_genre = NULL;
366 g_free (src->iradio_url);
367 src->iradio_url = NULL;
368 g_free (src->iradio_title);
369 src->iradio_title = NULL;
371 gst_caps_unref (src->icy_caps);
372 src->icy_caps = NULL;
375 G_OBJECT_CLASS (parent_class)->dispose (gobject);
379 gst_soup_http_src_set_property (GObject * object, guint prop_id,
380 const GValue * value, GParamSpec * pspec)
382 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
387 const gchar *location;
389 location = g_value_get_string (value);
391 if (location == NULL) {
392 GST_WARNING ("location property cannot be NULL");
395 if (!gst_soup_http_src_set_location (src, location)) {
396 GST_WARNING ("badly formatted location");
401 case PROP_USER_AGENT:
403 g_free (src->user_agent);
404 src->user_agent = g_value_dup_string (value);
406 case PROP_IRADIO_MODE:
407 src->iradio_mode = g_value_get_boolean (value);
410 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
412 case PROP_AUTOMATIC_REDIRECT:
413 src->automatic_redirect = g_value_get_boolean (value);
419 proxy = g_value_get_string (value);
422 GST_WARNING ("proxy property cannot be NULL");
425 if (!gst_soup_http_src_set_proxy (src, proxy)) {
426 GST_WARNING ("badly formatted proxy URI");
432 g_strfreev (src->cookies);
433 src->cookies = g_strdupv (g_value_get_boxed (value));
436 gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
444 gst_soup_http_src_get_property (GObject * object, guint prop_id,
445 GValue * value, GParamSpec * pspec)
447 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
451 g_value_set_string (value, src->location);
453 case PROP_USER_AGENT:
454 g_value_set_string (value, src->user_agent);
456 case PROP_AUTOMATIC_REDIRECT:
457 g_value_set_boolean (value, src->automatic_redirect);
460 if (src->proxy == NULL)
461 g_value_set_string (value, "");
463 char *proxy = soup_uri_to_string (src->proxy, FALSE);
465 g_value_set_string (value, proxy);
470 g_value_set_boxed (value, g_strdupv (src->cookies));
473 g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
475 case PROP_IRADIO_MODE:
476 g_value_set_boolean (value, src->iradio_mode);
478 case PROP_IRADIO_NAME:
479 g_value_set_string (value, src->iradio_name);
481 case PROP_IRADIO_GENRE:
482 g_value_set_string (value, src->iradio_genre);
484 case PROP_IRADIO_URL:
485 g_value_set_string (value, src->iradio_url);
487 case PROP_IRADIO_TITLE:
488 g_value_set_string (value, src->iradio_title);
491 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
497 gst_soup_http_src_unicodify (const gchar * str)
499 const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
500 "GST_TAG_ENCODING", NULL
503 return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
507 gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src)
509 if (src->msg != NULL) {
510 src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED;
511 soup_session_cancel_message (src->session, src->msg, SOUP_STATUS_CANCELLED);
513 src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
518 gst_soup_http_src_queue_message (GstSoupHTTPSrc * src)
520 soup_session_queue_message (src->session, src->msg,
521 (SoupSessionCallback) gst_soup_http_src_response_cb, src);
522 src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED;
526 gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset)
532 soup_message_headers_remove (src->msg->request_headers, "Range");
534 rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-", offset);
535 if (rc > sizeof (buf) || rc < 0)
537 soup_message_headers_append (src->msg->request_headers, "Range", buf);
539 src->read_position = offset;
544 gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src)
546 soup_session_unpause_message (src->session, src->msg);
550 gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src)
552 soup_session_pause_message (src->session, src->msg);
556 gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
559 soup_session_abort (src->session); /* This unrefs the message. */
560 g_object_unref (src->session);
567 gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
571 GstTagList *tag_list;
577 GST_DEBUG_OBJECT (src, "got headers");
579 if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
580 GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code,
581 soup_message_headers_get (msg->response_headers, "Location"));
585 if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
588 src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING;
590 /* Parse Content-Length. */
591 if (soup_message_headers_get_encoding (msg->response_headers) ==
592 SOUP_ENCODING_CONTENT_LENGTH) {
593 newsize = src->request_position +
594 soup_message_headers_get_content_length (msg->response_headers);
595 if (!src->have_size || (src->content_size != newsize)) {
596 src->content_size = newsize;
597 src->have_size = TRUE;
598 GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size);
600 basesrc = GST_BASE_SRC_CAST (src);
601 gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES,
603 gst_element_post_message (GST_ELEMENT (src),
604 gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_BYTES,
610 tag_list = gst_tag_list_new ();
613 soup_message_headers_get (msg->response_headers,
614 "icy-metaint")) != NULL) {
615 gint icy_metaint = atoi (value);
617 GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
618 if (icy_metaint > 0) {
620 gst_caps_unref (src->icy_caps);
622 src->icy_caps = gst_caps_new_simple ("application/x-icy",
623 "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
628 soup_message_headers_get (msg->response_headers,
629 "icy-name")) != NULL) {
630 g_free (src->iradio_name);
631 src->iradio_name = gst_soup_http_src_unicodify (value);
632 if (src->iradio_name) {
633 g_object_notify (G_OBJECT (src), "iradio-name");
634 gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
635 src->iradio_name, NULL);
639 soup_message_headers_get (msg->response_headers,
640 "icy-genre")) != NULL) {
641 g_free (src->iradio_genre);
642 src->iradio_genre = gst_soup_http_src_unicodify (value);
643 if (src->iradio_genre) {
644 g_object_notify (G_OBJECT (src), "iradio-genre");
645 gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
646 src->iradio_genre, NULL);
649 if ((value = soup_message_headers_get (msg->response_headers, "icy-url"))
651 g_free (src->iradio_url);
652 src->iradio_url = gst_soup_http_src_unicodify (value);
653 if (src->iradio_url) {
654 g_object_notify (G_OBJECT (src), "iradio-url");
655 gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
656 src->iradio_url, NULL);
659 if (!gst_tag_list_is_empty (tag_list)) {
660 GST_DEBUG_OBJECT (src,
661 "calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
662 gst_element_found_tags (GST_ELEMENT_CAST (src), tag_list);
664 gst_tag_list_free (tag_list);
667 /* Handle HTTP errors. */
668 gst_soup_http_src_parse_status (msg, src);
670 /* Check if Range header was respected. */
671 if (src->ret == GST_FLOW_CUSTOM_ERROR &&
672 src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
673 src->seekable = FALSE;
674 GST_ELEMENT_ERROR (src, RESOURCE, READ,
675 ("\"%s\": failed to seek; server does not accept Range HTTP header",
676 src->location), (NULL));
677 src->ret = GST_FLOW_ERROR;
681 /* Have body. Signal EOS. */
683 gst_soup_http_src_got_body_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
685 if (G_UNLIKELY (msg != src->msg)) {
686 GST_DEBUG_OBJECT (src, "got body, but not for current message");
689 if (G_UNLIKELY (src->session_io_status !=
690 GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
691 /* Probably a redirect. */
694 GST_DEBUG_OBJECT (src, "got body");
695 src->ret = GST_FLOW_UNEXPECTED;
697 g_main_loop_quit (src->loop);
698 gst_soup_http_src_session_pause_message (src);
701 /* Finished. Signal EOS. */
703 gst_soup_http_src_finished_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
705 if (G_UNLIKELY (msg != src->msg)) {
706 GST_DEBUG_OBJECT (src, "finished, but not for current message");
709 GST_DEBUG_OBJECT (src, "finished");
710 src->ret = GST_FLOW_UNEXPECTED;
711 if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED) {
712 /* gst_soup_http_src_cancel_message() triggered this; probably a seek
713 * that occurred in the QUEUEING state; i.e. before the connection setup
714 * was complete. Do nothing */
715 } else if (src->session_io_status ==
716 GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING && src->read_position > 0) {
717 /* The server disconnected while streaming. Reconnect and seeking to the
720 src->ret = GST_FLOW_CUSTOM_ERROR;
721 } else if (G_UNLIKELY (src->session_io_status !=
722 GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
723 GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
724 ("%s", msg->reason_phrase),
725 ("libsoup status code %d", msg->status_code));
728 g_main_loop_quit (src->loop);
731 /* Buffer lifecycle management.
733 * gst_soup_http_src_create() runs the GMainLoop for this element, to let
735 * A GstBuffer is allocated in gst_soup_http_src_chunk_allocator() and
736 * associated with a SoupBuffer.
737 * Soup reads HTTP data in the GstBuffer's data buffer.
738 * The gst_soup_http_src_got_chunk_cb() is then called with the SoupBuffer.
739 * That sets gst_soup_http_src_create()'s return argument to the GstBuffer,
740 * increments its refcount (to 2), pauses the flow of data from the HTTP
741 * source to prevent gst_soup_http_src_got_chunk_cb() from being called
742 * again and breaks out of the GMainLoop.
743 * Because the SOUP_MESSAGE_OVERWRITE_CHUNKS flag is set, Soup frees the
744 * SoupBuffer and calls gst_soup_http_src_chunk_free(), which decrements the
746 * gst_soup_http_src_create() returns the GstBuffer. It will be freed by a
747 * downstream element.
748 * If Soup fails to read HTTP data, it does not call
749 * gst_soup_http_src_got_chunk_cb(), but still frees the SoupBuffer and
750 * calls gst_soup_http_src_chunk_free(), which decrements the GstBuffer's
751 * refcount to 0, freeing it.
755 gst_soup_http_src_chunk_free (gpointer gstbuf)
757 gst_buffer_unref (GST_BUFFER_CAST (gstbuf));
761 gst_soup_http_src_chunk_allocator (SoupMessage * msg, gsize max_len,
764 GstSoupHTTPSrc *src = (GstSoupHTTPSrc *) user_data;
766 GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src);
777 length = MIN (basesrc->blocksize, max_len);
779 length = basesrc->blocksize;
780 GST_DEBUG_OBJECT (src, "alloc %" G_GSIZE_FORMAT " bytes <= %" G_GSIZE_FORMAT,
784 rc = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc),
785 GST_BUFFER_OFFSET_NONE, length,
786 src->icy_caps ? src->icy_caps :
787 GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)), &gstbuf);
788 if (G_UNLIKELY (rc != GST_FLOW_OK)) {
789 /* Failed to allocate buffer. Stall SoupSession and return error code
792 g_main_loop_quit (src->loop);
796 soupbuf = soup_buffer_new_with_owner (GST_BUFFER_DATA (gstbuf), length,
797 gstbuf, gst_soup_http_src_chunk_free);
803 gst_soup_http_src_got_chunk_cb (SoupMessage * msg, SoupBuffer * chunk,
804 GstSoupHTTPSrc * src)
808 guint64 new_position;
810 if (G_UNLIKELY (msg != src->msg)) {
811 GST_DEBUG_OBJECT (src, "got chunk, but not for current message");
814 if (G_UNLIKELY (src->session_io_status !=
815 GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
816 /* Probably a redirect. */
819 basesrc = GST_BASE_SRC_CAST (src);
820 GST_DEBUG_OBJECT (src, "got chunk of %" G_GSIZE_FORMAT " bytes",
823 /* Extract the GstBuffer from the SoupBuffer and set its fields. */
824 *src->outbuf = GST_BUFFER_CAST (soup_buffer_get_owner (chunk));
825 gst_buffer_ref (*src->outbuf);
826 GST_BUFFER_SIZE (*src->outbuf) = chunk->length;
827 GST_BUFFER_OFFSET (*src->outbuf) = basesrc->segment.last_stop;
829 gst_buffer_set_caps (*src->outbuf,
830 (src->icy_caps) ? src->icy_caps :
831 GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)));
833 new_position = src->read_position + chunk->length;
834 if (G_LIKELY (src->request_position == src->read_position))
835 src->request_position = new_position;
836 src->read_position = new_position;
838 src->ret = GST_FLOW_OK;
839 g_main_loop_quit (src->loop);
840 gst_soup_http_src_session_pause_message (src);
844 gst_soup_http_src_response_cb (SoupSession * session, SoupMessage * msg,
845 GstSoupHTTPSrc * src)
847 if (G_UNLIKELY (msg != src->msg)) {
848 GST_DEBUG_OBJECT (src, "got response %d: %s, but not for current message",
849 msg->status_code, msg->reason_phrase);
852 if (G_UNLIKELY (src->session_io_status !=
853 GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)
854 && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
855 /* Ignore redirections. */
858 GST_DEBUG_OBJECT (src, "got response %d: %s", msg->status_code,
860 if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING &&
861 src->read_position > 0) {
862 /* The server disconnected while streaming. Reconnect and seeking to the
866 gst_soup_http_src_parse_status (msg, src);
867 /* The session's SoupMessage object expires after this callback returns. */
869 g_main_loop_quit (src->loop);
873 gst_soup_http_src_parse_status (SoupMessage * msg, GstSoupHTTPSrc * src)
875 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
876 switch (msg->status_code) {
877 case SOUP_STATUS_CANT_RESOLVE:
878 GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
879 ("\"%s\": %s", src->location, msg->reason_phrase),
880 ("libsoup status code %d", msg->status_code));
881 src->ret = GST_FLOW_ERROR;
883 case SOUP_STATUS_CANT_RESOLVE_PROXY:
884 GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
885 ("%s", msg->reason_phrase),
886 ("libsoup status code %d", msg->status_code));
887 src->ret = GST_FLOW_ERROR;
889 case SOUP_STATUS_CANT_CONNECT:
890 case SOUP_STATUS_CANT_CONNECT_PROXY:
891 case SOUP_STATUS_SSL_FAILED:
892 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
893 ("\"%s\": %s", src->location, msg->reason_phrase),
894 ("libsoup status code %d", msg->status_code));
895 src->ret = GST_FLOW_ERROR;
897 case SOUP_STATUS_IO_ERROR:
898 case SOUP_STATUS_MALFORMED:
899 GST_ELEMENT_ERROR (src, RESOURCE, READ,
900 ("\"%s\": %s", src->location, msg->reason_phrase),
901 ("libsoup status code %d", msg->status_code));
902 src->ret = GST_FLOW_ERROR;
904 case SOUP_STATUS_CANCELLED:
905 /* No error message when interrupted by program. */
908 } else if (SOUP_STATUS_IS_CLIENT_ERROR (msg->status_code) ||
909 SOUP_STATUS_IS_REDIRECTION (msg->status_code) ||
910 SOUP_STATUS_IS_SERVER_ERROR (msg->status_code)) {
911 /* Report HTTP error. */
912 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
913 ("\"%s\": %s", src->location, msg->reason_phrase),
914 ("%d %s", msg->status_code, msg->reason_phrase));
915 src->ret = GST_FLOW_ERROR;
920 gst_soup_http_src_build_message (GstSoupHTTPSrc * src)
922 src->msg = soup_message_new (SOUP_METHOD_GET, src->location);
924 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
925 (NULL), ("Error parsing URL \"%s\"", src->location));
928 src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
929 soup_message_headers_append (src->msg->request_headers, "Connection",
931 if (src->iradio_mode) {
932 soup_message_headers_append (src->msg->request_headers, "icy-metadata",
938 for (cookie = src->cookies; *cookie != NULL; cookie++) {
939 soup_message_headers_append (src->msg->request_headers, "Cookie",
943 soup_message_headers_append (src->msg->request_headers,
944 "transferMode.dlna.org", "Streaming");
947 g_signal_connect (src->msg, "got_headers",
948 G_CALLBACK (gst_soup_http_src_got_headers_cb), src);
949 g_signal_connect (src->msg, "got_body",
950 G_CALLBACK (gst_soup_http_src_got_body_cb), src);
951 g_signal_connect (src->msg, "finished",
952 G_CALLBACK (gst_soup_http_src_finished_cb), src);
953 g_signal_connect (src->msg, "got_chunk",
954 G_CALLBACK (gst_soup_http_src_got_chunk_cb), src);
955 soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS |
956 (src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT));
957 soup_message_set_chunk_allocator (src->msg,
958 gst_soup_http_src_chunk_allocator, src, NULL);
959 gst_soup_http_src_add_range_header (src, src->request_position);
965 gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
969 src = GST_SOUP_HTTP_SRC (psrc);
971 if (src->msg && (src->request_position != src->read_position)) {
972 if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
973 gst_soup_http_src_add_range_header (src, src->request_position);
975 GST_DEBUG_OBJECT (src, "Seek from position %" G_GUINT64_FORMAT
976 " to %" G_GUINT64_FORMAT ": requeueing connection request",
977 src->read_position, src->request_position);
978 gst_soup_http_src_cancel_message (src);
982 if (!gst_soup_http_src_build_message (src))
983 return GST_FLOW_ERROR;
985 src->ret = GST_FLOW_CUSTOM_ERROR;
986 src->outbuf = outbuf;
988 if (src->interrupted) {
989 GST_DEBUG_OBJECT (src, "interrupted");
993 GST_DEBUG_OBJECT (src, "Reconnecting");
994 if (!gst_soup_http_src_build_message (src))
995 return GST_FLOW_ERROR;
1000 GST_DEBUG_OBJECT (src, "EOS reached");
1004 switch (src->session_io_status) {
1005 case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE:
1006 GST_DEBUG_OBJECT (src, "Queueing connection request");
1007 gst_soup_http_src_queue_message (src);
1009 case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED:
1011 case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING:
1012 gst_soup_http_src_session_unpause_message (src);
1014 case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED:
1019 if (src->ret == GST_FLOW_CUSTOM_ERROR)
1020 g_main_loop_run (src->loop);
1021 } while (src->ret == GST_FLOW_CUSTOM_ERROR);
1023 if (src->ret == GST_FLOW_CUSTOM_ERROR)
1024 src->ret = GST_FLOW_UNEXPECTED;
1029 gst_soup_http_src_start (GstBaseSrc * bsrc)
1031 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1033 GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
1035 if (!src->location) {
1036 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1037 (NULL), ("Missing location property"));
1041 src->context = g_main_context_new ();
1043 src->loop = g_main_loop_new (src->context, TRUE);
1045 GST_ELEMENT_ERROR (src, LIBRARY, INIT,
1046 (NULL), ("Failed to start GMainLoop"));
1047 g_main_context_unref (src->context);
1051 if (src->proxy == NULL)
1053 soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
1054 src->context, SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
1057 soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
1058 src->context, SOUP_SESSION_PROXY_URI, src->proxy,
1059 SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
1060 if (!src->session) {
1061 GST_ELEMENT_ERROR (src, LIBRARY, INIT,
1062 (NULL), ("Failed to create async session"));
1070 gst_soup_http_src_stop (GstBaseSrc * bsrc)
1072 GstSoupHTTPSrc *src;
1074 src = GST_SOUP_HTTP_SRC (bsrc);
1075 GST_DEBUG_OBJECT (src, "stop()");
1076 gst_soup_http_src_session_close (src);
1078 g_main_loop_unref (src->loop);
1079 g_main_context_unref (src->context);
1081 src->context = NULL;
1087 /* Interrupt a blocking request. */
1089 gst_soup_http_src_unlock (GstBaseSrc * bsrc)
1091 GstSoupHTTPSrc *src;
1093 src = GST_SOUP_HTTP_SRC (bsrc);
1094 GST_DEBUG_OBJECT (src, "unlock()");
1096 src->interrupted = TRUE;
1098 g_main_loop_quit (src->loop);
1102 /* Interrupt interrupt. */
1104 gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc)
1106 GstSoupHTTPSrc *src;
1108 src = GST_SOUP_HTTP_SRC (bsrc);
1109 GST_DEBUG_OBJECT (src, "unlock_stop()");
1111 src->interrupted = FALSE;
1116 gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size)
1118 GstSoupHTTPSrc *src;
1120 src = GST_SOUP_HTTP_SRC (bsrc);
1122 if (src->have_size) {
1123 GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT,
1125 *size = src->content_size;
1128 GST_DEBUG_OBJECT (src, "get_size() = FALSE");
1133 gst_soup_http_src_is_seekable (GstBaseSrc * bsrc)
1135 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1137 return src->seekable;
1141 gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
1143 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1145 GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT ")", segment->start);
1147 if (src->read_position == segment->start)
1153 /* Wait for create() to handle the jump in offset. */
1154 src->request_position = segment->start;
1159 gst_soup_http_src_set_location (GstSoupHTTPSrc * src, const gchar * uri)
1161 if (src->location) {
1162 g_free (src->location);
1163 src->location = NULL;
1165 src->location = g_strdup (uri);
1171 gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src, const gchar * uri)
1174 soup_uri_free (src->proxy);
1177 if (g_str_has_prefix (uri, "http://")) {
1178 src->proxy = soup_uri_new (uri);
1180 gchar *new_uri = g_strconcat ("http://", uri, NULL);
1182 src->proxy = soup_uri_new (new_uri);
1190 gst_soup_http_src_uri_get_type (void)
1196 gst_soup_http_src_uri_get_protocols (void)
1198 static gchar *protocols[] = { "http", "https", NULL };
1202 static const gchar *
1203 gst_soup_http_src_uri_get_uri (GstURIHandler * handler)
1205 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1207 return src->location;
1211 gst_soup_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
1213 GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1215 return gst_soup_http_src_set_location (src, uri);
1219 gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1221 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1223 iface->get_type = gst_soup_http_src_uri_get_type;
1224 iface->get_protocols = gst_soup_http_src_uri_get_protocols;
1225 iface->get_uri = gst_soup_http_src_uri_get_uri;
1226 iface->set_uri = gst_soup_http_src_uri_set_uri;
1230 plugin_init (GstPlugin * plugin)
1232 return gst_element_register (plugin, "souphttpsrc", GST_RANK_PRIMARY,
1233 GST_TYPE_SOUP_HTTP_SRC);
1236 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1239 "libsoup HTTP client src",
1240 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)