| /* |
| * 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> |
| * |
| * 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" |
| |
| #if ENABLE(VIDEO) |
| |
| #include "MediaPlayerPrivateGStreamer.h" |
| |
| #include "CString.h" |
| #include "GraphicsContext.h" |
| #include "IntRect.h" |
| #include "KURL.h" |
| #include "MIMETypeRegistry.h" |
| #include "MediaPlayer.h" |
| #include "NotImplemented.h" |
| #include "ScrollView.h" |
| #include "VideoSinkGStreamer.h" |
| #include "Widget.h" |
| |
| #include <gst/base/gstbasesrc.h> |
| #include <gst/gst.h> |
| #include <gst/interfaces/mixer.h> |
| #include <gst/interfaces/xoverlay.h> |
| #include <gst/video/video.h> |
| #include <limits> |
| #include <math.h> |
| #include <wtf/GOwnPtr.h> |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| gboolean mediaPlayerPrivateErrorCallback(GstBus* bus, GstMessage* message, gpointer data) |
| { |
| if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR) { |
| GOwnPtr<GError> err; |
| GOwnPtr<gchar> debug; |
| |
| gst_message_parse_error(message, &err.outPtr(), &debug.outPtr()); |
| LOG_VERBOSE(Media, "Error: %d, %s", err->code, err->message); |
| |
| MediaPlayer::NetworkState error = MediaPlayer::Empty; |
| if (err->domain == GST_CORE_ERROR || err->domain == GST_LIBRARY_ERROR) |
| error = MediaPlayer::DecodeError; |
| else if (err->domain == GST_RESOURCE_ERROR) |
| error = MediaPlayer::FormatError; |
| else if (err->domain == GST_STREAM_ERROR) |
| error = MediaPlayer::NetworkError; |
| |
| MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data); |
| if (mp) |
| mp->loadingFailed(error); |
| } |
| return true; |
| } |
| |
| gboolean mediaPlayerPrivateEOSCallback(GstBus* bus, GstMessage* message, gpointer data) |
| { |
| if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_EOS) { |
| LOG_VERBOSE(Media, "End of Stream"); |
| MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data); |
| mp->didEnd(); |
| } |
| return true; |
| } |
| |
| gboolean mediaPlayerPrivateStateCallback(GstBus* bus, GstMessage* message, gpointer data) |
| { |
| if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_STATE_CHANGED) { |
| MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data); |
| mp->updateStates(); |
| } |
| return true; |
| } |
| |
| gboolean mediaPlayerPrivateBufferingCallback(GstBus* bus, GstMessage* message, gpointer data) |
| { |
| if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_BUFFERING) { |
| gint percent = 0; |
| gst_message_parse_buffering(message, &percent); |
| LOG_VERBOSE(Media, "Buffering %d", percent); |
| } |
| return true; |
| } |
| |
| static void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, MediaPlayerPrivate* playerPrivate) |
| { |
| playerPrivate->repaint(); |
| } |
| |
| MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) |
| { |
| return new MediaPlayerPrivate(player); |
| } |
| |
| void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) |
| { |
| if (isAvailable()) |
| registrar(create, getSupportedTypes, supportsType); |
| } |
| |
| static bool gstInitialized = false; |
| |
| MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) |
| : m_player(player) |
| , m_playBin(0) |
| , m_videoSink(0) |
| , m_source(0) |
| , m_rate(1.0f) |
| , m_endTime(numeric_limits<float>::infinity()) |
| , m_volume(0.5f) |
| , m_networkState(MediaPlayer::Empty) |
| , m_readyState(MediaPlayer::HaveNothing) |
| , m_startedPlaying(false) |
| , m_isStreaming(false) |
| , m_size(IntSize()) |
| , m_visible(true) |
| , m_paused(true) |
| , m_seeking(false) |
| , m_errorOccured(false) |
| { |
| // FIXME: We should pass the arguments from the command line |
| if (!gstInitialized) { |
| gst_init(0, 0); |
| gstInitialized = true; |
| } |
| |
| // FIXME: The size shouldn't be fixed here, this is just a quick hack. |
| m_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 640, 480); |
| } |
| |
| MediaPlayerPrivate::~MediaPlayerPrivate() |
| { |
| if (m_surface) |
| cairo_surface_destroy(m_surface); |
| |
| if (m_playBin) { |
| gst_element_set_state(m_playBin, GST_STATE_NULL); |
| gst_object_unref(GST_OBJECT(m_playBin)); |
| } |
| } |
| |
| void MediaPlayerPrivate::load(const String& url) |
| { |
| LOG_VERBOSE(Media, "Load %s", url.utf8().data()); |
| if (m_networkState != MediaPlayer::Loading) { |
| m_networkState = MediaPlayer::Loading; |
| m_player->networkStateChanged(); |
| } |
| if (m_readyState != MediaPlayer::HaveNothing) { |
| m_readyState = MediaPlayer::HaveNothing; |
| m_player->readyStateChanged(); |
| } |
| |
| createGSTPlayBin(url); |
| pause(); |
| } |
| |
| void MediaPlayerPrivate::play() |
| { |
| LOG_VERBOSE(Media, "Play"); |
| gst_element_set_state(m_playBin, GST_STATE_PLAYING); |
| } |
| |
| void MediaPlayerPrivate::pause() |
| { |
| LOG_VERBOSE(Media, "Pause"); |
| gst_element_set_state(m_playBin, GST_STATE_PAUSED); |
| } |
| |
| float MediaPlayerPrivate::duration() const |
| { |
| if (!m_playBin) |
| return 0.0; |
| |
| if (m_errorOccured) |
| return 0.0; |
| |
| GstFormat timeFormat = GST_FORMAT_TIME; |
| gint64 timeLength = 0; |
| |
| // FIXME: We try to get the duration, but we do not trust the |
| // return value of the query function only; the problem we are |
| // trying to work-around here is that pipelines in stream mode may |
| // not be able to figure out the duration, but still return true! |
| // See https://bugs.webkit.org/show_bug.cgi?id=24639. |
| if (!gst_element_query_duration(m_playBin, &timeFormat, &timeLength) || timeLength <= 0) { |
| 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) (timeLength / 1000000000.0); |
| // FIXME: handle 3.14.9.5 properly |
| } |
| |
| float MediaPlayerPrivate::currentTime() const |
| { |
| if (!m_playBin) |
| return 0; |
| |
| if (m_errorOccured) |
| return 0; |
| |
| float ret = 0.0; |
| |
| GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); |
| if (!gst_element_query(m_playBin, query)) { |
| LOG_VERBOSE(Media, "Position query failed..."); |
| gst_query_unref(query); |
| return ret; |
| } |
| |
| gint64 position; |
| gst_query_parse_position(query, 0, &position); |
| ret = (float) (position / 1000000000.0); |
| LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); |
| |
| gst_query_unref(query); |
| |
| return ret; |
| } |
| |
| void MediaPlayerPrivate::seek(float time) |
| { |
| GstClockTime sec = (GstClockTime)(time * GST_SECOND); |
| |
| if (!m_playBin) |
| return; |
| |
| if (m_isStreaming) |
| return; |
| |
| if (m_errorOccured) |
| return; |
| |
| LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(sec)); |
| if (!gst_element_seek( m_playBin, m_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; |
| } |
| |
| void MediaPlayerPrivate::setEndTime(float time) |
| { |
| notImplemented(); |
| } |
| |
| void MediaPlayerPrivate::startEndPointTimerIfNeeded() |
| { |
| notImplemented(); |
| } |
| |
| void MediaPlayerPrivate::cancelSeek() |
| { |
| notImplemented(); |
| } |
| |
| void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*) |
| { |
| notImplemented(); |
| } |
| |
| bool MediaPlayerPrivate::paused() const |
| { |
| return m_paused; |
| } |
| |
| bool MediaPlayerPrivate::seeking() const |
| { |
| return m_seeking; |
| } |
| |
| // Returns the size of the video |
| IntSize MediaPlayerPrivate::naturalSize() const |
| { |
| if (!hasVideo()) |
| return IntSize(); |
| |
| int x = 0, y = 0; |
| if (GstPad* pad = gst_element_get_static_pad(m_videoSink, "sink")) { |
| gst_video_get_size(GST_PAD(pad), &x, &y); |
| gst_object_unref(GST_OBJECT(pad)); |
| } |
| |
| return IntSize(x, y); |
| } |
| |
| bool MediaPlayerPrivate::hasVideo() const |
| { |
| gint currentVideo = -1; |
| if (m_playBin) |
| g_object_get(G_OBJECT(m_playBin), "current-video", ¤tVideo, NULL); |
| return currentVideo > -1; |
| } |
| |
| bool MediaPlayerPrivate::hasAudio() const |
| { |
| gint currentAudio = -1; |
| if (m_playBin) |
| g_object_get(G_OBJECT(m_playBin), "current-audio", ¤tAudio, NULL); |
| return currentAudio > -1; |
| } |
| |
| void MediaPlayerPrivate::setVolume(float volume) |
| { |
| m_volume = volume; |
| LOG_VERBOSE(Media, "Volume to %f", volume); |
| setMuted(false); |
| } |
| |
| void MediaPlayerPrivate::setMuted(bool b) |
| { |
| if (!m_playBin) |
| return; |
| |
| if (b) { |
| g_object_get(G_OBJECT(m_playBin), "volume", &m_volume, NULL); |
| g_object_set(G_OBJECT(m_playBin), "volume", (double)0.0, NULL); |
| } else |
| g_object_set(G_OBJECT(m_playBin), "volume", m_volume, NULL); |
| |
| } |
| |
| void MediaPlayerPrivate::setRate(float rate) |
| { |
| if (rate == 0.0) { |
| gst_element_set_state(m_playBin, GST_STATE_PAUSED); |
| return; |
| } |
| |
| if (m_isStreaming) |
| return; |
| |
| m_rate = rate; |
| LOG_VERBOSE(Media, "Set Rate to %f", rate); |
| seek(currentTime()); |
| } |
| |
| int MediaPlayerPrivate::dataRate() const |
| { |
| notImplemented(); |
| return 1; |
| } |
| |
| MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const |
| { |
| return m_networkState; |
| } |
| |
| MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const |
| { |
| return m_readyState; |
| } |
| |
| PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const; |
| { |
| RefPtr<TimeRanges> timeRanges = TimeRanges::create(); |
| float loaded = maxTimeLoaded(); |
| if (!m_errorOccured && !m_isStreaming && loaded > 0) |
| timeRanges->add(0, loaded); |
| return timeRanges.release(); |
| } |
| |
| float MediaPlayerPrivate::maxTimeSeekable() const |
| { |
| if (m_errorOccured) |
| return 0.0; |
| |
| // TODO |
| LOG_VERBOSE(Media, "maxTimeSeekable"); |
| if (m_isStreaming) |
| return numeric_limits<float>::infinity(); |
| // infinite duration means live stream |
| return maxTimeLoaded(); |
| } |
| |
| float MediaPlayerPrivate::maxTimeLoaded() const |
| { |
| if (m_errorOccured) |
| return 0.0; |
| |
| // TODO |
| LOG_VERBOSE(Media, "maxTimeLoaded"); |
| notImplemented(); |
| return duration(); |
| } |
| |
| unsigned MediaPlayerPrivate::bytesLoaded() const |
| { |
| notImplemented(); |
| LOG_VERBOSE(Media, "bytesLoaded"); |
| /*if (!m_playBin) |
| return 0; |
| float dur = duration(); |
| float maxTime = maxTimeLoaded(); |
| if (!dur) |
| return 0;*/ |
| |
| return 1;//totalBytes() * maxTime / dur; |
| } |
| |
| bool MediaPlayerPrivate::totalBytesKnown() const |
| { |
| LOG_VERBOSE(Media, "totalBytesKnown"); |
| return totalBytes() > 0; |
| } |
| |
| unsigned MediaPlayerPrivate::totalBytes() const |
| { |
| LOG_VERBOSE(Media, "totalBytes"); |
| if (!m_source) |
| return 0; |
| |
| if (m_errorOccured) |
| return 0; |
| |
| GstFormat fmt = GST_FORMAT_BYTES; |
| gint64 length = 0; |
| gst_element_query_duration(m_source, &fmt, &length); |
| |
| return length; |
| } |
| |
| void MediaPlayerPrivate::cancelLoad() |
| { |
| notImplemented(); |
| } |
| |
| void MediaPlayerPrivate::updateStates() |
| { |
| // There is no (known) way to get such level of information about |
| // the state of GStreamer, therefore, when in PAUSED state, |
| // we are sure we can display the first frame and go to play |
| |
| 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)); |
| |
| if (state == GST_STATE_READY) { |
| m_readyState = MediaPlayer::HaveEnoughData; |
| } else if (state == GST_STATE_PAUSED) |
| m_readyState = MediaPlayer::HaveEnoughData; |
| |
| if (state == GST_STATE_PLAYING) |
| m_paused = false; |
| else |
| m_paused = true; |
| |
| if (m_seeking) { |
| shouldUpdateAfterSeek = true; |
| m_seeking = false; |
| } |
| |
| m_networkState = MediaPlayer::Loaded; |
| |
| g_object_get(m_playBin, "source", &m_source, NULL); |
| if (!m_source) |
| LOG_VERBOSE(Media, "m_source is 0"); |
| 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 |
| return; |
| 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::HaveFutureData; |
| } else if (state == GST_STATE_PAUSED) |
| m_readyState = MediaPlayer::HaveCurrentData; |
| |
| 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 MediaPlayerPrivate::loadStateChanged() |
| { |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivate::rateChanged() |
| { |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivate::sizeChanged() |
| { |
| notImplemented(); |
| } |
| |
| void MediaPlayerPrivate::timeChanged() |
| { |
| updateStates(); |
| m_player->timeChanged(); |
| } |
| |
| void MediaPlayerPrivate::volumeChanged() |
| { |
| m_player->volumeChanged(); |
| } |
| |
| void MediaPlayerPrivate::didEnd() |
| { |
| timeChanged(); |
| } |
| |
| void MediaPlayerPrivate::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 MediaPlayerPrivate::setSize(const IntSize& size) |
| { |
| m_size = size; |
| } |
| |
| void MediaPlayerPrivate::setVisible(bool visible) |
| { |
| m_visible = visible; |
| } |
| |
| void MediaPlayerPrivate::repaint() |
| { |
| m_player->repaint(); |
| } |
| |
| void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& rect) |
| { |
| if (context->paintingDisabled()) |
| return; |
| |
| if (!m_visible) |
| return; |
| |
| //TODO: m_size vs rect? |
| cairo_t* cr = context->platformContext(); |
| |
| cairo_save(cr); |
| cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
| cairo_translate(cr, rect.x(), rect.y()); |
| cairo_rectangle(cr, 0, 0, rect.width(), rect.height()); |
| cairo_set_source_surface(cr, m_surface, 0, 0); |
| cairo_fill(cr); |
| cairo_restore(cr); |
| } |
| |
| static HashSet<String> mimeTypeCache() |
| { |
| if (!gstInitialized) { |
| gst_init(0, NULL); |
| gstInitialized = true; |
| } |
| |
| static HashSet<String> cache; |
| static bool typeListInitialized = false; |
| |
| if (!typeListInitialized) { |
| // These subtypes are already beeing supported by WebKit itself |
| HashSet<String> ignoredApplicationSubtypes; |
| ignoredApplicationSubtypes.add(String("javascript")); |
| ignoredApplicationSubtypes.add(String("ecmascript")); |
| ignoredApplicationSubtypes.add(String("x-javascript")); |
| ignoredApplicationSubtypes.add(String("xml")); |
| ignoredApplicationSubtypes.add(String("xhtml+xml")); |
| ignoredApplicationSubtypes.add(String("rss+xml")); |
| ignoredApplicationSubtypes.add(String("atom+xml")); |
| ignoredApplicationSubtypes.add(String("x-ftp-directory")); |
| ignoredApplicationSubtypes.add(String("x-java-applet")); |
| ignoredApplicationSubtypes.add(String("x-java-bean")); |
| ignoredApplicationSubtypes.add(String("x-java-vm")); |
| ignoredApplicationSubtypes.add(String("x-shockwave-flash")); |
| |
| 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); |
| |
| // Splitting the capability by comma and taking the first part |
| // as capability can be something like "audio/x-wavpack, framed=(boolean)false" |
| GOwnPtr<gchar> capabilityString(gst_caps_to_string(caps)); |
| gchar** capability = g_strsplit(capabilityString.get(), ",", 2); |
| gchar** mimetype = g_strsplit(capability[0], "/", 2); |
| |
| // 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 |
| if (g_str_equal(mimetype[0], "audio") || |
| g_str_equal(mimetype[0], "video") || |
| (g_str_equal(mimetype[0], "application") && |
| !ignoredApplicationSubtypes.contains(String(mimetype[1])))) { |
| cache.add(String(capability[0])); |
| } |
| |
| g_strfreev(capability); |
| g_strfreev(mimetype); |
| } |
| |
| gst_plugin_feature_list_free(factories); |
| typeListInitialized = true; |
| } |
| |
| return cache; |
| } |
| |
| void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) |
| { |
| types = mimeTypeCache(); |
| } |
| |
| MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) |
| { |
| if (mimeTypeCache().contains(type)) |
| return !codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported; |
| return MediaPlayer::IsNotSupported; |
| } |
| |
| bool MediaPlayerPrivate::hasSingleSecurityOrigin() const |
| { |
| return true; |
| } |
| |
| bool MediaPlayerPrivate::supportsFullscreen() const |
| { |
| return true; |
| } |
| |
| void MediaPlayerPrivate::createGSTPlayBin(String url) |
| { |
| ASSERT(!m_playBin); |
| m_playBin = gst_element_factory_make("playbin2", "play"); |
| |
| GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin)); |
| gst_bus_add_signal_watch(bus); |
| g_signal_connect(bus, "message::error", G_CALLBACK(mediaPlayerPrivateErrorCallback), this); |
| g_signal_connect(bus, "message::eos", G_CALLBACK(mediaPlayerPrivateEOSCallback), this); |
| g_signal_connect(bus, "message::state-changed", G_CALLBACK(mediaPlayerPrivateStateCallback), this); |
| g_signal_connect(bus, "message::buffering", G_CALLBACK(mediaPlayerPrivateBufferingCallback), this); |
| gst_object_unref(bus); |
| |
| g_object_set(G_OBJECT(m_playBin), "uri", url.utf8().data(), NULL); |
| |
| m_videoSink = webkit_video_sink_new(m_surface); |
| g_object_set(m_playBin, "video-sink", m_videoSink, NULL); |
| |
| g_signal_connect(m_videoSink, "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this); |
| |
| setVolume(m_volume); |
| } |
| |
| } |
| |
| #endif |