WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-162902-20161025092021.patch (text/plain), 57.62 KB, created by
Enrique Ocaña
on 2016-10-25 02:24:07 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Enrique Ocaña
Created:
2016-10-25 02:24:07 PDT
Size:
57.62 KB
patch
obsolete
>Subversion Revision: 207639 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 6afb177a823056eb227660d01154ec42df95961e..1a8702226a1d48d2cb8d40aab84431202c1c0f5f 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,5 +1,58 @@ > 2016-10-04 Enrique Ocaña González <eocanha@igalia.com> > >+ [GStreamer][MSE] WebKitMediaSourceGStreamer refactoring >+ https://bugs.webkit.org/show_bug.cgi?id=162902 >+ >+ Reviewed by Xabier Rodriguez-Calvar. >+ >+ Move WebKitMediaSourceGStreamer to the mse directory, split public and private header >+ sections, manage stream data throttling and seek synchronization, audio/video/text stream >+ counting, improve GStreamer duration query, stream initialization, remove >+ MediaSourceClientGStreamer logic (factored out to its own class in another patch) and >+ interaction with MediaPlayerPrivateGStreamerMSE, PlaybackPipeline and >+ SourceBufferPrivateGStreamer. >+ >+ * platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp: Removed. >+ * platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp: Added. >+ (disabledAppsrcNeedData): >+ (disabledAppsrcEnoughData): >+ (disabledAppsrcSeekData): >+ (enabledAppsrcEnoughData): >+ (enabledAppsrcSeekData): >+ (getStreamByAppsrc): >+ (webkit_media_src_init): >+ (webKitMediaSrcFinalize): >+ (webKitMediaSrcSetProperty): >+ (webKitMediaSrcGetProperty): >+ (webKitMediaSrcDoAsyncStart): >+ (webKitMediaSrcDoAsyncDone): >+ (webKitMediaSrcChangeState): >+ (webKitMediaSrcGetSize): >+ (webKitMediaSrcQueryWithParent): >+ (webKitMediaSrcUpdatePresentationSize): >+ (webKitMediaSrcLinkStreamToSrcPad): >+ (webKitMediaSrcLinkParser): >+ (webKitMediaSrcFreeStream): >+ (webKitMediaSrcCheckAllTracksConfigured): >+ (webKitMediaSrcUriGetType): >+ (webKitMediaSrcGetProtocols): >+ (webKitMediaSrcGetUri): >+ (webKitMediaSrcSetUri): >+ (webKitMediaSrcUriHandlerInit): >+ (seekNeedsDataMainThread): >+ (notifyReadyForMoreSamplesMainThread): >+ (applicationMessageCallback): >+ (webKitMediaSrcSetMediaPlayerPrivate): >+ (webKitMediaSrcSetReadyForSamples): >+ (webKitMediaSrcPrepareSeek): >+ (WTF::adoptGRef): >+ (WTF::refGPtr<WebKitMediaSrc>): >+ (WTF::derefGPtr<WebKitMediaSrc>): >+ * platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.h: Renamed from Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h. >+ * platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamerPrivate.h: Added. >+ >+2016-10-04 Enrique Ocaña González <eocanha@igalia.com> >+ > [GStreamer][MSE] Playback pipeline > https://bugs.webkit.org/show_bug.cgi?id=162901 > >diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp >deleted file mode 100644 >index 31250ea3172984076308a97433ba322ba685f916..0000000000000000000000000000000000000000 >--- a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp >+++ /dev/null >@@ -1,461 +0,0 @@ >-/* >- * Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> >- * Copyright (C) 2013 Collabora Ltd. >- * Copyright (C) 2013 Orange >- * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com> >- * >- * 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 "WebKitMediaSourceGStreamer.h" >- >-#if ENABLE(VIDEO) && ENABLE(MEDIA_SOURCE) && USE(GSTREAMER) >- >-#include "GStreamerUtilities.h" >-#include "NotImplemented.h" >-#include "TimeRanges.h" >- >-#include <gst/app/gstappsrc.h> >-#include <gst/gst.h> >-#include <gst/pbutils/missing-plugins.h> >-#include <wtf/glib/GUniquePtr.h> >-#include <wtf/text/CString.h> >- >-typedef struct _Source Source; >-struct _Source { >- GstElement* src; >- // Just for identification >- WebCore::SourceBufferPrivate* sourceBuffer; >-}; >- >-struct _WebKitMediaSrcPrivate { >- GList* sources; >- gchar* location; >- GstClockTime duration; >- bool haveAppsrc; >- bool asyncStart; >- bool noMorePads; >-}; >- >-enum { >- Prop0, >- PropLocation >-}; >- >-static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src_%u", GST_PAD_SRC, >- GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); >- >-#define WEBKIT_MEDIA_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrcPrivate)) >- >-GST_DEBUG_CATEGORY_STATIC(webkit_media_src_debug); >-#define GST_CAT_DEFAULT webkit_media_src_debug >- >-static void webKitMediaSrcUriHandlerInit(gpointer gIface, gpointer ifaceData); >-static void webKitMediaSrcFinalize(GObject*); >-static void webKitMediaSrcSetProperty(GObject*, guint propertyId, const GValue*, GParamSpec*); >-static void webKitMediaSrcGetProperty(GObject*, guint propertyId, GValue*, GParamSpec*); >-static GstStateChangeReturn webKitMediaSrcChangeState(GstElement*, GstStateChange); >-static gboolean webKitMediaSrcQueryWithParent(GstPad*, GstObject*, GstQuery*); >- >-#define webkit_media_src_parent_class parent_class >-// We split this out into another macro to avoid a check-webkit-style error. >-#define WEBKIT_MEDIA_SRC_CATEGORY_INIT GST_DEBUG_CATEGORY_INIT(webkit_media_src_debug, "webkitmediasrc", 0, "websrc element"); >-G_DEFINE_TYPE_WITH_CODE(WebKitMediaSrc, webkit_media_src, GST_TYPE_BIN, >- G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, webKitMediaSrcUriHandlerInit); >- WEBKIT_MEDIA_SRC_CATEGORY_INIT); >- >-static void webkit_media_src_class_init(WebKitMediaSrcClass* klass) >-{ >- GObjectClass* oklass = G_OBJECT_CLASS(klass); >- GstElementClass* eklass = GST_ELEMENT_CLASS(klass); >- >- oklass->finalize = webKitMediaSrcFinalize; >- oklass->set_property = webKitMediaSrcSetProperty; >- oklass->get_property = webKitMediaSrcGetProperty; >- >- gst_element_class_add_pad_template(eklass, gst_static_pad_template_get(&srcTemplate)); >- >- gst_element_class_set_static_metadata(eklass, "WebKit Media source element", "Source", "Handles Blob uris", "Stephane Jadaud <sjadaud@sii.fr>, Sebastian Dröge <sebastian@centricular.com>"); >- >- /* Allows setting the uri using the 'location' property, which is used >- * for example by gst_element_make_from_uri() */ >- g_object_class_install_property(oklass, >- PropLocation, >- g_param_spec_string("location", "location", "Location to read from", 0, >- (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); >- >- eklass->change_state = webKitMediaSrcChangeState; >- >- g_type_class_add_private(klass, sizeof(WebKitMediaSrcPrivate)); >-} >- >-static void webkit_media_src_init(WebKitMediaSrc* src) >-{ >- src->priv = WEBKIT_MEDIA_SRC_GET_PRIVATE(src); >-} >- >-static void webKitMediaSrcFinalize(GObject* object) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(object); >- WebKitMediaSrcPrivate* priv = src->priv; >- >- // TODO: Free sources >- g_free(priv->location); >- >- GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); >-} >- >-static void webKitMediaSrcSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(object); >- >- switch (propId) { >- case PropLocation: >- gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), 0); >- break; >- default: >- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); >- break; >- } >-} >- >-static void webKitMediaSrcGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(object); >- WebKitMediaSrcPrivate* priv = src->priv; >- >- GST_OBJECT_LOCK(src); >- switch (propId) { >- case PropLocation: >- g_value_set_string(value, priv->location); >- break; >- default: >- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); >- break; >- } >- GST_OBJECT_UNLOCK(src); >-} >- >-static void webKitMediaSrcDoAsyncStart(WebKitMediaSrc* src) >-{ >- WebKitMediaSrcPrivate* priv = src->priv; >- priv->asyncStart = true; >- GST_BIN_CLASS(parent_class)->handle_message(GST_BIN(src), >- gst_message_new_async_start(GST_OBJECT(src))); >-} >- >-static void webKitMediaSrcDoAsyncDone(WebKitMediaSrc* src) >-{ >- WebKitMediaSrcPrivate* priv = src->priv; >- if (priv->asyncStart) { >- GST_BIN_CLASS(parent_class)->handle_message(GST_BIN(src), >- gst_message_new_async_done(GST_OBJECT(src), GST_CLOCK_TIME_NONE)); >- priv->asyncStart = false; >- } >-} >- >-static GstStateChangeReturn webKitMediaSrcChangeState(GstElement* element, GstStateChange transition) >-{ >- GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(element); >- WebKitMediaSrcPrivate* priv = src->priv; >- >- switch (transition) { >- case GST_STATE_CHANGE_READY_TO_PAUSED: >- priv->noMorePads = false; >- webKitMediaSrcDoAsyncStart(src); >- break; >- default: >- break; >- } >- >- ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); >- if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) { >- GST_DEBUG_OBJECT(src, "State change failed"); >- webKitMediaSrcDoAsyncDone(src); >- return ret; >- } >- >- switch (transition) { >- case GST_STATE_CHANGE_READY_TO_PAUSED: >- ret = GST_STATE_CHANGE_ASYNC; >- break; >- case GST_STATE_CHANGE_PAUSED_TO_READY: >- webKitMediaSrcDoAsyncDone(src); >- priv->noMorePads = false; >- break; >- default: >- break; >- } >- >- return ret; >-} >- >-static gboolean webKitMediaSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQuery* query) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(GST_ELEMENT(parent)); >- gboolean result = FALSE; >- >- switch (GST_QUERY_TYPE(query)) { >- case GST_QUERY_DURATION: { >- GstFormat format; >- gst_query_parse_duration(query, &format, NULL); >- >- GST_DEBUG_OBJECT(src, "duration query in format %s", gst_format_get_name(format)); >- GST_OBJECT_LOCK(src); >- if ((format == GST_FORMAT_TIME) && (src->priv->duration > 0)) { >- gst_query_set_duration(query, format, src->priv->duration); >- result = TRUE; >- } >- GST_OBJECT_UNLOCK(src); >- break; >- } >- case GST_QUERY_URI: { >- GST_OBJECT_LOCK(src); >- gst_query_set_uri(query, src->priv->location); >- GST_OBJECT_UNLOCK(src); >- result = TRUE; >- break; >- } >- default: { >- GRefPtr<GstPad> target = adoptGRef(gst_ghost_pad_get_target(GST_GHOST_PAD_CAST(pad))); >- // Forward the query to the proxy target pad. >- if (target) >- result = gst_pad_query(target.get(), query); >- break; >- } >- } >- >- return result; >-} >- >-// uri handler interface >-static GstURIType webKitMediaSrcUriGetType(GType) >-{ >- return GST_URI_SRC; >-} >- >-const gchar* const* webKitMediaSrcGetProtocols(GType) >-{ >- static const char* protocols[] = {"mediasourceblob", 0 }; >- return protocols; >-} >- >-static gchar* webKitMediaSrcGetUri(GstURIHandler* handler) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(handler); >- gchar* ret; >- >- GST_OBJECT_LOCK(src); >- ret = g_strdup(src->priv->location); >- GST_OBJECT_UNLOCK(src); >- return ret; >-} >- >-static gboolean webKitMediaSrcSetUri(GstURIHandler* handler, const gchar* uri, GError**) >-{ >- WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(handler); >- WebKitMediaSrcPrivate* priv = src->priv; >- >- if (GST_STATE(src) >= GST_STATE_PAUSED) { >- GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED"); >- return FALSE; >- } >- >- GST_OBJECT_LOCK(src); >- g_free(priv->location); >- priv->location = 0; >- if (!uri) { >- GST_OBJECT_UNLOCK(src); >- return TRUE; >- } >- >- WebCore::URL url(WebCore::URL(), uri); >- >- priv->location = g_strdup(url.string().utf8().data()); >- GST_OBJECT_UNLOCK(src); >- return TRUE; >-} >-static void webKitMediaSrcUriHandlerInit(gpointer gIface, gpointer) >-{ >- GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface; >- >- iface->get_type = webKitMediaSrcUriGetType; >- iface->get_protocols = webKitMediaSrcGetProtocols; >- iface->get_uri = webKitMediaSrcGetUri; >- iface->set_uri = webKitMediaSrcSetUri; >-} >- >-namespace WebCore { >-MediaSourceClientGStreamer::MediaSourceClientGStreamer(WebKitMediaSrc* src) >- : m_src(adoptGRef(static_cast<WebKitMediaSrc*>(gst_object_ref(src)))) >-{ >-} >- >-MediaSourceClientGStreamer::~MediaSourceClientGStreamer() >-{ >-} >- >-MediaSourcePrivate::AddStatus MediaSourceClientGStreamer::addSourceBuffer(PassRefPtr<SourceBufferPrivate> sourceBufferPrivate, const ContentType&) >-{ >- WebKitMediaSrcPrivate* priv = m_src->priv; >- >- if (priv->noMorePads) { >- GST_ERROR_OBJECT(m_src.get(), "Adding new source buffers after first data not supported yet"); >- return MediaSourcePrivate::NotSupported; >- } >- >- GST_DEBUG_OBJECT(m_src.get(), "State %d", static_cast<int>(GST_STATE(m_src.get()))); >- >- GST_OBJECT_LOCK(m_src.get()); >- >- Source* source = g_new0(Source, 1); >- guint numberOfSources = g_list_length(priv->sources); >- GUniquePtr<gchar> srcName(g_strdup_printf("src%u", numberOfSources)); >- >- source->src = gst_element_factory_make("appsrc", srcName.get()); >- source->sourceBuffer = sourceBufferPrivate.get(); >- >- GUniquePtr<gchar> padName(g_strdup_printf("src_%u", numberOfSources)); >- priv->sources = g_list_prepend(priv->sources, source); >- GST_OBJECT_UNLOCK(m_src.get()); >- >- priv->haveAppsrc = source->src; >- >- gst_bin_add(GST_BIN(m_src.get()), source->src); >- GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(source->src, "src")); >- GstPad* ghostPad = gst_ghost_pad_new_from_template(padName.get(), pad.get(), gst_static_pad_template_get(&srcTemplate)); >- gst_pad_set_query_function(ghostPad, webKitMediaSrcQueryWithParent); >- gst_pad_set_active(ghostPad, TRUE); >- gst_element_add_pad(GST_ELEMENT(m_src.get()), ghostPad); >- >- gst_element_sync_state_with_parent(source->src); >- >- return MediaSourcePrivate::Ok; >-} >- >-void MediaSourceClientGStreamer::durationChanged(const MediaTime& duration) >-{ >- WebKitMediaSrcPrivate* priv = m_src->priv; >- GstClockTime gstDuration = gst_util_uint64_scale(duration.timeValue(), GST_SECOND, duration.timeScale()); >- >- GST_DEBUG_OBJECT(m_src.get(), "Received duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(gstDuration)); >- >- GST_OBJECT_LOCK(m_src.get()); >- priv->duration = gstDuration; >- GST_OBJECT_UNLOCK(m_src.get()); >- gst_element_post_message(GST_ELEMENT(m_src.get()), gst_message_new_duration_changed(GST_OBJECT(m_src.get()))); >-} >- >-SourceBufferPrivateClient::AppendResult MediaSourceClientGStreamer::append(PassRefPtr<SourceBufferPrivate> sourceBufferPrivate, const unsigned char* data, unsigned length) >-{ >- WebKitMediaSrcPrivate* priv = m_src->priv; >- GstFlowReturn ret = GST_FLOW_OK; >- GstBuffer* buffer; >- Source* source = 0; >- >- if (!priv->noMorePads) { >- priv->noMorePads = true; >- gst_element_no_more_pads(GST_ELEMENT(m_src.get())); >- webKitMediaSrcDoAsyncDone(m_src.get()); >- } >- >- for (GList* iter = priv->sources; iter; iter = iter->next) { >- Source* tmp = static_cast<Source*>(iter->data); >- if (tmp->sourceBuffer == sourceBufferPrivate.get()) { >- source = tmp; >- break; >- } >- } >- >- if (!source || !source->src) >- return SourceBufferPrivateClient::ReadStreamFailed; >- >- buffer = gst_buffer_new_and_alloc(length); >- gst_buffer_fill(buffer, 0, data, length); >- >- ret = gst_app_src_push_buffer(GST_APP_SRC(source->src), buffer); >- GST_DEBUG_OBJECT(m_src.get(), "push buffer %d\n", static_cast<int>(ret)); >- >- if (ret == GST_FLOW_OK) >- return SourceBufferPrivateClient::AppendSucceeded; >- >- return SourceBufferPrivateClient::ReadStreamFailed; >-} >- >-void MediaSourceClientGStreamer::markEndOfStream(MediaSourcePrivate::EndOfStreamStatus) >-{ >- WebKitMediaSrcPrivate* priv = m_src->priv; >- >- GST_DEBUG_OBJECT(m_src.get(), "Have EOS"); >- >- if (!priv->noMorePads) { >- priv->noMorePads = true; >- gst_element_no_more_pads(GST_ELEMENT(m_src.get())); >- webKitMediaSrcDoAsyncDone(m_src.get()); >- } >- >- for (GList* iter = priv->sources; iter; iter = iter->next) { >- Source* source = static_cast<Source*>(iter->data); >- if (source->src) >- gst_app_src_end_of_stream(GST_APP_SRC(source->src)); >- } >-} >- >-void MediaSourceClientGStreamer::removedFromMediaSource(PassRefPtr<SourceBufferPrivate> sourceBufferPrivate) >-{ >- WebKitMediaSrcPrivate* priv = m_src->priv; >- Source* source = 0; >- >- for (GList* iter = priv->sources; iter; iter = iter->next) { >- Source* tmp = static_cast<Source*>(iter->data); >- if (tmp->sourceBuffer == sourceBufferPrivate.get()) { >- source = tmp; >- break; >- } >- } >- >- ASSERT(source && source->src); >- >- gst_app_src_end_of_stream(GST_APP_SRC(source->src)); >-} >- >-}; >- >-namespace WTF { >-template <> GRefPtr<WebKitMediaSrc> adoptGRef(WebKitMediaSrc* ptr) >-{ >- ASSERT(!ptr || !g_object_is_floating(G_OBJECT(ptr))); >- return GRefPtr<WebKitMediaSrc>(ptr, GRefPtrAdopt); >-} >- >-template <> WebKitMediaSrc* refGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr) >-{ >- if (ptr) >- gst_object_ref_sink(GST_OBJECT(ptr)); >- >- return ptr; >-} >- >-template <> void derefGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr) >-{ >- if (ptr) >- gst_object_unref(ptr); >-} >-}; >- >-#endif // USE(GSTREAMER) >- >diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h >deleted file mode 100644 >index c0effc10ffdcc2da47a63b8e0d6ce8d1497926f4..0000000000000000000000000000000000000000 >--- a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h >+++ /dev/null >@@ -1,91 +0,0 @@ >-/* >- * Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> >- * Copyright (C) 2013 Collabora Ltd. >- * Copyright (C) 2013 Orange >- * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com> >- * >- * 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 >- */ >- >-#pragma once >- >-#if ENABLE(VIDEO) && ENABLE(MEDIA_SOURCE) && USE(GSTREAMER) >- >-#include "GRefPtrGStreamer.h" >-#include "MediaPlayer.h" >-#include "MediaSource.h" >-#include "MediaSourcePrivate.h" >-#include "SourceBufferPrivate.h" >-#include "SourceBufferPrivateClient.h" >- >-#include <gst/gst.h> >- >-G_BEGIN_DECLS >- >-#define WEBKIT_TYPE_MEDIA_SRC (webkit_media_src_get_type ()) >-#define WEBKIT_MEDIA_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrc)) >-#define WEBKIT_MEDIA_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrcClass)) >-#define WEBKIT_IS_MEDIA_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_MEDIA_SRC)) >-#define WEBKIT_IS_MEDIA_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_MEDIA_SRC)) >- >-typedef struct _WebKitMediaSrc WebKitMediaSrc; >-typedef struct _WebKitMediaSrcClass WebKitMediaSrcClass; >-typedef struct _WebKitMediaSrcPrivate WebKitMediaSrcPrivate; >- >-struct _WebKitMediaSrc { >- GstBin parent; >- >- WebKitMediaSrcPrivate *priv; >-}; >- >-struct _WebKitMediaSrcClass { >- GstBinClass parentClass; >-}; >- >-GType webkit_media_src_get_type(void); >- >-G_END_DECLS >- >-namespace WTF { >-template<> GRefPtr<WebKitMediaSrc> adoptGRef(WebKitMediaSrc* ptr); >-template<> WebKitMediaSrc* refGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr); >-template<> void derefGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr); >-}; >- >-namespace WebCore { >- >-class ContentType; >- >-class MediaSourceClientGStreamer: public RefCounted<MediaSourceClientGStreamer> { >- public: >- MediaSourceClientGStreamer(WebKitMediaSrc*); >- virtual ~MediaSourceClientGStreamer(); >- >- // From MediaSourceGStreamer >- MediaSourcePrivate::AddStatus addSourceBuffer(PassRefPtr<SourceBufferPrivate>, const ContentType&); >- void durationChanged(const MediaTime&); >- void markEndOfStream(MediaSourcePrivate::EndOfStreamStatus); >- >- // From SourceBufferPrivateGStreamer >- SourceBufferPrivateClient::AppendResult append(PassRefPtr<SourceBufferPrivate>, const unsigned char*, unsigned); >- void removedFromMediaSource(PassRefPtr<SourceBufferPrivate>); >- >- private: >- GRefPtr<WebKitMediaSrc> m_src; >-}; >- >-}; >- >-#endif // USE(GSTREAMER) >diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp >new file mode 100644 >index 0000000000000000000000000000000000000000..87f5b80e9f5cabe71f0a359b63ed44c4e76ff090 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp >@@ -0,0 +1,775 @@ >+/* >+ * Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> >+ * Copyright (C) 2013 Collabora Ltd. >+ * Copyright (C) 2013 Orange >+ * Copyright (C) 2014, 2015 Sebastian Dröge <sebastian@centricular.com> >+ * Copyright (C) 2015, 2016 Metrological Group B.V. >+ * Copyright (C) 2015, 2016 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 "WebKitMediaSourceGStreamer.h" >+ >+#include "PlaybackPipeline.h" >+ >+#if ENABLE(VIDEO) && ENABLE(MEDIA_SOURCE) && USE(GSTREAMER) >+ >+#include "AudioTrackPrivateGStreamer.h" >+#include "GStreamerUtilities.h" >+#include "MediaDescription.h" >+#include "MediaPlayerPrivateGStreamerMSE.h" >+#include "MediaSample.h" >+#include "MediaSourceGStreamer.h" >+#include "NotImplemented.h" >+#include "SourceBufferPrivateGStreamer.h" >+#include "TimeRanges.h" >+#include "VideoTrackPrivateGStreamer.h" >+#include "WebKitMediaSourceGStreamerPrivate.h" >+ >+#include <gst/app/app.h> >+#include <gst/app/gstappsrc.h> >+#include <gst/gst.h> >+#include <gst/pbutils/missing-plugins.h> >+#include <gst/pbutils/pbutils.h> >+#include <gst/video/video.h> >+#include <wtf/Condition.h> >+#include <wtf/MainThread.h> >+#include <wtf/glib/GMutexLocker.h> >+#include <wtf/glib/GUniquePtr.h> >+#include <wtf/text/CString.h> >+ >+static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src_%u", GST_PAD_SRC, >+ GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); >+ >+static void enabledAppsrcNeedData(GstAppSrc*, guint, gpointer); >+static void enabledAppsrcEnoughData(GstAppSrc*, gpointer); >+static gboolean enabledAppsrcSeekData(GstAppSrc*, guint64, gpointer); >+ >+static void disabledAppsrcNeedData(GstAppSrc*, guint, gpointer) { }; >+static void disabledAppsrcEnoughData(GstAppSrc*, gpointer) { }; >+static gboolean disabledAppsrcSeekData(GstAppSrc*, guint64, gpointer) >+{ >+ return FALSE; >+}; >+ >+GstAppSrcCallbacks enabledAppsrcCallbacks = { >+ enabledAppsrcNeedData, >+ enabledAppsrcEnoughData, >+ enabledAppsrcSeekData, >+ { 0 } >+}; >+ >+GstAppSrcCallbacks disabledAppsrcCallbacks = { >+ disabledAppsrcNeedData, >+ disabledAppsrcEnoughData, >+ disabledAppsrcSeekData, >+ { 0 } >+}; >+ >+static Stream* getStreamByAppsrc(WebKitMediaSrc*, GstElement*); >+ >+static void enabledAppsrcNeedData(GstAppSrc* appsrc, guint, gpointer userData) >+{ >+ WebKitMediaSrc* webKitMediaSrc = static_cast<WebKitMediaSrc*>(userData); >+ ASSERT(WEBKIT_IS_MEDIA_SRC(webKitMediaSrc)); >+ >+ GST_OBJECT_LOCK(webKitMediaSrc); >+ OnSeekDataAction appsrcSeekDataNextAction = webKitMediaSrc->priv->appsrcSeekDataNextAction; >+ Stream* appsrcStream = getStreamByAppsrc(webKitMediaSrc, GST_ELEMENT(appsrc)); >+ bool allAppsrcNeedDataAfterSeek = false; >+ >+ if (webKitMediaSrc->priv->appsrcSeekDataCount > 0) { >+ if (appsrcStream && !appsrcStream->appsrcNeedDataFlag) { >+ ++webKitMediaSrc->priv->appsrcNeedDataCount; >+ appsrcStream->appsrcNeedDataFlag = true; >+ } >+ int numAppsrcs = webKitMediaSrc->priv->streams.size(); >+ if (webKitMediaSrc->priv->appsrcSeekDataCount == numAppsrcs && webKitMediaSrc->priv->appsrcNeedDataCount == numAppsrcs) { >+ GST_DEBUG("All needDatas completed"); >+ allAppsrcNeedDataAfterSeek = true; >+ webKitMediaSrc->priv->appsrcSeekDataCount = 0; >+ webKitMediaSrc->priv->appsrcNeedDataCount = 0; >+ webKitMediaSrc->priv->appsrcSeekDataNextAction = Nothing; >+ >+ for (Stream* stream : webKitMediaSrc->priv->streams) >+ stream->appsrcNeedDataFlag = false; >+ } >+ } >+ GST_OBJECT_UNLOCK(webKitMediaSrc); >+ >+ if (allAppsrcNeedDataAfterSeek) { >+ GST_DEBUG("All expected appsrcSeekData() and appsrcNeedData() calls performed. Running next action (%d)", static_cast<int>(appsrcSeekDataNextAction)); >+ >+ switch (appsrcSeekDataNextAction) { >+ case MediaSourceSeekToTime: { >+ GstStructure* structure = gst_structure_new_empty("seek-needs-data"); >+ GstMessage* message = gst_message_new_application(GST_OBJECT(appsrc), structure); >+ gst_bus_post(webKitMediaSrc->priv->bus.get(), message); >+ GST_TRACE("seek-needs-data message posted to the bus"); >+ break; >+ } >+ case Nothing: >+ break; >+ } >+ } else if (appsrcSeekDataNextAction == Nothing) { >+ LockHolder locker(webKitMediaSrc->priv->streamLock); >+ >+ GST_OBJECT_LOCK(webKitMediaSrc); >+ >+ // Search again for the Stream, just in case it was removed between the previous lock and this one. >+ appsrcStream = getStreamByAppsrc(webKitMediaSrc, GST_ELEMENT(appsrc)); >+ >+ if (appsrcStream && appsrcStream->type != WebCore::Invalid) { >+ GstStructure* structure = gst_structure_new("ready-for-more-samples", "appsrc-stream", G_TYPE_POINTER, appsrcStream, nullptr); >+ GstMessage* message = gst_message_new_application(GST_OBJECT(appsrc), structure); >+ gst_bus_post(webKitMediaSrc->priv->bus.get(), message); >+ GST_TRACE("ready-for-more-samples message posted to the bus"); >+ } >+ >+ GST_OBJECT_UNLOCK(webKitMediaSrc); >+ } >+} >+ >+static void enabledAppsrcEnoughData(GstAppSrc *appsrc, gpointer userData) >+{ >+ // No need to lock on webKitMediaSrc, we're on the main thread and nobody is going to remove the stream in the meantime. >+ ASSERT(WTF::isMainThread()); >+ >+ WebKitMediaSrc* webKitMediaSrc = static_cast<WebKitMediaSrc*>(userData); >+ ASSERT(WEBKIT_IS_MEDIA_SRC(webKitMediaSrc)); >+ Stream* stream = getStreamByAppsrc(webKitMediaSrc, GST_ELEMENT(appsrc)); >+ >+ // This callback might have been scheduled from a child thread before the stream was removed. >+ // Then, the removal code might have run, and later this callback. >+ // This check solves the race condition. >+ if (!stream || stream->type == WebCore::Invalid) >+ return; >+ >+ stream->sourceBuffer->setReadyForMoreSamples(false); >+} >+ >+static gboolean enabledAppsrcSeekData(GstAppSrc*, guint64, gpointer userData) >+{ >+ ASSERT(WTF::isMainThread()); >+ >+ WebKitMediaSrc* webKitMediaSrc = static_cast<WebKitMediaSrc*>(userData); >+ >+ ASSERT(WEBKIT_IS_MEDIA_SRC(webKitMediaSrc)); >+ >+ GST_OBJECT_LOCK(webKitMediaSrc); >+ webKitMediaSrc->priv->appsrcSeekDataCount++; >+ GST_OBJECT_UNLOCK(webKitMediaSrc); >+ >+ return TRUE; >+} >+ >+static Stream* getStreamByAppsrc(WebKitMediaSrc* source, GstElement* appsrc) >+{ >+ for (Stream* stream : source->priv->streams) { >+ if (stream->appsrc == appsrc) >+ return stream; >+ } >+ return nullptr; >+} >+ >+GST_DEBUG_CATEGORY_STATIC(webkit_media_src_debug); >+#define GST_CAT_DEFAULT webkit_media_src_debug >+ >+#define webkit_media_src_parent_class parent_class >+#define WEBKIT_MEDIA_SRC_CATEGORY_INIT GST_DEBUG_CATEGORY_INIT(webkit_media_src_debug, "webkitmediasrc", 0, "websrc element"); >+G_DEFINE_TYPE_WITH_CODE(WebKitMediaSrc, webkit_media_src, GST_TYPE_BIN, >+ G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, webKitMediaSrcUriHandlerInit); >+ WEBKIT_MEDIA_SRC_CATEGORY_INIT); >+ >+guint webKitMediaSrcSignals[LAST_SIGNAL] = { 0 }; >+ >+static void webkit_media_src_class_init(WebKitMediaSrcClass* klass) >+{ >+ GObjectClass* oklass = G_OBJECT_CLASS(klass); >+ GstElementClass* eklass = GST_ELEMENT_CLASS(klass); >+ >+ oklass->finalize = webKitMediaSrcFinalize; >+ oklass->set_property = webKitMediaSrcSetProperty; >+ oklass->get_property = webKitMediaSrcGetProperty; >+ >+ gst_element_class_add_pad_template(eklass, gst_static_pad_template_get(&srcTemplate)); >+ >+ gst_element_class_set_static_metadata(eklass, "WebKit Media source element", "Source", "Handles Blob uris", "Stephane Jadaud <sjadaud@sii.fr>, Sebastian Dröge <sebastian@centricular.com>, Enrique Ocaña González <eocanha@igalia.com>"); >+ >+ // Allows setting the uri using the 'location' property, which is used for example by gst_element_make_from_uri(). >+ g_object_class_install_property(oklass, >+ PROP_LOCATION, >+ g_param_spec_string("location", "location", "Location to read from", nullptr, >+ GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); >+ g_object_class_install_property(oklass, >+ PROP_N_AUDIO, >+ g_param_spec_int("n-audio", "Number Audio", "Total number of audio streams", >+ 0, G_MAXINT, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); >+ g_object_class_install_property(oklass, >+ PROP_N_VIDEO, >+ g_param_spec_int("n-video", "Number Video", "Total number of video streams", >+ 0, G_MAXINT, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); >+ g_object_class_install_property(oklass, >+ PROP_N_TEXT, >+ g_param_spec_int("n-text", "Number Text", "Total number of text streams", >+ 0, G_MAXINT, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); >+ >+ webKitMediaSrcSignals[SIGNAL_VIDEO_CHANGED] = >+ g_signal_new("video-changed", G_TYPE_FROM_CLASS(oklass), >+ G_SIGNAL_RUN_LAST, >+ G_STRUCT_OFFSET(WebKitMediaSrcClass, videoChanged), nullptr, nullptr, >+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); >+ webKitMediaSrcSignals[SIGNAL_AUDIO_CHANGED] = >+ g_signal_new("audio-changed", G_TYPE_FROM_CLASS(oklass), >+ G_SIGNAL_RUN_LAST, >+ G_STRUCT_OFFSET(WebKitMediaSrcClass, audioChanged), nullptr, nullptr, >+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); >+ webKitMediaSrcSignals[SIGNAL_TEXT_CHANGED] = >+ g_signal_new("text-changed", G_TYPE_FROM_CLASS(oklass), >+ G_SIGNAL_RUN_LAST, >+ G_STRUCT_OFFSET(WebKitMediaSrcClass, textChanged), nullptr, nullptr, >+ g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); >+ >+ eklass->change_state = webKitMediaSrcChangeState; >+ >+ g_type_class_add_private(klass, sizeof(WebKitMediaSrcPrivate)); >+} >+ >+static void webkit_media_src_init(WebKitMediaSrc* source) >+{ >+ source->priv = WEBKIT_MEDIA_SRC_GET_PRIVATE(source); >+ new (source->priv) WebKitMediaSrcPrivate(); >+ source->priv->seekTime = MediaTime::invalidTime(); >+ source->priv->appsrcSeekDataCount = 0; >+ source->priv->appsrcNeedDataCount = 0; >+ source->priv->appsrcSeekDataNextAction = Nothing; >+ >+ // No need to reset Stream.appsrcNeedDataFlag because there are no Streams at this point yet. >+} >+ >+void webKitMediaSrcFinalize(GObject* object) >+{ >+ ASSERT(WTF::isMainThread()); >+ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(object); >+ WebKitMediaSrcPrivate* priv = source->priv; >+ >+ Deque<Stream*> oldStreams; >+ source->priv->streams.swap(oldStreams); >+ >+ for (Stream* stream : oldStreams) >+ webKitMediaSrcFreeStream(source, stream); >+ >+ priv->seekTime = MediaTime::invalidTime(); >+ >+ if (priv->mediaPlayerPrivate) >+ webKitMediaSrcSetMediaPlayerPrivate(source, nullptr); >+ >+ // We used a placement new for construction, the destructor won't be called automatically. >+ priv->~_WebKitMediaSrcPrivate(); >+ >+ GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); >+} >+ >+void webKitMediaSrcSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(object); >+ >+ switch (propId) { >+ case PROP_LOCATION: >+ gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(source), g_value_get_string(value), nullptr); >+ break; >+ default: >+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); >+ break; >+ } >+} >+ >+void webKitMediaSrcGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(object); >+ WebKitMediaSrcPrivate* priv = source->priv; >+ >+ GST_OBJECT_LOCK(source); >+ switch (propId) { >+ case PROP_LOCATION: >+ g_value_set_string(value, priv->location.get()); >+ break; >+ case PROP_N_AUDIO: >+ g_value_set_int(value, priv->numberOfAudioStreams); >+ break; >+ case PROP_N_VIDEO: >+ g_value_set_int(value, priv->numberOfVideoStreams); >+ break; >+ case PROP_N_TEXT: >+ g_value_set_int(value, priv->numberOfTextStreams); >+ break; >+ default: >+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); >+ break; >+ } >+ GST_OBJECT_UNLOCK(source); >+} >+ >+void webKitMediaSrcDoAsyncStart(WebKitMediaSrc* source) >+{ >+ source->priv->asyncStart = true; >+ GST_BIN_CLASS(parent_class)->handle_message(GST_BIN(source), >+ gst_message_new_async_start(GST_OBJECT(source))); >+} >+ >+void webKitMediaSrcDoAsyncDone(WebKitMediaSrc* source) >+{ >+ WebKitMediaSrcPrivate* priv = source->priv; >+ if (priv->asyncStart) { >+ GST_BIN_CLASS(parent_class)->handle_message(GST_BIN(source), >+ gst_message_new_async_done(GST_OBJECT(source), GST_CLOCK_TIME_NONE)); >+ priv->asyncStart = false; >+ } >+} >+ >+GstStateChangeReturn webKitMediaSrcChangeState(GstElement* element, GstStateChange transition) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(element); >+ WebKitMediaSrcPrivate* priv = source->priv; >+ >+ switch (transition) { >+ case GST_STATE_CHANGE_READY_TO_PAUSED: >+ priv->allTracksConfigured = false; >+ webKitMediaSrcDoAsyncStart(source); >+ break; >+ default: >+ break; >+ } >+ >+ GstStateChangeReturn result = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); >+ if (G_UNLIKELY(result == GST_STATE_CHANGE_FAILURE)) { >+ GST_WARNING_OBJECT(source, "State change failed"); >+ webKitMediaSrcDoAsyncDone(source); >+ return result; >+ } >+ >+ switch (transition) { >+ case GST_STATE_CHANGE_READY_TO_PAUSED: >+ result = GST_STATE_CHANGE_ASYNC; >+ break; >+ case GST_STATE_CHANGE_PAUSED_TO_READY: >+ webKitMediaSrcDoAsyncDone(source); >+ priv->allTracksConfigured = false; >+ break; >+ default: >+ break; >+ } >+ >+ return result; >+} >+ >+gint64 webKitMediaSrcGetSize(WebKitMediaSrc* webKitMediaSrc) >+{ >+ gint64 duration = 0; >+ for (Stream* stream : webKitMediaSrc->priv->streams) >+ duration = std::max<gint64>(duration, gst_app_src_get_size(GST_APP_SRC(stream->appsrc))); >+ return duration; >+} >+ >+gboolean webKitMediaSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQuery* query) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(GST_ELEMENT(parent)); >+ gboolean result = FALSE; >+ >+ switch (GST_QUERY_TYPE(query)) { >+ case GST_QUERY_DURATION: { >+ GstFormat format; >+ gst_query_parse_duration(query, &format, nullptr); >+ >+ GST_DEBUG_OBJECT(source, "duration query in format %s", gst_format_get_name(format)); >+ GST_OBJECT_LOCK(source); >+ switch (format) { >+ case GST_FORMAT_TIME: { >+ if (source->priv && source->priv->mediaPlayerPrivate) { >+ float duration = source->priv->mediaPlayerPrivate->durationMediaTime().toFloat(); >+ if (duration > 0) { >+ gst_query_set_duration(query, format, WebCore::toGstClockTime(duration)); >+ GST_DEBUG_OBJECT(source, "Answering: duration=%" GST_TIME_FORMAT, GST_TIME_ARGS(WebCore::toGstClockTime(duration))); >+ result = TRUE; >+ } >+ } >+ break; >+ } >+ case GST_FORMAT_BYTES: { >+ if (source->priv) { >+ gint64 duration = webKitMediaSrcGetSize(source); >+ if (duration) { >+ gst_query_set_duration(query, format, duration); >+ GST_DEBUG_OBJECT(source, "size: %" G_GINT64_FORMAT, duration); >+ result = TRUE; >+ } >+ } >+ break; >+ } >+ default: >+ break; >+ } >+ >+ GST_OBJECT_UNLOCK(source); >+ break; >+ } >+ case GST_QUERY_URI: >+ if (source) { >+ GST_OBJECT_LOCK(source); >+ if (source->priv) >+ gst_query_set_uri(query, source->priv->location.get()); >+ GST_OBJECT_UNLOCK(source); >+ } >+ result = TRUE; >+ break; >+ default: { >+ GRefPtr<GstPad> target = adoptGRef(gst_ghost_pad_get_target(GST_GHOST_PAD_CAST(pad))); >+ // Forward the query to the proxy target pad. >+ if (target) >+ result = gst_pad_query(target.get(), query); >+ break; >+ } >+ } >+ >+ return result; >+} >+ >+void webKitMediaSrcUpdatePresentationSize(GstCaps* caps, Stream* stream) >+{ >+ GstStructure* structure = gst_caps_get_structure(caps, 0); >+ const gchar* structureName = gst_structure_get_name(structure); >+ GstVideoInfo info; >+ >+ GST_OBJECT_LOCK(stream->parent); >+ if (g_str_has_prefix(structureName, "video/") && gst_video_info_from_caps(&info, caps)) { >+ float width, height; >+ >+ // FIXME: Correct?. >+ width = info.width; >+ height = info.height * ((float) info.par_d / (float) info.par_n); >+ stream->presentationSize = WebCore::FloatSize(width, height); >+ } else >+ stream->presentationSize = WebCore::FloatSize(); >+ >+ gst_caps_ref(caps); >+ stream->caps = adoptGRef(caps); >+ GST_OBJECT_UNLOCK(stream->parent); >+} >+ >+void webKitMediaSrcLinkStreamToSrcPad(GstPad* sourcePad, Stream* stream) >+{ >+ unsigned padId = static_cast<unsigned>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sourcePad), "padId"))); >+ GST_DEBUG_OBJECT(stream->parent, "linking stream to src pad (id: %u)", padId); >+ >+ GUniquePtr<gchar> padName(g_strdup_printf("src_%u", padId)); >+ GstPad* ghostpad = WebCore::webkitGstGhostPadFromStaticTemplate(&srcTemplate, padName.get(), sourcePad); >+ >+ gst_pad_set_query_function(ghostpad, webKitMediaSrcQueryWithParent); >+ >+ gst_pad_set_active(ghostpad, TRUE); >+ gst_element_add_pad(GST_ELEMENT(stream->parent), ghostpad); >+ >+ if (stream->decodebinSinkPad) { >+ GST_DEBUG_OBJECT(stream->parent, "A decodebin was previously used for this source, trying to reuse it."); >+ // FIXME: error checking here. Not sure what to do if linking >+ // fails though, because decodebin is out of this source >+ // element's scope, in theory. >+ gst_pad_link(ghostpad, stream->decodebinSinkPad); >+ } >+} >+ >+void webKitMediaSrcLinkParser(GstPad* sourcePad, GstCaps* caps, Stream* stream) >+{ >+ ASSERT(caps && stream->parent); >+ if (!caps || !stream->parent) { >+ GST_ERROR("Unable to link parser"); >+ return; >+ } >+ >+ webKitMediaSrcUpdatePresentationSize(caps, stream); >+ >+ // FIXME: drop webKitMediaSrcLinkStreamToSrcPad() and move its code here. >+ if (!gst_pad_is_linked(sourcePad)) { >+ GST_DEBUG_OBJECT(stream->parent, "pad not linked yet"); >+ webKitMediaSrcLinkStreamToSrcPad(sourcePad, stream); >+ } >+ >+ webKitMediaSrcCheckAllTracksConfigured(stream->parent); >+} >+ >+void webKitMediaSrcFreeStream(WebKitMediaSrc* source, Stream* stream) >+{ >+ if (stream->appsrc) { >+ // Don't trigger callbacks from this appsrc to avoid using the stream anymore. >+ gst_app_src_set_callbacks(GST_APP_SRC(stream->appsrc), &disabledAppsrcCallbacks, nullptr, nullptr); >+ gst_app_src_end_of_stream(GST_APP_SRC(stream->appsrc)); >+ } >+ >+ if (stream->type != WebCore::Invalid) { >+ GST_DEBUG("Freeing track-related info on stream %p", stream); >+ >+ LockHolder locker(source->priv->streamLock); >+ >+ if (stream->caps) >+ stream->caps = nullptr; >+ >+ if (stream->audioTrack) >+ stream->audioTrack = nullptr; >+ if (stream->videoTrack) >+ stream->videoTrack = nullptr; >+ >+ int signal = -1; >+ switch (stream->type) { >+ case WebCore::Audio: >+ signal = SIGNAL_AUDIO_CHANGED; >+ break; >+ case WebCore::Video: >+ signal = SIGNAL_VIDEO_CHANGED; >+ break; >+ case WebCore::Text: >+ signal = SIGNAL_TEXT_CHANGED; >+ break; >+ default: >+ break; >+ } >+ stream->type = WebCore::Invalid; >+ >+ if (signal != -1) >+ g_signal_emit(G_OBJECT(source), webKitMediaSrcSignals[signal], 0, nullptr); >+ >+ source->priv->streamCondition.notifyOne(); >+ } >+ >+ GST_DEBUG("Releasing stream: %p", stream); >+ delete stream; >+} >+ >+void webKitMediaSrcCheckAllTracksConfigured(WebKitMediaSrc* webKitMediaSrc) >+{ >+ bool allTracksConfigured = false; >+ >+ GST_OBJECT_LOCK(webKitMediaSrc); >+ if (!webKitMediaSrc->priv->allTracksConfigured) { >+ allTracksConfigured = true; >+ for (Stream* stream : webKitMediaSrc->priv->streams) { >+ if (stream->type == WebCore::Invalid) { >+ allTracksConfigured = false; >+ break; >+ } >+ } >+ if (allTracksConfigured) >+ webKitMediaSrc->priv->allTracksConfigured = true; >+ } >+ GST_OBJECT_UNLOCK(webKitMediaSrc); >+ >+ if (allTracksConfigured) { >+ GST_DEBUG("All tracks attached. Completing async state change operation."); >+ gst_element_no_more_pads(GST_ELEMENT(webKitMediaSrc)); >+ webKitMediaSrcDoAsyncDone(webKitMediaSrc); >+ } >+} >+ >+// Uri handler interface. >+GstURIType webKitMediaSrcUriGetType(GType) >+{ >+ return GST_URI_SRC; >+} >+ >+const gchar* const* webKitMediaSrcGetProtocols(GType) >+{ >+ static const char* protocols[] = {"mediasourceblob", 0 }; >+ return protocols; >+} >+ >+gchar* webKitMediaSrcGetUri(GstURIHandler* handler) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(handler); >+ gchar* result; >+ >+ GST_OBJECT_LOCK(source); >+ result = g_strdup(source->priv->location.get()); >+ GST_OBJECT_UNLOCK(source); >+ return result; >+} >+ >+gboolean webKitMediaSrcSetUri(GstURIHandler* handler, const gchar* uri, GError**) >+{ >+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(handler); >+ >+ if (GST_STATE(source) >= GST_STATE_PAUSED) { >+ GST_ERROR_OBJECT(source, "URI can only be set in states < PAUSED"); >+ return FALSE; >+ } >+ >+ GST_OBJECT_LOCK(source); >+ WebKitMediaSrcPrivate* priv = source->priv; >+ priv->location = nullptr; >+ if (!uri) { >+ GST_OBJECT_UNLOCK(source); >+ return TRUE; >+ } >+ >+ WebCore::URL url(WebCore::URL(), uri); >+ >+ priv->location = GUniquePtr<gchar>(g_strdup(url.string().utf8().data())); >+ GST_OBJECT_UNLOCK(source); >+ return TRUE; >+} >+ >+void webKitMediaSrcUriHandlerInit(gpointer gIface, gpointer) >+{ >+ GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface; >+ >+ iface->get_type = webKitMediaSrcUriGetType; >+ iface->get_protocols = webKitMediaSrcGetProtocols; >+ iface->get_uri = webKitMediaSrcGetUri; >+ iface->set_uri = webKitMediaSrcSetUri; >+} >+ >+static void seekNeedsDataMainThread(WebKitMediaSrc* source) >+{ >+ GST_DEBUG("Buffering needed before seek"); >+ >+ ASSERT(WTF::isMainThread()); >+ >+ GST_OBJECT_LOCK(source); >+ MediaTime seekTime = source->priv->seekTime; >+ WebCore::MediaPlayerPrivateGStreamerMSE* mediaPlayerPrivate = source->priv->mediaPlayerPrivate; >+ >+ if (!mediaPlayerPrivate) { >+ GST_OBJECT_UNLOCK(source); >+ return; >+ } >+ >+ for (Stream* stream : source->priv->streams) { >+ if (stream->type != WebCore::Invalid) >+ stream->sourceBuffer->setReadyForMoreSamples(true); >+ } >+ GST_OBJECT_UNLOCK(source); >+ mediaPlayerPrivate->notifySeekNeedsDataForTime(seekTime); >+} >+ >+static void notifyReadyForMoreSamplesMainThread(WebKitMediaSrc* source, Stream* appsrcStream) >+{ >+ GST_OBJECT_LOCK(source); >+ >+ auto it = std::find(source->priv->streams.begin(), source->priv->streams.end(), appsrcStream); >+ if (it == source->priv->streams.end()) { >+ GST_OBJECT_UNLOCK(source); >+ return; >+ } >+ >+ WebCore::MediaPlayerPrivateGStreamerMSE* mediaPlayerPrivate = source->priv->mediaPlayerPrivate; >+ if (mediaPlayerPrivate && !mediaPlayerPrivate->seeking()) >+ appsrcStream->sourceBuffer->notifyReadyForMoreSamples(); >+ >+ GST_OBJECT_UNLOCK(source); >+} >+ >+static void applicationMessageCallback(GstBus*, GstMessage* message, WebKitMediaSrc* source) >+{ >+ ASSERT(WTF::isMainThread()); >+ ASSERT(GST_MESSAGE_TYPE(message) == GST_MESSAGE_APPLICATION); >+ >+ const GstStructure* structure = gst_message_get_structure(message); >+ >+ if (gst_structure_has_name(structure, "seek-needs-data")) { >+ seekNeedsDataMainThread(source); >+ return; >+ } >+ >+ if (gst_structure_has_name(structure, "ready-for-more-samples")) { >+ Stream* appsrcStream = nullptr; >+ gst_structure_get(structure, "appsrc-stream", G_TYPE_POINTER, &appsrcStream, nullptr); >+ ASSERT(appsrcStream); >+ >+ notifyReadyForMoreSamplesMainThread(source, appsrcStream); >+ return; >+ } >+ >+ ASSERT_NOT_REACHED(); >+} >+ >+void webKitMediaSrcSetMediaPlayerPrivate(WebKitMediaSrc* source, WebCore::MediaPlayerPrivateGStreamerMSE* mediaPlayerPrivate) >+{ >+ GST_OBJECT_LOCK(source); >+ if (source->priv->mediaPlayerPrivate && source->priv->mediaPlayerPrivate != mediaPlayerPrivate && source->priv->bus) >+ g_signal_handlers_disconnect_by_func(source->priv->bus.get(), gpointer(applicationMessageCallback), source); >+ >+ // Set to nullptr on MediaPlayerPrivateGStreamer destruction, never a dangling pointer. >+ source->priv->mediaPlayerPrivate = mediaPlayerPrivate; >+ source->priv->bus = mediaPlayerPrivate ? adoptGRef(gst_pipeline_get_bus(GST_PIPELINE(mediaPlayerPrivate->pipeline()))) : nullptr; >+ if (source->priv->bus) { >+ // MediaPlayerPrivateGStreamer has called gst_bus_add_signal_watch() at this point, so we can subscribe. >+ g_signal_connect(source->priv->bus.get(), "message::application", G_CALLBACK(applicationMessageCallback), source); >+ } >+ GST_OBJECT_UNLOCK(source); >+} >+ >+void webKitMediaSrcSetReadyForSamples(WebKitMediaSrc* source, bool isReady) >+{ >+ if (source) { >+ GST_OBJECT_LOCK(source); >+ for (Stream* stream : source->priv->streams) >+ stream->sourceBuffer->setReadyForMoreSamples(isReady); >+ GST_OBJECT_UNLOCK(source); >+ } >+} >+ >+void webKitMediaSrcPrepareSeek(WebKitMediaSrc* source, const MediaTime& time) >+{ >+ GST_OBJECT_LOCK(source); >+ source->priv->seekTime = time; >+ source->priv->appsrcSeekDataCount = 0; >+ source->priv->appsrcNeedDataCount = 0; >+ >+ for (Stream* stream : source->priv->streams) { >+ stream->appsrcNeedDataFlag = false; >+ // Don't allow samples away from the seekTime to be enqueued. >+ stream->lastEnqueuedTime = time; >+ } >+ >+ // The pending action will be performed in enabledAppsrcSeekData(). >+ source->priv->appsrcSeekDataNextAction = MediaSourceSeekToTime; >+ GST_OBJECT_UNLOCK(source); >+} >+ >+namespace WTF { >+template <> GRefPtr<WebKitMediaSrc> adoptGRef(WebKitMediaSrc* ptr) >+{ >+ ASSERT(!ptr || !g_object_is_floating(G_OBJECT(ptr))); >+ return GRefPtr<WebKitMediaSrc>(ptr, GRefPtrAdopt); >+} >+ >+template <> WebKitMediaSrc* refGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr) >+{ >+ if (ptr) >+ gst_object_ref_sink(GST_OBJECT(ptr)); >+ >+ return ptr; >+} >+ >+template <> void derefGPtr<WebKitMediaSrc>(WebKitMediaSrc* ptr) >+{ >+ if (ptr) >+ gst_object_unref(ptr); >+} >+}; >+ >+#endif // USE(GSTREAMER) >+ >diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.h >new file mode 100644 >index 0000000000000000000000000000000000000000..79086054c376972f9bf2adec72d0aea0ceb67949 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.h >@@ -0,0 +1,80 @@ >+/* >+ * Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> >+ * Copyright (C) 2013 Collabora Ltd. >+ * Copyright (C) 2013 Orange >+ * Copyright (C) 2014, 2015 Sebastian Dröge <sebastian@centricular.com> >+ * Copyright (C) 2015, 2016 Metrological Group B.V. >+ * Copyright (C) 2015, 2016 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 >+ */ >+ >+#pragma once >+ >+#if ENABLE(VIDEO) && ENABLE(MEDIA_SOURCE) && USE(GSTREAMER) >+ >+#include "GRefPtrGStreamer.h" >+#include "MediaPlayer.h" >+#include "MediaSource.h" >+#include "MediaSourcePrivate.h" >+#include "SourceBufferPrivate.h" >+#include "SourceBufferPrivateClient.h" >+#include <gst/gst.h> >+ >+namespace WebCore { >+ >+class MediaPlayerPrivateGStreamerMSE; >+ >+enum MediaSourceStreamTypeGStreamer { Invalid, Unknown, Audio, Video, Text }; >+ >+} >+ >+G_BEGIN_DECLS >+ >+#define WEBKIT_TYPE_MEDIA_SRC (webkit_media_src_get_type ()) >+#define WEBKIT_MEDIA_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrc)) >+#define WEBKIT_MEDIA_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrcClass)) >+#define WEBKIT_IS_MEDIA_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_MEDIA_SRC)) >+#define WEBKIT_IS_MEDIA_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_MEDIA_SRC)) >+ >+typedef struct _WebKitMediaSrc WebKitMediaSrc; >+typedef struct _WebKitMediaSrcClass WebKitMediaSrcClass; >+typedef struct _WebKitMediaSrcPrivate WebKitMediaSrcPrivate; >+ >+struct _WebKitMediaSrc { >+ GstBin parent; >+ >+ WebKitMediaSrcPrivate *priv; >+}; >+ >+struct _WebKitMediaSrcClass { >+ GstBinClass parentClass; >+ >+ // Notify app that number of audio/video/text streams changed. >+ void (*videoChanged)(WebKitMediaSrc*); >+ void (*audioChanged)(WebKitMediaSrc*); >+ void (*textChanged)(WebKitMediaSrc*); >+}; >+ >+GType webkit_media_src_get_type(void); >+ >+void webKitMediaSrcSetMediaPlayerPrivate(WebKitMediaSrc*, WebCore::MediaPlayerPrivateGStreamerMSE*); >+ >+void webKitMediaSrcPrepareSeek(WebKitMediaSrc*, const MediaTime&); >+void webKitMediaSrcSetReadyForSamples(WebKitMediaSrc*, bool); >+ >+G_END_DECLS >+ >+#endif // USE(GSTREAMER) >diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamerPrivate.h b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamerPrivate.h >new file mode 100644 >index 0000000000000000000000000000000000000000..83b523f7d2a440eea787c10911869c3468dfa9af >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamerPrivate.h >@@ -0,0 +1,143 @@ >+/* >+ * Copyright (C) 2016 Metrological Group B.V. >+ * Copyright (C) 2016 Igalia S.L >+ * >+ * 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 >+ * aint with this library; see the file COPYING.LIB. If not, write to >+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, >+ * Boston, MA 02110-1301, USA. >+ */ >+ >+#pragma once >+ >+#if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(MEDIA_SOURCE) >+ >+#include "AudioTrackPrivateGStreamer.h" >+#include "SourceBufferPrivateGStreamer.h" >+#include "VideoTrackPrivateGStreamer.h" >+#include "WebKitMediaSourceGStreamer.h" >+ >+#include <gst/app/gstappsrc.h> >+#include <gst/gst.h> >+#include <wtf/Condition.h> >+#include <wtf/RefPtr.h> >+#include <wtf/glib/GRefPtr.h> >+ >+namespace WebCore { >+ >+class MediaPlayerPrivateGStreamerMSE; >+ >+}; >+ >+void webKitMediaSrcUriHandlerInit(gpointer, gpointer); >+ >+#define WEBKIT_MEDIA_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_SRC, WebKitMediaSrcPrivate)) >+ >+typedef struct _Stream Stream; >+ >+struct _Stream { >+ // Fields filled when the Stream is created. >+ WebKitMediaSrc* parent; >+ >+ // AppSrc. >+ GstElement* appsrc; >+ GstPad* decodebinSinkPad; >+ WebCore::SourceBufferPrivateGStreamer* sourceBuffer; >+ >+ // Fields filled when the track is attached. >+ WebCore::MediaSourceStreamTypeGStreamer type; >+ // Might be 0, e.g. for VP8/VP9. >+ GstElement* parser; >+ GRefPtr<GstCaps> caps; >+ RefPtr<WebCore::AudioTrackPrivateGStreamer> audioTrack; >+ RefPtr<WebCore::VideoTrackPrivateGStreamer> videoTrack; >+ WebCore::FloatSize presentationSize; >+ >+ // This helps WebKitMediaSrcPrivate.appsrcNeedDataCount, ensuring that needDatas are >+ // counted only once per each appsrc. >+ bool appsrcNeedDataFlag; >+ >+ // Used to enforce continuity in the appended data and avoid breaking the decoder. >+ MediaTime lastEnqueuedTime; >+}; >+ >+enum { >+ PROP_0, >+ PROP_LOCATION, >+ PROP_N_AUDIO, >+ PROP_N_VIDEO, >+ PROP_N_TEXT, >+ PROP_LAST >+}; >+ >+enum { >+ SIGNAL_VIDEO_CHANGED, >+ SIGNAL_AUDIO_CHANGED, >+ SIGNAL_TEXT_CHANGED, >+ LAST_SIGNAL >+}; >+ >+enum OnSeekDataAction { >+ Nothing, >+ MediaSourceSeekToTime >+}; >+ >+struct _WebKitMediaSrcPrivate { >+ // Used to coordinate the release of Stream track info. >+ Lock streamLock; >+ Condition streamCondition; >+ >+ Deque<Stream*> streams; >+ GUniquePtr<gchar> location; >+ int numberOfAudioStreams; >+ int numberOfVideoStreams; >+ int numberOfTextStreams; >+ bool asyncStart; >+ bool allTracksConfigured; >+ unsigned numberOfPads; >+ >+ MediaTime seekTime; >+ >+ // On seek, we wait for all the seekDatas, then for all the needDatas, and then run the nextAction. >+ OnSeekDataAction appsrcSeekDataNextAction; >+ int appsrcSeekDataCount; >+ int appsrcNeedDataCount; >+ >+ GRefPtr<GstBus> bus; >+ WebCore::MediaPlayerPrivateGStreamerMSE* mediaPlayerPrivate; >+}; >+ >+extern guint webKitMediaSrcSignals[LAST_SIGNAL]; >+extern GstAppSrcCallbacks enabledAppsrcCallbacks; >+extern GstAppSrcCallbacks disabledAppsrcCallbacks; >+ >+void webKitMediaSrcUriHandlerInit(gpointer gIface, gpointer ifaceData); >+void webKitMediaSrcFinalize(GObject*); >+void webKitMediaSrcSetProperty(GObject*, guint propertyId, const GValue*, GParamSpec*); >+void webKitMediaSrcGetProperty(GObject*, guint propertyId, GValue*, GParamSpec*); >+void webKitMediaSrcDoAsyncStart(WebKitMediaSrc*); >+void webKitMediaSrcDoAsyncDone(WebKitMediaSrc*); >+GstStateChangeReturn webKitMediaSrcChangeState(GstElement*, GstStateChange); >+gint64 webKitMediaSrcGetSize(WebKitMediaSrc*); >+gboolean webKitMediaSrcQueryWithParent(GstPad*, GstObject*, GstQuery*); >+void webKitMediaSrcUpdatePresentationSize(GstCaps*, Stream*); >+void webKitMediaSrcLinkStreamToSrcPad(GstPad*, Stream*); >+void webKitMediaSrcLinkParser(GstPad*, GstCaps*, Stream*); >+void webKitMediaSrcFreeStream(WebKitMediaSrc*, Stream*); >+void webKitMediaSrcCheckAllTracksConfigured(WebKitMediaSrc*); >+GstURIType webKitMediaSrcUriGetType(GType); >+const gchar* const* webKitMediaSrcGetProtocols(GType); >+gchar* webKitMediaSrcGetUri(GstURIHandler*); >+gboolean webKitMediaSrcSetUri(GstURIHandler*, const gchar*, GError**); >+ >+#endif // USE(GSTREAMER)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 162902
:
290598
|
291766
|
292411
|
292416
|
292740
|
292744
|
292896