diff options
| author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
|---|---|---|
| committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
| commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
| tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/platform/graphics/gstreamer | |
| parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
| download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 | |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/platform/graphics/gstreamer')
19 files changed, 4073 insertions, 0 deletions
diff --git a/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.cpp new file mode 100644 index 0000000..06eec14 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 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 "GOwnPtrGStreamer.h" + +#if USE(GSTREAMER) +#include <gst/gstelement.h> + +namespace WTF { + +template <> void freeOwnedGPtr<GstElement>(GstElement* ptr) +{ + if (ptr) + gst_object_unref(ptr); +} + +} +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.h new file mode 100644 index 0000000..672a23d --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/GOwnPtrGStreamer.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#ifndef GOwnPtrGStreamer_h +#define GOwnPtrGStreamer_h +#if USE(GSTREAMER) + +#include "GOwnPtr.h" + +typedef struct _GstElement GstElement; + +namespace WTF { + +template<> void freeOwnedGPtr<GstElement>(GstElement* ptr); + +} + +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.cpp new file mode 100644 index 0000000..d179601 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#include "config.h" +#include "GStreamerGWorld.h" +#if USE(GSTREAMER) + +#include "GOwnPtrGStreamer.h" +#include <gst/gst.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/pbutils/pbutils.h> + +#if PLATFORM(GTK) +#include <gtk/gtk.h> +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> // for GDK_WINDOW_XID +#endif +#endif + +using namespace std; + +namespace WebCore { + +gboolean gstGWorldSyncMessageCallback(GstBus* bus, GstMessage* message, gpointer data) +{ + ASSERT(GST_MESSAGE_TYPE(message) == GST_MESSAGE_ELEMENT); + + GStreamerGWorld* gstGWorld = static_cast<GStreamerGWorld*>(data); + + if (gst_structure_has_name(message->structure, "prepare-xwindow-id")) + gstGWorld->setWindowOverlay(message); + return TRUE; +} + +PassRefPtr<GStreamerGWorld> GStreamerGWorld::createGWorld(GstElement* pipeline) +{ + return adoptRef(new GStreamerGWorld(pipeline)); +} + +GStreamerGWorld::GStreamerGWorld(GstElement* pipeline) + : m_pipeline(pipeline) + , m_dynamicPadName(0) +{ + // XOverlay messages need to be handled synchronously. + GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline)); + gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, this); + g_signal_connect(bus, "sync-message::element", G_CALLBACK(gstGWorldSyncMessageCallback), this); + gst_object_unref(bus); +} + +GStreamerGWorld::~GStreamerGWorld() +{ + exitFullscreen(); + + m_pipeline = 0; +} + +bool GStreamerGWorld::enterFullscreen() +{ + if (m_dynamicPadName) + return false; + + if (!m_videoWindow) + m_videoWindow = PlatformVideoWindow::createWindow(); + + GstElement* platformVideoSink = gst_element_factory_make("autovideosink", "platformVideoSink"); + GstElement* colorspace = gst_element_factory_make("ffmpegcolorspace", "colorspace"); + GstElement* queue = gst_element_factory_make("queue", "queue"); + GstElement* videoScale = gst_element_factory_make("videoscale", "videoScale"); + + // Get video sink bin and the tee inside. + GOwnPtr<GstElement> videoSink; + g_object_get(m_pipeline, "video-sink", &videoSink.outPtr(), NULL); + GstElement* tee = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoTee"); + GstElement* valve = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoValve"); + + g_object_set(valve, "drop-probability", 1.0, NULL); + + // Add and link a queue, ffmpegcolorspace, videoscale and sink in the bin. + gst_bin_add_many(GST_BIN(videoSink.get()), platformVideoSink, videoScale, colorspace, queue, NULL); +#if GST_CHECK_VERSION(0, 10, 30) + // Faster elements linking, if possible. + gst_element_link_pads_full(queue, "src", colorspace, "sink", GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full(colorspace, "src", videoScale, "sink", GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full(videoScale, "src", platformVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING); +#else + gst_element_link_many(queue, colorspace, videoScale, platformVideoSink, NULL); +#endif + + // Link a new src pad from tee to queue. + GstPad* srcPad = gst_element_get_request_pad(tee, "src%d"); + GstPad* sinkPad = gst_element_get_static_pad(queue, "sink"); + gst_pad_link(srcPad, sinkPad); + gst_object_unref(GST_OBJECT(sinkPad)); + + m_dynamicPadName = gst_pad_get_name(srcPad); + + // Roll new elements to pipeline state. + gst_element_sync_state_with_parent(queue); + gst_element_sync_state_with_parent(colorspace); + gst_element_sync_state_with_parent(videoScale); + gst_element_sync_state_with_parent(platformVideoSink); + + gst_object_unref(tee); + + // Query the current media segment informations and send them towards + // the new tee branch downstream. + + GstQuery* query = gst_query_new_segment(GST_FORMAT_TIME); + gboolean queryResult = gst_element_query(m_pipeline, query); + +#if GST_CHECK_VERSION(0, 10, 30) + if (!queryResult) { + gst_query_unref(query); + gst_object_unref(GST_OBJECT(srcPad)); + return true; + } +#else + // GStreamer < 0.10.30 doesn't set the query result correctly, so + // just ignore it to avoid a compilation warning. + // See https://bugzilla.gnome.org/show_bug.cgi?id=620490. + (void) queryResult; +#endif + + GstFormat format; + gint64 position; + if (!gst_element_query_position(m_pipeline, &format, &position)) + position = 0; + + gdouble rate; + gint64 startValue, stopValue; + gst_query_parse_segment(query, &rate, &format, &startValue, &stopValue); + + GstEvent* event = gst_event_new_new_segment(FALSE, rate, format, startValue, stopValue, position); + gst_pad_push_event(srcPad, event); + + gst_query_unref(query); + gst_object_unref(GST_OBJECT(srcPad)); + return true; +} + +void GStreamerGWorld::exitFullscreen() +{ + if (!m_dynamicPadName) + return; + + // Get video sink bin and the elements to remove. + GOwnPtr<GstElement> videoSink; + g_object_get(m_pipeline, "video-sink", &videoSink.outPtr(), NULL); + GstElement* tee = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoTee"); + GstElement* platformVideoSink = gst_bin_get_by_name(GST_BIN(videoSink.get()), "platformVideoSink"); + GstElement* queue = gst_bin_get_by_name(GST_BIN(videoSink.get()), "queue"); + GstElement* colorspace = gst_bin_get_by_name(GST_BIN(videoSink.get()), "colorspace"); + GstElement* videoScale = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoScale"); + + GstElement* valve = gst_bin_get_by_name(GST_BIN(videoSink.get()), "videoValve"); + + g_object_set(valve, "drop-probability", 0.0, NULL); + + // Get pads to unlink and remove. + GstPad* srcPad = gst_element_get_static_pad(tee, m_dynamicPadName); + GstPad* sinkPad = gst_element_get_static_pad(queue, "sink"); + + // Unlink and release request pad. + gst_pad_unlink(srcPad, sinkPad); + gst_element_release_request_pad(tee, srcPad); + gst_object_unref(GST_OBJECT(srcPad)); + gst_object_unref(GST_OBJECT(sinkPad)); + + // Unlink, remove and cleanup queue, ffmpegcolorspace, videoScale and sink. + gst_element_unlink_many(queue, colorspace, videoScale, platformVideoSink, NULL); + gst_bin_remove_many(GST_BIN(videoSink.get()), queue, colorspace, videoScale, platformVideoSink, NULL); + gst_element_set_state(queue, GST_STATE_NULL); + gst_element_set_state(colorspace, GST_STATE_NULL); + gst_element_set_state(videoScale, GST_STATE_NULL); + gst_element_set_state(platformVideoSink, GST_STATE_NULL); + gst_object_unref(queue); + gst_object_unref(colorspace); + gst_object_unref(videoScale); + gst_object_unref(platformVideoSink); + + gst_object_unref(tee); + m_dynamicPadName = 0; +} + +void GStreamerGWorld::setWindowOverlay(GstMessage* message) +{ + GstObject* sink = GST_MESSAGE_SRC(message); + + if (!GST_IS_X_OVERLAY(sink)) + return; + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(sink), "force-aspect-ratio")) + g_object_set(sink, "force-aspect-ratio", TRUE, NULL); + + if (m_videoWindow) { + m_videoWindow->prepareForOverlay(message); + +// gst_x_overlay_set_window_handle was introduced in -plugins-base +// 0.10.31, just like the macro for checking the version. +#ifdef GST_CHECK_PLUGINS_BASE_VERSION + gst_x_overlay_set_window_handle(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId()); +#else + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), m_videoWindow->videoWindowId()); +#endif + } +} + +} +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.h b/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.h new file mode 100644 index 0000000..f519911 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerGWorld.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + + +#ifndef GStreamerGWorld_h +#define GStreamerGWorld_h +#if USE(GSTREAMER) + +#include "PlatformVideoWindow.h" +#include "RefCounted.h" +#include "RefPtr.h" +#include <glib.h> + +typedef struct _GstElement GstElement; +typedef struct _GstMessage GstMessage; +typedef struct _GstBus GstBus; +typedef struct _GstBin GstBin; + +namespace WebCore { + +class MediaPlayerPrivateGStreamer; + +gboolean gstGWorldSyncMessageCallback(GstBus* bus, GstMessage* message, gpointer data); + +class GStreamerGWorld : public RefCounted<GStreamerGWorld> { + friend gboolean gstGWorldSyncMessageCallback(GstBus* bus, GstMessage* message, gpointer data); + +public: + static PassRefPtr<GStreamerGWorld> createGWorld(GstElement*); + ~GStreamerGWorld(); + + GstElement* pipeline() const { return m_pipeline; } + + // Returns the full-screen window created + bool enterFullscreen(); + void exitFullscreen(); + + void setWindowOverlay(GstMessage* message); + PlatformVideoWindow* platformVideoWindow() const { return m_videoWindow.get(); } + +private: + GStreamerGWorld(GstElement*); + GstElement* m_pipeline; + RefPtr<PlatformVideoWindow> m_videoWindow; + gchar* m_dynamicPadName; +}; + +} +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamer.h new file mode 100644 index 0000000..6e53cfc --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamer.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#ifndef ImageGStreamer_h +#define ImageGStreamer_h + +#if USE(GSTREAMER) + +#include "BitmapImage.h" +#include <gst/gst.h> +#include <gst/video/video.h> +#include <wtf/PassRefPtr.h> + +#if PLATFORM(CAIRO) +#include <cairo.h> +#endif + +namespace WebCore { +class IntSize; + +class ImageGStreamer : public RefCounted<ImageGStreamer> { + public: + static PassRefPtr<ImageGStreamer> createImage(GstBuffer*); + ~ImageGStreamer(); + + PassRefPtr<BitmapImage> image() + { + ASSERT(m_image); + return m_image.get(); + } + + private: + RefPtr<BitmapImage> m_image; + +#if PLATFORM(CAIRO) + ImageGStreamer(GstBuffer*&, IntSize, cairo_format_t&); +#endif + +#if PLATFORM(QT) + ImageGStreamer(GstBuffer*&, IntSize, QImage::Format); +#endif + +#if PLATFORM(MAC) + ImageGStreamer(GstBuffer*&, IntSize); +#endif + + }; +} + +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCG.mm b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCG.mm new file mode 100644 index 0000000..c9a6ca8 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCG.mm @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#include "config.h" + +#include "GraphicsContextCG.h" +#include "ImageGStreamer.h" +#if USE(GSTREAMER) + +using namespace WebCore; + +PassRefPtr<ImageGStreamer> ImageGStreamer::createImage(GstBuffer* buffer) +{ + int width = 0, height = 0; + GstCaps* caps = gst_buffer_get_caps(buffer); + GstVideoFormat format; + if (!gst_video_format_parse_caps(caps, &format, &width, &height)) { + gst_caps_unref(caps); + return NULL; + } + + return adoptRef(new ImageGStreamer(buffer, IntSize(width, height))); +} + +ImageGStreamer::ImageGStreamer(GstBuffer*& buffer, IntSize size) + : m_image(0) +{ + ASSERT(GST_BUFFER_SIZE(buffer)); + + RetainPtr<CFDataRef> data(AdoptCF, CFDataCreateWithBytesNoCopy(0, static_cast<UInt8*>(GST_BUFFER_DATA(buffer)), GST_BUFFER_SIZE(buffer), kCFAllocatorNull)); + RetainPtr<CGDataProviderRef> provider(AdoptCF, CGDataProviderCreateWithCFData(data.get())); + CGImageRef frameImage = CGImageCreate(size.width(), size.height(), 8, 32, size.width()*4, deviceRGBColorSpaceRef(), + kCGBitmapByteOrder32Little | kCGImageAlphaFirst, provider.get(), 0, false, kCGRenderingIntentDefault); + m_image = BitmapImage::create(frameImage); +} + +ImageGStreamer::~ImageGStreamer() +{ + if (m_image) + m_image.clear(); + + m_image = 0; +} + +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCairo.cpp b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCairo.cpp new file mode 100644 index 0000000..6a9d068 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerCairo.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#include "config.h" +#include "ImageGStreamer.h" + +#if USE(GSTREAMER) + +#include "GOwnPtr.h" + +using namespace std; +using namespace WebCore; + +PassRefPtr<ImageGStreamer> ImageGStreamer::createImage(GstBuffer* buffer) +{ + int width = 0, height = 0; + GstCaps* caps = gst_buffer_get_caps(buffer); + GstVideoFormat format; + if (!gst_video_format_parse_caps(caps, &format, &width, &height)) { + gst_caps_unref(caps); + return 0; + } + + gst_caps_unref(caps); + + cairo_format_t cairoFormat; + if (format == GST_VIDEO_FORMAT_ARGB || format == GST_VIDEO_FORMAT_BGRA) + cairoFormat = CAIRO_FORMAT_ARGB32; + else + cairoFormat = CAIRO_FORMAT_RGB24; + + return adoptRef(new ImageGStreamer(buffer, IntSize(width, height), cairoFormat)); +} + +ImageGStreamer::ImageGStreamer(GstBuffer*& buffer, IntSize size, cairo_format_t& cairoFormat) + : m_image(0) +{ + cairo_surface_t* surface = cairo_image_surface_create_for_data(GST_BUFFER_DATA(buffer), cairoFormat, + size.width(), size.height(), + cairo_format_stride_for_width(cairoFormat, size.width())); + ASSERT(cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS); + m_image = BitmapImage::create(surface); +} + +ImageGStreamer::~ImageGStreamer() +{ + if (m_image) + m_image.clear(); + + m_image = 0; +} +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp new file mode 100644 index 0000000..cf46f02 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + + 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 + along 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. +*/ + +#include "config.h" +#include "ImageGStreamer.h" + +#if USE(GSTREAMER) +#include "GOwnPtr.h" + +using namespace std; +using namespace WebCore; + +PassRefPtr<ImageGStreamer> ImageGStreamer::createImage(GstBuffer* buffer) +{ + int width = 0, height = 0; + GstCaps* caps = gst_buffer_get_caps(buffer); + GstVideoFormat format; + if (!gst_video_format_parse_caps(caps, &format, &width, &height)) { + gst_caps_unref(caps); + return 0; + } + + gst_caps_unref(caps); + + QImage::Format imageFormat; + if (format == GST_VIDEO_FORMAT_RGB) + imageFormat = QImage::Format_RGB888; + else + imageFormat = QImage::Format_RGB32; + + return adoptRef(new ImageGStreamer(buffer, IntSize(width, height), imageFormat)); +} + +ImageGStreamer::ImageGStreamer(GstBuffer*& buffer, IntSize size, QImage::Format imageFormat) + : m_image(0) +{ + QPixmap* surface = new QPixmap; + QImage image(GST_BUFFER_DATA(buffer), size.width(), size.height(), imageFormat); + surface->convertFromImage(image); + m_image = BitmapImage::create(surface); +} + +ImageGStreamer::~ImageGStreamer() +{ + if (m_image) + m_image.clear(); + + m_image = 0; +} +#endif // USE(GSTREAMER) + diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp new file mode 100644 index 0000000..c113c69 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -0,0 +1,1630 @@ +/* + * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2007 Collabora Ltd. All rights reserved. + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org> + * Copyright (C) 2009, 2010 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. + */ + +#include "config.h" +#include "MediaPlayerPrivateGStreamer.h" + +#if USE(GSTREAMER) + +#include "ColorSpace.h" +#include "Document.h" +#include "Frame.h" +#include "FrameView.h" +#include "GOwnPtrGStreamer.h" +#include "GStreamerGWorld.h" +#include "GraphicsContext.h" +#include "GraphicsTypes.h" +#include "ImageGStreamer.h" +#include "IntRect.h" +#include "KURL.h" +#include "MIMETypeRegistry.h" +#include "MediaPlayer.h" +#include "NotImplemented.h" +#include "SecurityOrigin.h" +#include "TimeRanges.h" +#include "VideoSinkGStreamer.h" +#include "WebKitWebSourceGStreamer.h" +#include <GOwnPtr.h> +#include <gst/gst.h> +#include <gst/video/video.h> +#include <limits> +#include <math.h> + +// GstPlayFlags flags from playbin2. It is the policy of GStreamer to +// not publicly expose element-specific enums. That's why this +// GstPlayFlags enum has been copied here. +typedef enum { + GST_PLAY_FLAG_VIDEO = 0x00000001, + GST_PLAY_FLAG_AUDIO = 0x00000002, + GST_PLAY_FLAG_TEXT = 0x00000004, + GST_PLAY_FLAG_VIS = 0x00000008, + GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010, + GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020, + GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040, + GST_PLAY_FLAG_DOWNLOAD = 0x00000080, + GST_PLAY_FLAG_BUFFERING = 0x000000100 +} GstPlayFlags; + +using namespace std; + +namespace WebCore { + +static int greatestCommonDivisor(int a, int b) +{ + while (b) { + int temp = a; + a = b; + b = temp % b; + } + + return ABS(a); +} + +gboolean mediaPlayerPrivateMessageCallback(GstBus* bus, GstMessage* message, gpointer data) +{ + GOwnPtr<GError> err; + GOwnPtr<gchar> debug; + MediaPlayer::NetworkState error; + MediaPlayerPrivateGStreamer* mp = reinterpret_cast<MediaPlayerPrivateGStreamer*>(data); + bool issueError = true; + bool attemptNextLocation = false; + GstElement* pipeline = mp->pipeline(); + + if (message->structure) { + const gchar* messageTypeName = gst_structure_get_name(message->structure); + + // Redirect messages are sent from elements, like qtdemux, to + // notify of the new location(s) of the media. + if (!g_strcmp0(messageTypeName, "redirect")) { + mp->mediaLocationChanged(message); + return true; + } + } + + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_ERROR: + if (mp && mp->pipelineReset()) + break; + gst_message_parse_error(message, &err.outPtr(), &debug.outPtr()); + LOG_VERBOSE(Media, "Error: %d, %s", err->code, err->message); + + error = MediaPlayer::Empty; + if (err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND + || err->code == GST_STREAM_ERROR_WRONG_TYPE + || err->code == GST_STREAM_ERROR_FAILED + || err->code == GST_CORE_ERROR_MISSING_PLUGIN + || err->code == GST_RESOURCE_ERROR_NOT_FOUND) + error = MediaPlayer::FormatError; + else if (err->domain == GST_STREAM_ERROR) { + error = MediaPlayer::DecodeError; + attemptNextLocation = true; + } else if (err->domain == GST_RESOURCE_ERROR) + error = MediaPlayer::NetworkError; + + if (mp) { + if (attemptNextLocation) + issueError = !mp->loadNextLocation(); + if (issueError) + mp->loadingFailed(error); + } + break; + case GST_MESSAGE_EOS: + LOG_VERBOSE(Media, "End of Stream"); + mp->didEnd(); + break; + case GST_MESSAGE_STATE_CHANGED: + // Ignore state changes if load is delayed (preload=none). The + // player state will be updated once commitLoad() is called. + if (mp->loadDelayed()) { + LOG_VERBOSE(Media, "Media load has been delayed. Ignoring state changes for now"); + break; + } + + // Ignore state changes from internal elements. They are + // forwarded to playbin2 anyway. + if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(pipeline)) + mp->updateStates(); + break; + case GST_MESSAGE_BUFFERING: + mp->processBufferingStats(message); + break; + case GST_MESSAGE_DURATION: + LOG_VERBOSE(Media, "Duration changed"); + mp->durationChanged(); + break; + default: + LOG_VERBOSE(Media, "Unhandled GStreamer message type: %s", + GST_MESSAGE_TYPE_NAME(message)); + break; + } + return true; +} + +void mediaPlayerPrivateSourceChangedCallback(GObject *object, GParamSpec *pspec, gpointer data) +{ + MediaPlayerPrivateGStreamer* mp = reinterpret_cast<MediaPlayerPrivateGStreamer*>(data); + GOwnPtr<GstElement> element; + + g_object_get(mp->m_playBin, "source", &element.outPtr(), NULL); + gst_object_replace((GstObject**) &mp->m_source, (GstObject*) element.get()); + + if (WEBKIT_IS_WEB_SRC(element.get())) { + Frame* frame = mp->m_player->frameView() ? mp->m_player->frameView()->frame() : 0; + + if (frame) + webKitWebSrcSetFrame(WEBKIT_WEB_SRC(element.get()), frame); + } +} + +void mediaPlayerPrivateVolumeChangedCallback(GObject *element, GParamSpec *pspec, gpointer data) +{ + // This is called when playbin receives the notify::volume signal. + MediaPlayerPrivateGStreamer* mp = reinterpret_cast<MediaPlayerPrivateGStreamer*>(data); + mp->volumeChanged(); +} + +gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) +{ + // This is the callback of the timeout source created in ::volumeChanged. + player->notifyPlayerOfVolumeChange(); + return FALSE; +} + +void mediaPlayerPrivateMuteChangedCallback(GObject *element, GParamSpec *pspec, gpointer data) +{ + // This is called when playbin receives the notify::mute signal. + MediaPlayerPrivateGStreamer* mp = reinterpret_cast<MediaPlayerPrivateGStreamer*>(data); + mp->muteChanged(); +} + +gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) +{ + // This is the callback of the timeout source created in ::muteChanged. + player->notifyPlayerOfMute(); + return FALSE; +} + +void mediaPlayerPrivateVideoTagsChangedCallback(GObject* element, gint streamId, MediaPlayerPrivateGStreamer* player) +{ + player->videoTagsChanged(streamId); +} + +void mediaPlayerPrivateAudioTagsChangedCallback(GObject* element, gint streamId, MediaPlayerPrivateGStreamer* player) +{ + player->audioTagsChanged(streamId); +} + +gboolean mediaPlayerPrivateAudioTagsChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) +{ + // This is the callback of the timeout source created in ::audioTagsChanged. + player->notifyPlayerOfAudioTags(); + return FALSE; +} + +gboolean mediaPlayerPrivateVideoTagsChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) +{ + // This is the callback of the timeout source created in ::videoTagsChanged. + player->notifyPlayerOfVideoTags(); + return FALSE; +} + +static float playbackPosition(GstElement* playbin) +{ + + float ret = 0.0f; + + GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); + if (!gst_element_query(playbin, query)) { + LOG_VERBOSE(Media, "Position query failed..."); + gst_query_unref(query); + return ret; + } + + gint64 position; + gst_query_parse_position(query, 0, &position); + + // Position is available only if the pipeline is not in GST_STATE_NULL or + // GST_STATE_READY state. + if (position != static_cast<gint64>(GST_CLOCK_TIME_NONE)) + ret = static_cast<float>(position) / static_cast<float>(GST_SECOND); + + LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); + + gst_query_unref(query); + + return ret; +} + + +void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivateGStreamer* playerPrivate) +{ + g_return_if_fail(GST_IS_BUFFER(buffer)); + gst_buffer_replace(&playerPrivate->m_buffer, buffer); + playerPrivate->repaint(); +} + +MediaPlayerPrivateInterface* MediaPlayerPrivateGStreamer::create(MediaPlayer* player) +{ + return new MediaPlayerPrivateGStreamer(player); +} + +void MediaPlayerPrivateGStreamer::registerMediaEngine(MediaEngineRegistrar registrar) +{ + if (isAvailable()) + registrar(create, getSupportedTypes, supportsType); +} + +static bool gstInitialized = false; + +static bool doGstInit() +{ + // FIXME: We should pass the arguments from the command line + if (!gstInitialized) { + GOwnPtr<GError> error; + gstInitialized = gst_init_check(0, 0, &error.outPtr()); + if (!gstInitialized) + LOG_VERBOSE(Media, "Could not initialize GStreamer: %s", + error ? error->message : "unknown error occurred"); + else + gst_element_register(0, "webkitwebsrc", GST_RANK_PRIMARY + 100, + WEBKIT_TYPE_WEB_SRC); + } + return gstInitialized; +} + +bool MediaPlayerPrivateGStreamer::isAvailable() +{ + if (!doGstInit()) + return false; + + GstElementFactory* factory = gst_element_factory_find("playbin2"); + if (factory) { + gst_object_unref(GST_OBJECT(factory)); + return true; + } + return false; +} + +MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) + : m_player(player) + , m_playBin(0) + , m_webkitVideoSink(0) + , m_fpsSink(0) + , m_source(0) + , m_seekTime(0) + , m_changingRate(false) + , m_endTime(numeric_limits<float>::infinity()) + , m_networkState(MediaPlayer::Empty) + , m_readyState(MediaPlayer::HaveNothing) + , m_isStreaming(false) + , m_size(IntSize()) + , m_buffer(0) + , m_mediaLocations(0) + , m_mediaLocationCurrentIndex(0) + , m_resetPipeline(false) + , m_paused(true) + , m_seeking(false) + , m_buffering(false) + , m_playbackRate(1) + , m_errorOccured(false) + , m_mediaDuration(0) + , m_startedBuffering(false) + , m_fillTimer(this, &MediaPlayerPrivateGStreamer::fillTimerFired) + , m_maxTimeLoaded(0) + , m_bufferingPercentage(0) + , m_preload(MediaPlayer::Auto) + , m_delayingLoad(false) + , m_mediaDurationKnown(true) + , m_volumeTimerHandler(0) + , m_muteTimerHandler(0) + , m_hasVideo(false) + , m_hasAudio(false) + , m_audioTagsTimerHandler(0) + , m_videoTagsTimerHandler(0) +{ + if (doGstInit()) + createGSTPlayBin(); +} + +MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() +{ + if (m_fillTimer.isActive()) + m_fillTimer.stop(); + + if (m_buffer) + gst_buffer_unref(m_buffer); + m_buffer = 0; + + if (m_mediaLocations) { + gst_structure_free(m_mediaLocations); + m_mediaLocations = 0; + } + + if (m_source) { + gst_object_unref(m_source); + m_source = 0; + } + + if (m_videoSinkBin) { + gst_object_unref(m_videoSinkBin); + m_videoSinkBin = 0; + } + + if (m_playBin) { + gst_element_set_state(m_playBin, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(m_playBin)); + m_playBin = 0; + } + + m_player = 0; + + if (m_muteTimerHandler) + g_source_remove(m_muteTimerHandler); + + if (m_volumeTimerHandler) + g_source_remove(m_volumeTimerHandler); + + if (m_videoTagsTimerHandler) + g_source_remove(m_videoTagsTimerHandler); + + if (m_audioTagsTimerHandler) + g_source_remove(m_audioTagsTimerHandler); +} + +void MediaPlayerPrivateGStreamer::load(const String& url) +{ + g_object_set(m_playBin, "uri", url.utf8().data(), NULL); + + LOG_VERBOSE(Media, "Load %s", url.utf8().data()); + + if (m_preload == MediaPlayer::None) { + LOG_VERBOSE(Media, "Delaying load."); + m_delayingLoad = true; + } + + // GStreamer needs to have the pipeline set to a paused state to + // start providing anything useful. + gst_element_set_state(m_playBin, GST_STATE_PAUSED); + + if (!m_delayingLoad) + commitLoad(); +} + +void MediaPlayerPrivateGStreamer::commitLoad() +{ + ASSERT(!m_delayingLoad); + LOG_VERBOSE(Media, "Committing load."); + updateStates(); +} + +bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState) +{ + ASSERT(newState == GST_STATE_PLAYING || newState == GST_STATE_PAUSED); + + GstState currentState; + GstState pending; + + gst_element_get_state(m_playBin, ¤tState, &pending, 0); + if (currentState != newState && pending != newState) { + GstStateChangeReturn ret = gst_element_set_state(m_playBin, newState); + GstState pausedOrPlaying = newState == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING; + if (currentState != pausedOrPlaying && ret == GST_STATE_CHANGE_FAILURE) { + loadingFailed(MediaPlayer::Empty); + return false; + } + } + return true; +} + +void MediaPlayerPrivateGStreamer::prepareToPlay() +{ + if (m_delayingLoad) { + m_delayingLoad = false; + commitLoad(); + } +} + +void MediaPlayerPrivateGStreamer::play() +{ + if (changePipelineState(GST_STATE_PLAYING)) + LOG_VERBOSE(Media, "Play"); +} + +void MediaPlayerPrivateGStreamer::pause() +{ + if (changePipelineState(GST_STATE_PAUSED)) + LOG_VERBOSE(Media, "Pause"); +} + +float MediaPlayerPrivateGStreamer::duration() const +{ + if (!m_playBin) + return 0.0f; + + if (m_errorOccured) + return 0.0f; + + // Media duration query failed already, don't attempt new useless queries. + if (!m_mediaDurationKnown) + return numeric_limits<float>::infinity(); + + if (m_mediaDuration) + return m_mediaDuration; + + GstFormat timeFormat = GST_FORMAT_TIME; + gint64 timeLength = 0; + + if (!gst_element_query_duration(m_playBin, &timeFormat, &timeLength) || timeFormat != GST_FORMAT_TIME || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE) { + LOG_VERBOSE(Media, "Time duration query failed."); + return numeric_limits<float>::infinity(); + } + + LOG_VERBOSE(Media, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(timeLength)); + + return (float) ((guint64) timeLength / 1000000000.0); + // FIXME: handle 3.14.9.5 properly +} + +float MediaPlayerPrivateGStreamer::currentTime() const +{ + if (!m_playBin) + return 0.0f; + + if (m_errorOccured) + return 0.0f; + + if (m_seeking) + return static_cast<float>(m_seekTime); + + return playbackPosition(m_playBin); + +} + +void MediaPlayerPrivateGStreamer::seek(float time) +{ + // Avoid useless seeking. + if (time == playbackPosition(m_playBin)) + return; + + if (!m_playBin) + return; + + if (m_errorOccured) + return; + + GstClockTime sec = (GstClockTime)(static_cast<float>(time * GST_SECOND)); + LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(sec)); + if (!gst_element_seek(m_playBin, m_player->rate(), + GST_FORMAT_TIME, + (GstSeekFlags)(GST_SEEK_FLAG_FLUSH), + GST_SEEK_TYPE_SET, sec, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + LOG_VERBOSE(Media, "Seek to %f failed", time); + else { + m_seeking = true; + m_seekTime = sec; + } +} + +bool MediaPlayerPrivateGStreamer::paused() const +{ + return m_paused; +} + +bool MediaPlayerPrivateGStreamer::seeking() const +{ + return m_seeking; +} + +// Returns the size of the video +IntSize MediaPlayerPrivateGStreamer::naturalSize() const +{ + if (!hasVideo()) + return IntSize(); + + GstPad* pad = gst_element_get_static_pad(m_webkitVideoSink, "sink"); + if (!pad) + return IntSize(); + + guint64 width = 0, height = 0; + GstCaps* caps = GST_PAD_CAPS(pad); + int pixelAspectRatioNumerator, pixelAspectRatioDenominator; + int displayWidth, displayHeight, displayAspectRatioGCD; + int originalWidth = 0, originalHeight = 0; + + // TODO: handle possible clean aperture data. See + // https://bugzilla.gnome.org/show_bug.cgi?id=596571 + // TODO: handle possible transformation matrix. See + // https://bugzilla.gnome.org/show_bug.cgi?id=596326 + + // Get the video PAR and original size. + if (!GST_IS_CAPS(caps) || !gst_caps_is_fixed(caps) + || !gst_video_format_parse_caps(caps, 0, &originalWidth, &originalHeight) + || !gst_video_parse_caps_pixel_aspect_ratio(caps, &pixelAspectRatioNumerator, + &pixelAspectRatioDenominator)) { + gst_object_unref(GST_OBJECT(pad)); + return IntSize(); + } + + gst_object_unref(GST_OBJECT(pad)); + + LOG_VERBOSE(Media, "Original video size: %dx%d", originalWidth, originalHeight); + LOG_VERBOSE(Media, "Pixel aspect ratio: %d/%d", pixelAspectRatioNumerator, pixelAspectRatioDenominator); + + // Calculate DAR based on PAR and video size. + displayWidth = originalWidth * pixelAspectRatioNumerator; + displayHeight = originalHeight * pixelAspectRatioDenominator; + + // Divide display width and height by their GCD to avoid possible overflows. + displayAspectRatioGCD = greatestCommonDivisor(displayWidth, displayHeight); + displayWidth /= displayAspectRatioGCD; + displayHeight /= displayAspectRatioGCD; + + // Apply DAR to original video size. This is the same behavior as in xvimagesink's setcaps function. + if (!(originalHeight % displayHeight)) { + LOG_VERBOSE(Media, "Keeping video original height"); + width = gst_util_uint64_scale_int(originalHeight, displayWidth, displayHeight); + height = static_cast<guint64>(originalHeight); + } else if (!(originalWidth % displayWidth)) { + LOG_VERBOSE(Media, "Keeping video original width"); + height = gst_util_uint64_scale_int(originalWidth, displayHeight, displayWidth); + width = static_cast<guint64>(originalWidth); + } else { + LOG_VERBOSE(Media, "Approximating while keeping original video height"); + width = gst_util_uint64_scale_int(originalHeight, displayWidth, displayHeight); + height = static_cast<guint64>(originalHeight); + } + + LOG_VERBOSE(Media, "Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height); + return IntSize(static_cast<int>(width), static_cast<int>(height)); +} + +void MediaPlayerPrivateGStreamer::videoTagsChanged(gint streamId) +{ + if (m_videoTagsTimerHandler) + g_source_remove(m_videoTagsTimerHandler); + m_videoTagsTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVideoTagsChangeTimeoutCallback), this); +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfVideoTags() +{ + m_videoTagsTimerHandler = 0; + + gint currentVideo = -1; + if (m_playBin) + g_object_get(m_playBin, "current-video", ¤tVideo, NULL); + m_hasVideo = currentVideo > -1; + m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player); +} + +void MediaPlayerPrivateGStreamer::audioTagsChanged(gint streamId) +{ + if (m_audioTagsTimerHandler) + g_source_remove(m_audioTagsTimerHandler); + m_audioTagsTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateAudioTagsChangeTimeoutCallback), this); +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfAudioTags() +{ + m_audioTagsTimerHandler = 0; + + gint currentAudio = -1; + if (m_playBin) + g_object_get(m_playBin, "current-audio", ¤tAudio, NULL); + m_hasAudio = currentAudio > -1; + m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player); +} + +void MediaPlayerPrivateGStreamer::setVolume(float volume) +{ + if (!m_playBin) + return; + + g_object_set(m_playBin, "volume", static_cast<double>(volume), NULL); +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfVolumeChange() +{ + m_volumeTimerHandler = 0; + + if (!m_player || !m_playBin) + return; + double volume; + g_object_get(m_playBin, "volume", &volume, NULL); + m_player->volumeChanged(static_cast<float>(volume)); +} + +void MediaPlayerPrivateGStreamer::volumeChanged() +{ + if (m_volumeTimerHandler) + g_source_remove(m_volumeTimerHandler); + m_volumeTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVolumeChangeTimeoutCallback), this); +} + +void MediaPlayerPrivateGStreamer::setRate(float rate) +{ + // Avoid useless playback rate update. + if (m_playbackRate == rate) + return; + + GstState state; + GstState pending; + + gst_element_get_state(m_playBin, &state, &pending, 0); + if ((state != GST_STATE_PLAYING && state != GST_STATE_PAUSED) + || (pending == GST_STATE_PAUSED)) + return; + + if (m_isStreaming) + return; + + m_playbackRate = rate; + m_changingRate = true; + float currentPosition = static_cast<float>(playbackPosition(m_playBin) * GST_SECOND); + GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH); + gint64 start, end; + bool mute = false; + + LOG_VERBOSE(Media, "Set Rate to %f", rate); + if (rate >= 0) { + // Mute the sound if the playback rate is too extreme. + // TODO: in other cases we should perform pitch adjustments. + mute = (bool) (rate < 0.8 || rate > 2); + start = currentPosition; + end = GST_CLOCK_TIME_NONE; + } else { + start = 0; + mute = true; + + // If we are at beginning of media, start from the end to + // avoid immediate EOS. + if (currentPosition <= 0) + end = static_cast<gint64>(duration() * GST_SECOND); + else + end = currentPosition; + } + + LOG_VERBOSE(Media, "Need to mute audio: %d", (int) mute); + + if (!gst_element_seek(m_playBin, rate, GST_FORMAT_TIME, flags, + GST_SEEK_TYPE_SET, start, + GST_SEEK_TYPE_SET, end)) + LOG_VERBOSE(Media, "Set rate to %f failed", rate); + else + g_object_set(m_playBin, "mute", mute, NULL); +} + +MediaPlayer::NetworkState MediaPlayerPrivateGStreamer::networkState() const +{ + return m_networkState; +} + +MediaPlayer::ReadyState MediaPlayerPrivateGStreamer::readyState() const +{ + return m_readyState; +} + +PassRefPtr<TimeRanges> MediaPlayerPrivateGStreamer::buffered() const +{ + RefPtr<TimeRanges> timeRanges = TimeRanges::create(); + if (m_errorOccured || m_isStreaming) + return timeRanges.release(); + +#if GST_CHECK_VERSION(0, 10, 31) + float mediaDuration(duration()); + if (!mediaDuration || isinf(mediaDuration)) + return timeRanges.release(); + + GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT); + + if (!gst_element_query(m_playBin, query)) { + gst_query_unref(query); + return timeRanges.release(); + } + + gint64 rangeStart = 0, rangeStop = 0; + for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) { + if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop)) + timeRanges->add(static_cast<float>((rangeStart * mediaDuration) / 100), + static_cast<float>((rangeStop * mediaDuration) / 100)); + } + + // Fallback to the more general maxTimeLoaded() if no range has + // been found. + if (!timeRanges->length()) + if (float loaded = maxTimeLoaded()) + timeRanges->add(0, loaded); + + gst_query_unref(query); +#else + float loaded = maxTimeLoaded(); + if (!m_errorOccured && !m_isStreaming && loaded > 0) + timeRanges->add(0, loaded); +#endif + return timeRanges.release(); +} + +void MediaPlayerPrivateGStreamer::processBufferingStats(GstMessage* message) +{ + // This is the immediate buffering that needs to happen so we have + // enough to play right now. + m_buffering = true; + const GstStructure *structure = gst_message_get_structure(message); + gst_structure_get_int(structure, "buffer-percent", &m_bufferingPercentage); + + LOG_VERBOSE(Media, "[Buffering] Buffering: %d%%.", m_bufferingPercentage); + + GstBufferingMode mode; + gst_message_parse_buffering_stats(message, &mode, 0, 0, 0); + if (mode != GST_BUFFERING_DOWNLOAD) { + updateStates(); + return; + } + + // This is on-disk buffering, that allows us to download much more + // than needed for right now. + if (!m_startedBuffering) { + LOG_VERBOSE(Media, "[Buffering] Starting on-disk buffering."); + + m_startedBuffering = true; + + if (m_fillTimer.isActive()) + m_fillTimer.stop(); + + m_fillTimer.startRepeating(0.2); + } +} + +void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStreamer>*) +{ + GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT); + + if (!gst_element_query(m_playBin, query)) { + gst_query_unref(query); + return; + } + + gint64 start, stop; + gdouble fillStatus = 100.0; + + gst_query_parse_buffering_range(query, 0, &start, &stop, 0); + gst_query_unref(query); + + if (stop != -1) + fillStatus = 100.0 * stop / GST_FORMAT_PERCENT_MAX; + + LOG_VERBOSE(Media, "[Buffering] Download buffer filled up to %f%%", fillStatus); + + if (!m_mediaDuration) + durationChanged(); + + // Update maxTimeLoaded only if the media duration is + // available. Otherwise we can't compute it. + if (m_mediaDuration) { + if (fillStatus == 100.0) + m_maxTimeLoaded = m_mediaDuration; + else + m_maxTimeLoaded = static_cast<float>((fillStatus * m_mediaDuration) / 100.0); + LOG_VERBOSE(Media, "[Buffering] Updated maxTimeLoaded: %f", m_maxTimeLoaded); + } + + if (fillStatus != 100.0) { + updateStates(); + return; + } + + // Media is now fully loaded. It will play even if network + // connection is cut. Buffering is done, remove the fill source + // from the main loop. + m_fillTimer.stop(); + m_startedBuffering = false; + updateStates(); +} + +float MediaPlayerPrivateGStreamer::maxTimeSeekable() const +{ + if (m_errorOccured) + return 0.0f; + + LOG_VERBOSE(Media, "maxTimeSeekable"); + // infinite duration means live stream + if (isinf(duration())) + return 0.0f; + + return duration(); +} + +float MediaPlayerPrivateGStreamer::maxTimeLoaded() const +{ + if (m_errorOccured) + return 0.0f; + + float loaded = m_maxTimeLoaded; + if (!loaded && !m_fillTimer.isActive()) + loaded = duration(); + LOG_VERBOSE(Media, "maxTimeLoaded: %f", loaded); + return loaded; +} + +unsigned MediaPlayerPrivateGStreamer::bytesLoaded() const +{ + if (!m_playBin) + return 0; + + if (!m_mediaDuration) + return 0; + + unsigned loaded = totalBytes() * maxTimeLoaded() / m_mediaDuration; + LOG_VERBOSE(Media, "bytesLoaded: %d", loaded); + return loaded; +} + +unsigned MediaPlayerPrivateGStreamer::totalBytes() const +{ + if (!m_source) + return 0; + + if (m_errorOccured) + return 0; + + GstFormat fmt = GST_FORMAT_BYTES; + gint64 length = 0; + if (gst_element_query_duration(m_source, &fmt, &length)) { + LOG_VERBOSE(Media, "totalBytes %" G_GINT64_FORMAT, length); + return static_cast<unsigned>(length); + } + + // Fall back to querying the source pads manually. + // See also https://bugzilla.gnome.org/show_bug.cgi?id=638749 + GstIterator* iter = gst_element_iterate_src_pads(m_source); + bool done = false; + while (!done) { + gpointer data; + + switch (gst_iterator_next(iter, &data)) { + case GST_ITERATOR_OK: { + GstPad* pad = GST_PAD_CAST(data); + gint64 padLength = 0; + if (gst_pad_query_duration(pad, &fmt, &padLength) + && padLength > length) + length = padLength; + gst_object_unref(pad); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync(iter); + break; + case GST_ITERATOR_ERROR: + // Fall through. + case GST_ITERATOR_DONE: + done = true; + break; + } + } + gst_iterator_free(iter); + + LOG_VERBOSE(Media, "totalBytes %" G_GINT64_FORMAT, length); + + return static_cast<unsigned>(length); +} + +void MediaPlayerPrivateGStreamer::cancelLoad() +{ + if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) + return; + + if (m_playBin) + gst_element_set_state(m_playBin, GST_STATE_NULL); +} + +void MediaPlayerPrivateGStreamer::updateStates() +{ + if (!m_playBin) + return; + + if (m_errorOccured) + return; + + MediaPlayer::NetworkState oldNetworkState = m_networkState; + MediaPlayer::ReadyState oldReadyState = m_readyState; + GstState state; + GstState pending; + + GstStateChangeReturn ret = gst_element_get_state(m_playBin, + &state, &pending, 250 * GST_NSECOND); + + bool shouldUpdateAfterSeek = false; + switch (ret) { + case GST_STATE_CHANGE_SUCCESS: + LOG_VERBOSE(Media, "State: %s, pending: %s", + gst_element_state_get_name(state), + gst_element_state_get_name(pending)); + + m_resetPipeline = state <= GST_STATE_READY; + + // Try to figure out ready and network states. + if (state == GST_STATE_READY) { + m_readyState = MediaPlayer::HaveMetadata; + m_networkState = MediaPlayer::Empty; + // Cache the duration without emiting the durationchange + // event because it's taken care of by the media element + // in this precise case. + cacheDuration(); + } else if (maxTimeLoaded() == duration()) { + m_networkState = MediaPlayer::Loaded; + m_readyState = MediaPlayer::HaveEnoughData; + } else { + m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; + m_networkState = MediaPlayer::Loading; + } + + if (m_buffering && state != GST_STATE_READY) { + m_readyState = MediaPlayer::HaveCurrentData; + m_networkState = MediaPlayer::Loading; + } + + // Now let's try to get the states in more detail using + // information from GStreamer, while we sync states where + // needed. + if (state == GST_STATE_PAUSED) { + if (m_buffering && m_bufferingPercentage == 100) { + m_buffering = false; + m_bufferingPercentage = 0; + m_readyState = MediaPlayer::HaveEnoughData; + + LOG_VERBOSE(Media, "[Buffering] Complete."); + + if (!m_paused) { + LOG_VERBOSE(Media, "[Buffering] Restarting playback."); + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + } + } else if (!m_buffering && (currentTime() < duration())) { + m_paused = true; + } + } else if (state == GST_STATE_PLAYING) { + m_readyState = MediaPlayer::HaveEnoughData; + m_paused = false; + + if (m_buffering) { + m_readyState = MediaPlayer::HaveCurrentData; + m_networkState = MediaPlayer::Loading; + + LOG_VERBOSE(Media, "[Buffering] Pausing stream for buffering."); + + gst_element_set_state(m_playBin, GST_STATE_PAUSED); + } + } else + m_paused = true; + + // Is on-disk buffering in progress? + if (m_fillTimer.isActive()) + m_networkState = MediaPlayer::Loading; + + if (m_changingRate) { + m_player->rateChanged(); + m_changingRate = false; + } + + if (m_seeking) { + shouldUpdateAfterSeek = true; + m_seeking = false; + } + + break; + case GST_STATE_CHANGE_ASYNC: + LOG_VERBOSE(Media, "Async: State: %s, pending: %s", + gst_element_state_get_name(state), + gst_element_state_get_name(pending)); + // Change in progress + + if (!m_isStreaming && !m_buffering) + return; + + // Resume playback if a seek was performed in a live pipeline + // or during progressive download. That second use-case + // happens when the seek is performed to a region of the media + // that hasn't been downloaded yet. + if (m_seeking) { + shouldUpdateAfterSeek = true; + m_seeking = false; + if (m_paused) + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + } + break; + case GST_STATE_CHANGE_FAILURE: + LOG_VERBOSE(Media, "Failure: State: %s, pending: %s", + gst_element_state_get_name(state), + gst_element_state_get_name(pending)); + // Change failed + return; + case GST_STATE_CHANGE_NO_PREROLL: + LOG_VERBOSE(Media, "No preroll: State: %s, pending: %s", + gst_element_state_get_name(state), + gst_element_state_get_name(pending)); + + if (state == GST_STATE_READY) + m_readyState = MediaPlayer::HaveNothing; + else if (state == GST_STATE_PAUSED) { + m_readyState = MediaPlayer::HaveEnoughData; + m_paused = true; + // Live pipelines go in PAUSED without prerolling. + m_isStreaming = true; + } else if (state == GST_STATE_PLAYING) + m_paused = false; + + if (m_seeking) { + shouldUpdateAfterSeek = true; + m_seeking = false; + if (!m_paused) + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + } else if (!m_paused) + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + + m_networkState = MediaPlayer::Loading; + break; + default: + LOG_VERBOSE(Media, "Else : %d", ret); + break; + } + + if (seeking()) + m_readyState = MediaPlayer::HaveNothing; + + if (shouldUpdateAfterSeek) + timeChanged(); + + if (m_networkState != oldNetworkState) { + LOG_VERBOSE(Media, "Network State Changed from %u to %u", + oldNetworkState, m_networkState); + m_player->networkStateChanged(); + } + if (m_readyState != oldReadyState) { + LOG_VERBOSE(Media, "Ready State Changed from %u to %u", + oldReadyState, m_readyState); + m_player->readyStateChanged(); + } +} + +void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message) +{ + if (m_mediaLocations) + gst_structure_free(m_mediaLocations); + + if (message->structure) { + // This structure can contain: + // - both a new-location string and embedded locations structure + // - or only a new-location string. + m_mediaLocations = gst_structure_copy(message->structure); + const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations"); + + if (locations) + m_mediaLocationCurrentIndex = static_cast<int>(gst_value_list_get_size(locations)) -1; + + loadNextLocation(); + } +} + +bool MediaPlayerPrivateGStreamer::loadNextLocation() +{ + if (!m_mediaLocations) + return false; + + const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations"); + const gchar* newLocation = 0; + + if (!locations) { + // Fallback on new-location string. + newLocation = gst_structure_get_string(m_mediaLocations, "new-location"); + if (!newLocation) + return false; + } + + if (!newLocation) { + if (m_mediaLocationCurrentIndex < 0) { + m_mediaLocations = 0; + return false; + } + + const GValue* location = gst_value_list_get_value(locations, + m_mediaLocationCurrentIndex); + const GstStructure* structure = gst_value_get_structure(location); + + if (!structure) { + m_mediaLocationCurrentIndex--; + return false; + } + + newLocation = gst_structure_get_string(structure, "new-location"); + } + + if (newLocation) { + // Found a candidate. new-location is not always an absolute url + // though. We need to take the base of the current url and + // append the value of new-location to it. + + gchar* currentLocation = 0; + g_object_get(m_playBin, "uri", ¤tLocation, NULL); + + KURL currentUrl(KURL(), currentLocation); + g_free(currentLocation); + + KURL newUrl; + + if (gst_uri_is_valid(newLocation)) + newUrl = KURL(KURL(), newLocation); + else + newUrl = KURL(KURL(), currentUrl.baseAsString() + newLocation); + + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(currentUrl); + if (securityOrigin->canRequest(newUrl)) { + LOG_VERBOSE(Media, "New media url: %s", newUrl.string().utf8().data()); + + // Reset player states. + m_networkState = MediaPlayer::Loading; + m_player->networkStateChanged(); + m_readyState = MediaPlayer::HaveNothing; + m_player->readyStateChanged(); + + // Reset pipeline state. + m_resetPipeline = true; + gst_element_set_state(m_playBin, GST_STATE_READY); + + GstState state; + gst_element_get_state(m_playBin, &state, 0, 0); + if (state <= GST_STATE_READY) { + // Set the new uri and start playing. + g_object_set(m_playBin, "uri", newUrl.string().utf8().data(), NULL); + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + return true; + } + } + } + m_mediaLocationCurrentIndex--; + return false; + +} + +void MediaPlayerPrivateGStreamer::loadStateChanged() +{ + updateStates(); +} + +void MediaPlayerPrivateGStreamer::sizeChanged() +{ + notImplemented(); +} + +void MediaPlayerPrivateGStreamer::timeChanged() +{ + updateStates(); + m_player->timeChanged(); +} + +void MediaPlayerPrivateGStreamer::didEnd() +{ + // EOS was reached but in case of reverse playback the position is + // not always 0. So to not confuse the HTMLMediaElement we + // synchronize position and duration values. + float now = currentTime(); + if (now > 0) { + m_mediaDuration = now; + m_mediaDurationKnown = true; + m_player->durationChanged(); + } + + gst_element_set_state(m_playBin, GST_STATE_PAUSED); + + timeChanged(); +} + +void MediaPlayerPrivateGStreamer::cacheDuration() +{ + // Reset cached media duration + m_mediaDuration = 0; + + // And re-cache it if possible. + GstState state; + gst_element_get_state(m_playBin, &state, 0, 0); + float newDuration = duration(); + + if (state <= GST_STATE_READY) { + // Don't set m_mediaDurationKnown yet if the pipeline is not + // paused. This allows duration() query to fail at least once + // before playback starts and duration becomes known. + if (!isinf(newDuration)) + m_mediaDuration = newDuration; + } else { + m_mediaDurationKnown = !isinf(newDuration); + if (m_mediaDurationKnown) + m_mediaDuration = newDuration; + } + + if (!isinf(newDuration)) + m_mediaDuration = newDuration; +} + +void MediaPlayerPrivateGStreamer::durationChanged() +{ + float previousDuration = m_mediaDuration; + + cacheDuration(); + // Avoid emiting durationchanged in the case where the previous + // duration was 0 because that case is already handled by the + // HTMLMediaElement. + if (previousDuration && m_mediaDuration != previousDuration) + m_player->durationChanged(); +} + +bool MediaPlayerPrivateGStreamer::supportsMuting() const +{ + return true; +} + +void MediaPlayerPrivateGStreamer::setMuted(bool muted) +{ + if (!m_playBin) + return; + + g_object_set(m_playBin, "mute", muted, NULL); +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfMute() +{ + m_muteTimerHandler = 0; + + if (!m_player || !m_playBin) + return; + + gboolean muted; + g_object_get(m_playBin, "mute", &muted, NULL); + m_player->muteChanged(static_cast<bool>(muted)); +} + +void MediaPlayerPrivateGStreamer::muteChanged() +{ + if (m_muteTimerHandler) + g_source_remove(m_muteTimerHandler); + m_muteTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateMuteChangeTimeoutCallback), this); +} + +void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState error) +{ + m_errorOccured = true; + if (m_networkState != error) { + m_networkState = error; + m_player->networkStateChanged(); + } + if (m_readyState != MediaPlayer::HaveNothing) { + m_readyState = MediaPlayer::HaveNothing; + m_player->readyStateChanged(); + } +} + +void MediaPlayerPrivateGStreamer::setSize(const IntSize& size) +{ + m_size = size; +} + +void MediaPlayerPrivateGStreamer::setVisible(bool visible) +{ +} + +void MediaPlayerPrivateGStreamer::repaint() +{ + m_player->repaint(); +} + +void MediaPlayerPrivateGStreamer::paint(GraphicsContext* context, const IntRect& rect) +{ + if (context->paintingDisabled()) + return; + + if (!m_player->visible()) + return; + if (!m_buffer) + return; + + RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_buffer); + if (!gstImage) + return; + + context->drawImage(reinterpret_cast<Image*>(gstImage->image().get()), ColorSpaceSRGB, + rect, CompositeCopy, false); +} + +static HashSet<String> mimeTypeCache() +{ + + doGstInit(); + + DEFINE_STATIC_LOCAL(HashSet<String>, cache, ()); + static bool typeListInitialized = false; + + if (!typeListInitialized) { + // Build a whitelist of mime-types known to be supported by + // GStreamer. + HashSet<String> handledApplicationSubtypes; + handledApplicationSubtypes.add(String("ogg")); + handledApplicationSubtypes.add(String("vnd.rn-realmedia")); + handledApplicationSubtypes.add(String("x-pn-realaudio")); + + GList* factories = gst_type_find_factory_get_list(); + for (GList* iterator = factories; iterator; iterator = iterator->next) { + GstTypeFindFactory* factory = GST_TYPE_FIND_FACTORY(iterator->data); + GstCaps* caps = gst_type_find_factory_get_caps(factory); + gchar** extensions; + + if (!caps) + continue; + + for (guint structureIndex = 0; structureIndex < gst_caps_get_size(caps); structureIndex++) { + GstStructure* structure = gst_caps_get_structure(caps, structureIndex); + const gchar* name = gst_structure_get_name(structure); + bool cached = false; + + // These formats are supported by GStreamer, but not + // correctly advertised. + if (g_str_equal(name, "video/x-h264") + || g_str_equal(name, "audio/x-m4a")) { + cache.add(String("video/mp4")); + cache.add(String("audio/aac")); + cache.add(String("audio/mp4")); + cached = true; + } + + if (g_str_equal(name, "application/x-3gp")) { + cache.add(String("audio/3gpp")); + cache.add(String("video/3gpp")); + cache.add(String("application/x-3gp")); + cached = true; + } + + if (g_str_equal(name, "video/x-theora")) { + cache.add(String("video/ogg")); + cached = true; + } + + if (g_str_equal(name, "audio/x-vorbis")) { + cache.add(String("audio/ogg")); + cache.add(String("audio/x-vorbis+ogg")); + cached = true; + } + + if (g_str_equal(name, "audio/x-wav")) { + cache.add(String("audio/wav")); + cache.add(String("audio/x-wav")); + cached = true; + } + + if (g_str_equal(name, "audio/mpeg")) { + cache.add(String(name)); + cache.add(String("audio/x-mpeg")); + cached = true; + + // This is what we are handling: + // mpegversion=(int)1, layer=(int)[ 1, 3 ] + gint mpegVersion = 0; + if (gst_structure_get_int(structure, "mpegversion", &mpegVersion) && (mpegVersion == 1)) { + const GValue* layer = gst_structure_get_value(structure, "layer"); + if (G_VALUE_TYPE(layer) == GST_TYPE_INT_RANGE) { + gint minLayer = gst_value_get_int_range_min(layer); + gint maxLayer = gst_value_get_int_range_max(layer); + if (minLayer <= 1 && 1 <= maxLayer) + cache.add(String("audio/mp1")); + if (minLayer <= 2 && 2 <= maxLayer) + cache.add(String("audio/mp2")); + if (minLayer <= 3 && 3 <= maxLayer) { + cache.add(String("audio/x-mp3")); + cache.add(String("audio/mp3")); + } + } + } + } + + if (!cached) { + // GStreamer plugins can be capable of supporting + // types which WebKit supports by default. In that + // case, we should not consider these types + // supportable by GStreamer. Examples of what + // GStreamer can support but should not be added: + // text/plain, text/html, image/jpeg, + // application/xml + gchar** mimetype = g_strsplit(name, "/", 2); + if (g_str_equal(mimetype[0], "audio") + || g_str_equal(mimetype[0], "video") + || (g_str_equal(mimetype[0], "application") + && handledApplicationSubtypes.contains(String(mimetype[1])))) + cache.add(String(name)); + + g_strfreev(mimetype); + } + + // As a last resort try some special cases depending + // on the file extensions registered with the typefind + // factory. + if (!cached && (extensions = gst_type_find_factory_get_extensions(factory))) { + for (int index = 0; extensions[index]; index++) { + if (g_str_equal(extensions[index], "m4v")) + cache.add(String("video/x-m4v")); + } + } + } + } + + gst_plugin_feature_list_free(factories); + typeListInitialized = true; + } + + return cache; +} + +void MediaPlayerPrivateGStreamer::getSupportedTypes(HashSet<String>& types) +{ + types = mimeTypeCache(); +} + +MediaPlayer::SupportsType MediaPlayerPrivateGStreamer::supportsType(const String& type, const String& codecs) +{ + if (type.isNull() || type.isEmpty()) + return MediaPlayer::IsNotSupported; + + // spec says we should not return "probably" if the codecs string is empty + if (mimeTypeCache().contains(type)) + return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported; + return MediaPlayer::IsNotSupported; +} + +bool MediaPlayerPrivateGStreamer::hasSingleSecurityOrigin() const +{ + return true; +} + +bool MediaPlayerPrivateGStreamer::supportsFullscreen() const +{ +#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) + // See <rdar://problem/7389945> + return false; +#else + return true; +#endif +} + +PlatformMedia MediaPlayerPrivateGStreamer::platformMedia() const +{ + PlatformMedia p; + p.type = PlatformMedia::GStreamerGWorldType; + p.media.gstreamerGWorld = m_gstGWorld.get(); + return p; +} + +void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload) +{ + ASSERT(m_playBin); + + m_preload = preload; + + GstPlayFlags flags; + g_object_get(m_playBin, "flags", &flags, NULL); + if (preload == MediaPlayer::None) + g_object_set(m_playBin, "flags", flags & ~GST_PLAY_FLAG_DOWNLOAD, NULL); + else + g_object_set(m_playBin, "flags", flags | GST_PLAY_FLAG_DOWNLOAD, NULL); + + if (m_delayingLoad && m_preload != MediaPlayer::None) { + m_delayingLoad = false; + commitLoad(); + } +} + +void MediaPlayerPrivateGStreamer::createGSTPlayBin() +{ + ASSERT(!m_playBin); + m_playBin = gst_element_factory_make("playbin2", "play"); + + m_gstGWorld = GStreamerGWorld::createGWorld(m_playBin); + + GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin)); + gst_bus_add_signal_watch(bus); + g_signal_connect(bus, "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this); + gst_object_unref(bus); + + g_object_set(m_playBin, "mute", m_player->muted(), "volume", m_player->volume(), NULL); + + g_signal_connect(m_playBin, "notify::volume", G_CALLBACK(mediaPlayerPrivateVolumeChangedCallback), this); + g_signal_connect(m_playBin, "notify::source", G_CALLBACK(mediaPlayerPrivateSourceChangedCallback), this); + g_signal_connect(m_playBin, "notify::mute", G_CALLBACK(mediaPlayerPrivateMuteChangedCallback), this); + g_signal_connect(m_playBin, "video-tags-changed", G_CALLBACK(mediaPlayerPrivateVideoTagsChangedCallback), this); + g_signal_connect(m_playBin, "audio-tags-changed", G_CALLBACK(mediaPlayerPrivateAudioTagsChangedCallback), this); + + m_webkitVideoSink = webkit_video_sink_new(); + + g_signal_connect(m_webkitVideoSink, "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this); + + m_videoSinkBin = gst_bin_new("sink"); + GstElement* videoTee = gst_element_factory_make("tee", "videoTee"); + GstElement* queue = gst_element_factory_make("queue", 0); + GstElement* identity = gst_element_factory_make("identity", "videoValve"); + + // Take ownership. + gst_object_ref_sink(m_videoSinkBin); + + // Build a new video sink consisting of a bin containing a tee + // (meant to distribute data to multiple video sinks) and our + // internal video sink. For fullscreen we create an autovideosink + // and initially block the data flow towards it and configure it + + gst_bin_add_many(GST_BIN(m_videoSinkBin), videoTee, queue, identity, NULL); + + // Link a new src pad from tee to queue1. + GstPad* srcPad = gst_element_get_request_pad(videoTee, "src%d"); + GstPad* sinkPad = gst_element_get_static_pad(queue, "sink"); + gst_pad_link(srcPad, sinkPad); + gst_object_unref(GST_OBJECT(srcPad)); + gst_object_unref(GST_OBJECT(sinkPad)); + + WTFLogChannel* channel = getChannelFromName("Media"); + if (channel->state == WTFLogChannelOn) { + m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink"); + if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink), "video-sink")) { + g_object_set(m_fpsSink, "video-sink", m_webkitVideoSink, NULL); + gst_bin_add(GST_BIN(m_videoSinkBin), m_fpsSink); +#if GST_CHECK_VERSION(0, 10, 30) + // Faster elements linking, if possible. + gst_element_link_pads_full(queue, "src", m_fpsSink, "sink", GST_PAD_LINK_CHECK_NOTHING); +#else + gst_element_link(queue, m_fpsSink); +#endif + } else { + m_fpsSink = 0; + gst_bin_add(GST_BIN(m_videoSinkBin), m_webkitVideoSink); +#if GST_CHECK_VERSION(0, 10, 30) + // Faster elements linking, if possible. + gst_element_link_pads_full(queue, "src", m_webkitVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING); +#else + gst_element_link(queue, m_webkitVideoSink); +#endif + LOG_VERBOSE(Media, "Can't display FPS statistics, you need gst-plugins-bad >= 0.10.18"); + } + } else { + gst_bin_add(GST_BIN(m_videoSinkBin), m_webkitVideoSink); +#if GST_CHECK_VERSION(0, 10, 30) + // Faster elements linking, if possible. + gst_element_link_pads_full(queue, "src", identity, "sink", GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full(identity, "src", m_webkitVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING); +#else + gst_element_link_many(queue, identity, m_webkitVideoSink, NULL); +#endif + } + + // Add a ghostpad to the bin so it can proxy to tee. + GstPad* pad = gst_element_get_static_pad(videoTee, "sink"); + gst_element_add_pad(m_videoSinkBin, gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + // Set the bin as video sink of playbin. + g_object_set(m_playBin, "video-sink", m_videoSinkBin, NULL); +} + +} + +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h new file mode 100644 index 0000000..11eb81b --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2007 Collabora Ltd. All rights reserved. + * Copyright (C) 2007 Alp Toker <alp@atoker.com> + * Copyright (C) 2009, 2010 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. + */ + +#ifndef MediaPlayerPrivateGStreamer_h +#define MediaPlayerPrivateGStreamer_h +#if USE(GSTREAMER) + +#include <wtf/Forward.h> +#include "MediaPlayerPrivate.h" +#include "Timer.h" + +#include <glib.h> +#include <gst/gst.h> + +typedef struct _WebKitVideoSink WebKitVideoSink; +typedef struct _GstBuffer GstBuffer; +typedef struct _GstMessage GstMessage; +typedef struct _GstElement GstElement; +typedef struct _GstBus GstBus; + +namespace WebCore { + +class GraphicsContext; +class IntSize; +class IntRect; +class GStreamerGWorld; +class MediaPlayerPrivateGStreamer; + +gboolean mediaPlayerPrivateMessageCallback(GstBus* bus, GstMessage* message, gpointer data); +void mediaPlayerPrivateVolumeChangedCallback(GObject* element, GParamSpec* pspec, gpointer data); +void mediaPlayerPrivateMuteChangedCallback(GObject* element, GParamSpec* pspec, gpointer data); +void mediaPlayerPrivateSourceChangedCallback(GObject* element, GParamSpec* pspec, gpointer data); +void mediaPlayerPrivateVideoTagsChangedCallback(GObject* element, gint, MediaPlayerPrivateGStreamer*); +void mediaPlayerPrivateAudioTagsChangedCallback(GObject* element, gint, MediaPlayerPrivateGStreamer*); +gboolean mediaPlayerPrivateAudioTagsChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player); +gboolean mediaPlayerPrivateVideoTagsChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player); + +gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamer*); +gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamer*); + +class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface { + friend gboolean mediaPlayerPrivateMessageCallback(GstBus* bus, GstMessage* message, gpointer data); + friend void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer* buffer, MediaPlayerPrivateGStreamer* playerPrivate); + friend void mediaPlayerPrivateSourceChangedCallback(GObject* element, GParamSpec* pspec, gpointer data); + + public: + static void registerMediaEngine(MediaEngineRegistrar); + + IntSize naturalSize() const; + bool hasVideo() const { return m_hasVideo; } + bool hasAudio() const { return m_hasAudio; } + + void load(const String &url); + void commitLoad(); + void cancelLoad(); + bool loadNextLocation(); + + void prepareToPlay(); + void play(); + void pause(); + + bool paused() const; + bool seeking() const; + + float duration() const; + float currentTime() const; + void seek(float); + + void setRate(float); + + void setVolume(float); + void volumeChanged(); + void notifyPlayerOfVolumeChange(); + + bool supportsMuting() const; + void setMuted(bool); + void muteChanged(); + void notifyPlayerOfMute(); + + bool loadDelayed() const { return m_delayingLoad; } + void setPreload(MediaPlayer::Preload); + void fillTimerFired(Timer<MediaPlayerPrivateGStreamer>*); + + MediaPlayer::NetworkState networkState() const; + MediaPlayer::ReadyState readyState() const; + + PassRefPtr<TimeRanges> buffered() const; + float maxTimeSeekable() const; + unsigned bytesLoaded() const; + unsigned totalBytes() const; + + void setVisible(bool); + void setSize(const IntSize&); + + void mediaLocationChanged(GstMessage*); + void loadStateChanged(); + void sizeChanged(); + void timeChanged(); + void didEnd(); + void durationChanged(); + void loadingFailed(MediaPlayer::NetworkState); + + void repaint(); + void paint(GraphicsContext*, const IntRect&); + + bool hasSingleSecurityOrigin() const; + + bool supportsFullscreen() const; + PlatformMedia platformMedia() const; + + GstElement* pipeline() const { return m_playBin; } + bool pipelineReset() const { return m_resetPipeline; } + + void videoTagsChanged(gint); + void audioTagsChanged(gint); + void notifyPlayerOfVideoTags(); + void notifyPlayerOfAudioTags(); + + private: + MediaPlayerPrivateGStreamer(MediaPlayer*); + ~MediaPlayerPrivateGStreamer(); + + static MediaPlayerPrivateInterface* create(MediaPlayer* player); + + static void getSupportedTypes(HashSet<String>&); + static MediaPlayer::SupportsType supportsType(const String& type, const String& codecs); + static bool isAvailable(); + + void cacheDuration(); + void updateStates(); + float maxTimeLoaded() const; + + void createGSTPlayBin(); + bool changePipelineState(GstState state); + + void processBufferingStats(GstMessage* message); + + private: + MediaPlayer* m_player; + GstElement* m_playBin; + GstElement* m_webkitVideoSink; + GstElement* m_videoSinkBin; + GstElement* m_fpsSink; + GstElement* m_source; + GstClockTime m_seekTime; + bool m_changingRate; + float m_endTime; + bool m_isEndReached; + MediaPlayer::NetworkState m_networkState; + MediaPlayer::ReadyState m_readyState; + mutable bool m_isStreaming; + IntSize m_size; + GstBuffer* m_buffer; + GstStructure* m_mediaLocations; + int m_mediaLocationCurrentIndex; + bool m_resetPipeline; + bool m_paused; + bool m_seeking; + bool m_buffering; + float m_playbackRate; + bool m_errorOccured; + gfloat m_mediaDuration; + bool m_startedBuffering; + Timer<MediaPlayerPrivateGStreamer> m_fillTimer; + float m_maxTimeLoaded; + int m_bufferingPercentage; + MediaPlayer::Preload m_preload; + bool m_delayingLoad; + bool m_mediaDurationKnown; + RefPtr<GStreamerGWorld> m_gstGWorld; + guint m_volumeTimerHandler; + guint m_muteTimerHandler; + bool m_hasVideo; + bool m_hasAudio; + guint m_audioTagsTimerHandler; + guint m_videoTagsTimerHandler; + }; +} + +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindow.h b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindow.h new file mode 100644 index 0000000..f2a3ff2 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindow.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#ifndef PlatformVideoWindow_h +#define PlatformVideoWindow_h +#if USE(GSTREAMER) + +#include "Widget.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +typedef struct _GstMessage GstMessage; + +namespace WebCore { + +class PlatformVideoWindow : public RefCounted<PlatformVideoWindow> { + public: + static PassRefPtr<PlatformVideoWindow> createWindow() { return adoptRef(new PlatformVideoWindow()); } + + PlatformVideoWindow(); + ~PlatformVideoWindow(); + + + void prepareForOverlay(GstMessage*); + PlatformWidget window() const { return m_window; } + unsigned long videoWindowId() const { return m_videoWindowId; } + + private: + unsigned long m_videoWindowId; + PlatformWidget m_videoWindow; + PlatformWidget m_window; + }; +} + +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowEfl.cpp b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowEfl.cpp new file mode 100644 index 0000000..0097716 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowEfl.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 Samsung Electronics + * + * 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 + * along 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. + */ + +#include "config.h" +#include "PlatformVideoWindow.h" +#if USE(GSTREAMER) + +#include "NotImplemented.h" + +using namespace WebCore; + +PlatformVideoWindow::PlatformVideoWindow() +{ + notImplemented(); +} + +PlatformVideoWindow::~PlatformVideoWindow() +{ + notImplemented(); +} + +void PlatformVideoWindow::prepareForOverlay(GstMessage*) +{ +} + +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowGtk.cpp b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowGtk.cpp new file mode 100644 index 0000000..c2f76cd --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowGtk.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 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 + * along 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. + */ + +#include "config.h" +#include "PlatformVideoWindow.h" +#if USE(GSTREAMER) + +#include <gtk/gtk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> // for GDK_WINDOW_XID +#endif + +using namespace WebCore; + +PlatformVideoWindow::PlatformVideoWindow() +{ + m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_events(m_window, GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK | GDK_FOCUS_CHANGE_MASK); + + m_videoWindow = gtk_drawing_area_new(); + gtk_widget_set_double_buffered(m_videoWindow, FALSE); + gtk_container_add(GTK_CONTAINER(m_window), m_videoWindow); + + gtk_widget_realize(m_window); + +#ifdef GDK_WINDOWING_X11 + m_videoWindowId = GDK_WINDOW_XID(gtk_widget_get_window(m_window)); +#endif + +} + +PlatformVideoWindow::~PlatformVideoWindow() +{ + if (m_videoWindow && m_window) { + gtk_container_remove(GTK_CONTAINER(m_window), m_videoWindow); + gtk_widget_destroy(m_videoWindow); + m_videoWindow = 0; + } + + if (m_window) { + gtk_widget_destroy(m_window); + m_window = 0; + } + + m_videoWindowId = 0; +} + +void PlatformVideoWindow::prepareForOverlay(GstMessage*) +{ +} +#endif // USE(GSTREAMER) + diff --git a/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowPrivate.h b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowPrivate.h new file mode 100644 index 0000000..0ae4587 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowPrivate.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + + 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 + along 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. +*/ + +#ifndef PlatformVideoWindowPrivate_h +#define PlatformVideoWindowPrivate_h + +#include <QWidget> + +class QKeyEvent; + +namespace WebCore { + +class FullScreenVideoWindow: public QWidget { +Q_OBJECT +public: + FullScreenVideoWindow(); +signals: + void closed(); +protected: + void keyPressEvent(QKeyEvent* ev); + bool event(QEvent* ev); +}; + + +} // namespace WebCore + + +#endif // PlatformVideoWindowPrivate_h diff --git a/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowQt.cpp b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowQt.cpp new file mode 100644 index 0000000..872d055 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/PlatformVideoWindowQt.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + + 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 + along 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. +*/ + +#include "config.h" +#include "PlatformVideoWindow.h" + +#include "PlatformVideoWindowPrivate.h" + +#include <QApplication> +#include <QDesktopWidget> +#include <QKeyEvent> +#include <QPalette> +using namespace WebCore; + +FullScreenVideoWindow::FullScreenVideoWindow() + : QWidget(0, Qt::Window) +{ + setAttribute(Qt::WA_NativeWindow); + // Setting these values ensures smooth resizing since it + // will prevent the system from clearing the background. + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_PaintOnScreen, true); +} + +void FullScreenVideoWindow::keyPressEvent(QKeyEvent* ev) +{ + if (ev->key() == Qt::Key_Escape) { + close(); + emit closed(); + } +} + +bool FullScreenVideoWindow::event(QEvent* ev) +{ + switch (ev->type()) { + case QEvent::MouseButtonDblClick: + close(); + ev->accept(); + return true; + default: + return QWidget::event(ev); + } +} + + +PlatformVideoWindow::PlatformVideoWindow() +{ + m_window = new FullScreenVideoWindow(); + m_window->setWindowFlags(m_window->windowFlags() | Qt::FramelessWindowHint); + QPalette p; + p.setColor(QPalette::Base, Qt::black); + p.setColor(QPalette::Window, Qt::black); + m_window->setPalette(p); + m_window->showFullScreen(); + m_videoWindowId = m_window->winId(); +} + +PlatformVideoWindow::~PlatformVideoWindow() +{ + delete m_window; + m_videoWindowId = 0; +} + +void PlatformVideoWindow::prepareForOverlay(GstMessage*) +{ +} diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp new file mode 100644 index 0000000..4319f6c --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2007 OpenedHand + * Copyright (C) 2007 Alp Toker <alp@atoker.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 + */ + +/** + * SECTION:webkit-video-sink + * @short_description: GStreamer video sink + * + * #WebKitVideoSink is a GStreamer sink element that triggers + * repaints in the WebKit GStreamer media player for the + * current video buffer. + */ + +#include "config.h" +#include "VideoSinkGStreamer.h" +#if USE(GSTREAMER) + +#include <glib.h> +#include <gst/gst.h> +#include <gst/video/video.h> + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, +// CAIRO_FORMAT_RGB24 used to render the video buffers is little/big endian dependant. +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + GST_STATIC_CAPS(GST_VIDEO_CAPS_BGRx ";" GST_VIDEO_CAPS_BGRA) +#else + GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_ARGB) +#endif +); + +GST_DEBUG_CATEGORY_STATIC(webkit_video_sink_debug); +#define GST_CAT_DEFAULT webkit_video_sink_debug + +enum { + REPAINT_REQUESTED, + LAST_SIGNAL +}; + +enum { + PROP_0 +}; + +static guint webkit_video_sink_signals[LAST_SIGNAL] = { 0, }; + +struct _WebKitVideoSinkPrivate { + GstBuffer* buffer; + guint timeout_id; + GMutex* buffer_mutex; + GCond* data_cond; + + // If this is TRUE all processing should finish ASAP + // This is necessary because there could be a race between + // unlock() and render(), where unlock() wins, signals the + // GCond, then render() tries to render a frame although + // everything else isn't running anymore. This will lead + // to deadlocks because render() holds the stream lock. + // + // Protected by the buffer mutex + gboolean unlocked; +}; + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT(webkit_video_sink_debug, \ + "webkitsink", \ + 0, \ + "webkit video sink") + +GST_BOILERPLATE_FULL(WebKitVideoSink, + webkit_video_sink, + GstVideoSink, + GST_TYPE_VIDEO_SINK, + _do_init); + +static void +webkit_video_sink_base_init(gpointer g_class) +{ + GstElementClass* element_class = GST_ELEMENT_CLASS(g_class); + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sinktemplate)); + gst_element_class_set_details_simple(element_class, "WebKit video sink", + "Sink/Video", "Sends video data from a GStreamer pipeline to a Cairo surface", + "Alp Toker <alp@atoker.com>"); +} + +static void +webkit_video_sink_init(WebKitVideoSink* sink, WebKitVideoSinkClass* klass) +{ + WebKitVideoSinkPrivate* priv; + + sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); + priv->data_cond = g_cond_new(); + priv->buffer_mutex = g_mutex_new(); +} + +static gboolean +webkit_video_sink_timeout_func(gpointer data) +{ + WebKitVideoSink* sink = reinterpret_cast<WebKitVideoSink*>(data); + WebKitVideoSinkPrivate* priv = sink->priv; + GstBuffer* buffer; + + g_mutex_lock(priv->buffer_mutex); + buffer = priv->buffer; + priv->buffer = 0; + priv->timeout_id = 0; + + if (!buffer || priv->unlocked || G_UNLIKELY(!GST_IS_BUFFER(buffer))) { + g_cond_signal(priv->data_cond); + g_mutex_unlock(priv->buffer_mutex); + return FALSE; + } + + g_signal_emit(sink, webkit_video_sink_signals[REPAINT_REQUESTED], 0, buffer); + gst_buffer_unref(buffer); + g_cond_signal(priv->data_cond); + g_mutex_unlock(priv->buffer_mutex); + + return FALSE; +} + +static GstFlowReturn +webkit_video_sink_render(GstBaseSink* bsink, GstBuffer* buffer) +{ + WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); + WebKitVideoSinkPrivate* priv = sink->priv; + + g_mutex_lock(priv->buffer_mutex); + + if (priv->unlocked) { + g_mutex_unlock(priv->buffer_mutex); + return GST_FLOW_OK; + } + + priv->buffer = gst_buffer_ref(buffer); + + // For the unlikely case where the buffer has no caps, the caps + // are implicitely the caps of the pad. This shouldn't happen. + if (G_UNLIKELY(!GST_BUFFER_CAPS(buffer))) { + buffer = priv->buffer = gst_buffer_make_metadata_writable(priv->buffer); + gst_buffer_set_caps(priv->buffer, GST_PAD_CAPS(GST_BASE_SINK_PAD(bsink))); + } + + GstCaps *caps = GST_BUFFER_CAPS(buffer); + GstVideoFormat format; + int width, height; + if (G_UNLIKELY(!gst_video_format_parse_caps(caps, &format, &width, &height))) { + gst_buffer_unref(buffer); + g_mutex_unlock(priv->buffer_mutex); + return GST_FLOW_ERROR; + } + + // Cairo's ARGB has pre-multiplied alpha while GStreamer's doesn't. + // Here we convert to Cairo's ARGB. + if (format == GST_VIDEO_FORMAT_ARGB || format == GST_VIDEO_FORMAT_BGRA) { + // Because GstBaseSink::render() only owns the buffer reference in the + // method scope we can't use gst_buffer_make_writable() here. Also + // The buffer content should not be changed here because the same buffer + // could be passed multiple times to this method (in theory) + GstBuffer *newBuffer = gst_buffer_try_new_and_alloc(GST_BUFFER_SIZE(buffer)); + + // Check if allocation failed + if (G_UNLIKELY(!newBuffer)) { + gst_buffer_unref(buffer); + g_mutex_unlock(priv->buffer_mutex); + return GST_FLOW_ERROR; + } + + gst_buffer_copy_metadata(newBuffer, buffer, (GstBufferCopyFlags) GST_BUFFER_COPY_ALL); + + // We don't use Color::premultipliedARGBFromColor() here because + // one function call per video pixel is just too expensive: + // For 720p/PAL for example this means 1280*720*25=23040000 + // function calls per second! + unsigned short alpha; + const guint8 *source = GST_BUFFER_DATA(buffer); + guint8 *destination = GST_BUFFER_DATA(newBuffer); + + for (int x = 0; x < height; x++) { + for (int y = 0; y < width; y++) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + alpha = source[3]; + destination[0] = (source[0] * alpha + 128) / 255; + destination[1] = (source[1] * alpha + 128) / 255; + destination[2] = (source[2] * alpha + 128) / 255; + destination[3] = alpha; +#else + alpha = source[0]; + destination[0] = alpha; + destination[1] = (source[1] * alpha + 128) / 255; + destination[2] = (source[2] * alpha + 128) / 255; + destination[3] = (source[3] * alpha + 128) / 255; +#endif + source += 4; + destination += 4; + } + } + gst_buffer_unref(buffer); + buffer = priv->buffer = newBuffer; + } + + // This should likely use a lower priority, but glib currently starves + // lower priority sources. + // See: https://bugzilla.gnome.org/show_bug.cgi?id=610830. + priv->timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, + webkit_video_sink_timeout_func, + gst_object_ref(sink), + (GDestroyNotify)gst_object_unref); + + g_cond_wait(priv->data_cond, priv->buffer_mutex); + g_mutex_unlock(priv->buffer_mutex); + return GST_FLOW_OK; +} + +static void +webkit_video_sink_dispose(GObject* object) +{ + WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); + WebKitVideoSinkPrivate* priv = sink->priv; + + if (priv->data_cond) { + g_cond_free(priv->data_cond); + priv->data_cond = 0; + } + + if (priv->buffer_mutex) { + g_mutex_free(priv->buffer_mutex); + priv->buffer_mutex = 0; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static void +unlock_buffer_mutex(WebKitVideoSinkPrivate* priv) +{ + g_mutex_lock(priv->buffer_mutex); + + if (priv->buffer) { + gst_buffer_unref(priv->buffer); + priv->buffer = 0; + } + + priv->unlocked = TRUE; + + g_cond_signal(priv->data_cond); + g_mutex_unlock(priv->buffer_mutex); +} + +static gboolean +webkit_video_sink_unlock(GstBaseSink* object) +{ + WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); + + unlock_buffer_mutex(sink->priv); + + return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock, + (object), TRUE); +} + +static gboolean +webkit_video_sink_unlock_stop(GstBaseSink* object) +{ + WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); + WebKitVideoSinkPrivate* priv = sink->priv; + + g_mutex_lock(priv->buffer_mutex); + priv->unlocked = FALSE; + g_mutex_unlock(priv->buffer_mutex); + + return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock_stop, + (object), TRUE); +} + +static gboolean +webkit_video_sink_stop(GstBaseSink* base_sink) +{ + WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; + + unlock_buffer_mutex(priv); + return TRUE; +} + +static gboolean +webkit_video_sink_start(GstBaseSink* base_sink) +{ + WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; + + g_mutex_lock(priv->buffer_mutex); + priv->unlocked = FALSE; + g_mutex_unlock(priv->buffer_mutex); + return TRUE; +} + +static void +marshal_VOID__MINIOBJECT(GClosure * closure, GValue * return_value, + guint n_param_values, const GValue * param_values, + gpointer invocation_hint, gpointer marshal_data) +{ + typedef void (*marshalfunc_VOID__MINIOBJECT) (gpointer obj, gpointer arg1, gpointer data2); + marshalfunc_VOID__MINIOBJECT callback; + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + + g_return_if_fail(n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } else { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + callback = (marshalfunc_VOID__MINIOBJECT) (marshal_data ? marshal_data : cc->callback); + + callback(data1, gst_value_get_mini_object(param_values + 1), data2); +} + +static void +webkit_video_sink_class_init(WebKitVideoSinkClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass); + + g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate)); + + gobject_class->dispose = webkit_video_sink_dispose; + + gstbase_sink_class->unlock = webkit_video_sink_unlock; + gstbase_sink_class->unlock_stop = webkit_video_sink_unlock_stop; + gstbase_sink_class->render = webkit_video_sink_render; + gstbase_sink_class->preroll = webkit_video_sink_render; + gstbase_sink_class->stop = webkit_video_sink_stop; + gstbase_sink_class->start = webkit_video_sink_start; + + webkit_video_sink_signals[REPAINT_REQUESTED] = g_signal_new("repaint-requested", + G_TYPE_FROM_CLASS(klass), + (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + 0, + 0, + 0, + marshal_VOID__MINIOBJECT, + G_TYPE_NONE, 1, GST_TYPE_BUFFER); +} + +/** + * webkit_video_sink_new: + * + * Creates a new GStreamer video sink. + * + * Return value: a #GstElement for the newly created video sink + */ +GstElement* +webkit_video_sink_new(void) +{ + return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, 0); +} + +#endif // USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.h new file mode 100644 index 0000000..6cd86c2 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 OpenedHand + * Copyright (C) 2007 Alp Toker <alp@atoker.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 + */ + +#ifndef VideoSinkGStreamer_h +#define VideoSinkGStreamer_h + +#if USE(GSTREAMER) + +#include <glib-object.h> +#include <gst/video/gstvideosink.h> + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_VIDEO_SINK webkit_video_sink_get_type() + +#define WEBKIT_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSink)) + +#define WEBKIT_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkClass)) + +#define WEBKIT_IS_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + WEBKIT_TYPE_VIDEO_SINK)) + +#define WEBKIT_IS_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), \ + WEBKIT_TYPE_VIDEO_SINK)) + +#define WEBKIT_VIDEO_SINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), \ + WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkClass)) + +typedef struct _WebKitVideoSink WebKitVideoSink; +typedef struct _WebKitVideoSinkClass WebKitVideoSinkClass; +typedef struct _WebKitVideoSinkPrivate WebKitVideoSinkPrivate; + +struct _WebKitVideoSink { + /*< private >*/ + GstVideoSink parent; + WebKitVideoSinkPrivate *priv; +}; + +struct _WebKitVideoSinkClass { + /*< private >*/ + GstVideoSinkClass parent_class; + + /* Future padding */ + void (* _webkit_reserved1)(void); + void (* _webkit_reserved2)(void); + void (* _webkit_reserved3)(void); + void (* _webkit_reserved4)(void); + void (* _webkit_reserved5)(void); + void (* _webkit_reserved6)(void); +}; + +GType webkit_video_sink_get_type(void) G_GNUC_CONST; +GstElement *webkit_video_sink_new(void); + +G_END_DECLS + +#endif // USE(GSTREAMER) +#endif diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp new file mode 100644 index 0000000..e10e61f --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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 "WebKitWebSourceGStreamer.h" +#if USE(GSTREAMER) + +#include "Document.h" +#include "GOwnPtr.h" +#include "GRefPtr.h" +#include "NetworkingContext.h" +#include "Noncopyable.h" +#include "NotImplemented.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include <wtf/text/CString.h> +#include <gst/app/gstappsrc.h> +#include <gst/pbutils/missing-plugins.h> + +using namespace WebCore; + +class StreamingClient : public Noncopyable, public ResourceHandleClient { + public: + StreamingClient(WebKitWebSrc*); + virtual ~StreamingClient(); + + virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&); + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int, int); + virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/); + virtual void didFail(ResourceHandle*, const ResourceError&); + virtual void wasBlocked(ResourceHandle*); + virtual void cannotShowURL(ResourceHandle*); + + private: + WebKitWebSrc* m_src; +}; + +#define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate)) +struct _WebKitWebSrcPrivate { + GstAppSrc* appsrc; + GstPad* srcpad; + gchar* uri; + + RefPtr<WebCore::Frame> frame; + + StreamingClient* client; + RefPtr<ResourceHandle> resourceHandle; + + guint64 offset; + guint64 size; + gboolean seekable; + gboolean paused; + + guint64 requestedOffset; + + guint needDataID; + guint enoughDataID; + guint seekID; + + // icecast stuff + gboolean iradioMode; + gchar* iradioName; + gchar* iradioGenre; + gchar* iradioUrl; + gchar* iradioTitle; + + // TRUE if appsrc's version is >= 0.10.27, see + // https://bugzilla.gnome.org/show_bug.cgi?id=609423 + gboolean haveAppSrc27; +}; + +enum { + PROP_IRADIO_MODE = 1, + PROP_IRADIO_NAME, + PROP_IRADIO_GENRE, + PROP_IRADIO_URL, + PROP_IRADIO_TITLE +}; + +static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC(webkit_web_src_debug); +#define GST_CAT_DEFAULT webkit_web_src_debug + +static void webKitWebSrcUriHandlerInit(gpointer gIface, + gpointer ifaceData); + +static void webKitWebSrcFinalize(GObject* object); +static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec); +static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec); +static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition); +static gboolean webKitWebSrcQuery(GstPad* pad, GstQuery* query); + +static void webKitWebSrcNeedDataCb(GstAppSrc* appsrc, guint length, gpointer userData); +static void webKitWebSrcEnoughDataCb(GstAppSrc* appsrc, gpointer userData); +static gboolean webKitWebSrcSeekDataCb(GstAppSrc* appsrc, guint64 offset, gpointer userData); + +static void webKitWebSrcStop(WebKitWebSrc* src, bool seeking); + +static GstAppSrcCallbacks appsrcCallbacks = { + webKitWebSrcNeedDataCb, + webKitWebSrcEnoughDataCb, + webKitWebSrcSeekDataCb, + { 0 } +}; + +static void doInit(GType gtype) +{ + static const GInterfaceInfo uriHandlerInfo = { + webKitWebSrcUriHandlerInit, + 0, 0 + }; + + GST_DEBUG_CATEGORY_INIT(webkit_web_src_debug, "webkitwebsrc", 0, "websrc element"); + g_type_add_interface_static(gtype, GST_TYPE_URI_HANDLER, + &uriHandlerInfo); +} + +GST_BOILERPLATE_FULL(WebKitWebSrc, webkit_web_src, GstBin, GST_TYPE_BIN, doInit); + +static void webkit_web_src_base_init(gpointer klass) +{ + GstElementClass* eklass = GST_ELEMENT_CLASS(klass); + + gst_element_class_add_pad_template(eklass, + gst_static_pad_template_get(&srcTemplate)); + gst_element_class_set_details_simple(eklass, + (gchar*) "WebKit Web source element", + (gchar*) "Source", + (gchar*) "Handles HTTP/HTTPS uris", + (gchar*) "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); +} + +static void webkit_web_src_class_init(WebKitWebSrcClass* klass) +{ + GObjectClass* oklass = G_OBJECT_CLASS(klass); + GstElementClass* eklass = GST_ELEMENT_CLASS(klass); + + oklass->finalize = webKitWebSrcFinalize; + oklass->set_property = webKitWebSrcSetProperty; + oklass->get_property = webKitWebSrcGetProperty; + + // icecast stuff + g_object_class_install_property(oklass, + PROP_IRADIO_MODE, + g_param_spec_boolean("iradio-mode", + "iradio-mode", + "Enable internet radio mode (extraction of shoutcast/icecast metadata)", + FALSE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, + PROP_IRADIO_NAME, + g_param_spec_string("iradio-name", + "iradio-name", + "Name of the stream", + 0, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, + PROP_IRADIO_GENRE, + g_param_spec_string("iradio-genre", + "iradio-genre", + "Genre of the stream", + 0, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, + PROP_IRADIO_URL, + g_param_spec_string("iradio-url", + "iradio-url", + "Homepage URL for radio stream", + 0, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, + PROP_IRADIO_TITLE, + g_param_spec_string("iradio-title", + "iradio-title", + "Name of currently playing song", + 0, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + eklass->change_state = webKitWebSrcChangeState; + + g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate)); +} + +static void webkit_web_src_init(WebKitWebSrc* src, + WebKitWebSrcClass* gKlass) +{ + GstPadTemplate* padTemplate = gst_static_pad_template_get(&srcTemplate); + GstPad* targetpad; + WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src); + + src->priv = priv; + + priv->client = new StreamingClient(src); + + priv->srcpad = gst_ghost_pad_new_no_target_from_template("src", + padTemplate); + + gst_element_add_pad(GST_ELEMENT(src), priv->srcpad); + gst_pad_set_query_function(priv->srcpad, webKitWebSrcQuery); + + priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", 0)); + if (!priv->appsrc) { + GST_ERROR_OBJECT(src, "Failed to create appsrc"); + return; + } + + GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(priv->appsrc)->elementfactory); + priv->haveAppSrc27 = gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 27); + + gst_bin_add(GST_BIN(src), GST_ELEMENT(priv->appsrc)); + + targetpad = gst_element_get_static_pad(GST_ELEMENT(priv->appsrc), "src"); + gst_ghost_pad_set_target(GST_GHOST_PAD(priv->srcpad), targetpad); + gst_object_unref(targetpad); + + gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, 0); + gst_app_src_set_emit_signals(priv->appsrc, FALSE); + gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_SEEKABLE); + + // 512k is a abitrary number but we should choose a value + // here to not pause/unpause the SoupMessage too often and + // to make sure there's always some data available for + // GStreamer to handle. + gst_app_src_set_max_bytes(priv->appsrc, 512 * 1024); + + // Emit the need-data signal if the queue contains less + // than 20% of data. Without this the need-data signal + // is emitted when the queue is empty, we then dispatch + // the soup message unpausing to the main loop and from + // there unpause the soup message. This already takes + // quite some time and libsoup even needs some more time + // to actually provide data again. If we do all this + // already if the queue is 20% empty, it's much more + // likely that libsoup already provides new data before + // the queue is really empty. + // This might need tweaking for ports not using libsoup. + if (priv->haveAppSrc27) + g_object_set(priv->appsrc, "min-percent", 20, NULL); + + webKitWebSrcStop(src, false); +} + +static void webKitWebSrcFinalize(GObject* object) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(object); + WebKitWebSrcPrivate* priv = src->priv; + + delete priv->client; + + g_free(priv->uri); + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, ((GObject* )(src))); +} + +static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(object); + WebKitWebSrcPrivate* priv = src->priv; + + switch (propID) { + case PROP_IRADIO_MODE: + priv->iradioMode = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); + break; + } +} + +static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(object); + WebKitWebSrcPrivate* priv = src->priv; + + switch (propID) { + case PROP_IRADIO_MODE: + g_value_set_boolean(value, priv->iradioMode); + break; + case PROP_IRADIO_NAME: + g_value_set_string(value, priv->iradioName); + break; + case PROP_IRADIO_GENRE: + g_value_set_string(value, priv->iradioGenre); + break; + case PROP_IRADIO_URL: + g_value_set_string(value, priv->iradioUrl); + break; + case PROP_IRADIO_TITLE: + g_value_set_string(value, priv->iradioTitle); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); + break; + } +} + + +static void webKitWebSrcStop(WebKitWebSrc* src, bool seeking) +{ + WebKitWebSrcPrivate* priv = src->priv; + + if (priv->resourceHandle) { + priv->resourceHandle->cancel(); + priv->resourceHandle.release(); + } + priv->resourceHandle = 0; + + if (priv->frame) + priv->frame.release(); + + GST_OBJECT_LOCK(src); + if (priv->needDataID) + g_source_remove(priv->needDataID); + priv->needDataID = 0; + + if (priv->enoughDataID) + g_source_remove(priv->enoughDataID); + priv->enoughDataID = 0; + + if (priv->seekID) + g_source_remove(priv->seekID); + priv->seekID = 0; + + priv->paused = FALSE; + GST_OBJECT_UNLOCK(src); + + g_free(priv->iradioName); + priv->iradioName = 0; + + g_free(priv->iradioGenre); + priv->iradioGenre = 0; + + g_free(priv->iradioUrl); + priv->iradioUrl = 0; + + g_free(priv->iradioTitle); + priv->iradioTitle = 0; + + if (priv->appsrc) { + gst_app_src_set_caps(priv->appsrc, 0); + if (!seeking) + gst_app_src_set_size(priv->appsrc, -1); + } + + priv->offset = 0; + priv->seekable = FALSE; + + if (!seeking) { + priv->size = 0; + priv->requestedOffset = 0; + } + + GST_DEBUG_OBJECT(src, "Stopped request"); +} + +static bool webKitWebSrcStart(WebKitWebSrc* src) +{ + WebKitWebSrcPrivate* priv = src->priv; + + if (!priv->uri) { + GST_ERROR_OBJECT(src, "No URI provided"); + return false; + } + + KURL url = KURL(KURL(), priv->uri); + + ResourceRequest request(url); + request.setTargetType(ResourceRequestBase::TargetIsMedia); + request.setAllowCookies(true); + + NetworkingContext* context = 0; + if (priv->frame) { + Document* document = priv->frame->document(); + if (document) + request.setHTTPReferrer(document->documentURI()); + + FrameLoader* loader = priv->frame->loader(); + if (loader) { + loader->addExtraFieldsToSubresourceRequest(request); + context = loader->networkingContext(); + } + } + + // Let Apple web servers know we want to access their nice movie trailers. + if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data()) + || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data())) + request.setHTTPUserAgent("Quicktime/7.6.6"); + + if (priv->requestedOffset) { + GOwnPtr<gchar> val; + + val.set(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedOffset)); + request.setHTTPHeaderField("Range", val.get()); + } + + if (priv->iradioMode) + request.setHTTPHeaderField("icy-metadata", "1"); + + // Needed to use DLNA streaming servers + request.setHTTPHeaderField("transferMode.dlna", "Streaming"); + + priv->resourceHandle = ResourceHandle::create(context, request, priv->client, false, false); + if (!priv->resourceHandle) { + GST_ERROR_OBJECT(src, "Failed to create ResourceHandle"); + return false; + } + + GST_DEBUG_OBJECT(src, "Started request"); + + return true; +} + +static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + WebKitWebSrc* src = WEBKIT_WEB_SRC(element); + WebKitWebSrcPrivate* priv = src->priv; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!priv->appsrc) { + gst_element_post_message(element, + gst_missing_element_message_new(element, "appsrc")); + GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (0), ("no appsrc")); + 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)) { + GST_DEBUG_OBJECT(src, "State change failed"); + return ret; + } + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT(src, "READY->PAUSED"); + if (!webKitWebSrcStart(src)) + ret = GST_STATE_CHANGE_FAILURE; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT(src, "PAUSED->READY"); + webKitWebSrcStop(src, false); + break; + default: + break; + } + + return ret; +} + +static gboolean webKitWebSrcQuery(GstPad* pad, GstQuery* query) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(gst_pad_get_parent(pad)); + 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)); + if ((format == GST_FORMAT_BYTES) && (src->priv->size > 0)) { + gst_query_set_duration(query, format, src->priv->size); + result = TRUE; + } + break; + } + default: + break; + } + + if (!result) + result = gst_pad_query_default(pad, query); + + gst_object_unref(src); + return result; +} + +// uri handler interface + +static GstURIType webKitWebSrcUriGetType(void) +{ + return GST_URI_SRC; +} + +static gchar** webKitWebSrcGetProtocols(void) +{ + static gchar* protocols[] = {(gchar*) "http", (gchar*) "https", 0 }; + + return protocols; +} + +static const gchar* webKitWebSrcGetUri(GstURIHandler* handler) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(handler); + WebKitWebSrcPrivate* priv = src->priv; + + return priv->uri; +} + +static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(handler); + WebKitWebSrcPrivate* priv = src->priv; + + if (GST_STATE(src) >= GST_STATE_PAUSED) { + GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED"); + return FALSE; + } + + g_free(priv->uri); + priv->uri = 0; + + if (!uri) + return TRUE; + + KURL url(KURL(), uri); + + if (!url.isValid() || !url.protocolInHTTPFamily()) { + GST_ERROR_OBJECT(src, "Invalid URI '%s'", uri); + return FALSE; + } + + priv->uri = g_strdup(url.string().utf8().data()); + return TRUE; +} + +static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer ifaceData) +{ + GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface; + + iface->get_type = webKitWebSrcUriGetType; + iface->get_protocols = webKitWebSrcGetProtocols; + iface->get_uri = webKitWebSrcGetUri; + iface->set_uri = webKitWebSrcSetUri; +} + +// appsrc callbacks + +static gboolean webKitWebSrcNeedDataMainCb(WebKitWebSrc* src) +{ + WebKitWebSrcPrivate* priv = src->priv; + + priv->resourceHandle->setDefersLoading(false); + + GST_OBJECT_LOCK(src); + priv->paused = FALSE; + priv->needDataID = 0; + GST_OBJECT_UNLOCK(src); + return FALSE; +} + +static void webKitWebSrcNeedDataCb(GstAppSrc* appsrc, guint length, gpointer userData) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); + WebKitWebSrcPrivate* priv = src->priv; + + GST_DEBUG_OBJECT(src, "Need more data: %u", length); + + GST_OBJECT_LOCK(src); + if (priv->needDataID || !priv->paused) { + GST_OBJECT_UNLOCK(src); + return; + } + + priv->needDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcNeedDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GST_OBJECT_UNLOCK(src); +} + +static gboolean webKitWebSrcEnoughDataMainCb(WebKitWebSrc* src) +{ + WebKitWebSrcPrivate* priv = src->priv; + + priv->resourceHandle->setDefersLoading(true); + + GST_OBJECT_LOCK(src); + priv->paused = TRUE; + priv->enoughDataID = 0; + GST_OBJECT_UNLOCK(src); + + return FALSE; +} + +static void webKitWebSrcEnoughDataCb(GstAppSrc* appsrc, gpointer userData) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); + WebKitWebSrcPrivate* priv = src->priv; + + GST_DEBUG_OBJECT(src, "Have enough data"); + + GST_OBJECT_LOCK(src); + if (priv->enoughDataID || priv->paused) { + GST_OBJECT_UNLOCK(src); + return; + } + + priv->enoughDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcEnoughDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GST_OBJECT_UNLOCK(src); +} + +static gboolean webKitWebSrcSeekMainCb(WebKitWebSrc* src) +{ + webKitWebSrcStop(src, true); + webKitWebSrcStart(src); + + return FALSE; +} + +static gboolean webKitWebSrcSeekDataCb(GstAppSrc* appsrc, guint64 offset, gpointer userData) +{ + WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); + WebKitWebSrcPrivate* priv = src->priv; + + GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, offset); + if (offset == priv->offset) + return TRUE; + + if (!priv->seekable) + return FALSE; + if (offset > priv->size) + return FALSE; + + GST_DEBUG_OBJECT(src, "Doing range-request seek"); + priv->requestedOffset = offset; + + GST_OBJECT_LOCK(src); + if (priv->seekID) + g_source_remove(priv->seekID); + priv->seekID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcSeekMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GST_OBJECT_UNLOCK(src); + + return TRUE; +} + +void webKitWebSrcSetFrame(WebKitWebSrc* src, WebCore::Frame* frame) +{ + WebKitWebSrcPrivate* priv = src->priv; + + priv->frame = frame; +} + +StreamingClient::StreamingClient(WebKitWebSrc* src) : m_src(src) +{ + +} + +StreamingClient::~StreamingClient() +{ + +} + +void StreamingClient::willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&) +{ +} + +void StreamingClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response) +{ + WebKitWebSrcPrivate* priv = m_src->priv; + + GST_DEBUG_OBJECT(m_src, "Received response: %d", response.httpStatusCode()); + + // If we seeked we need 206 == PARTIAL_CONTENT + if (priv->requestedOffset && response.httpStatusCode() != 206) { + GST_ELEMENT_ERROR(m_src, RESOURCE, READ, (0), (0)); + gst_app_src_end_of_stream(priv->appsrc); + webKitWebSrcStop(m_src, false); + return; + } + + long long length = response.expectedContentLength(); + if (length > 0) { + length += priv->requestedOffset; + gst_app_src_set_size(priv->appsrc, length); + if (!priv->haveAppSrc27) { + gst_segment_set_duration(&GST_BASE_SRC(priv->appsrc)->segment, GST_FORMAT_BYTES, length); + gst_element_post_message(GST_ELEMENT(priv->appsrc), + gst_message_new_duration(GST_OBJECT(priv->appsrc), + GST_FORMAT_BYTES, length)); + } + } + + priv->size = length >= 0 ? length : 0; + priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField("Accept-Ranges").utf8().data()); + + // icecast stuff + String value = response.httpHeaderField("icy-metaint"); + if (!value.isEmpty()) { + gchar* endptr = 0; + gint64 icyMetaInt = g_ascii_strtoll(value.utf8().data(), &endptr, 10); + + if (endptr && *endptr == '\0' && icyMetaInt > 0) { + GstCaps* caps = gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, (gint) icyMetaInt, NULL); + + gst_app_src_set_caps(priv->appsrc, caps); + gst_caps_unref(caps); + } + } + + GstTagList* tags = gst_tag_list_new(); + value = response.httpHeaderField("icy-name"); + if (!value.isEmpty()) { + g_free(priv->iradioName); + priv->iradioName = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(m_src), "iradio-name"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION, priv->iradioName, NULL); + } + value = response.httpHeaderField("icy-genre"); + if (!value.isEmpty()) { + g_free(priv->iradioGenre); + priv->iradioGenre = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(m_src), "iradio-genre"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, priv->iradioGenre, NULL); + } + value = response.httpHeaderField("icy-url"); + if (!value.isEmpty()) { + g_free(priv->iradioUrl); + priv->iradioUrl = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(m_src), "iradio-url"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION, priv->iradioUrl, NULL); + } + value = response.httpHeaderField("icy-title"); + if (!value.isEmpty()) { + g_free(priv->iradioTitle); + priv->iradioTitle = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(m_src), "iradio-title"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE, priv->iradioTitle, NULL); + } + + if (gst_tag_list_is_empty(tags)) + gst_tag_list_free(tags); + else + gst_element_found_tags_for_pad(GST_ELEMENT(m_src), m_src->priv->srcpad, tags); +} + +void StreamingClient::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived) +{ + WebKitWebSrcPrivate* priv = m_src->priv; + + GST_LOG_OBJECT(m_src, "Have %d bytes of data", length); + + if (priv->seekID || handle != priv->resourceHandle) { + GST_DEBUG_OBJECT(m_src, "Seek in progress, ignoring data"); + return; + } + + GstBuffer* buffer = gst_buffer_new_and_alloc(length); + + memcpy(GST_BUFFER_DATA(buffer), data, length); + GST_BUFFER_OFFSET(buffer) = priv->offset; + priv->offset += length; + GST_BUFFER_OFFSET_END(buffer) = priv->offset; + + GstFlowReturn ret = gst_app_src_push_buffer(priv->appsrc, buffer); + if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) + GST_ELEMENT_ERROR(m_src, CORE, FAILED, (0), (0)); +} + +void StreamingClient::didFinishLoading(ResourceHandle*, double) +{ + WebKitWebSrcPrivate* priv = m_src->priv; + + GST_DEBUG_OBJECT(m_src, "Have EOS"); + + if (!priv->seekID) + gst_app_src_end_of_stream(m_src->priv->appsrc); +} + +void StreamingClient::didFail(ResourceHandle*, const ResourceError& error) +{ + GST_ERROR_OBJECT(m_src, "Have failure: %s", error.localizedDescription().utf8().data()); + GST_ELEMENT_ERROR(m_src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (0)); + gst_app_src_end_of_stream(m_src->priv->appsrc); +} + +void StreamingClient::wasBlocked(ResourceHandle*) +{ + GST_ERROR_OBJECT(m_src, "Request was blocked"); + GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Access to \"%s\" was blocked", m_src->priv->uri), (0)); +} + +void StreamingClient::cannotShowURL(ResourceHandle*) +{ + GST_ERROR_OBJECT(m_src, "Cannot show URL"); + GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Can't show \"%s\"", m_src->priv->uri), (0)); +} + +#endif // USE(GSTREAMER) + diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.h new file mode 100644 index 0000000..bdb0833 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009,2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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 + */ + +#ifndef WebKitWebSourceGStreamer_h +#define WebKitWebSourceGStreamer_h +#if USE(GSTREAMER) + +#include "Frame.h" +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_WEB_SRC (webkit_web_src_get_type ()) +#define WEBKIT_WEB_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrc)) +#define WEBKIT_WEB_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcClass)) +#define WEBKIT_IS_WEB_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_WEB_SRC)) +#define WEBKIT_IS_WEB_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_WEB_SRC)) + +typedef struct _WebKitWebSrc WebKitWebSrc; +typedef struct _WebKitWebSrcClass WebKitWebSrcClass; +typedef struct _WebKitWebSrcPrivate WebKitWebSrcPrivate; + +struct _WebKitWebSrc { + GstBin parent; + + WebKitWebSrcPrivate *priv; +}; + +struct _WebKitWebSrcClass { + GstBinClass parentClass; +}; + +GType webkit_web_src_get_type(void); +void webKitWebSrcSetFrame(WebKitWebSrc* src, WebCore::Frame* frame); + +G_END_DECLS + +#endif // USE(GSTREAMER) +#endif |
