diff options
Diffstat (limited to 'WebCore/platform/graphics/gtk/MediaPlayerPrivateGStreamer.cpp')
-rw-r--r-- | WebCore/platform/graphics/gtk/MediaPlayerPrivateGStreamer.cpp | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/WebCore/platform/graphics/gtk/MediaPlayerPrivateGStreamer.cpp b/WebCore/platform/graphics/gtk/MediaPlayerPrivateGStreamer.cpp new file mode 100644 index 0000000..1f0cac6 --- /dev/null +++ b/WebCore/platform/graphics/gtk/MediaPlayerPrivateGStreamer.cpp @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2007 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 "VideoSinkGStreamer.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 "Widget.h" +#include <wtf/GOwnPtr.h> + +#include <gdk/gdkx.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> + +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()); + if (err->code == 3) { + LOG_VERBOSE(Media, "File not found"); + MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data); + if (mp) + mp->loadingFailed(); + } else + LOG_VERBOSE(Media, "Error: %d, %s", err->code, err->message); + } + 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; +} + +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_isEndReached(false) + , m_volume(0.5f) + , m_networkState(MediaPlayer::Empty) + , m_readyState(MediaPlayer::DataUnavailable) + , m_startedPlaying(false) + , m_isStreaming(false) + , m_rect(IntRect()) + , m_visible(true) +{ + + static bool gstInitialized = false; + // FIXME: We should pass the arguments from the command line + if (!gstInitialized) { + gst_init(0, NULL); + 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(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::DataUnavailable) { + m_readyState = MediaPlayer::DataUnavailable; + m_player->readyStateChanged(); + } + + createGSTPlayBin(url); + pause(); +} + +void MediaPlayerPrivate::play() +{ + LOG_VERBOSE(Media, "Play"); + // When end reached, rewind for Test video-seek-past-end-playing + if (m_isEndReached) + seek(0); + m_isEndReached = false; + + gst_element_set_state(m_playBin, GST_STATE_PLAYING); + m_startedPlaying = true; +} + +void MediaPlayerPrivate::pause() +{ + LOG_VERBOSE(Media, "Pause"); + gst_element_set_state(m_playBin, GST_STATE_PAUSED); + m_startedPlaying = false; +} + +float MediaPlayerPrivate::duration() +{ + if (!m_playBin) + return 0.0; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 len = 0; + + if (gst_element_query_duration(m_playBin, &fmt, &len)) + LOG_VERBOSE(Media, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(len)); + else + LOG_VERBOSE(Media, "Duration query failed "); + + if ((GstClockTime)len == GST_CLOCK_TIME_NONE) { + m_isStreaming = true; + return numeric_limits<float>::infinity(); + } + return (float) (len / 1000000000.0); + // FIXME: handle 3.14.9.5 properly +} + +float MediaPlayerPrivate::currentTime() const +{ + if (!m_playBin) + return 0; + // Necessary as sometimes, gstreamer return 0:00 at the EOS + if (m_isEndReached) + return m_endTime; + + float ret; + + GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); + if (gst_element_query(m_playBin, query)) { + gint64 position; + gst_query_parse_position(query, NULL, &position); + ret = (float) (position / 1000000000.0); + LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); + } else { + LOG_VERBOSE(Media, "Position query failed..."); + ret = 0.0; + } + 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; + + LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(sec)); + // FIXME: What happens when the seeked position is not available? + 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); +} + +void MediaPlayerPrivate::setEndTime(float time) +{ + if (!m_playBin) + return; + if (m_isStreaming) + return; + if (m_endTime != time) { + m_endTime = time; + GstClockTime start = (GstClockTime)(currentTime() * GST_SECOND); + GstClockTime end = (GstClockTime)(time * GST_SECOND); + LOG_VERBOSE(Media, "setEndTime: %" GST_TIME_FORMAT, GST_TIME_ARGS(end)); + // FIXME: What happens when the seeked position is not available? + if (!gst_element_seek(m_playBin, m_rate, + GST_FORMAT_TIME, + (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, start, + GST_SEEK_TYPE_SET, end )) + LOG_VERBOSE(Media, "Seek to %f failed", time); + } +} + +void MediaPlayerPrivate::startEndPointTimerIfNeeded() +{ + notImplemented(); +} + +void MediaPlayerPrivate::cancelSeek() +{ + notImplemented(); +} + +void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*) +{ + notImplemented(); +} + +bool MediaPlayerPrivate::paused() const +{ + return !m_startedPlaying; +} + +bool MediaPlayerPrivate::seeking() const +{ + return false; +} + +// Returns the size of the video +IntSize MediaPlayerPrivate::naturalSize() +{ + 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() +{ + gint currentVideo = -1; + if (m_playBin) + g_object_get(G_OBJECT(m_playBin), "current-video", ¤tVideo, NULL); + return currentVideo > -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); + if (!gst_element_seek(m_playBin, rate, + GST_FORMAT_TIME, + (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, (GstClockTime) (currentTime() * GST_SECOND), + GST_SEEK_TYPE_SET, (GstClockTime) (m_endTime * GST_SECOND))) + LOG_VERBOSE(Media, "Set Rate to %f failed", rate); +} + +int MediaPlayerPrivate::dataRate() const +{ + notImplemented(); + return 1; +} + +MediaPlayer::NetworkState MediaPlayerPrivate::networkState() +{ + return m_networkState; +} + +MediaPlayer::ReadyState MediaPlayerPrivate::readyState() +{ + return m_readyState; +} + +float MediaPlayerPrivate::maxTimeBuffered() +{ + notImplemented(); + LOG_VERBOSE(Media, "maxTimeBuffered"); + // rtsp streams are not buffered + return m_isStreaming ? 0 : maxTimeLoaded(); +} + +float MediaPlayerPrivate::maxTimeSeekable() +{ + // TODO + LOG_VERBOSE(Media, "maxTimeSeekable"); + if (m_isStreaming) + return numeric_limits<float>::infinity(); + // infinite duration means live stream + return maxTimeLoaded(); +} + +float MediaPlayerPrivate::maxTimeLoaded() +{ + // TODO + LOG_VERBOSE(Media, "maxTimeLoaded"); + notImplemented(); + return duration(); +} + +unsigned MediaPlayerPrivate::bytesLoaded() +{ + 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() +{ + notImplemented(); + LOG_VERBOSE(Media, "totalBytesKnown"); + return totalBytes() > 0; +} + +unsigned MediaPlayerPrivate::totalBytes() +{ + notImplemented(); + LOG_VERBOSE(Media, "totalBytes"); + if (!m_playBin) + return 0; + + if (!m_source) + return 0; + + // Do something with m_source to get the total bytes of the media + + return 100; +} + +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 + + MediaPlayer::NetworkState oldNetworkState = m_networkState; + MediaPlayer::ReadyState oldReadyState = m_readyState; + GstState state; + GstState pending; + + if (!m_playBin) + return; + + GstStateChangeReturn ret = gst_element_get_state (m_playBin, + &state, &pending, 250 * GST_NSECOND); + + 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::CanPlayThrough; + } else if (state == GST_STATE_PAUSED) { + m_readyState = MediaPlayer::CanPlayThrough; + } + if (m_networkState < MediaPlayer::Loaded) + m_networkState = MediaPlayer::Loaded; + + g_object_get(m_playBin, "source", &m_source, NULL); + if (!m_source) + LOG_VERBOSE(Media, "m_source is NULL"); + 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; + break; + 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::CanPlay; + } else if (state == GST_STATE_PAUSED) { + m_readyState = MediaPlayer::CanPlay; + } + if (m_networkState < MediaPlayer::LoadedMetaData) + m_networkState = MediaPlayer::LoadedMetaData; + break; + default: + LOG_VERBOSE(Media, "Else : %d", ret); + break; + } + + if (seeking()) + m_readyState = MediaPlayer::DataUnavailable; + + 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() +{ + m_isEndReached = true; + pause(); + timeChanged(); +} + +void MediaPlayerPrivate::loadingFailed() +{ + if (m_networkState != MediaPlayer::LoadFailed) { + m_networkState = MediaPlayer::LoadFailed; + m_player->networkStateChanged(); + } + if (m_readyState != MediaPlayer::DataUnavailable) { + m_readyState = MediaPlayer::DataUnavailable; + m_player->readyStateChanged(); + } +} + +void MediaPlayerPrivate::setRect(const IntRect& rect) +{ + m_rect = rect; +} + +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_rect 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); +} + +void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) +{ + // FIXME: do the real thing + notImplemented(); + types.add(String("video/x-theora+ogg")); +} + +void MediaPlayerPrivate::createGSTPlayBin(String url) +{ + ASSERT(!m_playBin); + m_playBin = gst_element_factory_make("playbin", "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); + + GstElement* audioSink = gst_element_factory_make("gconfaudiosink", NULL); + m_videoSink = webkit_video_sink_new(m_surface); + + g_object_set(m_playBin, "audio-sink", audioSink, NULL); + g_object_set(m_playBin, "video-sink", m_videoSink, NULL); + + setVolume(m_volume); +} + +} + +#endif + |