| /* |
| * 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 <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 <libgnomevfs/gnome-vfs.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) |
| { |
| GError* err; |
| gchar* debug; |
| |
| gst_message_parse_error(message, &err, &debug); |
| 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); |
| g_error_free(err); |
| g_free(debug); |
| } |
| } |
| 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 |
| |