2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Collabora Ltd. All rights reserved.
4 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5 * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>
6 * Copyright (C) 2009, 2010, 2011, 2012, 2013 Igalia S.L
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * aint with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
25 #include "MediaPlayerPrivateGStreamer.h"
27 #if ENABLE(VIDEO) && USE(GSTREAMER)
29 #include "GStreamerUtilities.h"
30 #include "GStreamerVersioning.h"
33 #include "MIMETypeRegistry.h"
34 #include "MediaPlayer.h"
35 #include "SecurityOrigin.h"
36 #include "TimeRanges.h"
37 #include "WebKitWebSourceGStreamer.h"
40 #include <wtf/gobject/GOwnPtr.h>
41 #include <wtf/text/CString.h>
43 // GstPlayFlags flags from playbin2. It is the policy of GStreamer to
44 // not publicly expose element-specific enums. That's why this
45 // GstPlayFlags enum has been copied here.
47 GST_PLAY_FLAG_VIDEO = 0x00000001,
48 GST_PLAY_FLAG_AUDIO = 0x00000002,
49 GST_PLAY_FLAG_TEXT = 0x00000004,
50 GST_PLAY_FLAG_VIS = 0x00000008,
51 GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010,
52 GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020,
53 GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040,
54 GST_PLAY_FLAG_DOWNLOAD = 0x00000080,
55 GST_PLAY_FLAG_BUFFERING = 0x000000100
58 // gPercentMax is used when parsing buffering ranges with
59 // gst_query_parse_nth_buffering_range as there was a bug in GStreamer
60 // 0.10 that was using 100 instead of GST_FORMAT_PERCENT_MAX. This was
61 // corrected in 1.0. gst_query_parse_buffering_range worked as
62 // expected with GST_FORMAT_PERCENT_MAX in both cases.
63 #ifdef GST_API_VERSION_1
64 static const char* gPlaybinName = "playbin";
65 static const gint64 gPercentMax = GST_FORMAT_PERCENT_MAX;
67 static const char* gPlaybinName = "playbin2";
68 static const gint64 gPercentMax = 100;
71 GST_DEBUG_CATEGORY_STATIC(webkit_media_player_debug);
72 #define GST_CAT_DEFAULT webkit_media_player_debug
78 static gboolean mediaPlayerPrivateMessageCallback(GstBus*, GstMessage* message, MediaPlayerPrivateGStreamer* player)
80 return player->handleMessage(message);
83 static void mediaPlayerPrivateSourceChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
85 player->sourceChanged();
88 static void mediaPlayerPrivateVideoSinkCapsChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
90 player->videoChanged();
93 static void mediaPlayerPrivateVideoChangedCallback(GObject*, MediaPlayerPrivateGStreamer* player)
95 player->videoChanged();
98 static void mediaPlayerPrivateAudioChangedCallback(GObject*, MediaPlayerPrivateGStreamer* player)
100 player->audioChanged();
103 static gboolean mediaPlayerPrivateAudioChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
105 // This is the callback of the timeout source created in ::audioChanged.
106 player->notifyPlayerOfAudio();
110 static gboolean mediaPlayerPrivateVideoChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
112 // This is the callback of the timeout source created in ::videoChanged.
113 player->notifyPlayerOfVideo();
117 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateGStreamer::create(MediaPlayer* player)
119 return adoptPtr(new MediaPlayerPrivateGStreamer(player));
122 void MediaPlayerPrivateGStreamer::registerMediaEngine(MediaEngineRegistrar registrar)
125 registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
128 bool initializeGStreamerAndRegisterWebKitElements()
130 if (!initializeGStreamer())
133 GRefPtr<GstElementFactory> srcFactory = gst_element_factory_find("webkitwebsrc");
135 GST_DEBUG_CATEGORY_INIT(webkit_media_player_debug, "webkitmediaplayer", 0, "WebKit media player");
136 return gst_element_register(0, "webkitwebsrc", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_WEB_SRC);
142 bool MediaPlayerPrivateGStreamer::isAvailable()
144 if (!initializeGStreamerAndRegisterWebKitElements())
147 GRefPtr<GstElementFactory> factory = gst_element_factory_find(gPlaybinName);
151 MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)
152 : MediaPlayerPrivateGStreamerBase(player)
155 , m_changingRate(false)
156 , m_endTime(numeric_limits<float>::infinity())
157 , m_isEndReached(false)
158 , m_isStreaming(false)
159 , m_mediaLocations(0)
160 , m_mediaLocationCurrentIndex(0)
161 , m_resetPipeline(false)
166 , m_errorOccured(false)
168 , m_startedBuffering(false)
169 , m_fillTimer(this, &MediaPlayerPrivateGStreamer::fillTimerFired)
171 , m_bufferingPercentage(0)
172 , m_preload(MediaPlayer::Auto)
173 , m_delayingLoad(false)
174 , m_mediaDurationKnown(true)
175 , m_maxTimeLoadedAtLastDidLoadingProgress(0)
178 , m_audioTimerHandler(0)
179 , m_videoTimerHandler(0)
180 , m_webkitAudioSink(0)
182 , m_originalPreloadWasAutoAndWasOverridden(false)
183 , m_preservesPitch(false)
187 MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer()
189 if (m_fillTimer.isActive())
192 if (m_mediaLocations) {
193 gst_structure_free(m_mediaLocations);
194 m_mediaLocations = 0;
198 GRefPtr<GstBus> bus = webkitGstPipelineGetBus(GST_PIPELINE(m_playBin.get()));
200 g_signal_handlers_disconnect_by_func(bus.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateMessageCallback), this);
201 gst_bus_remove_signal_watch(bus.get());
203 g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateSourceChangedCallback), this);
204 g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateVideoChangedCallback), this);
205 g_signal_handlers_disconnect_by_func(m_playBin.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateAudioChangedCallback), this);
207 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
211 GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink"));
212 g_signal_handlers_disconnect_by_func(videoSinkPad.get(), reinterpret_cast<gpointer>(mediaPlayerPrivateVideoSinkCapsChangedCallback), this);
214 if (m_videoTimerHandler)
215 g_source_remove(m_videoTimerHandler);
217 if (m_audioTimerHandler)
218 g_source_remove(m_audioTimerHandler);
221 void MediaPlayerPrivateGStreamer::load(const String& url)
223 if (!initializeGStreamerAndRegisterWebKitElements())
226 KURL kurl(KURL(), url);
227 String cleanUrl(url);
229 // Clean out everything after file:// url path.
230 if (kurl.isLocalFile())
231 cleanUrl = cleanUrl.substring(0, kurl.pathEnd());
235 setDownloadBuffering();
240 m_url = KURL(KURL(), cleanUrl);
241 g_object_set(m_playBin.get(), "uri", cleanUrl.utf8().data(), NULL);
243 LOG_MEDIA_MESSAGE("Load %s", cleanUrl.utf8().data());
245 if (m_preload == MediaPlayer::None) {
246 LOG_MEDIA_MESSAGE("Delaying load.");
247 m_delayingLoad = true;
250 // Reset network and ready states. Those will be set properly once
251 // the pipeline pre-rolled.
252 m_networkState = MediaPlayer::Loading;
253 m_player->networkStateChanged();
254 m_readyState = MediaPlayer::HaveNothing;
255 m_player->readyStateChanged();
256 m_volumeAndMuteInitialized = false;
258 // GStreamer needs to have the pipeline set to a paused state to
259 // start providing anything useful.
260 gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED);
266 void MediaPlayerPrivateGStreamer::commitLoad()
268 ASSERT(!m_delayingLoad);
269 LOG_MEDIA_MESSAGE("Committing load.");
273 float MediaPlayerPrivateGStreamer::playbackPosition() const
275 if (m_isEndReached) {
276 // Position queries on a null pipeline return 0. If we're at
277 // the end of the stream the pipeline is null but we want to
278 // report either the seek time or the duration because this is
279 // what the Media element spec expects us to do.
283 return m_mediaDuration;
288 GstQuery* query = gst_query_new_position(GST_FORMAT_TIME);
289 if (!gst_element_query(m_playBin.get(), query)) {
290 LOG_MEDIA_MESSAGE("Position query failed...");
291 gst_query_unref(query);
296 gst_query_parse_position(query, 0, &position);
298 // Position is available only if the pipeline is not in GST_STATE_NULL or
299 // GST_STATE_READY state.
300 if (position != static_cast<gint64>(GST_CLOCK_TIME_NONE))
301 ret = static_cast<double>(position) / GST_SECOND;
303 LOG_MEDIA_MESSAGE("Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position));
305 gst_query_unref(query);
310 bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState)
312 ASSERT(newState == GST_STATE_PLAYING || newState == GST_STATE_PAUSED);
314 GstState currentState;
317 gst_element_get_state(m_playBin.get(), ¤tState, &pending, 0);
318 LOG_MEDIA_MESSAGE("Current state: %s, pending: %s", gst_element_state_get_name(currentState), gst_element_state_get_name(pending));
319 if (currentState == newState || pending == newState)
322 GstStateChangeReturn setStateResult = gst_element_set_state(m_playBin.get(), newState);
323 GstState pausedOrPlaying = newState == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING;
324 if (currentState != pausedOrPlaying && setStateResult == GST_STATE_CHANGE_FAILURE) {
325 loadingFailed(MediaPlayer::Empty);
331 void MediaPlayerPrivateGStreamer::prepareToPlay()
333 m_isEndReached = false;
336 if (m_delayingLoad) {
337 m_delayingLoad = false;
342 void MediaPlayerPrivateGStreamer::play()
344 if (changePipelineState(GST_STATE_PLAYING)) {
345 m_isEndReached = false;
346 LOG_MEDIA_MESSAGE("Play");
350 void MediaPlayerPrivateGStreamer::pause()
355 if (changePipelineState(GST_STATE_PAUSED))
356 LOG_MEDIA_MESSAGE("Pause");
359 float MediaPlayerPrivateGStreamer::duration() const
367 // Media duration query failed already, don't attempt new useless queries.
368 if (!m_mediaDurationKnown)
369 return numeric_limits<float>::infinity();
372 return m_mediaDuration;
374 GstFormat timeFormat = GST_FORMAT_TIME;
375 gint64 timeLength = 0;
377 #ifdef GST_API_VERSION_1
378 bool failure = !gst_element_query_duration(m_playBin.get(), timeFormat, &timeLength) || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE;
380 bool failure = !gst_element_query_duration(m_playBin.get(), &timeFormat, &timeLength) || timeFormat != GST_FORMAT_TIME || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE;
383 LOG_MEDIA_MESSAGE("Time duration query failed for %s", m_url.string().utf8().data());
384 return numeric_limits<float>::infinity();
387 LOG_MEDIA_MESSAGE("Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(timeLength));
389 return static_cast<double>(timeLength) / GST_SECOND;
390 // FIXME: handle 3.14.9.5 properly
393 float MediaPlayerPrivateGStreamer::currentTime() const
405 // https://bugzilla.gnome.org/show_bug.cgi?id=639941 In GStreamer
406 // 0.10.35 basesink reports wrong duration in case of EOS and
407 // negative playback rate. There's no upstream accepted patch for
408 // this bug yet, hence this temporary workaround.
409 if (m_isEndReached && m_playbackRate < 0)
412 return playbackPosition();
416 void MediaPlayerPrivateGStreamer::seek(float time)
424 LOG_MEDIA_MESSAGE("Seek attempt to %f secs", time);
426 // Avoid useless seeking.
427 if (time == currentTime())
430 // Extract the integer part of the time (seconds) and the
431 // fractional part (microseconds). Attempt to round the
432 // microseconds so no floating point precision is lost and we can
433 // perform an accurate seek.
435 float microSeconds = modf(time, &seconds) * 1000000;
437 timeValue.tv_sec = static_cast<glong>(seconds);
438 timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000);
440 GstClockTime clockTime = GST_TIMEVAL_TO_TIME(timeValue);
441 LOG_MEDIA_MESSAGE("Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(clockTime));
443 if (!gst_element_seek(m_playBin.get(), m_player->rate(),
445 (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
446 GST_SEEK_TYPE_SET, clockTime,
447 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
448 LOG_MEDIA_MESSAGE("Seek to %f failed", time);
455 bool MediaPlayerPrivateGStreamer::paused() const
457 if (m_isEndReached) {
458 LOG_MEDIA_MESSAGE("Ignoring pause at EOS");
463 gst_element_get_state(m_playBin.get(), &state, 0, 0);
464 return state == GST_STATE_PAUSED;
467 bool MediaPlayerPrivateGStreamer::seeking() const
472 void MediaPlayerPrivateGStreamer::videoChanged()
474 if (m_videoTimerHandler)
475 g_source_remove(m_videoTimerHandler);
476 m_videoTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVideoChangeTimeoutCallback), this);
479 void MediaPlayerPrivateGStreamer::notifyPlayerOfVideo()
481 m_videoTimerHandler = 0;
483 gint videoTracks = 0;
485 g_object_get(m_playBin.get(), "n-video", &videoTracks, NULL);
487 m_hasVideo = videoTracks > 0;
489 m_videoSize = IntSize();
491 m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player);
494 void MediaPlayerPrivateGStreamer::audioChanged()
496 if (m_audioTimerHandler)
497 g_source_remove(m_audioTimerHandler);
498 m_audioTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateAudioChangeTimeoutCallback), this);
501 void MediaPlayerPrivateGStreamer::notifyPlayerOfAudio()
503 m_audioTimerHandler = 0;
505 gint audioTracks = 0;
507 g_object_get(m_playBin.get(), "n-audio", &audioTracks, NULL);
508 m_hasAudio = audioTracks > 0;
509 m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player);
512 void MediaPlayerPrivateGStreamer::setRate(float rate)
514 // Avoid useless playback rate update.
515 if (m_playbackRate == rate)
521 gst_element_get_state(m_playBin.get(), &state, &pending, 0);
522 if ((state != GST_STATE_PLAYING && state != GST_STATE_PAUSED)
523 || (pending == GST_STATE_PAUSED))
529 m_playbackRate = rate;
530 m_changingRate = true;
533 gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED);
537 float currentPosition = static_cast<float>(playbackPosition() * GST_SECOND);
538 GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH);
542 LOG_MEDIA_MESSAGE("Set Rate to %f", rate);
544 // Mute the sound if the playback rate is too extreme.
545 // TODO: in other cases we should perform pitch adjustments.
546 mute = (bool) (rate < 0.8 || rate > 2);
547 start = currentPosition;
548 end = GST_CLOCK_TIME_NONE;
553 // If we are at beginning of media, start from the end to
554 // avoid immediate EOS.
555 if (currentPosition <= 0)
556 end = static_cast<gint64>(duration() * GST_SECOND);
558 end = currentPosition;
561 LOG_MEDIA_MESSAGE("Need to mute audio: %d", (int) mute);
563 if (!gst_element_seek(m_playBin.get(), rate, GST_FORMAT_TIME, flags,
564 GST_SEEK_TYPE_SET, start,
565 GST_SEEK_TYPE_SET, end))
566 LOG_MEDIA_MESSAGE("Set rate to %f failed", rate);
568 g_object_set(m_playBin.get(), "mute", mute, NULL);
571 void MediaPlayerPrivateGStreamer::setPreservesPitch(bool preservesPitch)
573 m_preservesPitch = preservesPitch;
576 PassRefPtr<TimeRanges> MediaPlayerPrivateGStreamer::buffered() const
578 RefPtr<TimeRanges> timeRanges = TimeRanges::create();
579 if (m_errorOccured || isLiveStream())
580 return timeRanges.release();
582 #if GST_CHECK_VERSION(0, 10, 31)
583 float mediaDuration(duration());
584 if (!mediaDuration || isinf(mediaDuration))
585 return timeRanges.release();
587 GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT);
589 if (!gst_element_query(m_playBin.get(), query)) {
590 gst_query_unref(query);
591 return timeRanges.release();
594 for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) {
595 gint64 rangeStart = 0, rangeStop = 0;
596 if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop))
597 timeRanges->add(static_cast<float>((rangeStart * mediaDuration) / gPercentMax),
598 static_cast<float>((rangeStop * mediaDuration) / gPercentMax));
601 // Fallback to the more general maxTimeLoaded() if no range has
603 if (!timeRanges->length())
604 if (float loaded = maxTimeLoaded())
605 timeRanges->add(0, loaded);
607 gst_query_unref(query);
609 float loaded = maxTimeLoaded();
610 if (!m_errorOccured && !isLiveStream() && loaded > 0)
611 timeRanges->add(0, loaded);
613 return timeRanges.release();
616 gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
619 GOwnPtr<gchar> debug;
620 MediaPlayer::NetworkState error;
621 bool issueError = true;
622 bool attemptNextLocation = false;
623 const GstStructure* structure = gst_message_get_structure(message);
626 const gchar* messageTypeName = gst_structure_get_name(structure);
628 // Redirect messages are sent from elements, like qtdemux, to
629 // notify of the new location(s) of the media.
630 if (!g_strcmp0(messageTypeName, "redirect")) {
631 mediaLocationChanged(message);
636 LOG_MEDIA_MESSAGE("Message received from element %s", GST_MESSAGE_SRC_NAME(message));
637 switch (GST_MESSAGE_TYPE(message)) {
638 case GST_MESSAGE_ERROR:
641 gst_message_parse_error(message, &err.outPtr(), &debug.outPtr());
642 LOG_MEDIA_MESSAGE("Error %d: %s (url=%s)", err->code, err->message, m_url.string().utf8().data());
644 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, "webkit-video.error");
646 error = MediaPlayer::Empty;
647 if (err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND
648 || err->code == GST_STREAM_ERROR_WRONG_TYPE
649 || err->code == GST_STREAM_ERROR_FAILED
650 || err->code == GST_CORE_ERROR_MISSING_PLUGIN
651 || err->code == GST_RESOURCE_ERROR_NOT_FOUND)
652 error = MediaPlayer::FormatError;
653 else if (err->domain == GST_STREAM_ERROR) {
654 // Let the mediaPlayerClient handle the stream error, in
655 // this case the HTMLMediaElement will emit a stalled
657 if (err->code == GST_STREAM_ERROR_TYPE_NOT_FOUND) {
658 LOG_MEDIA_MESSAGE("Decode error, let the Media element emit a stalled event.");
661 error = MediaPlayer::DecodeError;
662 attemptNextLocation = true;
663 } else if (err->domain == GST_RESOURCE_ERROR)
664 error = MediaPlayer::NetworkError;
666 if (attemptNextLocation)
667 issueError = !loadNextLocation();
669 loadingFailed(error);
671 case GST_MESSAGE_EOS:
672 LOG_MEDIA_MESSAGE("End of Stream");
675 case GST_MESSAGE_STATE_CHANGED:
676 // Ignore state changes if load is delayed (preload=none). The
677 // player state will be updated once commitLoad() is called.
678 if (m_delayingLoad) {
679 LOG_MEDIA_MESSAGE("Media load has been delayed. Ignoring state changes for now");
683 // Ignore state changes from internal elements. They are
684 // forwarded to playbin2 anyway.
685 if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get())) {
688 // Construct a filename for the graphviz dot file output.
689 GstState oldState, newState;
690 gst_message_parse_state_changed(message, &oldState, &newState, 0);
692 CString dotFileName = String::format("webkit-video.%s_%s",
693 gst_element_state_get_name(oldState),
694 gst_element_state_get_name(newState)).utf8();
696 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data());
699 case GST_MESSAGE_BUFFERING:
700 processBufferingStats(message);
702 #ifdef GST_API_VERSION_1
703 case GST_MESSAGE_DURATION_CHANGED:
705 case GST_MESSAGE_DURATION:
707 LOG_MEDIA_MESSAGE("Duration changed");
711 LOG_MEDIA_MESSAGE("Unhandled GStreamer message type: %s",
712 GST_MESSAGE_TYPE_NAME(message));
718 void MediaPlayerPrivateGStreamer::processBufferingStats(GstMessage* message)
720 // This is the immediate buffering that needs to happen so we have
721 // enough to play right now.
723 const GstStructure *structure = gst_message_get_structure(message);
724 gst_structure_get_int(structure, "buffer-percent", &m_bufferingPercentage);
726 LOG_MEDIA_MESSAGE("[Buffering] Buffering: %d%%.", m_bufferingPercentage);
728 GstBufferingMode mode;
729 gst_message_parse_buffering_stats(message, &mode, 0, 0, 0);
730 if (mode != GST_BUFFERING_DOWNLOAD) {
735 // This is on-disk buffering, that allows us to download much more
736 // than needed for right now.
737 if (!m_startedBuffering) {
738 LOG_MEDIA_MESSAGE("[Buffering] Starting on-disk buffering.");
740 m_startedBuffering = true;
742 if (m_fillTimer.isActive())
745 m_fillTimer.startRepeating(0.2);
749 void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStreamer>*)
751 GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT);
753 if (!gst_element_query(m_playBin.get(), query)) {
754 gst_query_unref(query);
759 gdouble fillStatus = 100.0;
761 gst_query_parse_buffering_range(query, 0, &start, &stop, 0);
762 gst_query_unref(query);
765 fillStatus = 100.0 * stop / GST_FORMAT_PERCENT_MAX;
767 LOG_MEDIA_MESSAGE("[Buffering] Download buffer filled up to %f%%", fillStatus);
769 if (!m_mediaDuration)
772 // Update maxTimeLoaded only if the media duration is
773 // available. Otherwise we can't compute it.
774 if (m_mediaDuration) {
775 if (fillStatus == 100.0)
776 m_maxTimeLoaded = m_mediaDuration;
778 m_maxTimeLoaded = static_cast<float>((fillStatus * m_mediaDuration) / 100.0);
779 LOG_MEDIA_MESSAGE("[Buffering] Updated maxTimeLoaded: %f", m_maxTimeLoaded);
782 if (fillStatus != 100.0) {
787 // Media is now fully loaded. It will play even if network
788 // connection is cut. Buffering is done, remove the fill source
789 // from the main loop.
791 m_startedBuffering = false;
795 float MediaPlayerPrivateGStreamer::maxTimeSeekable() const
800 LOG_MEDIA_MESSAGE("maxTimeSeekable");
801 // infinite duration means live stream
802 if (isinf(duration()))
808 float MediaPlayerPrivateGStreamer::maxTimeLoaded() const
813 float loaded = m_maxTimeLoaded;
814 if (!loaded && !m_fillTimer.isActive())
816 LOG_MEDIA_MESSAGE("maxTimeLoaded: %f", loaded);
820 bool MediaPlayerPrivateGStreamer::didLoadingProgress() const
822 if (!m_playBin || !m_mediaDuration || !totalBytes())
824 float currentMaxTimeLoaded = maxTimeLoaded();
825 bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress;
826 m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded;
827 LOG_MEDIA_MESSAGE("didLoadingProgress: %d", didLoadingProgress);
828 return didLoadingProgress;
831 unsigned MediaPlayerPrivateGStreamer::totalBytes() const
836 if (m_totalBytes != -1)
842 GstFormat fmt = GST_FORMAT_BYTES;
844 #ifdef GST_API_VERSION_1
845 if (gst_element_query_duration(m_source.get(), fmt, &length)) {
847 if (gst_element_query_duration(m_source.get(), &fmt, &length)) {
849 LOG_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length);
850 m_totalBytes = static_cast<unsigned>(length);
851 m_isStreaming = !length;
855 // Fall back to querying the source pads manually.
856 // See also https://bugzilla.gnome.org/show_bug.cgi?id=638749
857 GstIterator* iter = gst_element_iterate_src_pads(m_source.get());
860 #ifdef GST_API_VERSION_1
861 GValue item = G_VALUE_INIT;
862 switch (gst_iterator_next(iter, &item)) {
863 case GST_ITERATOR_OK: {
864 GstPad* pad = static_cast<GstPad*>(g_value_get_object(&item));
865 gint64 padLength = 0;
866 if (gst_pad_query_duration(pad, fmt, &padLength) && padLength > length)
873 switch (gst_iterator_next(iter, &data)) {
874 case GST_ITERATOR_OK: {
875 GRefPtr<GstPad> pad = adoptGRef(GST_PAD_CAST(data));
876 gint64 padLength = 0;
877 if (gst_pad_query_duration(pad.get(), &fmt, &padLength) && padLength > length)
882 case GST_ITERATOR_RESYNC:
883 gst_iterator_resync(iter);
885 case GST_ITERATOR_ERROR:
887 case GST_ITERATOR_DONE:
892 #ifdef GST_API_VERSION_1
893 g_value_unset(&item);
897 gst_iterator_free(iter);
899 LOG_MEDIA_MESSAGE("totalBytes %" G_GINT64_FORMAT, length);
900 m_totalBytes = static_cast<unsigned>(length);
901 m_isStreaming = !length;
905 void MediaPlayerPrivateGStreamer::updateAudioSink()
910 GstElement* sinkPtr = 0;
912 g_object_get(m_playBin.get(), "audio-sink", &sinkPtr, NULL);
913 m_webkitAudioSink = adoptGRef(sinkPtr);
917 GstElement* MediaPlayerPrivateGStreamer::audioSink() const
919 return m_webkitAudioSink.get();
922 void MediaPlayerPrivateGStreamer::sourceChanged()
924 GstElement* srcPtr = 0;
926 g_object_get(m_playBin.get(), "source", &srcPtr, NULL);
927 m_source = adoptGRef(srcPtr);
929 if (WEBKIT_IS_WEB_SRC(m_source.get()))
930 webKitWebSrcSetMediaPlayer(WEBKIT_WEB_SRC(m_source.get()), m_player);
933 void MediaPlayerPrivateGStreamer::cancelLoad()
935 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
939 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
942 void MediaPlayerPrivateGStreamer::updateStates()
950 MediaPlayer::NetworkState oldNetworkState = m_networkState;
951 MediaPlayer::ReadyState oldReadyState = m_readyState;
955 GstStateChangeReturn ret = gst_element_get_state(m_playBin.get(),
956 &state, &pending, 250 * GST_NSECOND);
958 bool shouldUpdateAfterSeek = false;
960 case GST_STATE_CHANGE_SUCCESS:
961 LOG_MEDIA_MESSAGE("State: %s, pending: %s",
962 gst_element_state_get_name(state),
963 gst_element_state_get_name(pending));
965 m_resetPipeline = state <= GST_STATE_READY;
967 // Try to figure out ready and network states.
968 if (state == GST_STATE_READY) {
969 m_readyState = MediaPlayer::HaveMetadata;
970 m_networkState = MediaPlayer::Empty;
971 // Cache the duration without emiting the durationchange
972 // event because it's taken care of by the media element
973 // in this precise case.
976 } else if ((state == GST_STATE_NULL) || (maxTimeLoaded() == duration())) {
977 m_networkState = MediaPlayer::Loaded;
978 m_readyState = MediaPlayer::HaveEnoughData;
980 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
981 m_networkState = MediaPlayer::Loading;
984 if (m_buffering && state != GST_STATE_READY) {
985 m_readyState = MediaPlayer::HaveCurrentData;
986 m_networkState = MediaPlayer::Loading;
989 // Now let's try to get the states in more detail using
990 // information from GStreamer, while we sync states where
992 if (state == GST_STATE_PAUSED) {
993 if (!m_webkitAudioSink)
996 if (!m_volumeAndMuteInitialized) {
997 notifyPlayerOfVolumeChange();
998 notifyPlayerOfMute();
999 m_volumeAndMuteInitialized = true;
1002 if (m_buffering && m_bufferingPercentage == 100) {
1003 m_buffering = false;
1004 m_bufferingPercentage = 0;
1005 m_readyState = MediaPlayer::HaveEnoughData;
1007 LOG_MEDIA_MESSAGE("[Buffering] Complete.");
1010 LOG_MEDIA_MESSAGE("[Buffering] Restarting playback.");
1011 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
1013 } else if (!m_buffering && (currentTime() < duration())) {
1016 } else if (state == GST_STATE_PLAYING) {
1017 m_readyState = MediaPlayer::HaveEnoughData;
1020 if (m_buffering && !isLiveStream()) {
1021 m_readyState = MediaPlayer::HaveCurrentData;
1022 m_networkState = MediaPlayer::Loading;
1024 LOG_MEDIA_MESSAGE("[Buffering] Pausing stream for buffering.");
1026 gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED);
1031 // Is on-disk buffering in progress?
1032 if (m_fillTimer.isActive())
1033 m_networkState = MediaPlayer::Loading;
1035 if (m_changingRate) {
1036 m_player->rateChanged();
1037 m_changingRate = false;
1041 shouldUpdateAfterSeek = true;
1046 case GST_STATE_CHANGE_ASYNC:
1047 LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s",
1048 gst_element_state_get_name(state),
1049 gst_element_state_get_name(pending));
1050 // Change in progress
1052 // On-disk buffering was attempted but the media is live. This
1053 // can't work so disable on-disk buffering and reset the
1055 if (state == GST_STATE_READY && isLiveStream() && m_preload == MediaPlayer::Auto) {
1056 setPreload(MediaPlayer::None);
1057 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
1058 gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED);
1061 // A live stream was paused, reset the pipeline.
1062 if (state == GST_STATE_PAUSED && pending == GST_STATE_PLAYING && isLiveStream()) {
1063 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
1064 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
1067 if (!isLiveStream() && !m_buffering)
1071 shouldUpdateAfterSeek = true;
1075 case GST_STATE_CHANGE_FAILURE:
1076 LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s",
1077 gst_element_state_get_name(state),
1078 gst_element_state_get_name(pending));
1081 case GST_STATE_CHANGE_NO_PREROLL:
1082 LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s",
1083 gst_element_state_get_name(state),
1084 gst_element_state_get_name(pending));
1086 if (state == GST_STATE_READY)
1087 m_readyState = MediaPlayer::HaveNothing;
1088 else if (state == GST_STATE_PAUSED) {
1089 m_readyState = MediaPlayer::HaveEnoughData;
1091 // Live pipelines go in PAUSED without prerolling.
1092 m_isStreaming = true;
1093 } else if (state == GST_STATE_PLAYING)
1097 shouldUpdateAfterSeek = true;
1100 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
1101 } else if (!m_paused)
1102 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
1104 m_networkState = MediaPlayer::Loading;
1107 LOG_MEDIA_MESSAGE("Else : %d", ret);
1112 m_readyState = MediaPlayer::HaveNothing;
1114 if (shouldUpdateAfterSeek)
1117 if (m_networkState != oldNetworkState) {
1118 LOG_MEDIA_MESSAGE("Network State Changed from %u to %u",
1119 oldNetworkState, m_networkState);
1120 m_player->networkStateChanged();
1122 if (m_readyState != oldReadyState) {
1123 LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u",
1124 oldReadyState, m_readyState);
1125 m_player->readyStateChanged();
1129 void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message)
1131 if (m_mediaLocations)
1132 gst_structure_free(m_mediaLocations);
1134 const GstStructure* structure = gst_message_get_structure(message);
1136 // This structure can contain:
1137 // - both a new-location string and embedded locations structure
1138 // - or only a new-location string.
1139 m_mediaLocations = gst_structure_copy(structure);
1140 const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations");
1143 m_mediaLocationCurrentIndex = static_cast<int>(gst_value_list_get_size(locations)) -1;
1149 bool MediaPlayerPrivateGStreamer::loadNextLocation()
1151 if (!m_mediaLocations)
1154 const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations");
1155 const gchar* newLocation = 0;
1158 // Fallback on new-location string.
1159 newLocation = gst_structure_get_string(m_mediaLocations, "new-location");
1165 if (m_mediaLocationCurrentIndex < 0) {
1166 m_mediaLocations = 0;
1170 const GValue* location = gst_value_list_get_value(locations,
1171 m_mediaLocationCurrentIndex);
1172 const GstStructure* structure = gst_value_get_structure(location);
1175 m_mediaLocationCurrentIndex--;
1179 newLocation = gst_structure_get_string(structure, "new-location");
1183 // Found a candidate. new-location is not always an absolute url
1184 // though. We need to take the base of the current url and
1185 // append the value of new-location to it.
1187 gchar* currentLocation = 0;
1188 g_object_get(m_playBin.get(), "uri", ¤tLocation, NULL);
1190 KURL currentUrl(KURL(), currentLocation);
1191 g_free(currentLocation);
1195 if (gst_uri_is_valid(newLocation))
1196 newUrl = KURL(KURL(), newLocation);
1198 newUrl = KURL(KURL(), currentUrl.baseAsString() + newLocation);
1200 RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(currentUrl);
1201 if (securityOrigin->canRequest(newUrl)) {
1202 LOG_MEDIA_MESSAGE("New media url: %s", newUrl.string().utf8().data());
1204 // Reset player states.
1205 m_networkState = MediaPlayer::Loading;
1206 m_player->networkStateChanged();
1207 m_readyState = MediaPlayer::HaveNothing;
1208 m_player->readyStateChanged();
1210 // Reset pipeline state.
1211 m_resetPipeline = true;
1212 gst_element_set_state(m_playBin.get(), GST_STATE_READY);
1215 gst_element_get_state(m_playBin.get(), &state, 0, 0);
1216 if (state <= GST_STATE_READY) {
1217 // Set the new uri and start playing.
1218 g_object_set(m_playBin.get(), "uri", newUrl.string().utf8().data(), NULL);
1219 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
1224 m_mediaLocationCurrentIndex--;
1229 void MediaPlayerPrivateGStreamer::loadStateChanged()
1234 void MediaPlayerPrivateGStreamer::timeChanged()
1237 m_player->timeChanged();
1240 void MediaPlayerPrivateGStreamer::didEnd()
1242 // Synchronize position and duration values to not confuse the
1243 // HTMLMediaElement. In some cases like reverse playback the
1244 // position is not always reported as 0 for instance.
1245 float now = currentTime();
1246 if (now > 0 && now <= duration() && m_mediaDuration != now) {
1247 m_mediaDurationKnown = true;
1248 m_mediaDuration = now;
1249 m_player->durationChanged();
1252 m_isEndReached = true;
1255 if (!m_player->mediaPlayerClient()->mediaPlayerIsLooping()) {
1257 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
1261 void MediaPlayerPrivateGStreamer::cacheDuration()
1263 // Reset cached media duration
1264 m_mediaDuration = 0;
1266 // And re-cache it if possible.
1268 gst_element_get_state(m_playBin.get(), &state, 0, 0);
1269 float newDuration = duration();
1271 if (state <= GST_STATE_READY) {
1272 // Don't set m_mediaDurationKnown yet if the pipeline is not
1273 // paused. This allows duration() query to fail at least once
1274 // before playback starts and duration becomes known.
1275 if (!isinf(newDuration))
1276 m_mediaDuration = newDuration;
1278 m_mediaDurationKnown = !isinf(newDuration);
1279 if (m_mediaDurationKnown)
1280 m_mediaDuration = newDuration;
1283 if (!isinf(newDuration))
1284 m_mediaDuration = newDuration;
1287 void MediaPlayerPrivateGStreamer::durationChanged()
1289 float previousDuration = m_mediaDuration;
1292 // Avoid emiting durationchanged in the case where the previous
1293 // duration was 0 because that case is already handled by the
1294 // HTMLMediaElement.
1295 if (previousDuration && m_mediaDuration != previousDuration)
1296 m_player->durationChanged();
1298 if (m_preload == MediaPlayer::None && m_originalPreloadWasAutoAndWasOverridden) {
1300 if (totalBytes() && !isLiveStream()) {
1301 setPreload(MediaPlayer::Auto);
1302 gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
1303 gst_element_set_state(m_playBin.get(), GST_STATE_PAUSED);
1308 void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState error)
1310 m_errorOccured = true;
1311 if (m_networkState != error) {
1312 m_networkState = error;
1313 m_player->networkStateChanged();
1315 if (m_readyState != MediaPlayer::HaveNothing) {
1316 m_readyState = MediaPlayer::HaveNothing;
1317 m_player->readyStateChanged();
1321 static HashSet<String> mimeTypeCache()
1323 initializeGStreamerAndRegisterWebKitElements();
1325 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1326 static bool typeListInitialized = false;
1328 if (typeListInitialized)
1331 const char* mimeTypes[] = {
1333 "application/vnd.apple.mpegurl",
1334 "application/vnd.rn-realmedia",
1335 "application/x-3gp",
1336 "application/x-pn-realaudio",
1356 "audio/x-amr-nb-sh",
1357 "audio/x-amr-wb-sh",
1388 "audio/x-vorbis+ogg",
1392 "audio/x-wavpack-correction",
1419 for (unsigned i = 0; i < (sizeof(mimeTypes) / sizeof(*mimeTypes)); ++i)
1420 cache.add(String(mimeTypes[i]));
1422 typeListInitialized = true;
1426 void MediaPlayerPrivateGStreamer::getSupportedTypes(HashSet<String>& types)
1428 types = mimeTypeCache();
1431 MediaPlayer::SupportsType MediaPlayerPrivateGStreamer::supportsType(const String& type, const String& codecs, const KURL&)
1433 if (type.isNull() || type.isEmpty())
1434 return MediaPlayer::IsNotSupported;
1436 // spec says we should not return "probably" if the codecs string is empty
1437 if (mimeTypeCache().contains(type))
1438 return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
1439 return MediaPlayer::IsNotSupported;
1442 void MediaPlayerPrivateGStreamer::setDownloadBuffering()
1448 g_object_get(m_playBin.get(), "flags", &flags, NULL);
1449 if (m_preload == MediaPlayer::Auto) {
1450 LOG_MEDIA_MESSAGE("Enabling on-disk buffering");
1451 g_object_set(m_playBin.get(), "flags", flags | GST_PLAY_FLAG_DOWNLOAD, NULL);
1453 LOG_MEDIA_MESSAGE("Disabling on-disk buffering");
1454 g_object_set(m_playBin.get(), "flags", flags & ~GST_PLAY_FLAG_DOWNLOAD, NULL);
1458 void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload)
1460 m_originalPreloadWasAutoAndWasOverridden = m_preload != preload && m_preload == MediaPlayer::Auto;
1462 m_preload = preload;
1464 setDownloadBuffering();
1466 if (m_delayingLoad && m_preload != MediaPlayer::None) {
1467 m_delayingLoad = false;
1472 void MediaPlayerPrivateGStreamer::createAudioSink()
1474 // Construct audio sink if pitch preserving is enabled.
1475 if (!m_preservesPitch)
1481 GstElement* scale = gst_element_factory_make("scaletempo", 0);
1483 GST_WARNING("Failed to create scaletempo");
1487 GstElement* convert = gst_element_factory_make("audioconvert", 0);
1488 GstElement* resample = gst_element_factory_make("audioresample", 0);
1489 GstElement* sink = gst_element_factory_make("autoaudiosink", 0);
1491 GstElement* audioSink = gst_bin_new("audio-sink");
1492 gst_bin_add_many(GST_BIN(audioSink), scale, convert, resample, sink, NULL);
1494 if (!gst_element_link_many(scale, convert, resample, sink, NULL)) {
1495 GST_WARNING("Failed to link audio sink elements");
1496 gst_object_unref(audioSink);
1500 GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(scale, "sink"));
1501 gst_element_add_pad(audioSink, gst_ghost_pad_new("sink", pad.get()));
1503 g_object_set(m_playBin.get(), "audio-sink", audioSink, NULL);
1506 void MediaPlayerPrivateGStreamer::createGSTPlayBin()
1510 // gst_element_factory_make() returns a floating reference so
1511 // we should not adopt.
1512 m_playBin = gst_element_factory_make(gPlaybinName, "play");
1513 setStreamVolumeElement(GST_STREAM_VOLUME(m_playBin.get()));
1515 GRefPtr<GstBus> bus = webkitGstPipelineGetBus(GST_PIPELINE(m_playBin.get()));
1516 gst_bus_add_signal_watch(bus.get());
1517 g_signal_connect(bus.get(), "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this);
1519 g_object_set(m_playBin.get(), "mute", m_player->muted(), NULL);
1521 g_signal_connect(m_playBin.get(), "notify::source", G_CALLBACK(mediaPlayerPrivateSourceChangedCallback), this);
1522 g_signal_connect(m_playBin.get(), "video-changed", G_CALLBACK(mediaPlayerPrivateVideoChangedCallback), this);
1523 g_signal_connect(m_playBin.get(), "audio-changed", G_CALLBACK(mediaPlayerPrivateAudioChangedCallback), this);
1525 GstElement* videoElement = createVideoSink(m_playBin.get());
1527 g_object_set(m_playBin.get(), "video-sink", videoElement, NULL);
1529 GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink.get(), "sink"));
1531 g_signal_connect(videoSinkPad.get(), "notify::caps", G_CALLBACK(mediaPlayerPrivateVideoSinkCapsChangedCallback), this);
1538 #endif // USE(GSTREAMER)