1/*
2 * Copyright (C) 2007, 2009 Apple Inc.  All rights reserved.
3 * Copyright (C) 2007 Collabora Ltd.  All rights reserved.
4 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5 * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>
6 * Copyright (C) 2009, 2010 Igalia S.L
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * aint with this library; see the file COPYING.LIB.  If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25#include "MediaPlayerPrivateGStreamerBase.h"
26
27#if ENABLE(VIDEO) && USE(GSTREAMER)
28
29#include "ColorSpace.h"
30#include "FullscreenVideoControllerGStreamer.h"
31#include "GStreamerGWorld.h"
32#include "GStreamerUtilities.h"
33#include "GStreamerVersioning.h"
34#include "GraphicsContext.h"
35#include "GraphicsTypes.h"
36#include "ImageGStreamer.h"
37#include "ImageOrientation.h"
38#include "IntRect.h"
39#include "Logging.h"
40#include "MediaPlayer.h"
41#include "NotImplemented.h"
42#include "VideoSinkGStreamer.h"
43#include "WebKitWebSourceGStreamer.h"
44#include <gst/gst.h>
45#include <gst/video/video.h>
46#include <wtf/text/CString.h>
47
48#ifdef GST_API_VERSION_1
49#include <gst/audio/streamvolume.h>
50#else
51#include <gst/interfaces/streamvolume.h>
52#endif
53
54#if GST_CHECK_VERSION(1, 1, 0) && USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)
55#include "TextureMapperGL.h"
56#endif
57
58GST_DEBUG_CATEGORY(webkit_media_player_debug);
59#define GST_CAT_DEFAULT webkit_media_player_debug
60
61using namespace std;
62
63namespace WebCore {
64
65static int greatestCommonDivisor(int a, int b)
66{
67    while (b) {
68        int temp = a;
69        a = b;
70        b = temp % b;
71    }
72
73    return ABS(a);
74}
75
76static void mediaPlayerPrivateVolumeChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamerBase* player)
77{
78    // This is called when m_volumeElement receives the notify::volume signal.
79    player->volumeChanged();
80}
81
82static gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamerBase* player)
83{
84    // This is the callback of the timeout source created in ::volumeChanged.
85    player->notifyPlayerOfVolumeChange();
86    return FALSE;
87}
88
89static void mediaPlayerPrivateMuteChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamerBase* player)
90{
91    // This is called when m_volumeElement receives the notify::mute signal.
92    player->muteChanged();
93}
94
95static gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamerBase* player)
96{
97    // This is the callback of the timeout source created in ::muteChanged.
98    player->notifyPlayerOfMute();
99    return FALSE;
100}
101
102static void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivateGStreamerBase* playerPrivate)
103{
104    playerPrivate->triggerRepaint(buffer);
105}
106
107MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase(MediaPlayer* player)
108    : m_player(player)
109    , m_fpsSink(0)
110    , m_readyState(MediaPlayer::HaveNothing)
111    , m_networkState(MediaPlayer::Empty)
112    , m_buffer(0)
113    , m_volumeTimerHandler(0)
114    , m_muteTimerHandler(0)
115    , m_repaintHandler(0)
116    , m_volumeSignalHandler(0)
117    , m_muteSignalHandler(0)
118#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
119    , m_texture(0)
120#endif
121{
122#if GLIB_CHECK_VERSION(2, 31, 0)
123    m_bufferMutex = WTF::fastNew<GMutex>();
124    g_mutex_init(m_bufferMutex);
125#else
126    m_bufferMutex = g_mutex_new();
127#endif
128}
129
130MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase()
131{
132    g_signal_handler_disconnect(m_webkitVideoSink.get(), m_repaintHandler);
133
134#if GLIB_CHECK_VERSION(2, 31, 0)
135    g_mutex_clear(m_bufferMutex);
136    WTF::fastDelete(m_bufferMutex);
137#else
138    g_mutex_free(m_bufferMutex);
139#endif
140
141    if (m_buffer)
142        gst_buffer_unref(m_buffer);
143    m_buffer = 0;
144
145    m_player = 0;
146
147    if (m_muteTimerHandler)
148        g_source_remove(m_muteTimerHandler);
149
150    if (m_volumeTimerHandler)
151        g_source_remove(m_volumeTimerHandler);
152
153    if (m_volumeSignalHandler) {
154        g_signal_handler_disconnect(m_volumeElement.get(), m_volumeSignalHandler);
155        m_volumeSignalHandler = 0;
156    }
157
158    if (m_muteSignalHandler) {
159        g_signal_handler_disconnect(m_volumeElement.get(), m_muteSignalHandler);
160        m_muteSignalHandler = 0;
161    }
162
163#if USE(NATIVE_FULLSCREEN_VIDEO)
164    if (m_fullscreenVideoController)
165        exitFullscreen();
166#endif
167}
168
169// Returns the size of the video
170IntSize MediaPlayerPrivateGStreamerBase::naturalSize() const
171{
172    if (!hasVideo())
173        return IntSize();
174
175    if (!m_videoSize.isEmpty())
176        return m_videoSize;
177
178#ifdef GST_API_VERSION_1
179    /* FIXME this has a race with the pad setting caps as the buffer (m_buffer)
180     * and the caps won't match and might cause a crash. (In case a
181     * renegotiation happens)
182     */
183    GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get());
184#else
185    g_mutex_lock(m_bufferMutex);
186    GRefPtr<GstCaps> caps = m_buffer ? GST_BUFFER_CAPS(m_buffer) : 0;
187    g_mutex_unlock(m_bufferMutex);
188#endif
189    if (!caps)
190        return IntSize();
191
192
193    // TODO: handle possible clean aperture data. See
194    // https://bugzilla.gnome.org/show_bug.cgi?id=596571
195    // TODO: handle possible transformation matrix. See
196    // https://bugzilla.gnome.org/show_bug.cgi?id=596326
197
198    // Get the video PAR and original size, if this fails the
199    // video-sink has likely not yet negotiated its caps.
200    int pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride;
201    IntSize originalSize;
202    GstVideoFormat format;
203    if (!getVideoSizeAndFormatFromCaps(caps.get(), originalSize, format, pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride))
204        return IntSize();
205
206    LOG_MEDIA_MESSAGE("Original video size: %dx%d", originalSize.width(), originalSize.height());
207    LOG_MEDIA_MESSAGE("Pixel aspect ratio: %d/%d", pixelAspectRatioNumerator, pixelAspectRatioDenominator);
208
209    // Calculate DAR based on PAR and video size.
210    int displayWidth = originalSize.width() * pixelAspectRatioNumerator;
211    int displayHeight = originalSize.height() * pixelAspectRatioDenominator;
212
213    // Divide display width and height by their GCD to avoid possible overflows.
214    int displayAspectRatioGCD = greatestCommonDivisor(displayWidth, displayHeight);
215    displayWidth /= displayAspectRatioGCD;
216    displayHeight /= displayAspectRatioGCD;
217
218    // Apply DAR to original video size. This is the same behavior as in xvimagesink's setcaps function.
219    guint64 width = 0, height = 0;
220    if (!(originalSize.height() % displayHeight)) {
221        LOG_MEDIA_MESSAGE("Keeping video original height");
222        width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
223        height = static_cast<guint64>(originalSize.height());
224    } else if (!(originalSize.width() % displayWidth)) {
225        LOG_MEDIA_MESSAGE("Keeping video original width");
226        height = gst_util_uint64_scale_int(originalSize.width(), displayHeight, displayWidth);
227        width = static_cast<guint64>(originalSize.width());
228    } else {
229        LOG_MEDIA_MESSAGE("Approximating while keeping original video height");
230        width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
231        height = static_cast<guint64>(originalSize.height());
232    }
233
234    LOG_MEDIA_MESSAGE("Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height);
235    m_videoSize = IntSize(static_cast<int>(width), static_cast<int>(height));
236    return m_videoSize;
237}
238
239void MediaPlayerPrivateGStreamerBase::setVolume(float volume)
240{
241    if (!m_volumeElement)
242        return;
243
244    gst_stream_volume_set_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC, static_cast<double>(volume));
245}
246
247float MediaPlayerPrivateGStreamerBase::volume() const
248{
249    if (!m_volumeElement)
250        return 0;
251
252    return gst_stream_volume_get_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC);
253}
254
255
256void MediaPlayerPrivateGStreamerBase::notifyPlayerOfVolumeChange()
257{
258    m_volumeTimerHandler = 0;
259
260    if (!m_player || !m_volumeElement)
261        return;
262    double volume;
263    volume = gst_stream_volume_get_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_CUBIC);
264    // get_volume() can return values superior to 1.0 if the user
265    // applies software user gain via third party application (GNOME
266    // volume control for instance).
267    volume = CLAMP(volume, 0.0, 1.0);
268    m_player->volumeChanged(static_cast<float>(volume));
269}
270
271void MediaPlayerPrivateGStreamerBase::volumeChanged()
272{
273    if (m_volumeTimerHandler)
274        g_source_remove(m_volumeTimerHandler);
275    m_volumeTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVolumeChangeTimeoutCallback), this);
276}
277
278MediaPlayer::NetworkState MediaPlayerPrivateGStreamerBase::networkState() const
279{
280    return m_networkState;
281}
282
283MediaPlayer::ReadyState MediaPlayerPrivateGStreamerBase::readyState() const
284{
285    return m_readyState;
286}
287
288void MediaPlayerPrivateGStreamerBase::sizeChanged()
289{
290    notImplemented();
291}
292
293void MediaPlayerPrivateGStreamerBase::setMuted(bool muted)
294{
295    if (!m_volumeElement)
296        return;
297
298    g_object_set(m_volumeElement.get(), "mute", muted, NULL);
299}
300
301bool MediaPlayerPrivateGStreamerBase::muted() const
302{
303    if (!m_volumeElement)
304        return false;
305
306    bool muted;
307    g_object_get(m_volumeElement.get(), "mute", &muted, NULL);
308    return muted;
309}
310
311void MediaPlayerPrivateGStreamerBase::notifyPlayerOfMute()
312{
313    m_muteTimerHandler = 0;
314
315    if (!m_player || !m_volumeElement)
316        return;
317
318    gboolean muted;
319    g_object_get(m_volumeElement.get(), "mute", &muted, NULL);
320    m_player->muteChanged(static_cast<bool>(muted));
321}
322
323void MediaPlayerPrivateGStreamerBase::muteChanged()
324{
325    if (m_muteTimerHandler)
326        g_source_remove(m_muteTimerHandler);
327    m_muteTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateMuteChangeTimeoutCallback), this);
328}
329
330
331#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
332void MediaPlayerPrivateGStreamerBase::updateTexture(GstBuffer* buffer)
333{
334    if (!m_texture)
335        return;
336
337    if (!client())
338        return;
339
340    const void* srcData = 0;
341    IntSize size = naturalSize();
342
343    if (m_texture->size() != size)
344        m_texture->reset(size);
345
346#if GST_CHECK_VERSION(1, 1, 0)
347    GstVideoGLTextureUploadMeta* meta;
348    if ((meta = gst_buffer_get_video_gl_texture_upload_meta(buffer))) {
349        if (meta->n_textures == 1) { // BRGx & BGRA formats use only one texture.
350            const BitmapTextureGL* textureGL = static_cast<const BitmapTextureGL*>(m_texture.get());
351            guint ids[4] = { textureGL->id(), 0, 0, 0 };
352
353            if (gst_video_gl_texture_upload_meta_upload(meta, ids)) {
354                client()->setPlatformLayerNeedsDisplay();
355                return;
356            }
357        }
358    }
359#endif
360
361#ifdef GST_API_VERSION_1
362    GstMapInfo srcInfo;
363    gst_buffer_map(buffer, &srcInfo, GST_MAP_READ);
364    srcData = srcInfo.data;
365#else
366    srcData = GST_BUFFER_DATA(buffer);
367#endif
368
369    m_texture->updateContents(srcData, WebCore::IntRect(WebCore::IntPoint(0, 0), size), WebCore::IntPoint(0, 0), size.width() * 4, BitmapTexture::UpdateCannotModifyOriginalImageData);
370
371#ifdef GST_API_VERSION_1
372    gst_buffer_unmap(buffer, &srcInfo);
373#endif
374
375    client()->setPlatformLayerNeedsDisplay();
376}
377#endif
378
379void MediaPlayerPrivateGStreamerBase::triggerRepaint(GstBuffer* buffer)
380{
381    g_return_if_fail(GST_IS_BUFFER(buffer));
382
383#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
384    if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
385        updateTexture(buffer);
386    else
387#endif
388    {
389        g_mutex_lock(m_bufferMutex);
390        gst_buffer_replace(&m_buffer, buffer);
391        g_mutex_unlock(m_bufferMutex);
392        m_player->repaint();
393    }
394}
395
396void MediaPlayerPrivateGStreamerBase::setSize(const IntSize& size)
397{
398    m_size = size;
399}
400
401void MediaPlayerPrivateGStreamerBase::paint(GraphicsContext* context, const IntRect& rect)
402{
403#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
404    if (m_texture)
405        return;
406#endif
407
408    if (context->paintingDisabled())
409        return;
410
411    if (!m_player->visible())
412        return;
413
414    g_mutex_lock(m_bufferMutex);
415    if (!m_buffer) {
416        g_mutex_unlock(m_bufferMutex);
417        return;
418    }
419
420#ifdef GST_API_VERSION_1
421    /* FIXME this has a race with the pad setting caps as the buffer (m_buffer)
422     * and the caps won't match and might cause a crash. (In case a
423     * renegotiation happens)
424     */
425    GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get());
426#else
427    GRefPtr<GstCaps> caps = GST_BUFFER_CAPS(m_buffer);
428#endif
429    if (!caps) {
430        g_mutex_unlock(m_bufferMutex);
431        return;
432    }
433
434    RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_buffer, caps.get());
435    if (!gstImage) {
436        g_mutex_unlock(m_bufferMutex);
437        return;
438    }
439
440    context->drawImage(reinterpret_cast<Image*>(gstImage->image().get()), ColorSpaceSRGB,
441        rect, gstImage->rect(), CompositeCopy, DoNotRespectImageOrientation, false);
442    g_mutex_unlock(m_bufferMutex);
443}
444
445#if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL) && !USE(COORDINATED_GRAPHICS)
446void MediaPlayerPrivateGStreamerBase::paintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity)
447{
448    if (textureMapper->accelerationMode() != TextureMapper::OpenGLMode)
449        return;
450
451    if (!m_texture) {
452        m_texture = textureMapper->acquireTextureFromPool(naturalSize());
453        return;
454    }
455
456    textureMapper->drawTexture(*m_texture.get(), targetRect, matrix, opacity);
457}
458#endif
459
460#if USE(NATIVE_FULLSCREEN_VIDEO)
461void MediaPlayerPrivateGStreamerBase::enterFullscreen()
462{
463    ASSERT(!m_fullscreenVideoController);
464    m_fullscreenVideoController = FullscreenVideoControllerGStreamer::create(this);
465    if (m_fullscreenVideoController)
466        m_fullscreenVideoController->enterFullscreen();
467}
468
469void MediaPlayerPrivateGStreamerBase::exitFullscreen()
470{
471    if (!m_fullscreenVideoController)
472        return;
473    m_fullscreenVideoController->exitFullscreen();
474    m_fullscreenVideoController.release();
475}
476#endif
477
478bool MediaPlayerPrivateGStreamerBase::supportsFullscreen() const
479{
480    return true;
481}
482
483PlatformMedia MediaPlayerPrivateGStreamerBase::platformMedia() const
484{
485#if USE(NATIVE_FULLSCREEN_VIDEO)
486    PlatformMedia p;
487    p.type = PlatformMedia::GStreamerGWorldType;
488    p.media.gstreamerGWorld = m_gstGWorld.get();
489    return p;
490#else
491    return NoPlatformMedia;
492#endif
493}
494
495MediaPlayer::MovieLoadType MediaPlayerPrivateGStreamerBase::movieLoadType() const
496{
497    if (m_readyState == MediaPlayer::HaveNothing)
498        return MediaPlayer::Unknown;
499
500    if (isLiveStream())
501        return MediaPlayer::LiveStream;
502
503    return MediaPlayer::Download;
504}
505
506// This function creates and initializes some internal variables, and returns a
507// pointer to the element that should receive the data flow first
508GstElement* MediaPlayerPrivateGStreamerBase::createVideoSink(GstElement* pipeline)
509{
510    if (!initializeGStreamer())
511        return 0;
512
513#if USE(NATIVE_FULLSCREEN_VIDEO)
514    m_gstGWorld = GStreamerGWorld::createGWorld(pipeline);
515    m_webkitVideoSink = webkitVideoSinkNew(m_gstGWorld.get());
516#else
517    UNUSED_PARAM(pipeline);
518    m_webkitVideoSink = webkitVideoSinkNew();
519#endif
520    m_videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink"));
521
522    m_repaintHandler = g_signal_connect(m_webkitVideoSink.get(), "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this);
523
524#if USE(NATIVE_FULLSCREEN_VIDEO)
525    // Build a new video sink consisting of a bin containing a tee
526    // (meant to distribute data to multiple video sinks) and our
527    // internal video sink. For fullscreen we create an autovideosink
528    // and initially block the data flow towards it and configure it
529
530    m_videoSinkBin = gst_bin_new("video-sink");
531
532    GstElement* videoTee = gst_element_factory_make("tee", "videoTee");
533    GstElement* queue = gst_element_factory_make("queue", 0);
534
535#ifdef GST_API_VERSION_1
536    GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(videoTee, "sink"));
537    GST_OBJECT_FLAG_SET(GST_OBJECT(sinkPad.get()), GST_PAD_FLAG_PROXY_ALLOCATION);
538#endif
539
540    gst_bin_add_many(GST_BIN(m_videoSinkBin.get()), videoTee, queue, NULL);
541
542    // Link a new src pad from tee to queue1.
543    gst_element_link_pads_full(videoTee, 0, queue, "sink", GST_PAD_LINK_CHECK_NOTHING);
544#endif
545
546    GstElement* actualVideoSink = 0;
547    m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink");
548    if (m_fpsSink) {
549        // The verbose property has been added in -bad 0.10.22. Making
550        // this whole code depend on it because we don't want
551        // fpsdiplaysink to spit data on stdout.
552        GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(m_fpsSink)->elementfactory);
553        if (gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 22)) {
554            g_object_set(m_fpsSink, "silent", TRUE , NULL);
555
556            // Turn off text overlay unless logging is enabled.
557#if LOG_DISABLED
558            g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
559#else
560            WTFLogChannel* channel = getChannelFromName("Media");
561            if (channel->state != WTFLogChannelOn)
562                g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
563#endif // LOG_DISABLED
564
565            if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink), "video-sink")) {
566                g_object_set(m_fpsSink, "video-sink", m_webkitVideoSink.get(), NULL);
567#if USE(NATIVE_FULLSCREEN_VIDEO)
568                gst_bin_add(GST_BIN(m_videoSinkBin.get()), m_fpsSink);
569#endif
570                actualVideoSink = m_fpsSink;
571            } else
572                m_fpsSink = 0;
573        } else
574            m_fpsSink = 0;
575    }
576
577    if (!m_fpsSink) {
578#if USE(NATIVE_FULLSCREEN_VIDEO)
579        gst_bin_add(GST_BIN(m_videoSinkBin.get()), m_webkitVideoSink.get());
580#endif
581        actualVideoSink = m_webkitVideoSink.get();
582    }
583
584    ASSERT(actualVideoSink);
585
586#if USE(NATIVE_FULLSCREEN_VIDEO)
587    // Faster elements linking.
588    gst_element_link_pads_full(queue, "src", actualVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING);
589
590    // Add a ghostpad to the bin so it can proxy to tee.
591    GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(videoTee, "sink"));
592    gst_element_add_pad(m_videoSinkBin.get(), gst_ghost_pad_new("sink", pad.get()));
593
594    // Set the bin as video sink of playbin.
595    return m_videoSinkBin.get();
596#else
597    return actualVideoSink;
598#endif
599}
600
601void MediaPlayerPrivateGStreamerBase::setStreamVolumeElement(GstStreamVolume* volume)
602{
603    ASSERT(!m_volumeElement);
604    m_volumeElement = volume;
605
606    g_object_set(m_volumeElement.get(), "mute", m_player->muted(), "volume", m_player->volume(), NULL);
607
608    m_volumeSignalHandler = g_signal_connect(m_volumeElement.get(), "notify::volume", G_CALLBACK(mediaPlayerPrivateVolumeChangedCallback), this);
609    m_muteSignalHandler = g_signal_connect(m_volumeElement.get(), "notify::mute", G_CALLBACK(mediaPlayerPrivateMuteChangedCallback), this);
610}
611
612unsigned MediaPlayerPrivateGStreamerBase::decodedFrameCount() const
613{
614    guint64 decodedFrames = 0;
615    if (m_fpsSink)
616        g_object_get(m_fpsSink, "frames-rendered", &decodedFrames, NULL);
617    return static_cast<unsigned>(decodedFrames);
618}
619
620unsigned MediaPlayerPrivateGStreamerBase::droppedFrameCount() const
621{
622    guint64 framesDropped = 0;
623    if (m_fpsSink)
624        g_object_get(m_fpsSink, "frames-dropped", &framesDropped, NULL);
625    return static_cast<unsigned>(framesDropped);
626}
627
628unsigned MediaPlayerPrivateGStreamerBase::audioDecodedByteCount() const
629{
630    GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
631    gint64 position = 0;
632
633    if (audioSink() && gst_element_query(audioSink(), query))
634        gst_query_parse_position(query, 0, &position);
635
636    gst_query_unref(query);
637    return static_cast<unsigned>(position);
638}
639
640unsigned MediaPlayerPrivateGStreamerBase::videoDecodedByteCount() const
641{
642    GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
643    gint64 position = 0;
644
645    if (gst_element_query(m_webkitVideoSink.get(), query))
646        gst_query_parse_position(query, 0, &position);
647
648    gst_query_unref(query);
649    return static_cast<unsigned>(position);
650}
651
652}
653
654#endif // USE(GSTREAMER)
655