| /* |
| * 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; |
| } |