/* | |
* Copyright (C) 2009 Igalia S.L | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser 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 | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with this library; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
#include "config.h" | |
#include "DataSourceGStreamer.h" | |
#include <gio/gio.h> | |
#include <glib.h> | |
#include <gst/gst.h> | |
#include <gst/pbutils/missing-plugins.h> | |
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("src", | |
GST_PAD_SRC, | |
GST_PAD_ALWAYS, | |
GST_STATIC_CAPS_ANY); | |
GST_DEBUG_CATEGORY_STATIC(webkit_data_src_debug); | |
#define GST_CAT_DEFAULT webkit_data_src_debug | |
static void webkit_data_src_uri_handler_init(gpointer g_iface, | |
gpointer iface_data); | |
static void webkit_data_src_finalize(WebkitDataSrc* src); | |
static GstStateChangeReturn webkit_data_src_change_state(GstElement* element, | |
GstStateChange transition); | |
static const GInterfaceInfo urihandler_info = { | |
webkit_data_src_uri_handler_init, | |
0, 0 | |
}; | |
static void _do_init(GType datasrc_type) | |
{ | |
GST_DEBUG_CATEGORY_INIT(webkit_data_src_debug, "webkit_data_src", 0, "datasrc element"); | |
g_type_add_interface_static(datasrc_type, GST_TYPE_URI_HANDLER, | |
&urihandler_info); | |
} | |
GST_BOILERPLATE_FULL(WebkitDataSrc, webkit_data_src, GstBin, GST_TYPE_BIN, _do_init); | |
static void webkit_data_src_base_init(gpointer klass) | |
{ | |
GstElementClass* element_class = GST_ELEMENT_CLASS(klass); | |
gst_element_class_add_pad_template(element_class, | |
gst_static_pad_template_get(&src_template)); | |
gst_element_class_set_details_simple(element_class, (gchar*) "WebKit data source element", | |
(gchar*) "Source", | |
(gchar*) "Handles data: uris", | |
(gchar*) "Philippe Normand <pnormand@igalia.com>"); | |
} | |
static void webkit_data_src_class_init(WebkitDataSrcClass* klass) | |
{ | |
GObjectClass* oklass = G_OBJECT_CLASS(klass); | |
GstElementClass* eklass = GST_ELEMENT_CLASS(klass); | |
oklass->finalize = (GObjectFinalizeFunc) webkit_data_src_finalize; | |
eklass->change_state = webkit_data_src_change_state; | |
} | |
static gboolean webkit_data_src_reset(WebkitDataSrc* src) | |
{ | |
GstPad* targetpad; | |
if (src->kid) { | |
gst_element_set_state(src->kid, GST_STATE_NULL); | |
gst_bin_remove(GST_BIN(src), src->kid); | |
} | |
src->kid = gst_element_factory_make("giostreamsrc", "streamsrc"); | |
if (!src->kid) { | |
GST_ERROR_OBJECT(src, "Failed to create giostreamsrc"); | |
return FALSE; | |
} | |
gst_bin_add(GST_BIN(src), src->kid); | |
targetpad = gst_element_get_static_pad(src->kid, "src"); | |
gst_ghost_pad_set_target(GST_GHOST_PAD(src->pad), targetpad); | |
gst_object_unref(targetpad); | |
return TRUE; | |
} | |
static void webkit_data_src_init(WebkitDataSrc* src, | |
WebkitDataSrcClass* g_class) | |
{ | |
GstPadTemplate* pad_template = gst_static_pad_template_get(&src_template); | |
src->pad = gst_ghost_pad_new_no_target_from_template("src", | |
pad_template); | |
gst_element_add_pad(GST_ELEMENT(src), src->pad); | |
webkit_data_src_reset(src); | |
} | |
static void webkit_data_src_finalize(WebkitDataSrc* src) | |
{ | |
g_free(src->uri); | |
if (src->kid) { | |
GST_DEBUG_OBJECT(src, "Removing giostreamsrc element"); | |
gst_element_set_state(src->kid, GST_STATE_NULL); | |
gst_bin_remove(GST_BIN(src), src->kid); | |
src->kid = 0; | |
} | |
GST_CALL_PARENT(G_OBJECT_CLASS, finalize, ((GObject* )(src))); | |
} | |
static GstStateChangeReturn webkit_data_src_change_state(GstElement* element, GstStateChange transition) | |
{ | |
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; | |
WebkitDataSrc* src = WEBKIT_DATA_SRC(element); | |
switch (transition) { | |
case GST_STATE_CHANGE_NULL_TO_READY: | |
if (!src->kid) { | |
gst_element_post_message(element, | |
gst_missing_element_message_new(element, "giostreamsrc")); | |
GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (0), ("no giostreamsrc")); | |
return GST_STATE_CHANGE_FAILURE; | |
} | |
break; | |
default: | |
break; | |
} | |
ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); | |
if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) | |
return ret; | |
// Downwards state change code should be here, after chaining up | |
// to the parent class. | |
return ret; | |
} | |
/*** GSTURIHANDLER INTERFACE *************************************************/ | |
static GstURIType webkit_data_src_uri_get_type(void) | |
{ | |
return GST_URI_SRC; | |
} | |
static gchar** webkit_data_src_uri_get_protocols(void) | |
{ | |
static gchar* protocols[] = {(gchar*) "data", 0 }; | |
return protocols; | |
} | |
static const gchar* webkit_data_src_uri_get_uri(GstURIHandler* handler) | |
{ | |
WebkitDataSrc* src = WEBKIT_DATA_SRC(handler); | |
return src->uri; | |
} | |
static gboolean webkit_data_src_uri_set_uri(GstURIHandler* handler, const gchar* uri) | |
{ | |
WebkitDataSrc* src = WEBKIT_DATA_SRC(handler); | |
// URI as defined in RFC2397: | |
// "data:" [ mediatype ] [ ";base64" ] "," data | |
// we parse URIs like this one: | |
// data:audio/3gpp;base64,AA... | |
gchar** scheme_and_remains = g_strsplit(uri, ":", 2); | |
gchar** mime_type_and_options = g_strsplit(scheme_and_remains[1], ";", 0); | |
gint options_size = g_strv_length(mime_type_and_options); | |
gchar* data = 0; | |
gchar* mime_type = 0; | |
gint ret = FALSE; | |
// we require uris with a specified mime-type and base64-encoded | |
// data. It doesn't make much sense anyway to play plain/text data | |
// with very few allowed characters (as per the RFC). | |
if (GST_STATE(src) >= GST_STATE_PAUSED) { | |
GST_ERROR_OBJECT(src, "Element already configured. Reset it and retry"); | |
} else if (!options_size) | |
GST_ERROR_OBJECT(src, "A mime-type is needed in %s", uri); | |
else { | |
mime_type = mime_type_and_options[0]; | |
data = mime_type_and_options[options_size-1]; | |
guchar* decoded_data = 0; | |
gsize decoded_size; | |
if (!g_str_has_prefix(data, "base64")) | |
GST_ERROR_OBJECT(src, "Data has to be base64-encoded in %s", uri); | |
else { | |
decoded_data = g_base64_decode(data+7, &decoded_size); | |
GInputStream* stream = g_memory_input_stream_new_from_data(decoded_data, | |
decoded_size, | |
g_free); | |
g_object_set(src->kid, "stream", stream, NULL); | |
g_object_unref(stream); | |
if (src->uri) { | |
g_free(src->uri); | |
src->uri = 0; | |
} | |
src->uri = g_strdup(uri); | |
ret = TRUE; | |
} | |
} | |
g_strfreev(scheme_and_remains); | |
g_strfreev(mime_type_and_options); | |
return ret; | |
} | |
static void webkit_data_src_uri_handler_init(gpointer g_iface, gpointer iface_data) | |
{ | |
GstURIHandlerInterface* iface = (GstURIHandlerInterface *) g_iface; | |
iface->get_type = webkit_data_src_uri_get_type; | |
iface->get_protocols = webkit_data_src_uri_get_protocols; | |
iface->get_uri = webkit_data_src_uri_get_uri; | |
iface->set_uri = webkit_data_src_uri_set_uri; | |
} |