GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
#define GST_CAT_DEFAULT souphttpsrc_debug
+#define GST_SOUP_SESSION_CONTEXT "gst.soup.session"
+
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
#define DEFAULT_IRADIO_MODE TRUE
#define DEFAULT_SOUP_LOG_LEVEL SOUP_LOGGER_LOG_HEADERS
#define DEFAULT_COMPRESS FALSE
-#define DEFAULT_KEEP_ALIVE FALSE
+#define DEFAULT_KEEP_ALIVE TRUE
#define DEFAULT_SSL_STRICT TRUE
#define DEFAULT_SSL_CA_FILE NULL
#define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE
#define REDUCE_BLOCKSIZE_LIMIT 0.20
#define REDUCE_BLOCKSIZE_COUNT 2
#define REDUCE_BLOCKSIZE_FACTOR 0.5
+#define GROW_TIME_LIMIT (1 * GST_SECOND)
#ifdef TIZEN_FEATURE_SOUP_MODIFICATION
#define DLNA_OP_TIMED_SEEK 0x02
static GstStateChangeReturn gst_soup_http_src_change_state (GstElement *
element, GstStateChange transition);
+static void gst_soup_http_src_set_context (GstElement * element,
+ GstContext * context);
static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
GstBuffer ** outbuf);
static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
"Wouter Cloetens <wouter@mind.be>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_soup_http_src_change_state);
+ gstelement_class->set_context =
+ GST_DEBUG_FUNCPTR (gst_soup_http_src_set_context);
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
src->reduce_blocksize_count = 0;
src->increase_blocksize_count = 0;
+ src->last_socket_read_time = 0;
#ifdef TIZEN_FEATURE_SOUP_MODIFICATION
if (src->dash_oldest_segment) {
#endif
g_cancellable_reset (src->cancellable);
+ g_mutex_lock (&src->mutex);
if (src->input_stream) {
g_object_unref (src->input_stream);
src->input_stream = NULL;
}
+ g_mutex_unlock (&src->mutex);
gst_caps_replace (&src->src_caps, NULL);
g_free (src->iradio_name);
src->cookies = NULL;
src->iradio_mode = DEFAULT_IRADIO_MODE;
src->session = NULL;
+ src->external_session = NULL;
+ src->forced_external_session = FALSE;
src->msg = NULL;
src->timeout = DEFAULT_TIMEOUT;
src->log_level = DEFAULT_SOUP_LOG_LEVEL;
+ src->compress = DEFAULT_COMPRESS;
+ src->keep_alive = DEFAULT_KEEP_ALIVE;
src->ssl_strict = DEFAULT_SSL_STRICT;
src->ssl_use_system_ca_file = DEFAULT_SSL_USE_SYSTEM_CA_FILE;
src->tls_database = DEFAULT_TLS_DATABASE;
gst_soup_http_src_session_close (src);
+ if (src->external_session) {
+ g_object_unref (src->external_session);
+ src->external_session = NULL;
+ }
+
G_OBJECT_CLASS (parent_class)->dispose (gobject);
}
stop_offset);
} else {
rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-",
- offset);
+ 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;
}
}
if (!src->session) {
- GST_DEBUG_OBJECT (src, "Creating session");
- if (src->proxy == NULL) {
- src->session =
- soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
- src->user_agent, SOUP_SESSION_TIMEOUT, src->timeout,
- SOUP_SESSION_SSL_STRICT, src->ssl_strict, NULL);
-// SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
+ GstQuery *query;
+ gboolean can_share = (src->timeout == DEFAULT_TIMEOUT)
+ && (src->ssl_strict == DEFAULT_SSL_STRICT)
+ && (src->tls_interaction == NULL) && (src->proxy == NULL)
+ && (src->tls_database == DEFAULT_TLS_DATABASE)
+ && (src->ssl_ca_file == DEFAULT_SSL_CA_FILE)
+ && (src->ssl_use_system_ca_file == DEFAULT_SSL_USE_SYSTEM_CA_FILE);
+
+ query = gst_query_new_context (GST_SOUP_SESSION_CONTEXT);
+ if (gst_pad_peer_query (GST_BASE_SRC_PAD (src), query)) {
+ GstContext *context;
+
+ gst_query_parse_context (query, &context);
+ gst_element_set_context (GST_ELEMENT_CAST (src), context);
} else {
- src->session =
- soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
- SOUP_SESSION_TIMEOUT, src->timeout,
- SOUP_SESSION_SSL_STRICT, src->ssl_strict,
- SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
-// SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
+ GstMessage *message;
+
+ message =
+ gst_message_new_need_context (GST_OBJECT_CAST (src),
+ GST_SOUP_SESSION_CONTEXT);
+ gst_element_post_message (GST_ELEMENT_CAST (src), message);
+ }
+ gst_query_unref (query);
+
+ GST_OBJECT_LOCK (src);
+ if (src->external_session && (can_share || src->forced_external_session)) {
+ GST_DEBUG_OBJECT (src, "Using external session %p",
+ src->external_session);
+ src->session = g_object_ref (src->external_session);
+ src->session_is_shared = TRUE;
+ } else {
+ GST_DEBUG_OBJECT (src, "Creating session (can share %d)", can_share);
+
+ /* We explicitly set User-Agent to NULL here and overwrite it per message
+ * to be able to have the same session with different User-Agents per
+ * source */
+ if (src->proxy == NULL) {
+ src->session =
+ soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
+ NULL, SOUP_SESSION_TIMEOUT, src->timeout,
+ SOUP_SESSION_SSL_STRICT, src->ssl_strict,
+ SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
+ } else {
+ src->session =
+ soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
+ SOUP_SESSION_TIMEOUT, src->timeout,
+ SOUP_SESSION_SSL_STRICT, src->ssl_strict,
+ SOUP_SESSION_USER_AGENT, NULL,
+ SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
+ }
+
+ if (src->session) {
+ gst_soup_util_log_setup (src->session, src->log_level,
+ GST_ELEMENT (src));
+ soup_session_add_feature_by_type (src->session,
+ SOUP_TYPE_CONTENT_DECODER);
+ soup_session_add_feature_by_type (src->session, SOUP_TYPE_COOKIE_JAR);
+
+ if (can_share) {
+ GstContext *context;
+ GstMessage *message;
+ GstStructure *s;
+
+ GST_DEBUG_OBJECT (src, "Sharing session %p", src->session);
+ src->session_is_shared = TRUE;
+
+ /* Unset the limit the number of maximum allowed connection */
+ g_object_set (src->session, SOUP_SESSION_MAX_CONNS, G_MAXINT,
+ SOUP_SESSION_MAX_CONNS_PER_HOST, G_MAXINT, NULL);
+
+ context = gst_context_new (GST_SOUP_SESSION_CONTEXT, TRUE);
+ s = gst_context_writable_structure (context);
+ gst_structure_set (s, "session", SOUP_TYPE_SESSION, src->session,
+ "force", G_TYPE_BOOLEAN, FALSE, NULL);
+
+ gst_object_ref (src->session);
+ GST_OBJECT_UNLOCK (src);
+ gst_element_set_context (GST_ELEMENT_CAST (src), context);
+ message =
+ gst_message_new_have_context (GST_OBJECT_CAST (src), context);
+ gst_element_post_message (GST_ELEMENT_CAST (src), message);
+ GST_OBJECT_LOCK (src);
+ gst_object_unref (src->session);
+ } else {
+ src->session_is_shared = FALSE;
+ }
+ }
}
if (!src->session) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
- (NULL), ("Failed to create async session"));
+ (NULL), ("Failed to create session"));
+ GST_OBJECT_UNLOCK (src);
return FALSE;
}
g_signal_connect (src->session, "authenticate",
G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
- /* Set up logging */
- gst_soup_util_log_setup (src->session, src->log_level, GST_ELEMENT (src));
- if (src->tls_database)
- g_object_set (src->session, "tls-database", src->tls_database, NULL);
- else if (src->ssl_ca_file)
- g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
- else
- g_object_set (src->session, "ssl-use-system-ca-file",
- src->ssl_use_system_ca_file, NULL);
+ if (!src->session_is_shared) {
+ if (src->tls_database)
+ g_object_set (src->session, "tls-database", src->tls_database, NULL);
+ else if (src->ssl_ca_file)
+ g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
+ else
+ g_object_set (src->session, "ssl-use-system-ca-file",
+ src->ssl_use_system_ca_file, NULL);
+ }
+ GST_OBJECT_UNLOCK (src);
} else {
GST_DEBUG_OBJECT (src, "Re-using session");
}
- if (src->compress)
- soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER);
- else
- soup_session_remove_feature_by_type (src->session,
- SOUP_TYPE_CONTENT_DECODER);
-
return TRUE;
}
}
if (src->session) {
+ if (!src->session_is_shared)
#ifdef TIZEN_FEATURE_SOUP_MODIFICATION
/* When Playback is ongoing and Browser is moved to background ( Pressing Menu or Home Key ), The Session gets destroyed.
But the cookie_jar property remains unfreed. This results in garbage pointer and causes crash.
Removing the cookie_jar feature during close session of browser to handle the issue. */
- GST_DEBUG_OBJECT (src, "Removing Cookie Jar instance");
- soup_session_remove_feature_by_type(src->session, SOUP_TYPE_COOKIE_JAR);
- src->cookie_jar = NULL;
+ {
+ GST_DEBUG_OBJECT (src, "Removing Cookie Jar instance");
+ soup_session_remove_feature_by_type(src->session, SOUP_TYPE_COOKIE_JAR);
+ src->cookie_jar = NULL;
+ soup_session_abort (src->session);
+ }
+#else
+ soup_session_abort (src->session);
#endif
- soup_session_abort (src->session);
+ g_signal_handlers_disconnect_by_func (src->session,
+ G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
g_object_unref (src->session);
src->session = NULL;
}
+
g_mutex_unlock (&src->mutex);
}
gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg,
SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src)
{
+ /* Might be from another user of the shared session */
+ if (!GST_IS_SOUP_HTTP_SRC (src) || msg != src->msg)
+ return;
+
if (!retrying) {
/* First time authentication only, if we fail and are called again with retry true fall through */
if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
{
GST_INFO_OBJECT (src, " %s: %s", name, val);
- if (g_ascii_strcasecmp (name, "Set-Cookie") == 0)
- {
- if (val)
- {
+ if (g_ascii_strcasecmp (name, "Set-Cookie") == 0) {
+ if (val) {
gboolean bret = FALSE;
GstStructure *s = NULL;
GstSoupHTTPSrc * tmp = src;
GST_INFO_OBJECT (src, "request url [%s], posted cookies [%s] msg and returned = %d", tmp->location, val, bret);
}
- }
- else if (g_ascii_strcasecmp (name, "Dash-Oldest-Segment") == 0)
- {
- if (val)
- {
+ } else if (g_ascii_strcasecmp (name, "Dash-Oldest-Segment") == 0) {
+ if (val) {
GstSoupHTTPSrc * tmp = src;
tmp->dash_oldest_segment = g_strdup (val);
GST_INFO_OBJECT (src, "Dash-Oldest-Segment set as %s ", tmp->dash_oldest_segment);
}
- }
- else if (g_ascii_strcasecmp (name, "Dash-Newest-Segment") == 0)
- {
- if (val)
- {
+ } else if (g_ascii_strcasecmp (name, "Dash-Newest-Segment") == 0) {
+ if (val) {
GstSoupHTTPSrc * tmp = src;
tmp->dash_newest_segment = g_strdup (val);
GST_INFO_OBJECT (src, "Dash-Newest-Segment set as %s ", tmp->dash_newest_segment);
return GST_FLOW_OK;
}
- if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
- /* force an error */
- return gst_soup_http_src_parse_status (msg, src);
- }
-
- src->got_headers = TRUE;
- g_cond_broadcast (&src->have_headers_cond);
-
http_headers = gst_structure_new_empty ("http-headers");
- gst_structure_set (http_headers, "uri", G_TYPE_STRING, src->location, NULL);
+ gst_structure_set (http_headers, "uri", G_TYPE_STRING, src->location,
+ "http-status-code", G_TYPE_UINT, msg->status_code, NULL);
if (src->redirection_uri)
gst_structure_set (http_headers, "redirection-uri", G_TYPE_STRING,
src->redirection_uri, NULL);
headers, NULL);
gst_structure_free (headers);
+ gst_element_post_message (GST_ELEMENT_CAST (src),
+ gst_message_new_element (GST_OBJECT_CAST (src),
+ gst_structure_copy (http_headers)));
+
+ if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
+ /* force an error */
+ gst_structure_free (http_headers);
+ return gst_soup_http_src_parse_status (msg, src);
+ }
+
+ src->got_headers = TRUE;
+ g_cond_broadcast (&src->have_headers_cond);
+
http_headers_event =
gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
gst_event_replace (&src->http_headers_event, http_headers_event);
("Error parsing URL."), ("URL: %s", src->location));
return FALSE;
}
+
+ /* Duplicating the defaults of libsoup here. We don't want to set a
+ * User-Agent in the session as each source might have its own User-Agent
+ * set */
+ if (!src->user_agent || !*src->user_agent) {
+ gchar *user_agent =
+ g_strdup_printf ("libsoup/%u.%u.%u", soup_get_major_version (),
+ soup_get_minor_version (), soup_get_micro_version ());
+ soup_message_headers_append (src->msg->request_headers, "User-Agent",
+ user_agent);
+ g_free (user_agent);
+ } else if (g_str_has_suffix (src->user_agent, " ")) {
+ gchar *user_agent = g_strdup_printf ("%slibsoup/%u.%u.%u", src->user_agent,
+ soup_get_major_version (),
+ soup_get_minor_version (), soup_get_micro_version ());
+ soup_message_headers_append (src->msg->request_headers, "User-Agent",
+ user_agent);
+ g_free (user_agent);
+ } else {
+ soup_message_headers_append (src->msg->request_headers, "User-Agent",
+ src->user_agent);
+ }
+
if (!src->keep_alive) {
soup_message_headers_append (src->msg->request_headers, "Connection",
"close");
}
#endif
+ if (!src->compress)
+ soup_message_disable_feature (src->msg, SOUP_TYPE_CONTENT_DECODER);
+
soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS |
(src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT));
return TRUE;
}
+/* Lock taken */
static GstFlowReturn
gst_soup_http_src_send_message (GstSoupHTTPSrc * src)
{
GError *error = NULL;
g_return_val_if_fail (src->msg != NULL, GST_FLOW_ERROR);
+ g_assert (src->input_stream == NULL);
src->input_stream =
soup_session_send (src->session, src->msg, src->cancellable, &error);
if (src->msg && src->request_position > 0) {
gst_soup_http_src_add_range_header (src, src->request_position,
src->stop_position);
- }
+ } else if (src->msg && src->request_position == 0)
+ soup_message_headers_remove (src->msg->request_headers, "Range");
+
+ /* add_range_header() has the side effect of setting read_position to
+ * the requested position. This *needs* to be set regardless of having
+ * a message or not. Failure to do so would result in calculation being
+ * done with stale/wrong read position */
+ src->read_position = src->request_position;
if (!src->msg) {
if (!gst_soup_http_src_build_message (src, method)) {
{
guint blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src));
- GST_LOG_OBJECT (src, "Checking to update blocksize. Read:%" G_GINT64_FORMAT
- " blocksize:%u", bytes_read, blocksize);
+ gint64 time_since_last_read =
+ g_get_monotonic_time () * GST_USECOND - src->last_socket_read_time;
- if (bytes_read >= blocksize * GROW_BLOCKSIZE_LIMIT) {
+ GST_LOG_OBJECT (src, "Checking to update blocksize. Read: %" G_GINT64_FORMAT
+ " bytes, blocksize: %u bytes, time since last read: %" GST_TIME_FORMAT,
+ bytes_read, blocksize, GST_TIME_ARGS (time_since_last_read));
+
+ if (bytes_read >= blocksize * GROW_BLOCKSIZE_LIMIT
+ && time_since_last_read <= GROW_TIME_LIMIT) {
src->reduce_blocksize_count = 0;
src->increase_blocksize_count++;
gst_base_src_set_blocksize (GST_BASE_SRC_CAST (src), blocksize);
src->increase_blocksize_count = 0;
}
- } else if (bytes_read < blocksize * REDUCE_BLOCKSIZE_LIMIT) {
+ } else if (bytes_read < blocksize * REDUCE_BLOCKSIZE_LIMIT
+ || time_since_last_read > GROW_TIME_LIMIT) {
src->reduce_blocksize_count++;
src->increase_blocksize_count = 0;
gst_soup_http_src_check_update_blocksize (src, read_bytes);
+ src->last_socket_read_time = g_get_monotonic_time () * GST_USECOND;
+
/* If we're at the end of a range request, read again to let libsoup
* finalize the request. This allows to reuse the connection again later,
* otherwise we would have to cancel the message and close the connection
goto retry;
}
}
+
+ if (ret == GST_FLOW_FLUSHING) {
+ g_mutex_lock (&src->mutex);
+ src->retry_count = 0;
+ g_mutex_unlock (&src->mutex);
+ }
+
return ret;
}
src = GST_SOUP_HTTP_SRC (bsrc);
GST_DEBUG_OBJECT (src, "stop()");
- if (src->keep_alive && !src->msg)
+ if (src->keep_alive && !src->msg && !src->session_is_shared)
gst_soup_http_src_cancel_message (src);
else
gst_soup_http_src_session_close (src);
return ret;
}
+static void
+gst_soup_http_src_set_context (GstElement * element, GstContext * context)
+{
+ GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (element);
+
+ if (g_strcmp0 (gst_context_get_context_type (context),
+ GST_SOUP_SESSION_CONTEXT) == 0) {
+ const GstStructure *s = gst_context_get_structure (context);
+
+ GST_OBJECT_LOCK (src);
+ if (src->external_session)
+ g_object_unref (src->external_session);
+ src->external_session = NULL;
+ gst_structure_get (s, "session", SOUP_TYPE_SESSION, &src->external_session,
+ NULL);
+ src->forced_external_session = FALSE;
+ gst_structure_get (s, "force", G_TYPE_BOOLEAN,
+ &src->forced_external_session, NULL);
+
+ GST_DEBUG_OBJECT (src, "Setting external session %p (force: %d)",
+ src->external_session, src->forced_external_session);
+ GST_OBJECT_UNLOCK (src);
+ }
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
/* Interrupt a blocking request. */
static gboolean
gst_soup_http_src_unlock (GstBaseSrc * bsrc)
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_SCHEDULING:
-
gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
flags |= GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
}
#endif
gst_query_set_scheduling (query, flags, minsize, maxsize, align);
-
break;
default:
break;
if (uri == NULL || *uri == '\0')
return TRUE;
- if (g_str_has_prefix (uri, "http://")) {
+ if (g_strstr_len (uri, -1, "://")) {
src->proxy = soup_uri_new (uri);
} else {
gchar *new_uri = g_strconcat ("http://", uri, NULL);
return (src->proxy != NULL);
}
-static guint
+static GstURIType
gst_soup_http_src_uri_get_type (GType type)
{
return GST_URI_SRC;