diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp')
-rw-r--r-- | Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp | 1294 |
1 files changed, 520 insertions, 774 deletions
diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index 6b69cb006..de27ee07d 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -3,7 +3,7 @@ * 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 + * Copyright (C) 2009, 2010, 2011, 2012, 2013 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 @@ -26,30 +26,19 @@ #if ENABLE(VIDEO) && USE(GSTREAMER) -#include "ColorSpace.h" -#include "Document.h" -#include "Frame.h" -#include "FrameView.h" -#include "GStreamerGWorld.h" #include "GStreamerUtilities.h" #include "GStreamerVersioning.h" -#include "GraphicsContext.h" -#include "GraphicsTypes.h" -#include "ImageGStreamer.h" -#include "ImageOrientation.h" -#include "IntRect.h" #include "KURL.h" +#include "Logging.h" #include "MIMETypeRegistry.h" #include "MediaPlayer.h" #include "NotImplemented.h" #include "SecurityOrigin.h" #include "TimeRanges.h" -#include "VideoSinkGStreamer.h" #include "WebKitWebSourceGStreamer.h" #include <gst/gst.h> -#include <gst/video/video.h> +#include <gst/pbutils/missing-plugins.h> #include <limits> -#include <math.h> #include <wtf/gobject/GOwnPtr.h> #include <wtf/text/CString.h> @@ -87,24 +76,13 @@ static const char* gPlaybinName = "playbin2"; static const gint64 gPercentMax = 100; #endif -GST_DEBUG_CATEGORY_STATIC(webkit_media_player_debug); +GST_DEBUG_CATEGORY_EXTERN(webkit_media_player_debug); #define GST_CAT_DEFAULT webkit_media_player_debug 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); -} - static gboolean mediaPlayerPrivateMessageCallback(GstBus*, GstMessage* message, MediaPlayerPrivateGStreamer* player) { return player->handleMessage(message); @@ -115,32 +93,6 @@ static void mediaPlayerPrivateSourceChangedCallback(GObject*, GParamSpec*, Media player->sourceChanged(); } -static void mediaPlayerPrivateVolumeChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player) -{ - // This is called when playbin receives the notify::volume signal. - player->volumeChanged(); -} - -static gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) -{ - // This is the callback of the timeout source created in ::volumeChanged. - player->notifyPlayerOfVolumeChange(); - return FALSE; -} - -static void mediaPlayerPrivateMuteChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player) -{ - // This is called when playbin receives the notify::mute signal. - player->muteChanged(); -} - -static gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) -{ - // This is the callback of the timeout source created in ::muteChanged. - player->notifyPlayerOfMute(); - return FALSE; -} - static void mediaPlayerPrivateVideoSinkCapsChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player) { player->videoChanged(); @@ -163,6 +115,16 @@ static gboolean mediaPlayerPrivateAudioChangeTimeoutCallback(MediaPlayerPrivateG return FALSE; } +#ifdef GST_API_VERSION_1 +static void setAudioStreamPropertiesCallback(GstChildProxy*, GObject* object, gchar*, + MediaPlayerPrivateGStreamer* player) +#else +static void setAudioStreamPropertiesCallback(GstChildProxy*, GObject* object, MediaPlayerPrivateGStreamer* player) +#endif +{ + player->setAudioStreamProperties(object); +} + static gboolean mediaPlayerPrivateVideoChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player) { // This is the callback of the timeout source created in ::videoChanged. @@ -170,9 +132,36 @@ static gboolean mediaPlayerPrivateVideoChangeTimeoutCallback(MediaPlayerPrivateG return FALSE; } -static void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivateGStreamer* playerPrivate) +static void mediaPlayerPrivatePluginInstallerResultFunction(GstInstallPluginsReturn result, gpointer userData) { - playerPrivate->triggerRepaint(buffer); + MediaPlayerPrivateGStreamer* player = reinterpret_cast<MediaPlayerPrivateGStreamer*>(userData); + player->handlePluginInstallerResult(result); +} + +static GstClockTime toGstClockTime(float time) +{ + // Extract the integer part of the time (seconds) and the fractional part (microseconds). Attempt to + // round the microseconds so no floating point precision is lost and we can perform an accurate seek. + float seconds; + float microSeconds = modf(time, &seconds) * 1000000; + GTimeVal timeValue; + timeValue.tv_sec = static_cast<glong>(seconds); + timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000); + return GST_TIMEVAL_TO_TIME(timeValue); +} + +void MediaPlayerPrivateGStreamer::setAudioStreamProperties(GObject* object) +{ + if (g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink")) + return; + + const char* role = m_player->mediaPlayerClient() && m_player->mediaPlayerClient()->mediaPlayerIsVideo() + ? "video" : "music"; + GstStructure* structure = gst_structure_new("stream-properties", "media.role", G_TYPE_STRING, role, NULL); + g_object_set(object, "stream-properties", structure, NULL); + gst_structure_free(structure); + GOwnPtr<gchar> elementName(gst_element_get_name(GST_ELEMENT(object))); + LOG_MEDIA_MESSAGE("Set media.role as %s at %s", role, elementName.get()); } PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateGStreamer::create(MediaPlayer* player) @@ -210,48 +199,43 @@ bool MediaPlayerPrivateGStreamer::isAvailable() } MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) - : m_player(player) - , m_webkitVideoSink(0) - , m_fpsSink(0) + : MediaPlayerPrivateGStreamerBase(player) , m_source(0) , m_seekTime(0) , m_changingRate(false) , m_endTime(numeric_limits<float>::infinity()) , m_isEndReached(false) - , 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_seekIsPending(false) + , m_timeOfOverlappingSeek(-1) , m_buffering(false) , m_playbackRate(1) , m_errorOccured(false) , m_mediaDuration(0) - , m_startedBuffering(false) + , m_downloadFinished(false) , m_fillTimer(this, &MediaPlayerPrivateGStreamer::fillTimerFired) , m_maxTimeLoaded(0) , m_bufferingPercentage(0) - , m_preload(MediaPlayer::Auto) + , m_preload(player->preload()) , m_delayingLoad(false) , m_mediaDurationKnown(true) , m_maxTimeLoadedAtLastDidLoadingProgress(0) - , m_volumeTimerHandler(0) - , m_muteTimerHandler(0) + , m_volumeAndMuteInitialized(false) , m_hasVideo(false) , m_hasAudio(false) , m_audioTimerHandler(0) , m_videoTimerHandler(0) , m_webkitAudioSink(0) , m_totalBytes(-1) - , m_originalPreloadWasAutoAndWasOverridden(false) + , m_preservesPitch(false) + , m_requestedState(GST_STATE_VOID_PENDING) + , m_missingPlugins(false) { - if (initializeGStreamerAndRegisterWebKitElements()) - createGSTPlayBin(); } MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() @@ -259,34 +243,31 @@ 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; } -#ifndef GST_API_VERSION_1 - if (m_videoSinkBin) { - gst_object_unref(m_videoSinkBin); - m_videoSinkBin = 0; - } -#endif + if (m_autoAudioSink) + g_signal_handlers_disconnect_by_func(G_OBJECT(m_autoAudioSink.get()), + reinterpret_cast<gpointer>(setAudioStreamPropertiesCallback), this); if (m_playBin) { - gst_element_set_state(m_playBin.get(), GST_STATE_NULL); - m_playBin = 0; - } + GRefPtr<GstBus> bus = webkitGstPipelineGetBus(GST_PIPELINE(m_playBin.get())); + ASSERT(bus); + g_signal_handlers_disconnect_by_func(bus.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateMessageCallback), this); + gst_bus_remove_signal_watch(bus.get()); - m_player = 0; + g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateSourceChangedCallback), this); + g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateVideoChangedCallback), this); + g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateAudioChangedCallback), this); - if (m_muteTimerHandler) - g_source_remove(m_muteTimerHandler); + gst_element_set_state(m_playBin.get(), GST_STATE_NULL); + m_playBin.clear(); + } - if (m_volumeTimerHandler) - g_source_remove(m_volumeTimerHandler); + GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink")); + g_signal_handlers_disconnect_by_func(videoSinkPad.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateVideoSinkCapsChangedCallback), this); if (m_videoTimerHandler) g_source_remove(m_videoTimerHandler); @@ -297,6 +278,8 @@ MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() void MediaPlayerPrivateGStreamer::load(const String& url) { + if (!initializeGStreamerAndRegisterWebKitElements()) + return; KURL kurl(KURL(), url); String cleanUrl(url); @@ -305,10 +288,15 @@ void MediaPlayerPrivateGStreamer::load(const String& url) if (kurl.isLocalFile()) cleanUrl = cleanUrl.substring(0, kurl.pathEnd()); + if (!m_playBin) + createGSTPlayBin(); + + ASSERT(m_playBin); + m_url = KURL(KURL(), cleanUrl); g_object_set(m_playBin.get(), "uri", cleanUrl.utf8().data(), NULL); - LOG_MEDIA_MESSAGE("Load %s", cleanUrl.utf8().data()); + INFO_MEDIA_MESSAGE("Load %s", cleanUrl.utf8().data()); if (m_preload == MediaPlayer::None) { LOG_MEDIA_MESSAGE("Delaying load."); @@ -321,19 +309,29 @@ void MediaPlayerPrivateGStreamer::load(const String& url) m_player->networkStateChanged(); m_readyState = MediaPlayer::HaveNothing; m_player->readyStateChanged(); - - // GStreamer needs to have the pipeline set to a paused state to - // start providing anything useful. - gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED); + m_volumeAndMuteInitialized = false; if (!m_delayingLoad) commitLoad(); } +#if ENABLE(MEDIA_SOURCE) +void MediaPlayerPrivateGStreamer::load(const String& url, PassRefPtr<MediaSource>) +{ + notImplemented(); +} +#endif + void MediaPlayerPrivateGStreamer::commitLoad() { ASSERT(!m_delayingLoad); LOG_MEDIA_MESSAGE("Committing load."); + + // GStreamer needs to have the pipeline set to a paused state to + // start providing anything useful. + gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED); + + setDownloadBuffering(); updateStates(); } @@ -348,30 +346,26 @@ float MediaPlayerPrivateGStreamer::playbackPosition() const return m_seekTime; if (m_mediaDuration) return m_mediaDuration; + return 0; } - float ret = 0.0f; - - GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); - if (!gst_element_query(m_playBin.get(), query)) { - LOG_MEDIA_MESSAGE("Position query failed..."); - gst_query_unref(query); - return ret; - } - - gint64 position; - gst_query_parse_position(query, 0, &position); + // Position is only available if no async state change is going on and the state is either paused or playing. + gint64 position = GST_CLOCK_TIME_NONE; + GstQuery* query= gst_query_new_position(GST_FORMAT_TIME); + if (gst_element_query(m_playBin.get(), query)) + 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<double>(position) / GST_SECOND; + float result = 0.0f; + if (static_cast<GstClockTime>(position) != GST_CLOCK_TIME_NONE) + result = static_cast<double>(position) / GST_SECOND; + else if (m_canFallBackToLastFinishedSeekPositon) + result = m_seekTime; LOG_MEDIA_MESSAGE("Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); gst_query_unref(query); - return ret; + return result; } bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState) @@ -382,9 +376,14 @@ bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState) GstState pending; gst_element_get_state(m_playBin.get(), ¤tState, &pending, 0); - LOG_MEDIA_MESSAGE("Current state: %s, pending: %s", gst_element_state_get_name(currentState), gst_element_state_get_name(pending)); - if (currentState == newState || pending == newState) + if (currentState == newState || pending == newState) { + LOG_MEDIA_MESSAGE("Rejected state change to %s from %s with %s pending", gst_element_state_get_name(newState), + gst_element_state_get_name(currentState), gst_element_state_get_name(pending)); return true; + } + + LOG_MEDIA_MESSAGE("Changing state change to %s from %s with %s pending", gst_element_state_get_name(newState), + gst_element_state_get_name(currentState), gst_element_state_get_name(pending)); GstStateChangeReturn setStateResult = gst_element_set_state(m_playBin.get(), newState); GstState pausedOrPlaying = newState == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING; @@ -397,9 +396,7 @@ bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState) void MediaPlayerPrivateGStreamer::prepareToPlay() { - m_isEndReached = false; - m_seeking = false; - + m_preload = MediaPlayer::Auto; if (m_delayingLoad) { m_delayingLoad = false; commitLoad(); @@ -410,17 +407,22 @@ void MediaPlayerPrivateGStreamer::play() { if (changePipelineState(GST_STATE_PLAYING)) { m_isEndReached = false; + m_delayingLoad = false; + m_preload = MediaPlayer::Auto; + setDownloadBuffering(); LOG_MEDIA_MESSAGE("Play"); } } void MediaPlayerPrivateGStreamer::pause() { - if (m_isEndReached) + GstState currentState, pendingState; + gst_element_get_state(m_playBin.get(), ¤tState, &pendingState, 0); + if (currentState < GST_STATE_PAUSED && pendingState <= GST_STATE_PAUSED) return; if (changePipelineState(GST_STATE_PAUSED)) - LOG_MEDIA_MESSAGE("Pause"); + INFO_MEDIA_MESSAGE("Pause"); } float MediaPlayerPrivateGStreamer::duration() const @@ -453,7 +455,8 @@ float MediaPlayerPrivateGStreamer::duration() const LOG_MEDIA_MESSAGE("Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(timeLength)); - return static_cast<double>(timeLength) / GST_SECOND; + m_mediaDuration = static_cast<double>(timeLength) / GST_SECOND; + return m_mediaDuration; // FIXME: handle 3.14.9.5 properly } @@ -477,7 +480,6 @@ float MediaPlayerPrivateGStreamer::currentTime() const return 0.0f; return playbackPosition(); - } void MediaPlayerPrivateGStreamer::seek(float time) @@ -488,35 +490,51 @@ void MediaPlayerPrivateGStreamer::seek(float time) if (m_errorOccured) return; - LOG_MEDIA_MESSAGE("Seek attempt to %f secs", time); + INFO_MEDIA_MESSAGE("[Seek] seek attempt to %f secs", time); // Avoid useless seeking. if (time == currentTime()) return; - // Extract the integer part of the time (seconds) and the - // fractional part (microseconds). Attempt to round the - // microseconds so no floating point precision is lost and we can - // perform an accurate seek. - float seconds; - float microSeconds = modf(time, &seconds) * 1000000; - GTimeVal timeValue; - timeValue.tv_sec = static_cast<glong>(seconds); - timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000); + if (isLiveStream()) + return; - GstClockTime clockTime = GST_TIMEVAL_TO_TIME(timeValue); - LOG_MEDIA_MESSAGE("Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(clockTime)); - - if (!gst_element_seek(m_playBin.get(), m_player->rate(), - GST_FORMAT_TIME, - (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), - GST_SEEK_TYPE_SET, clockTime, - GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) - LOG_MEDIA_MESSAGE("Seek to %f failed", time); - else { - m_seeking = true; - m_seekTime = time; + GstClockTime clockTime = toGstClockTime(time); + INFO_MEDIA_MESSAGE("[Seek] seeking to %" GST_TIME_FORMAT " (%f)", GST_TIME_ARGS(clockTime), time); + + if (m_seeking) { + m_timeOfOverlappingSeek = time; + if (m_seekIsPending) { + m_seekTime = time; + return; + } + } + + GstState state; + GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, 0, 0); + if (getStateResult == GST_STATE_CHANGE_FAILURE || getStateResult == GST_STATE_CHANGE_NO_PREROLL) { + LOG_MEDIA_MESSAGE("[Seek] cannot seek, current state change is %s", gst_element_state_change_return_get_name(getStateResult)); + return; } + if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED || m_isEndReached) { + m_seekIsPending = true; + if (m_isEndReached) { + LOG_MEDIA_MESSAGE("[Seek] reset pipeline"); + m_resetPipeline = true; + changePipelineState(GST_STATE_PAUSED); + } + } else { + // We can seek now. + if (!gst_element_seek(m_playBin.get(), m_player->rate(), GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, clockTime, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { + LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", time); + return; + } + } + + m_seeking = true; + m_seekTime = time; + m_isEndReached = false; } bool MediaPlayerPrivateGStreamer::paused() const @@ -536,66 +554,6 @@ bool MediaPlayerPrivateGStreamer::seeking() const return m_seeking; } -// Returns the size of the video -IntSize MediaPlayerPrivateGStreamer::naturalSize() const -{ - if (!hasVideo()) - return IntSize(); - - if (!m_videoSize.isEmpty()) - return m_videoSize; - - GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get()); - if (!caps) - return IntSize(); - - - // 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 this fails the - // video-sink has likely not yet negotiated its caps. - int pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride; - IntSize originalSize; - GstVideoFormat format; - if (!getVideoSizeAndFormatFromCaps(caps.get(), originalSize, format, pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride)) - return IntSize(); - - LOG_MEDIA_MESSAGE("Original video size: %dx%d", originalSize.width(), originalSize.height()); - LOG_MEDIA_MESSAGE("Pixel aspect ratio: %d/%d", pixelAspectRatioNumerator, pixelAspectRatioDenominator); - - // Calculate DAR based on PAR and video size. - int displayWidth = originalSize.width() * pixelAspectRatioNumerator; - int displayHeight = originalSize.height() * pixelAspectRatioDenominator; - - // Divide display width and height by their GCD to avoid possible overflows. - int 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. - guint64 width = 0, height = 0; - if (!(originalSize.height() % displayHeight)) { - LOG_MEDIA_MESSAGE("Keeping video original height"); - width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight); - height = static_cast<guint64>(originalSize.height()); - } else if (!(originalSize.width() % displayWidth)) { - LOG_MEDIA_MESSAGE("Keeping video original width"); - height = gst_util_uint64_scale_int(originalSize.width(), displayHeight, displayWidth); - width = static_cast<guint64>(originalSize.width()); - } else { - LOG_MEDIA_MESSAGE("Approximating while keeping original video height"); - width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight); - height = static_cast<guint64>(originalSize.height()); - } - - LOG_MEDIA_MESSAGE("Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height); - m_videoSize = IntSize(static_cast<int>(width), static_cast<int>(height)); - return m_videoSize; -} - void MediaPlayerPrivateGStreamer::videoChanged() { if (m_videoTimerHandler) @@ -636,37 +594,6 @@ void MediaPlayerPrivateGStreamer::notifyPlayerOfAudio() m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player); } -void MediaPlayerPrivateGStreamer::setVolume(float volume) -{ - if (!m_playBin) - return; - - gst_stream_volume_set_volume(GST_STREAM_VOLUME(m_playBin.get()), GST_STREAM_VOLUME_FORMAT_CUBIC, - static_cast<double>(volume)); -} - -void MediaPlayerPrivateGStreamer::notifyPlayerOfVolumeChange() -{ - m_volumeTimerHandler = 0; - - if (!m_player || !m_playBin) - return; - double volume; - volume = gst_stream_volume_get_volume(GST_STREAM_VOLUME(m_playBin.get()), GST_STREAM_VOLUME_FORMAT_CUBIC); - // get_volume() can return values superior to 1.0 if the user - // applies software user gain via third party application (GNOME - // volume control for instance). - volume = CLAMP(volume, 0.0, 1.0); - 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. @@ -697,11 +624,11 @@ void MediaPlayerPrivateGStreamer::setRate(float rate) gint64 start, end; bool mute = false; - LOG_MEDIA_MESSAGE("Set Rate to %f", rate); + INFO_MEDIA_MESSAGE("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); + // Mute the sound if the playback rate is too extreme and + // audio pitch is not adjusted. + mute = (!m_preservesPitch && (rate < 0.8 || rate > 2)); start = currentPosition; end = GST_CLOCK_TIME_NONE; } else { @@ -716,24 +643,19 @@ void MediaPlayerPrivateGStreamer::setRate(float rate) end = currentPosition; } - LOG_MEDIA_MESSAGE("Need to mute audio: %d", (int) mute); + INFO_MEDIA_MESSAGE("Need to mute audio?: %d", (int) mute); if (!gst_element_seek(m_playBin.get(), rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, end)) - LOG_MEDIA_MESSAGE("Set rate to %f failed", rate); + ERROR_MEDIA_MESSAGE("Set rate to %f failed", rate); else g_object_set(m_playBin.get(), "mute", mute, NULL); } -MediaPlayer::NetworkState MediaPlayerPrivateGStreamer::networkState() const -{ - return m_networkState; -} - -MediaPlayer::ReadyState MediaPlayerPrivateGStreamer::readyState() const +void MediaPlayerPrivateGStreamer::setPreservesPitch(bool preservesPitch) { - return m_readyState; + m_preservesPitch = preservesPitch; } PassRefPtr<TimeRanges> MediaPlayerPrivateGStreamer::buffered() const @@ -744,7 +666,7 @@ PassRefPtr<TimeRanges> MediaPlayerPrivateGStreamer::buffered() const #if GST_CHECK_VERSION(0, 10, 31) float mediaDuration(duration()); - if (!mediaDuration || isinf(mediaDuration)) + if (!mediaDuration || std::isinf(mediaDuration)) return timeRanges.release(); GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT); @@ -784,6 +706,9 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) bool issueError = true; bool attemptNextLocation = false; const GstStructure* structure = gst_message_get_structure(message); + GstState requestedState, currentState; + + m_canFallBackToLastFinishedSeekPositon = false; if (structure) { const gchar* messageTypeName = gst_structure_get_name(structure); @@ -796,13 +721,18 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) } } - LOG_MEDIA_MESSAGE("Message received from element %s", GST_MESSAGE_SRC_NAME(message)); + // We ignore state changes from internal elements. They are forwarded to playbin2 anyway. + bool messageSourceIsPlaybin = GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get()); + + LOG_MEDIA_MESSAGE("Message %s received from element %s", GST_MESSAGE_TYPE_NAME(message), GST_MESSAGE_SRC_NAME(message)); switch (GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_ERROR: if (m_resetPipeline) break; + if (m_missingPlugins) + break; gst_message_parse_error(message, &err.outPtr(), &debug.outPtr()); - LOG_MEDIA_MESSAGE("Error %d: %s (url=%s)", err->code, err->message, m_url.string().utf8().data()); + ERROR_MEDIA_MESSAGE("Error %d: %s (url=%s)", err->code, err->message, m_url.string().utf8().data()); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, "webkit-video.error"); @@ -818,7 +748,7 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) // this case the HTMLMediaElement will emit a stalled // event. if (err->code == GST_STREAM_ERROR_TYPE_NOT_FOUND) { - LOG_MEDIA_MESSAGE("Decode error, let the Media element emit a stalled event."); + ERROR_MEDIA_MESSAGE("Decode error, let the Media element emit a stalled event."); break; } error = MediaPlayer::DecodeError; @@ -832,33 +762,26 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) loadingFailed(error); break; case GST_MESSAGE_EOS: - LOG_MEDIA_MESSAGE("End of Stream"); 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 (m_delayingLoad) { - LOG_MEDIA_MESSAGE("Media load has been delayed. Ignoring state changes for now"); + case GST_MESSAGE_ASYNC_DONE: + if (!messageSourceIsPlaybin || m_delayingLoad) break; - } - - // Ignore state changes from internal elements. They are - // forwarded to playbin2 anyway. - if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get())) { - updateStates(); - - // Construct a filename for the graphviz dot file output. - GstState oldState, newState; - gst_message_parse_state_changed(message, &oldState, &newState, 0); + asyncStateChangeDone(); + break; + case GST_MESSAGE_STATE_CHANGED: { + if (!messageSourceIsPlaybin || m_delayingLoad) + break; + updateStates(); - CString dotFileName = String::format("webkit-video.%s_%s", - gst_element_state_get_name(oldState), - gst_element_state_get_name(newState)).utf8(); + // Construct a filename for the graphviz dot file output. + GstState newState; + gst_message_parse_state_changed(message, ¤tState, &newState, 0); + CString dotFileName = String::format("webkit-video.%s_%s", gst_element_state_get_name(currentState), gst_element_state_get_name(newState)).utf8(); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data()); - GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data()); - } break; + } case GST_MESSAGE_BUFFERING: processBufferingStats(message); break; @@ -867,8 +790,28 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) #else case GST_MESSAGE_DURATION: #endif - LOG_MEDIA_MESSAGE("Duration changed"); - durationChanged(); + if (messageSourceIsPlaybin) + durationChanged(); + break; + case GST_MESSAGE_REQUEST_STATE: + gst_message_parse_request_state(message, &requestedState); + gst_element_get_state(m_playBin.get(), ¤tState, NULL, 250); + if (requestedState < currentState) { + GOwnPtr<gchar> elementName(gst_element_get_name(GST_ELEMENT(message))); + INFO_MEDIA_MESSAGE("Element %s requested state change to %s", elementName.get(), + gst_element_state_get_name(requestedState)); + m_requestedState = requestedState; + changePipelineState(requestedState); + } + break; + case GST_MESSAGE_ELEMENT: + if (gst_is_missing_plugin_message(message)) { + gchar* detail = gst_missing_plugin_message_get_installer_detail(message); + gchar* detailArray[2] = {detail, 0}; + GstInstallPluginsReturn result = gst_install_plugins_async(detailArray, 0, mediaPlayerPrivatePluginInstallerResultFunction, this); + m_missingPlugins = result == GST_INSTALL_PLUGINS_STARTED_OK; + g_free(detail); + } break; default: LOG_MEDIA_MESSAGE("Unhandled GStreamer message type: %s", @@ -878,35 +821,24 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) return TRUE; } +void MediaPlayerPrivateGStreamer::handlePluginInstallerResult(GstInstallPluginsReturn result) +{ + m_missingPlugins = false; + if (result == GST_INSTALL_PLUGINS_SUCCESS) { + gst_element_set_state(m_playBin.get(), GST_STATE_READY); + gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED); + } +} + 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_MEDIA_MESSAGE("[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_MEDIA_MESSAGE("[Buffering] Starting on-disk buffering."); - - m_startedBuffering = true; - - if (m_fillTimer.isActive()) - m_fillTimer.stop(); - - m_fillTimer.startRepeating(0.2); - } + updateStates(); } void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStreamer>*) @@ -942,7 +874,8 @@ void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStream LOG_MEDIA_MESSAGE("[Buffering] Updated maxTimeLoaded: %f", m_maxTimeLoaded); } - if (fillStatus != 100.0) { + m_downloadFinished = fillStatus == 100.0; + if (!m_downloadFinished) { updateStates(); return; } @@ -951,7 +884,6 @@ void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStream // connection is cut. Buffering is done, remove the fill source // from the main loop. m_fillTimer.stop(); - m_startedBuffering = false; updateStates(); } @@ -962,7 +894,7 @@ float MediaPlayerPrivateGStreamer::maxTimeSeekable() const LOG_MEDIA_MESSAGE("maxTimeSeekable"); // infinite duration means live stream - if (isinf(duration())) + if (std::isinf(duration())) return 0.0f; return duration(); @@ -974,8 +906,8 @@ float MediaPlayerPrivateGStreamer::maxTimeLoaded() const return 0.0f; float loaded = m_maxTimeLoaded; - if (!loaded && !m_fillTimer.isActive()) - loaded = duration(); + if (m_isEndReached && m_mediaDuration) + loaded = m_mediaDuration; LOG_MEDIA_MESSAGE("maxTimeLoaded: %f", loaded); return loaded; } @@ -1009,7 +941,7 @@ unsigned MediaPlayerPrivateGStreamer::totalBytes() const #else if (gst_element_query_duration(m_source.get(), &fmt, &length)) { #endif - LOG_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length); + INFO_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length); m_totalBytes = static_cast<unsigned>(length); m_isStreaming = !length; return m_totalBytes; @@ -1059,52 +991,12 @@ unsigned MediaPlayerPrivateGStreamer::totalBytes() const gst_iterator_free(iter); - LOG_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length); + INFO_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length); m_totalBytes = static_cast<unsigned>(length); m_isStreaming = !length; return m_totalBytes; } -unsigned MediaPlayerPrivateGStreamer::decodedFrameCount() const -{ - guint64 decodedFrames = 0; - if (m_fpsSink) - g_object_get(m_fpsSink, "frames-rendered", &decodedFrames, NULL); - return static_cast<unsigned>(decodedFrames); -} - -unsigned MediaPlayerPrivateGStreamer::droppedFrameCount() const -{ - guint64 framesDropped = 0; - if (m_fpsSink) - g_object_get(m_fpsSink, "frames-dropped", &framesDropped, NULL); - return static_cast<unsigned>(framesDropped); -} - -unsigned MediaPlayerPrivateGStreamer::audioDecodedByteCount() const -{ - GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES); - gint64 position = 0; - - if (m_webkitAudioSink && gst_element_query(m_webkitAudioSink.get(), query)) - gst_query_parse_position(query, 0, &position); - - gst_query_unref(query); - return static_cast<unsigned>(position); -} - -unsigned MediaPlayerPrivateGStreamer::videoDecodedByteCount() const -{ - GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES); - gint64 position = 0; - - if (gst_element_query(m_webkitVideoSink, query)) - gst_query_parse_position(query, 0, &position); - - gst_query_unref(query); - return static_cast<unsigned>(position); -} - void MediaPlayerPrivateGStreamer::updateAudioSink() { if (!m_playBin) @@ -1117,6 +1009,10 @@ void MediaPlayerPrivateGStreamer::updateAudioSink() } +GstElement* MediaPlayerPrivateGStreamer::audioSink() const +{ + return m_webkitAudioSink.get(); +} void MediaPlayerPrivateGStreamer::sourceChanged() { @@ -1138,6 +1034,33 @@ void MediaPlayerPrivateGStreamer::cancelLoad() gst_element_set_state(m_playBin.get(), GST_STATE_NULL); } +void MediaPlayerPrivateGStreamer::asyncStateChangeDone() +{ + if (!m_playBin || m_errorOccured) + return; + + if (m_seeking) { + if (m_seekIsPending) + updateStates(); + else { + LOG_MEDIA_MESSAGE("[Seek] seeked to %f", m_seekTime); + m_seeking = false; + if (m_timeOfOverlappingSeek != m_seekTime && m_timeOfOverlappingSeek != -1) { + seek(m_timeOfOverlappingSeek); + m_timeOfOverlappingSeek = -1; + return; + } + m_timeOfOverlappingSeek = -1; + + // The pipeline can still have a pending state. In this case a position query will fail. + // Right now we can use m_seekTime as a fallback. + m_canFallBackToLastFinishedSeekPositon = true; + timeChanged(); + } + } else + updateStates(); +} + void MediaPlayerPrivateGStreamer::updateStates() { if (!m_playBin) @@ -1151,104 +1074,99 @@ void MediaPlayerPrivateGStreamer::updateStates() GstState state; GstState pending; - GstStateChangeReturn ret = gst_element_get_state(m_playBin.get(), - &state, &pending, 250 * GST_NSECOND); + GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, &pending, 250 * GST_NSECOND); - bool shouldUpdateAfterSeek = false; - switch (ret) { - case GST_STATE_CHANGE_SUCCESS: - LOG_MEDIA_MESSAGE("State: %s, pending: %s", - gst_element_state_get_name(state), - gst_element_state_get_name(pending)); + bool shouldUpdatePlaybackState = false; + switch (getStateResult) { + case GST_STATE_CHANGE_SUCCESS: { + LOG_MEDIA_MESSAGE("State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); - m_resetPipeline = state <= GST_STATE_READY; + if (state <= GST_STATE_READY) { + m_resetPipeline = true; + m_mediaDuration = 0; + } else { + m_resetPipeline = false; + cacheDuration(); + } + + bool didBuffering = m_buffering; - // Try to figure out ready and network states. - if (state == GST_STATE_READY) { + // Update ready and network states. + switch (state) { + case GST_STATE_NULL: + m_readyState = MediaPlayer::HaveNothing; + m_networkState = MediaPlayer::Empty; + break; + case 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. - if (!m_isEndReached) - cacheDuration(); - } else if ((state == GST_STATE_NULL) || (maxTimeLoaded() == duration())) { - m_networkState = MediaPlayer::Loaded; - m_readyState = MediaPlayer::HaveEnoughData; - } else { - m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; - m_networkState = MediaPlayer::Loading; - } + break; + case GST_STATE_PAUSED: + case GST_STATE_PLAYING: + if (m_buffering) { + if (m_bufferingPercentage == 100) { + LOG_MEDIA_MESSAGE("[Buffering] Complete."); + m_buffering = false; + m_readyState = MediaPlayer::HaveEnoughData; + m_networkState = m_downloadFinished ? MediaPlayer::Idle : MediaPlayer::Loading; + } else { + m_readyState = MediaPlayer::HaveCurrentData; + m_networkState = MediaPlayer::Loading; + } + } else if (m_downloadFinished) { + m_readyState = MediaPlayer::HaveEnoughData; + m_networkState = MediaPlayer::Loaded; + } else { + m_readyState = MediaPlayer::HaveFutureData; + m_networkState = MediaPlayer::Loading; + } - if (m_buffering && state != GST_STATE_READY) { - m_readyState = MediaPlayer::HaveCurrentData; - m_networkState = MediaPlayer::Loading; + break; + default: + ASSERT_NOT_REACHED(); + break; } - // Now let's try to get the states in more detail using - // information from GStreamer, while we sync states where - // needed. + // Sync states where needed. if (state == GST_STATE_PAUSED) { if (!m_webkitAudioSink) updateAudioSink(); - if (m_buffering && m_bufferingPercentage == 100) { - m_buffering = false; - m_bufferingPercentage = 0; - m_readyState = MediaPlayer::HaveEnoughData; - LOG_MEDIA_MESSAGE("[Buffering] Complete."); + if (!m_volumeAndMuteInitialized) { + notifyPlayerOfVolumeChange(); + notifyPlayerOfMute(); + m_volumeAndMuteInitialized = true; + } - if (!m_paused) { - LOG_MEDIA_MESSAGE("[Buffering] Restarting playback."); - gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); - } - } else if (!m_buffering && (currentTime() < duration())) { - m_paused = true; + if (didBuffering && !m_buffering && !m_paused) { + LOG_MEDIA_MESSAGE("[Buffering] Restarting playback."); + gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); } } else if (state == GST_STATE_PLAYING) { - m_readyState = MediaPlayer::HaveEnoughData; m_paused = false; if (m_buffering && !isLiveStream()) { - m_readyState = MediaPlayer::HaveCurrentData; - m_networkState = MediaPlayer::Loading; - LOG_MEDIA_MESSAGE("[Buffering] Pausing stream for buffering."); - gst_element_set_state(m_playBin.get(), 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; + if (m_requestedState == GST_STATE_PAUSED && state == GST_STATE_PAUSED) { + shouldUpdatePlaybackState = true; + LOG_MEDIA_MESSAGE("Requested state change to %s was completed", gst_element_state_get_name(state)); } break; + } case GST_STATE_CHANGE_ASYNC: - LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s", - gst_element_state_get_name(state), - gst_element_state_get_name(pending)); - // Change in progress - - // On-disk buffering was attempted but the media is live. This - // can't work so disable on-disk buffering and reset the - // pipeline. - if (state == GST_STATE_READY && isLiveStream() && m_preload == MediaPlayer::Auto) { - setPreload(MediaPlayer::None); - gst_element_set_state(m_playBin.get(), GST_STATE_NULL); - gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED); - } + LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); + // Change in progress. // A live stream was paused, reset the pipeline. if (state == GST_STATE_PAUSED && pending == GST_STATE_PLAYING && isLiveStream()) { @@ -1256,66 +1174,58 @@ void MediaPlayerPrivateGStreamer::updateStates() gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); } - if (!isLiveStream() && !m_buffering) - return; - - if (m_seeking) { - shouldUpdateAfterSeek = true; - m_seeking = false; - } break; case GST_STATE_CHANGE_FAILURE: - LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s", - gst_element_state_get_name(state), - gst_element_state_get_name(pending)); + LOG_MEDIA_MESSAGE("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_MEDIA_MESSAGE("No preroll: State: %s, pending: %s", - gst_element_state_get_name(state), - gst_element_state_get_name(pending)); + LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); + + // Live pipelines go in PAUSED without prerolling. + m_isStreaming = true; + setDownloadBuffering(); 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.get(), GST_STATE_PLAYING); - } else if (!m_paused) + if (!m_paused) gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); m_networkState = MediaPlayer::Loading; break; default: - LOG_MEDIA_MESSAGE("Else : %d", ret); + LOG_MEDIA_MESSAGE("Else : %d", getStateResult); break; } - if (seeking()) - m_readyState = MediaPlayer::HaveNothing; + m_requestedState = GST_STATE_VOID_PENDING; - if (shouldUpdateAfterSeek) - timeChanged(); + if (shouldUpdatePlaybackState) + m_player->playbackStateChanged(); if (m_networkState != oldNetworkState) { - LOG_MEDIA_MESSAGE("Network State Changed from %u to %u", - oldNetworkState, m_networkState); + LOG_MEDIA_MESSAGE("Network State Changed from %u to %u", oldNetworkState, m_networkState); m_player->networkStateChanged(); } if (m_readyState != oldReadyState) { - LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u", - oldReadyState, m_readyState); + LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u", oldReadyState, m_readyState); m_player->readyStateChanged(); } + + if (m_seekIsPending && getStateResult == GST_STATE_CHANGE_SUCCESS && (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)) { + LOG_MEDIA_MESSAGE("[Seek] committing pending seek to %f", m_seekTime); + m_seekIsPending = false; + m_seeking = gst_element_seek(m_playBin.get(), m_player->rate(), GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), + GST_SEEK_TYPE_SET, toGstClockTime(m_seekTime), GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (!m_seeking) + LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", m_seekTime); + } } void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message) @@ -1375,23 +1285,12 @@ bool MediaPlayerPrivateGStreamer::loadNextLocation() // 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. + KURL baseUrl = gst_uri_is_valid(newLocation) ? KURL() : m_url; + KURL newUrl = KURL(baseUrl, newLocation); - gchar* currentLocation = 0; - g_object_get(m_playBin.get(), "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); + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(m_url); if (securityOrigin->canRequest(newUrl)) { - LOG_MEDIA_MESSAGE("New media url: %s", newUrl.string().utf8().data()); + INFO_MEDIA_MESSAGE("New media url: %s", newUrl.string().utf8().data()); // Reset player states. m_networkState = MediaPlayer::Loading; @@ -1408,14 +1307,15 @@ bool MediaPlayerPrivateGStreamer::loadNextLocation() if (state <= GST_STATE_READY) { // Set the new uri and start playing. g_object_set(m_playBin.get(), "uri", newUrl.string().utf8().data(), NULL); + m_url = newUrl; gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); return true; } - } + } else + INFO_MEDIA_MESSAGE("Not allowed to load new media location: %s", newUrl.string().utf8().data()); } m_mediaLocationCurrentIndex--; return false; - } void MediaPlayerPrivateGStreamer::loadStateChanged() @@ -1423,11 +1323,6 @@ void MediaPlayerPrivateGStreamer::loadStateChanged() updateStates(); } -void MediaPlayerPrivateGStreamer::sizeChanged() -{ - notImplemented(); -} - void MediaPlayerPrivateGStreamer::timeChanged() { updateStates(); @@ -1452,33 +1347,25 @@ void MediaPlayerPrivateGStreamer::didEnd() if (!m_player->mediaPlayerClient()->mediaPlayerIsLooping()) { m_paused = true; gst_element_set_state(m_playBin.get(), GST_STATE_NULL); + m_downloadFinished = false; } } void MediaPlayerPrivateGStreamer::cacheDuration() { - // Reset cached media duration - m_mediaDuration = 0; + if (m_mediaDuration || !m_mediaDurationKnown) + return; - // And re-cache it if possible. - GstState state; - gst_element_get_state(m_playBin.get(), &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 (std::isinf(newDuration)) { + // Only pretend that duration is not available if the the query failed in a stable pipeline state. + GstState state; + if (gst_element_get_state(m_playBin.get(), &state, 0, 0) == GST_STATE_CHANGE_SUCCESS && state > GST_STATE_READY) + m_mediaDurationKnown = false; + return; } - if (!isinf(newDuration)) - m_mediaDuration = newDuration; + m_mediaDuration = newDuration; } void MediaPlayerPrivateGStreamer::durationChanged() @@ -1491,47 +1378,6 @@ void MediaPlayerPrivateGStreamer::durationChanged() // HTMLMediaElement. if (previousDuration && m_mediaDuration != previousDuration) m_player->durationChanged(); - - if (m_preload == MediaPlayer::None && m_originalPreloadWasAutoAndWasOverridden) { - m_totalBytes = -1; - if (totalBytes() && !isLiveStream()) { - setPreload(MediaPlayer::Auto); - gst_element_set_state(m_playBin.get(), GST_STATE_NULL); - gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED); - } - } -} - -bool MediaPlayerPrivateGStreamer::supportsMuting() const -{ - return true; -} - -void MediaPlayerPrivateGStreamer::setMuted(bool muted) -{ - if (!m_playBin) - return; - - g_object_set(m_playBin.get(), "mute", muted, NULL); -} - -void MediaPlayerPrivateGStreamer::notifyPlayerOfMute() -{ - m_muteTimerHandler = 0; - - if (!m_player || !m_playBin) - return; - - gboolean muted; - g_object_get(m_playBin.get(), "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) @@ -1547,46 +1393,6 @@ void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState error) } } -void MediaPlayerPrivateGStreamer::setSize(const IntSize& size) -{ - m_size = size; -} - -void MediaPlayerPrivateGStreamer::setVisible(bool) -{ -} - - -void MediaPlayerPrivateGStreamer::triggerRepaint(GstBuffer* buffer) -{ - g_return_if_fail(GST_IS_BUFFER(buffer)); - gst_buffer_replace(&m_buffer, buffer); - m_player->repaint(); -} - -void MediaPlayerPrivateGStreamer::paint(GraphicsContext* context, const IntRect& rect) -{ - if (context->paintingDisabled()) - return; - - if (!m_player->visible()) - return; - - if (!m_buffer) - return; - - GRefPtr<GstCaps> caps = webkitGstGetPadCaps(m_videoSinkPad.get()); - if (!caps) - return; - - RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_buffer, caps.get()); - if (!gstImage) - return; - - context->drawImage(reinterpret_cast<Image*>(gstImage->image().get()), ColorSpaceSRGB, - rect, gstImage->rect(), CompositeCopy, DoNotRespectImageOrientation, false); -} - static HashSet<String> mimeTypeCache() { initializeGStreamerAndRegisterWebKitElements(); @@ -1596,90 +1402,95 @@ static HashSet<String> mimeTypeCache() if (typeListInitialized) return cache; - const char* mimeTypes[] = {"application/ogg", - "application/vnd.apple.mpegurl", - "application/vnd.rn-realmedia", - "application/x-3gp", - "application/x-pn-realaudio", - "audio/3gpp", - "audio/aac", - "audio/flac", - "audio/iLBC-sh", - "audio/midi", - "audio/mobile-xmf", - "audio/mp1", - "audio/mp2", - "audio/mp3", - "audio/mp4", - "audio/mpeg", - "audio/ogg", - "audio/qcelp", - "audio/riff-midi", - "audio/wav", - "audio/webm", - "audio/x-ac3", - "audio/x-aiff", - "audio/x-amr-nb-sh", - "audio/x-amr-wb-sh", - "audio/x-au", - "audio/x-ay", - "audio/x-celt", - "audio/x-dts", - "audio/x-flac", - "audio/x-gbs", - "audio/x-gsm", - "audio/x-gym", - "audio/x-imelody", - "audio/x-ircam", - "audio/x-kss", - "audio/x-m4a", - "audio/x-mod", - "audio/x-mp3", - "audio/x-mpeg", - "audio/x-musepack", - "audio/x-nist", - "audio/x-nsf", - "audio/x-paris", - "audio/x-sap", - "audio/x-sbc", - "audio/x-sds", - "audio/x-shorten", - "audio/x-sid", - "audio/x-spc", - "audio/x-speex", - "audio/x-svx", - "audio/x-ttafile", - "audio/x-vgm", - "audio/x-voc", - "audio/x-vorbis+ogg", - "audio/x-w64", - "audio/x-wav", - "audio/x-wavpack", - "audio/x-wavpack-correction", - "video/3gpp", - "video/mj2", - "video/mp4", - "video/mpeg", - "video/mpegts", - "video/ogg", - "video/quicktime", - "video/vivo", - "video/webm", - "video/x-cdxa", - "video/x-dirac", - "video/x-dv", - "video/x-fli", - "video/x-flv", - "video/x-h263", - "video/x-ivf", - "video/x-m4v", - "video/x-matroska", - "video/x-mng", - "video/x-ms-asf", - "video/x-msvideo", - "video/x-mve", - "video/x-nuv", - "video/x-vcd"}; + + const char* mimeTypes[] = { + "application/ogg", + "application/vnd.apple.mpegurl", + "application/vnd.rn-realmedia", + "application/x-3gp", + "application/x-pn-realaudio", + "audio/3gpp", + "audio/aac", + "audio/flac", + "audio/iLBC-sh", + "audio/midi", + "audio/mobile-xmf", + "audio/mp1", + "audio/mp2", + "audio/mp3", + "audio/mp4", + "audio/mpeg", + "audio/ogg", + "audio/opus", + "audio/qcelp", + "audio/riff-midi", + "audio/speex", + "audio/wav", + "audio/webm", + "audio/x-ac3", + "audio/x-aiff", + "audio/x-amr-nb-sh", + "audio/x-amr-wb-sh", + "audio/x-au", + "audio/x-ay", + "audio/x-celt", + "audio/x-dts", + "audio/x-flac", + "audio/x-gbs", + "audio/x-gsm", + "audio/x-gym", + "audio/x-imelody", + "audio/x-ircam", + "audio/x-kss", + "audio/x-m4a", + "audio/x-mod", + "audio/x-mp3", + "audio/x-mpeg", + "audio/x-musepack", + "audio/x-nist", + "audio/x-nsf", + "audio/x-paris", + "audio/x-sap", + "audio/x-sbc", + "audio/x-sds", + "audio/x-shorten", + "audio/x-sid", + "audio/x-spc", + "audio/x-speex", + "audio/x-svx", + "audio/x-ttafile", + "audio/x-vgm", + "audio/x-voc", + "audio/x-vorbis+ogg", + "audio/x-w64", + "audio/x-wav", + "audio/x-wavpack", + "audio/x-wavpack-correction", + "video/3gpp", + "video/mj2", + "video/mp4", + "video/mpeg", + "video/mpegts", + "video/ogg", + "video/quicktime", + "video/vivo", + "video/webm", + "video/x-cdxa", + "video/x-dirac", + "video/x-dv", + "video/x-fli", + "video/x-flv", + "video/x-h263", + "video/x-ivf", + "video/x-m4v", + "video/x-matroska", + "video/x-mng", + "video/x-ms-asf", + "video/x-msvideo", + "video/x-mve", + "video/x-nuv", + "video/x-vcd" + }; for (unsigned i = 0; i < (sizeof(mimeTypes) / sizeof(*mimeTypes)); ++i) cache.add(String(mimeTypes[i])); @@ -1704,59 +1515,37 @@ MediaPlayer::SupportsType MediaPlayerPrivateGStreamer::supportsType(const String return MediaPlayer::IsNotSupported; } -bool MediaPlayerPrivateGStreamer::hasSingleSecurityOrigin() const +void MediaPlayerPrivateGStreamer::setDownloadBuffering() { - return true; -} - -bool MediaPlayerPrivateGStreamer::supportsFullscreen() const -{ -#if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1050 - // See <rdar://problem/7389945> - return false; -#else - return true; -#endif -} - -PlatformMedia MediaPlayerPrivateGStreamer::platformMedia() const -{ - PlatformMedia p; -#ifndef GST_API_VERSION_1 - p.type = PlatformMedia::GStreamerGWorldType; - p.media.gstreamerGWorld = m_gstGWorld.get(); -#endif - return p; -} - -MediaPlayer::MovieLoadType MediaPlayerPrivateGStreamer::movieLoadType() const -{ - if (m_readyState == MediaPlayer::HaveNothing) - return MediaPlayer::Unknown; - - if (isLiveStream()) - return MediaPlayer::LiveStream; - - return MediaPlayer::Download; -} - -void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload) -{ - m_originalPreloadWasAutoAndWasOverridden = m_preload != preload && m_preload == MediaPlayer::Auto; - - m_preload = preload; - - ASSERT(m_playBin); + if (!m_playBin) + return; GstPlayFlags flags; g_object_get(m_playBin.get(), "flags", &flags, NULL); - if (m_preload == MediaPlayer::Auto) { + + // We don't want to stop downloading if we already started it. + if (flags & GST_PLAY_FLAG_DOWNLOAD && m_readyState > MediaPlayer::HaveNothing && !m_resetPipeline) + return; + + bool shouldDownload = !isLiveStream() && m_preload == MediaPlayer::Auto; + if (shouldDownload) { LOG_MEDIA_MESSAGE("Enabling on-disk buffering"); g_object_set(m_playBin.get(), "flags", flags | GST_PLAY_FLAG_DOWNLOAD, NULL); + m_fillTimer.startRepeating(0.2); } else { LOG_MEDIA_MESSAGE("Disabling on-disk buffering"); g_object_set(m_playBin.get(), "flags", flags & ~GST_PLAY_FLAG_DOWNLOAD, NULL); + m_fillTimer.stop(); } +} + +void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload) +{ + if (preload == MediaPlayer::Auto && isLiveStream()) + return; + + m_preload = preload; + setDownloadBuffering(); if (m_delayingLoad && m_preload != MediaPlayer::None) { m_delayingLoad = false; @@ -1764,121 +1553,78 @@ void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload) } } -void MediaPlayerPrivateGStreamer::createGSTPlayBin() +void MediaPlayerPrivateGStreamer::createAudioSink() { - ASSERT(!m_playBin); - - // gst_element_factory_make() returns a floating reference so - // we should not adopt. - m_playBin = gst_element_factory_make(gPlaybinName, "play"); - -#ifndef GST_API_VERSION_1 - m_gstGWorld = GStreamerGWorld::createGWorld(m_playBin.get()); -#endif - - GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin.get())); - gst_bus_add_signal_watch(bus); - g_signal_connect(bus, "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this); - gst_object_unref(bus); - - g_object_set(m_playBin.get(), "mute", m_player->muted(), NULL); + // Construct audio sink if pitch preserving is enabled. + if (!m_preservesPitch) + return; - g_signal_connect(m_playBin.get(), "notify::volume", G_CALLBACK(mediaPlayerPrivateVolumeChangedCallback), this); - g_signal_connect(m_playBin.get(), "notify::source", G_CALLBACK(mediaPlayerPrivateSourceChangedCallback), this); - g_signal_connect(m_playBin.get(), "notify::mute", G_CALLBACK(mediaPlayerPrivateMuteChangedCallback), this); - g_signal_connect(m_playBin.get(), "video-changed", G_CALLBACK(mediaPlayerPrivateVideoChangedCallback), this); - g_signal_connect(m_playBin.get(), "audio-changed", G_CALLBACK(mediaPlayerPrivateAudioChangedCallback), this); + if (!m_playBin) + return; -#ifndef GST_API_VERSION_1 - m_webkitVideoSink = webkitVideoSinkNew(m_gstGWorld.get()); -#else - m_webkitVideoSink = webkitVideoSinkNew(); -#endif - m_videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink, "sink")); + GstElement* scale = gst_element_factory_make("scaletempo", 0); + if (!scale) { + GST_WARNING("Failed to create scaletempo"); + return; + } - g_signal_connect(m_webkitVideoSink, "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this); + GstElement* convert = gst_element_factory_make("audioconvert", 0); + GstElement* resample = gst_element_factory_make("audioresample", 0); + GstElement* sink = gst_element_factory_make("autoaudiosink", 0); + m_autoAudioSink = sink; -#ifndef GST_API_VERSION_1 - m_videoSinkBin = gst_bin_new("sink"); + g_signal_connect(sink, "child-added", G_CALLBACK(setAudioStreamPropertiesCallback), this); - GstElement* videoTee = gst_element_factory_make("tee", "videoTee"); - GstElement* queue = gst_element_factory_make("queue", 0); + GstElement* audioSink = gst_bin_new("audio-sink"); + gst_bin_add_many(GST_BIN(audioSink), scale, convert, resample, sink, NULL); - // Take ownership. - gst_object_ref_sink(m_videoSinkBin); + if (!gst_element_link_many(scale, convert, resample, sink, NULL)) { + GST_WARNING("Failed to link audio sink elements"); + gst_object_unref(audioSink); + return; + } - // 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 + GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(scale, "sink")); + gst_element_add_pad(audioSink, gst_ghost_pad_new("sink", pad.get())); - gst_bin_add_many(GST_BIN(m_videoSinkBin), videoTee, queue, NULL); + g_object_set(m_playBin.get(), "audio-sink", audioSink, NULL); +} - // Link a new src pad from tee to queue1. - GRefPtr<GstPad> srcPad = adoptGRef(gst_element_get_request_pad(videoTee, "src%d")); - GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(queue, "sink")); - gst_pad_link(srcPad.get(), sinkPad.get()); -#endif +void MediaPlayerPrivateGStreamer::createGSTPlayBin() +{ + ASSERT(!m_playBin); - GstElement* actualVideoSink = 0; - m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink"); - if (m_fpsSink) { - // The verbose property has been added in -bad 0.10.22. Making - // this whole code depend on it because we don't want - // fpsdiplaysink to spit data on stdout. - GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(m_fpsSink)->elementfactory); - if (gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 22)) { - g_object_set(m_fpsSink, "silent", TRUE , NULL); - - // Turn off text overlay unless logging is enabled. -#if LOG_DISABLED - g_object_set(m_fpsSink, "text-overlay", FALSE , NULL); -#else - WTFLogChannel* channel = getChannelFromName("Media"); - if (channel->state != WTFLogChannelOn) - g_object_set(m_fpsSink, "text-overlay", FALSE , NULL); -#endif // LOG_DISABLED - - if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink), "video-sink")) { - g_object_set(m_fpsSink, "video-sink", m_webkitVideoSink, NULL); -#ifndef GST_API_VERSION_1 - gst_bin_add(GST_BIN(m_videoSinkBin), m_fpsSink); -#endif - actualVideoSink = m_fpsSink; - } else - m_fpsSink = 0; - } else - m_fpsSink = 0; - } + // gst_element_factory_make() returns a floating reference so + // we should not adopt. + m_playBin = gst_element_factory_make(gPlaybinName, "play"); + setStreamVolumeElement(GST_STREAM_VOLUME(m_playBin.get())); - if (!m_fpsSink) { -#ifndef GST_API_VERSION_1 - gst_bin_add(GST_BIN(m_videoSinkBin), m_webkitVideoSink); -#endif - actualVideoSink = m_webkitVideoSink; - } + GRefPtr<GstBus> bus = webkitGstPipelineGetBus(GST_PIPELINE(m_playBin.get())); + gst_bus_add_signal_watch(bus.get()); + g_signal_connect(bus.get(), "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this); - ASSERT(actualVideoSink); + g_object_set(m_playBin.get(), "mute", m_player->muted(), NULL); -#ifndef GST_API_VERSION_1 - // Faster elements linking. - gst_element_link_pads_full(queue, "src", actualVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING); + g_signal_connect(m_playBin.get(), "notify::source", G_CALLBACK(mediaPlayerPrivateSourceChangedCallback), this); + g_signal_connect(m_playBin.get(), "video-changed", G_CALLBACK(mediaPlayerPrivateVideoChangedCallback), this); + g_signal_connect(m_playBin.get(), "audio-changed", G_CALLBACK(mediaPlayerPrivateAudioChangedCallback), this); - // Add a ghostpad to the bin so it can proxy to tee. - GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(videoTee, "sink")); - gst_element_add_pad(m_videoSinkBin, gst_ghost_pad_new("sink", pad.get())); + GstElement* videoElement = createVideoSink(m_playBin.get()); - // Set the bin as video sink of playbin. - g_object_set(m_playBin.get(), "video-sink", m_videoSinkBin, NULL); -#else - g_object_set(m_playBin.get(), "video-sink", actualVideoSink, NULL); -#endif + g_object_set(m_playBin.get(), "video-sink", videoElement, NULL); - GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink, "sink")); + GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink")); if (videoSinkPad) g_signal_connect(videoSinkPad.get(), "notify::caps", G_CALLBACK(mediaPlayerPrivateVideoSinkCapsChangedCallback), this); + createAudioSink(); +} + +void MediaPlayerPrivateGStreamer::simulateAudioInterruption() +{ + GstMessage* message = gst_message_new_request_state(GST_OBJECT(m_playBin.get()), GST_STATE_PAUSED); + gst_element_post_message(m_playBin.get(), message); } } |