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 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 "ColorSpace.h"
30 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
31 #include "CookieJar.h"
35 #include "FrameView.h"
36 #include "GStreamerGWorld.h"
37 #include "GStreamerUtilities.h"
38 #include "GStreamerVersioning.h"
39 #include "GraphicsContext.h"
40 #include "GraphicsTypes.h"
41 #if ENABLE(TIZEN_MEDIA_STREAM)
42 #include "HTMLMediaElement.h"
44 #include "ImageGStreamer.h"
45 #include "ImageOrientation.h"
48 #include "MIMETypeRegistry.h"
49 #include "MediaPlayer.h"
50 #include "NotImplemented.h"
51 #include "SecurityOrigin.h"
52 #include "TimeRanges.h"
53 #include "VideoSinkGStreamer.h"
54 #include "WebKitWebSourceGStreamer.h"
56 #include <gst/video/video.h>
59 #include <wtf/gobject/GOwnPtr.h>
60 #include <wtf/text/CString.h>
62 #ifdef GST_API_VERSION_1
63 #include <gst/audio/streamvolume.h>
65 #include <gst/interfaces/streamvolume.h>
68 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
71 #include "VideoLayerTizen.h"
72 #endif // ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
74 #if ENABLE(TIZEN_MEDIA_STREAM) || ENABLE(TIZEN_FILE_SYSTEM)
76 #endif // ENABLE(TIZEN_MEDIA_STREAM) || ENABLE(TIZEN_FILE_SYSTEM)
78 #if ENABLE(TIZEN_MEDIA_STREAM)
79 #include "WebKitCameraSourceGStreamer.h"
80 #endif // ENABLE(TIZEN_MEDIA_STREAM)
82 // GstPlayFlags flags from playbin2. It is the policy of GStreamer to
83 // not publicly expose element-specific enums. That's why this
84 // GstPlayFlags enum has been copied here.
86 GST_PLAY_FLAG_VIDEO = 0x00000001,
87 GST_PLAY_FLAG_AUDIO = 0x00000002,
88 GST_PLAY_FLAG_TEXT = 0x00000004,
89 GST_PLAY_FLAG_VIS = 0x00000008,
90 GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010,
91 GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020,
92 GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040,
93 GST_PLAY_FLAG_DOWNLOAD = 0x00000080,
94 GST_PLAY_FLAG_BUFFERING = 0x000000100
97 #ifdef GST_API_VERSION_1
98 static const char* gPlaybinName = "playbin";
100 static const char* gPlaybinName = "playbin2";
107 static int greatestCommonDivisor(int a, int b)
118 static gboolean mediaPlayerPrivateMessageCallback(GstBus*, GstMessage* message, MediaPlayerPrivateGStreamer* player)
120 return player->handleMessage(message);
123 static void mediaPlayerPrivateSourceChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
125 player->sourceChanged();
128 static void mediaPlayerPrivateVolumeChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
130 // This is called when playbin receives the notify::volume signal.
131 player->volumeChanged();
134 static gboolean mediaPlayerPrivateVolumeChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
136 // This is the callback of the timeout source created in ::volumeChanged.
137 player->notifyPlayerOfVolumeChange();
141 static void mediaPlayerPrivateMuteChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
143 // This is called when playbin receives the notify::mute signal.
144 player->muteChanged();
147 static gboolean mediaPlayerPrivateMuteChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
149 // This is the callback of the timeout source created in ::muteChanged.
150 player->notifyPlayerOfMute();
154 static void mediaPlayerPrivateVideoSinkCapsChangedCallback(GObject*, GParamSpec*, MediaPlayerPrivateGStreamer* player)
156 player->videoChanged();
159 static void mediaPlayerPrivateVideoChangedCallback(GObject*, MediaPlayerPrivateGStreamer* player)
161 player->videoChanged();
164 static void mediaPlayerPrivateAudioChangedCallback(GObject*, MediaPlayerPrivateGStreamer* player)
166 player->audioChanged();
169 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
170 static void mediaPlayerPrivateSourceSetupCallback(GObject*, GstElement* source, MediaPlayerPrivateGStreamer* player)
172 HTMLMediaElement* element = static_cast<HTMLMediaElement*>(player->player()->mediaPlayerClient());
174 // Grab the current document
175 Document* document = element->document();
177 document = element->ownerDocument();
179 // Grab the frame and network manager
180 Frame* frame = document ? document->frame() : 0;
181 FrameLoader* frameLoader = frame ? frame->loader() : 0;
183 String cookie = cookies(document, player->url());
184 GstStructure* headers = gst_structure_new("extra-headers", "Cookie", G_TYPE_STRING, cookie.utf8().data(), NULL);
185 g_object_set(source, "extra-headers", headers, NULL);
186 gst_structure_free(headers);
188 #if ENABLE(TIZEN_WEBKIT2_PROXY)
189 player->setProxy(source);
193 g_object_set(source, "user-agent", frameLoader->userAgent(player->url()).utf8().data(), NULL);
197 static gboolean mediaPlayerPrivateAudioChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
199 // This is the callback of the timeout source created in ::audioChanged.
200 player->notifyPlayerOfAudio();
204 static gboolean mediaPlayerPrivateVideoChangeTimeoutCallback(MediaPlayerPrivateGStreamer* player)
206 // This is the callback of the timeout source created in ::videoChanged.
207 player->notifyPlayerOfVideo();
211 static void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivateGStreamer* playerPrivate)
213 playerPrivate->triggerRepaint(buffer);
216 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
217 static GstBusSyncReply mediaPlayerPrivateSyncHandler(GstBus* bus, GstMessage* message, MediaPlayerPrivateGStreamer* playerPrivate)
219 // ignore anything but 'prepare-xid' message
220 if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT)
222 if (!gst_structure_has_name (message->structure, "prepare-xid"))
225 playerPrivate->xWindowIdPrepared(message);
230 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
231 static ASM_cb_result_t MediaPlayerAudioSessionEventSourcePause(ASM_event_sources_t eventSource, void* callbackData)
233 MediaPlayer* player = static_cast<MediaPlayer*>(callbackData);
235 return ASM_CB_RES_IGNORE;
237 switch (eventSource) {
238 case ASM_EVENT_SOURCE_CALL_START:
239 case ASM_EVENT_SOURCE_ALARM_START:
240 case ASM_EVENT_SOURCE_EARJACK_UNPLUG:
241 case ASM_EVENT_SOURCE_MEDIA:
242 case ASM_EVENT_SOURCE_EMERGENCY_START:
243 case ASM_EVENT_SOURCE_OTHER_PLAYER_APP:
244 case ASM_EVENT_SOURCE_RESOURCE_CONFLICT:
247 return ASM_CB_RES_PAUSE;
249 return ASM_CB_RES_NONE;
253 static ASM_cb_result_t MediaPlayerAudioSessionEventSourcePlay(ASM_event_sources_t eventSource, void* callbackData)
255 MediaPlayer* player = static_cast<MediaPlayer*>(callbackData);
257 return ASM_CB_RES_IGNORE;
259 switch (eventSource) {
260 case ASM_EVENT_SOURCE_ALARM_END:
261 if (!player->hasVideo()) {
263 return ASM_CB_RES_PLAYING;
265 return ASM_CB_RES_NONE;
267 return ASM_CB_RES_NONE;
271 static ASM_cb_result_t mediaPlayerPrivateAudioSessionNotifyCallback(int, ASM_event_sources_t eventSource, ASM_sound_commands_t command, unsigned int, void* callbackData)
273 if (command == ASM_COMMAND_STOP || command == ASM_COMMAND_PAUSE)
274 return MediaPlayerAudioSessionEventSourcePause(eventSource, callbackData);
275 if (command == ASM_COMMAND_PLAY || command == ASM_COMMAND_RESUME)
276 return MediaPlayerAudioSessionEventSourcePlay(eventSource, callbackData);
278 return ASM_CB_RES_NONE;
282 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateGStreamer::create(MediaPlayer* player)
284 return adoptPtr(new MediaPlayerPrivateGStreamer(player));
287 void MediaPlayerPrivateGStreamer::registerMediaEngine(MediaEngineRegistrar registrar)
290 registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
293 bool initializeGStreamerAndRegisterWebKitElements()
295 if (!initializeGStreamer())
298 GRefPtr<GstElementFactory> srcFactory = gst_element_factory_find("webkitwebsrc");
300 #if ENABLE(TIZEN_MEDIA_STREAM)
301 return gst_element_register(0, "webkitcamerasrc", GST_RANK_PRIMARY + 200, WEBKIT_TYPE_CAMERA_SRC);
303 return gst_element_register(0, "webkitwebsrc", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_WEB_SRC);
304 #endif // ENABLE(TIZEN_MEDIA_STREAM)
309 bool MediaPlayerPrivateGStreamer::isAvailable()
311 if (!initializeGStreamerAndRegisterWebKitElements())
314 GRefPtr<GstElementFactory> factory = gst_element_factory_find(gPlaybinName);
318 MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)
321 , m_webkitVideoSink(0)
325 , m_changingRate(false)
326 , m_endTime(numeric_limits<float>::infinity())
327 , m_isEndReached(false)
328 , m_networkState(MediaPlayer::Empty)
329 , m_readyState(MediaPlayer::HaveNothing)
330 , m_isStreaming(false)
333 , m_mediaLocations(0)
334 , m_mediaLocationCurrentIndex(0)
335 , m_resetPipeline(false)
340 , m_errorOccured(false)
342 , m_startedBuffering(false)
343 , m_fillTimer(this, &MediaPlayerPrivateGStreamer::fillTimerFired)
345 , m_bufferingPercentage(0)
346 , m_preload(MediaPlayer::Auto)
347 , m_delayingLoad(false)
348 , m_mediaDurationKnown(true)
349 , m_maxTimeLoadedAtLastDidLoadingProgress(0)
350 , m_volumeTimerHandler(0)
351 , m_muteTimerHandler(0)
354 , m_audioTimerHandler(0)
355 , m_videoTimerHandler(0)
356 , m_webkitAudioSink(0)
357 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
358 , m_videoLayer(VideoLayerTizen::create(static_cast<HTMLMediaElement*>(player->mediaPlayerClient())))
359 #endif // ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
360 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
361 , m_audioSessionManager(AudioSessionManagerGStreamerTizen::createAudioSessionManager())
364 if (initializeGStreamerAndRegisterWebKitElements())
368 MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer()
370 if (m_fillTimer.isActive())
374 gst_buffer_unref(m_buffer);
377 if (m_mediaLocations) {
378 gst_structure_free(m_mediaLocations);
379 m_mediaLocations = 0;
382 #ifndef GST_API_VERSION_1
383 if (m_videoSinkBin) {
384 gst_object_unref(m_videoSinkBin);
390 gst_element_set_state(m_playBin, GST_STATE_NULL);
391 gst_object_unref(GST_OBJECT(m_playBin));
395 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
396 if (m_audioSessionManager)
397 m_audioSessionManager->setSoundState(ASM_STATE_STOP);
402 if (m_muteTimerHandler)
403 g_source_remove(m_muteTimerHandler);
405 if (m_volumeTimerHandler)
406 g_source_remove(m_volumeTimerHandler);
408 if (m_videoTimerHandler)
409 g_source_remove(m_videoTimerHandler);
411 if (m_audioTimerHandler)
412 g_source_remove(m_audioTimerHandler);
415 void MediaPlayerPrivateGStreamer::load(const String& url)
417 #if ENABLE(TIZEN_MEDIA_STREAM) || ENABLE(TIZEN_FILE_SYSTEM)
419 #endif // ENABLE(TIZEN_MEDIA_STREAM) || ENABLE(TIZEN_FILE_SYSTEM)
420 KURL kurl(KURL(), url);
421 String cleanUrl(url);
423 // Clean out everything after file:// url path.
424 if (kurl.isLocalFile())
425 cleanUrl = cleanUrl.substring(0, kurl.pathEnd());
427 m_url = KURL(KURL(), cleanUrl);
428 g_object_set(m_playBin, "uri", cleanUrl.utf8().data(), NULL);
430 LOG_VERBOSE(Media, "Load %s", cleanUrl.utf8().data());
432 if (m_preload == MediaPlayer::None) {
433 LOG_VERBOSE(Media, "Delaying load.");
434 m_delayingLoad = true;
437 // GStreamer needs to have the pipeline set to a paused state to
438 // start providing anything useful.
439 gst_element_set_state(m_playBin, GST_STATE_PAUSED);
441 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
442 if (m_audioSessionManager)
443 m_audioSessionManager->setSoundState(ASM_STATE_PAUSE);
450 void MediaPlayerPrivateGStreamer::commitLoad()
452 ASSERT(!m_delayingLoad);
453 LOG_VERBOSE(Media, "Committing load.");
457 float MediaPlayerPrivateGStreamer::playbackPosition() const
459 if (m_isEndReached) {
460 // Position queries on a null pipeline return 0. If we're at
461 // the end of the stream the pipeline is null but we want to
462 // report either the seek time or the duration because this is
463 // what the Media element spec expects us to do.
467 return m_mediaDuration;
472 GstQuery* query = gst_query_new_position(GST_FORMAT_TIME);
473 if (!gst_element_query(m_playBin, query)) {
474 LOG_VERBOSE(Media, "Position query failed...");
475 gst_query_unref(query);
480 gst_query_parse_position(query, 0, &position);
482 // Position is available only if the pipeline is not in GST_STATE_NULL or
483 // GST_STATE_READY state.
484 if (position != static_cast<gint64>(GST_CLOCK_TIME_NONE))
485 ret = static_cast<double>(position) / GST_SECOND;
487 LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position));
489 gst_query_unref(query);
494 bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState)
496 ASSERT(newState == GST_STATE_PLAYING || newState == GST_STATE_PAUSED);
498 GstState currentState;
501 gst_element_get_state(m_playBin, ¤tState, &pending, 0);
502 LOG_VERBOSE(Media, "Current state: %s, pending: %s", gst_element_state_get_name(currentState), gst_element_state_get_name(pending));
503 if (currentState == newState || pending == newState)
506 GstStateChangeReturn setStateResult = gst_element_set_state(m_playBin, newState);
507 GstState pausedOrPlaying = newState == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING;
508 if (currentState != pausedOrPlaying && setStateResult == GST_STATE_CHANGE_FAILURE) {
509 loadingFailed(MediaPlayer::Empty);
515 void MediaPlayerPrivateGStreamer::prepareToPlay()
517 m_isEndReached = false;
520 if (m_delayingLoad) {
521 m_delayingLoad = false;
526 void MediaPlayerPrivateGStreamer::play()
528 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
529 if (m_audioSessionManager && !m_audioSessionManager->setSoundState(ASM_STATE_PLAYING))
532 if (m_playBin == NULL) {
533 m_player_deferred_state = 1; // play
537 if (changePipelineState(GST_STATE_PLAYING)) {
538 m_isEndReached = false;
539 LOG_VERBOSE(Media, "Play");
543 void MediaPlayerPrivateGStreamer::pause()
545 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
546 if (m_audioSessionManager && !m_audioSessionManager->setSoundState(ASM_STATE_PAUSE))
549 if (m_playBin == NULL) {
550 m_player_deferred_state = 0; // pause
557 if (changePipelineState(GST_STATE_PAUSED))
558 LOG_VERBOSE(Media, "Pause");
561 float MediaPlayerPrivateGStreamer::duration() const
569 // Media duration query failed already, don't attempt new useless queries.
570 if (!m_mediaDurationKnown)
571 return numeric_limits<float>::infinity();
574 return m_mediaDuration;
576 GstFormat timeFormat = GST_FORMAT_TIME;
577 gint64 timeLength = 0;
579 #ifdef GST_API_VERSION_1
580 bool failure = !gst_element_query_duration(m_playBin, timeFormat, &timeLength) || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE;
582 bool failure = !gst_element_query_duration(m_playBin, &timeFormat, &timeLength) || timeFormat != GST_FORMAT_TIME || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE;
585 LOG_VERBOSE(Media, "Time duration query failed.");
586 return numeric_limits<float>::infinity();
589 LOG_VERBOSE(Media, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(timeLength));
591 return static_cast<double>(timeLength) / GST_SECOND;
592 // FIXME: handle 3.14.9.5 properly
595 float MediaPlayerPrivateGStreamer::currentTime() const
607 // https://bugzilla.gnome.org/show_bug.cgi?id=639941 In GStreamer
608 // 0.10.35 basesink reports wrong duration in case of EOS and
609 // negative playback rate. There's no upstream accepted patch for
610 // this bug yet, hence this temporary workaround.
611 if (m_isEndReached && m_playbackRate < 0)
614 return playbackPosition();
618 void MediaPlayerPrivateGStreamer::seek(float time)
626 LOG_VERBOSE(Media, "Seek attempt to %f secs", time);
628 // Avoid useless seeking.
629 if (time == currentTime())
632 // Extract the integer part of the time (seconds) and the
633 // fractional part (microseconds). Attempt to round the
634 // microseconds so no floating point precision is lost and we can
635 // perform an accurate seek.
637 float microSeconds = modf(time, &seconds) * 1000000;
639 timeValue.tv_sec = static_cast<glong>(seconds);
640 timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000);
642 GstClockTime clockTime = GST_TIMEVAL_TO_TIME(timeValue);
643 LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(clockTime));
645 if (!gst_element_seek(m_playBin, m_player->rate(),
647 (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
648 GST_SEEK_TYPE_SET, clockTime,
649 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
650 LOG_VERBOSE(Media, "Seek to %f failed", time);
657 bool MediaPlayerPrivateGStreamer::paused() const
659 if (m_isEndReached) {
660 LOG_VERBOSE(Media, "Ignoring pause at EOS");
665 gst_element_get_state(m_playBin, &state, 0, 0);
666 return state == GST_STATE_PAUSED;
669 bool MediaPlayerPrivateGStreamer::seeking() const
674 // Returns the size of the video
675 IntSize MediaPlayerPrivateGStreamer::naturalSize() const
680 if (!m_videoSize.isEmpty())
683 GstCaps* caps = webkitGstGetPadCaps(m_videoSinkPad.get());
688 // TODO: handle possible clean aperture data. See
689 // https://bugzilla.gnome.org/show_bug.cgi?id=596571
690 // TODO: handle possible transformation matrix. See
691 // https://bugzilla.gnome.org/show_bug.cgi?id=596326
693 // Get the video PAR and original size, if this fails the
694 // video-sink has likely not yet negotiated its caps.
695 int pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride;
696 IntSize originalSize;
697 GstVideoFormat format;
698 if (!getVideoSizeAndFormatFromCaps(caps, originalSize, format, pixelAspectRatioNumerator, pixelAspectRatioDenominator, stride))
701 LOG_VERBOSE(Media, "Original video size: %dx%d", originalSize.width(), originalSize.height());
702 LOG_VERBOSE(Media, "Pixel aspect ratio: %d/%d", pixelAspectRatioNumerator, pixelAspectRatioDenominator);
704 // Calculate DAR based on PAR and video size.
705 int displayWidth = originalSize.width() * pixelAspectRatioNumerator;
706 int displayHeight = originalSize.height() * pixelAspectRatioDenominator;
708 // Divide display width and height by their GCD to avoid possible overflows.
709 int displayAspectRatioGCD = greatestCommonDivisor(displayWidth, displayHeight);
710 displayWidth /= displayAspectRatioGCD;
711 displayHeight /= displayAspectRatioGCD;
713 // Apply DAR to original video size. This is the same behavior as in xvimagesink's setcaps function.
714 guint64 width = 0, height = 0;
715 if (!(originalSize.height() % displayHeight)) {
716 LOG_VERBOSE(Media, "Keeping video original height");
717 width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
718 height = static_cast<guint64>(originalSize.height());
719 } else if (!(originalSize.width() % displayWidth)) {
720 LOG_VERBOSE(Media, "Keeping video original width");
721 height = gst_util_uint64_scale_int(originalSize.width(), displayHeight, displayWidth);
722 width = static_cast<guint64>(originalSize.width());
724 LOG_VERBOSE(Media, "Approximating while keeping original video height");
725 width = gst_util_uint64_scale_int(originalSize.height(), displayWidth, displayHeight);
726 height = static_cast<guint64>(originalSize.height());
729 LOG_VERBOSE(Media, "Natural size: %" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT, width, height);
730 m_videoSize = IntSize(static_cast<int>(width), static_cast<int>(height));
734 void MediaPlayerPrivateGStreamer::videoChanged()
736 if (m_videoTimerHandler)
737 g_source_remove(m_videoTimerHandler);
738 m_videoTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVideoChangeTimeoutCallback), this);
741 void MediaPlayerPrivateGStreamer::notifyPlayerOfVideo()
743 m_videoTimerHandler = 0;
745 gint videoTracks = 0;
747 g_object_get(m_playBin, "n-video", &videoTracks, NULL);
749 m_hasVideo = videoTracks > 0;
751 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
754 if (gst_video_get_size(m_videoSinkPad.get(), &width, &height)) {
755 IntSize size(width, height);
756 if (m_videoSize != size) {
757 m_videoSize = IntSize();
758 m_videoLayer->setOverlay(naturalSize());
762 m_videoSize = IntSize();
764 m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player);
767 void MediaPlayerPrivateGStreamer::audioChanged()
769 if (m_audioTimerHandler)
770 g_source_remove(m_audioTimerHandler);
771 m_audioTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateAudioChangeTimeoutCallback), this);
774 void MediaPlayerPrivateGStreamer::notifyPlayerOfAudio()
776 m_audioTimerHandler = 0;
778 gint audioTracks = 0;
780 g_object_get(m_playBin, "n-audio", &audioTracks, NULL);
781 m_hasAudio = audioTracks > 0;
782 m_player->mediaPlayerClient()->mediaPlayerEngineUpdated(m_player);
785 void MediaPlayerPrivateGStreamer::setVolume(float volume)
790 gst_stream_volume_set_volume(GST_STREAM_VOLUME(m_playBin), GST_STREAM_VOLUME_FORMAT_CUBIC,
791 static_cast<double>(volume));
794 void MediaPlayerPrivateGStreamer::notifyPlayerOfVolumeChange()
796 m_volumeTimerHandler = 0;
798 if (!m_player || !m_playBin)
801 volume = gst_stream_volume_get_volume(GST_STREAM_VOLUME(m_playBin), GST_STREAM_VOLUME_FORMAT_CUBIC);
802 // get_volume() can return values superior to 1.0 if the user
803 // applies software user gain via third party application (GNOME
804 // volume control for instance).
805 volume = CLAMP(volume, 0.0, 1.0);
806 m_player->volumeChanged(static_cast<float>(volume));
809 void MediaPlayerPrivateGStreamer::volumeChanged()
811 if (m_volumeTimerHandler)
812 g_source_remove(m_volumeTimerHandler);
813 m_volumeTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateVolumeChangeTimeoutCallback), this);
816 void MediaPlayerPrivateGStreamer::setRate(float rate)
818 // Avoid useless playback rate update.
819 if (m_playbackRate == rate)
825 gst_element_get_state(m_playBin, &state, &pending, 0);
826 if ((state != GST_STATE_PLAYING && state != GST_STATE_PAUSED)
827 || (pending == GST_STATE_PAUSED))
833 m_playbackRate = rate;
834 m_changingRate = true;
837 gst_element_set_state(m_playBin, GST_STATE_PAUSED);
839 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
840 if (m_audioSessionManager)
841 m_audioSessionManager->setSoundState(ASM_STATE_PAUSE);
846 float currentPosition = static_cast<float>(playbackPosition() * GST_SECOND);
847 GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH);
851 LOG_VERBOSE(Media, "Set Rate to %f", rate);
853 // Mute the sound if the playback rate is too extreme.
854 // TODO: in other cases we should perform pitch adjustments.
855 mute = (bool) (rate < 0.8 || rate > 2);
856 start = currentPosition;
857 end = GST_CLOCK_TIME_NONE;
862 // If we are at beginning of media, start from the end to
863 // avoid immediate EOS.
864 if (currentPosition <= 0)
865 end = static_cast<gint64>(duration() * GST_SECOND);
867 end = currentPosition;
870 LOG_VERBOSE(Media, "Need to mute audio: %d", (int) mute);
872 if (!gst_element_seek(m_playBin, rate, GST_FORMAT_TIME, flags,
873 GST_SEEK_TYPE_SET, start,
874 GST_SEEK_TYPE_SET, end))
875 LOG_VERBOSE(Media, "Set rate to %f failed", rate);
877 g_object_set(m_playBin, "mute", mute, NULL);
880 MediaPlayer::NetworkState MediaPlayerPrivateGStreamer::networkState() const
882 return m_networkState;
885 MediaPlayer::ReadyState MediaPlayerPrivateGStreamer::readyState() const
890 PassRefPtr<TimeRanges> MediaPlayerPrivateGStreamer::buffered() const
892 RefPtr<TimeRanges> timeRanges = TimeRanges::create();
893 if (m_errorOccured || m_isStreaming)
894 return timeRanges.release();
896 #if GST_CHECK_VERSION(0, 10, 31)
897 float mediaDuration(duration());
898 if (!mediaDuration || isinf(mediaDuration))
899 return timeRanges.release();
901 GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT);
903 if (!gst_element_query(m_playBin, query)) {
904 gst_query_unref(query);
905 return timeRanges.release();
908 gint64 rangeStart = 0, rangeStop = 0;
909 for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) {
910 if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop))
911 timeRanges->add(static_cast<float>((rangeStart * mediaDuration) / 100),
912 static_cast<float>((rangeStop * mediaDuration) / 100));
915 // Fallback to the more general maxTimeLoaded() if no range has
917 if (!timeRanges->length())
918 if (float loaded = maxTimeLoaded())
919 timeRanges->add(0, loaded);
921 gst_query_unref(query);
923 float loaded = maxTimeLoaded();
924 if (!m_errorOccured && !m_isStreaming && loaded > 0)
925 timeRanges->add(0, loaded);
927 return timeRanges.release();
930 gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
933 GOwnPtr<gchar> debug;
934 MediaPlayer::NetworkState error;
935 bool issueError = true;
936 bool attemptNextLocation = false;
937 const GstStructure* structure = gst_message_get_structure(message);
940 const gchar* messageTypeName = gst_structure_get_name(structure);
942 // Redirect messages are sent from elements, like qtdemux, to
943 // notify of the new location(s) of the media.
944 if (!g_strcmp0(messageTypeName, "redirect")) {
945 mediaLocationChanged(message);
950 switch (GST_MESSAGE_TYPE(message)) {
951 case GST_MESSAGE_ERROR:
954 gst_message_parse_error(message, &err.outPtr(), &debug.outPtr());
955 LOG_VERBOSE(Media, "Error: %d, %s", err->code, err->message);
957 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin), GST_DEBUG_GRAPH_SHOW_ALL, "webkit-video.error");
959 error = MediaPlayer::Empty;
960 if (err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND
961 || err->code == GST_STREAM_ERROR_WRONG_TYPE
962 || err->code == GST_STREAM_ERROR_FAILED
963 || err->code == GST_CORE_ERROR_MISSING_PLUGIN
964 || err->code == GST_RESOURCE_ERROR_NOT_FOUND)
965 error = MediaPlayer::FormatError;
966 else if (err->domain == GST_STREAM_ERROR) {
967 // Let the mediaPlayerClient handle the stream error, in
968 // this case the HTMLMediaElement will emit a stalled
970 if (err->code == GST_STREAM_ERROR_TYPE_NOT_FOUND) {
971 LOG_VERBOSE(Media, "Decode error, let the Media element emit a stalled event.");
974 error = MediaPlayer::DecodeError;
975 attemptNextLocation = true;
976 } else if (err->domain == GST_RESOURCE_ERROR)
977 error = MediaPlayer::NetworkError;
979 if (attemptNextLocation)
980 issueError = !loadNextLocation();
982 loadingFailed(error);
984 case GST_MESSAGE_EOS:
985 LOG_VERBOSE(Media, "End of Stream");
988 case GST_MESSAGE_STATE_CHANGED:
989 // Ignore state changes if load is delayed (preload=none). The
990 // player state will be updated once commitLoad() is called.
991 if (m_delayingLoad) {
992 LOG_VERBOSE(Media, "Media load has been delayed. Ignoring state changes for now");
996 // Ignore state changes from internal elements. They are
997 // forwarded to playbin2 anyway.
998 if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin)) {
1001 // Construct a filename for the graphviz dot file output.
1002 GstState oldState, newState;
1003 gst_message_parse_state_changed(message, &oldState, &newState, 0);
1005 CString dotFileName = String::format("webkit-video.%s_%s",
1006 gst_element_state_get_name(oldState),
1007 gst_element_state_get_name(newState)).utf8();
1009 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data());
1012 case GST_MESSAGE_BUFFERING:
1013 processBufferingStats(message);
1015 case GST_MESSAGE_DURATION:
1016 LOG_VERBOSE(Media, "Duration changed");
1020 LOG_VERBOSE(Media, "Unhandled GStreamer message type: %s",
1021 GST_MESSAGE_TYPE_NAME(message));
1027 void MediaPlayerPrivateGStreamer::processBufferingStats(GstMessage* message)
1029 // This is the immediate buffering that needs to happen so we have
1030 // enough to play right now.
1032 const GstStructure *structure = gst_message_get_structure(message);
1033 gst_structure_get_int(structure, "buffer-percent", &m_bufferingPercentage);
1035 LOG_VERBOSE(Media, "[Buffering] Buffering: %d%%.", m_bufferingPercentage);
1037 GstBufferingMode mode;
1038 gst_message_parse_buffering_stats(message, &mode, 0, 0, 0);
1039 if (mode != GST_BUFFERING_DOWNLOAD) {
1044 // This is on-disk buffering, that allows us to download much more
1045 // than needed for right now.
1046 if (!m_startedBuffering) {
1047 LOG_VERBOSE(Media, "[Buffering] Starting on-disk buffering.");
1049 m_startedBuffering = true;
1051 if (m_fillTimer.isActive())
1054 m_fillTimer.startRepeating(0.2);
1058 void MediaPlayerPrivateGStreamer::fillTimerFired(Timer<MediaPlayerPrivateGStreamer>*)
1060 GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT);
1062 if (!gst_element_query(m_playBin, query)) {
1063 gst_query_unref(query);
1068 gdouble fillStatus = 100.0;
1070 gst_query_parse_buffering_range(query, 0, &start, &stop, 0);
1071 gst_query_unref(query);
1074 fillStatus = 100.0 * stop / GST_FORMAT_PERCENT_MAX;
1076 LOG_VERBOSE(Media, "[Buffering] Download buffer filled up to %f%%", fillStatus);
1078 if (!m_mediaDuration)
1081 // Update maxTimeLoaded only if the media duration is
1082 // available. Otherwise we can't compute it.
1083 if (m_mediaDuration) {
1084 if (fillStatus == 100.0)
1085 m_maxTimeLoaded = m_mediaDuration;
1087 m_maxTimeLoaded = static_cast<float>((fillStatus * m_mediaDuration) / 100.0);
1088 LOG_VERBOSE(Media, "[Buffering] Updated maxTimeLoaded: %f", m_maxTimeLoaded);
1091 if (fillStatus != 100.0) {
1096 // Media is now fully loaded. It will play even if network
1097 // connection is cut. Buffering is done, remove the fill source
1098 // from the main loop.
1100 m_startedBuffering = false;
1104 float MediaPlayerPrivateGStreamer::maxTimeSeekable() const
1109 LOG_VERBOSE(Media, "maxTimeSeekable");
1110 // infinite duration means live stream
1111 if (isinf(duration()))
1117 float MediaPlayerPrivateGStreamer::maxTimeLoaded() const
1122 float loaded = m_maxTimeLoaded;
1123 if (!loaded && !m_fillTimer.isActive())
1124 loaded = duration();
1125 LOG_VERBOSE(Media, "maxTimeLoaded: %f", loaded);
1129 bool MediaPlayerPrivateGStreamer::didLoadingProgress() const
1131 if (!m_playBin || !m_mediaDuration || !totalBytes())
1133 float currentMaxTimeLoaded = maxTimeLoaded();
1134 bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress;
1135 m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded;
1136 LOG_VERBOSE(Media, "didLoadingProgress: %d", didLoadingProgress);
1137 return didLoadingProgress;
1140 unsigned MediaPlayerPrivateGStreamer::totalBytes() const
1148 GstFormat fmt = GST_FORMAT_BYTES;
1150 #ifdef GST_API_VERSION_1
1151 if (gst_element_query_duration(m_source.get(), fmt, &length)) {
1153 if (gst_element_query_duration(m_source.get(), &fmt, &length)) {
1155 LOG_VERBOSE(Media, "totalBytes %" G_GINT64_FORMAT, length);
1156 return static_cast<unsigned>(length);
1159 // Fall back to querying the source pads manually.
1160 // See also https://bugzilla.gnome.org/show_bug.cgi?id=638749
1161 GstIterator* iter = gst_element_iterate_src_pads(m_source.get());
1164 #ifdef GST_API_VERSION_1
1165 GValue item = G_VALUE_INIT;
1166 switch (gst_iterator_next(iter, &item)) {
1167 case GST_ITERATOR_OK: {
1168 GstPad* pad = static_cast<GstPad*>(g_value_get_object(&item));
1169 gint64 padLength = 0;
1170 if (gst_pad_query_duration(pad, fmt, &padLength) && padLength > length)
1177 switch (gst_iterator_next(iter, &data)) {
1178 case GST_ITERATOR_OK: {
1179 GRefPtr<GstPad> pad = adoptGRef(GST_PAD_CAST(data));
1180 gint64 padLength = 0;
1181 if (gst_pad_query_duration(pad.get(), &fmt, &padLength) && padLength > length)
1186 case GST_ITERATOR_RESYNC:
1187 gst_iterator_resync(iter);
1189 case GST_ITERATOR_ERROR:
1191 case GST_ITERATOR_DONE:
1196 #ifdef GST_API_VERSION_1
1197 g_value_unset(&item);
1201 gst_iterator_free(iter);
1203 LOG_VERBOSE(Media, "totalBytes %" G_GINT64_FORMAT, length);
1205 return static_cast<unsigned>(length);
1208 unsigned MediaPlayerPrivateGStreamer::decodedFrameCount() const
1210 guint64 decodedFrames = 0;
1212 g_object_get(m_fpsSink, "frames-rendered", &decodedFrames, NULL);
1213 return static_cast<unsigned>(decodedFrames);
1216 unsigned MediaPlayerPrivateGStreamer::droppedFrameCount() const
1218 guint64 framesDropped = 0;
1220 g_object_get(m_fpsSink, "frames-dropped", &framesDropped, NULL);
1221 return static_cast<unsigned>(framesDropped);
1224 unsigned MediaPlayerPrivateGStreamer::audioDecodedByteCount() const
1226 GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
1227 gint64 position = 0;
1229 if (m_webkitAudioSink && gst_element_query(m_webkitAudioSink.get(), query))
1230 gst_query_parse_position(query, 0, &position);
1232 gst_query_unref(query);
1233 return static_cast<unsigned>(position);
1236 unsigned MediaPlayerPrivateGStreamer::videoDecodedByteCount() const
1238 GstQuery* query = gst_query_new_position(GST_FORMAT_BYTES);
1239 gint64 position = 0;
1241 if (gst_element_query(m_webkitVideoSink, query))
1242 gst_query_parse_position(query, 0, &position);
1244 gst_query_unref(query);
1245 return static_cast<unsigned>(position);
1248 void MediaPlayerPrivateGStreamer::updateAudioSink()
1253 GstElement* sinkPtr = 0;
1255 g_object_get(m_playBin, "audio-sink", &sinkPtr, NULL);
1256 m_webkitAudioSink = adoptGRef(sinkPtr);
1261 void MediaPlayerPrivateGStreamer::sourceChanged()
1263 GstElement* srcPtr = 0;
1265 g_object_get(m_playBin, "source", &srcPtr, NULL);
1266 m_source = adoptGRef(srcPtr);
1268 if (WEBKIT_IS_WEB_SRC(m_source.get()))
1269 webKitWebSrcSetMediaPlayer(WEBKIT_WEB_SRC(m_source.get()), m_player);
1272 void MediaPlayerPrivateGStreamer::cancelLoad()
1274 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
1278 gst_element_set_state(m_playBin, GST_STATE_NULL);
1280 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1281 if (m_audioSessionManager)
1282 m_audioSessionManager->setSoundState(ASM_STATE_STOP);
1286 void MediaPlayerPrivateGStreamer::updateStates()
1294 MediaPlayer::NetworkState oldNetworkState = m_networkState;
1295 MediaPlayer::ReadyState oldReadyState = m_readyState;
1299 GstStateChangeReturn ret = gst_element_get_state(m_playBin,
1300 &state, &pending, 250 * GST_NSECOND);
1302 bool shouldUpdateAfterSeek = false;
1304 case GST_STATE_CHANGE_SUCCESS:
1305 LOG_VERBOSE(Media, "State: %s, pending: %s",
1306 gst_element_state_get_name(state),
1307 gst_element_state_get_name(pending));
1309 m_resetPipeline = state <= GST_STATE_READY;
1311 // Try to figure out ready and network states.
1312 if (state == GST_STATE_READY) {
1313 m_readyState = MediaPlayer::HaveMetadata;
1314 m_networkState = MediaPlayer::Empty;
1315 // Cache the duration without emiting the durationchange
1316 // event because it's taken care of by the media element
1317 // in this precise case.
1318 if (!m_isEndReached)
1320 } else if ((state == GST_STATE_NULL) || (maxTimeLoaded() == duration())) {
1321 m_networkState = MediaPlayer::Loaded;
1322 m_readyState = MediaPlayer::HaveEnoughData;
1324 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
1325 m_networkState = MediaPlayer::Loading;
1328 if (m_buffering && state != GST_STATE_READY) {
1329 m_readyState = MediaPlayer::HaveCurrentData;
1330 m_networkState = MediaPlayer::Loading;
1333 // Now let's try to get the states in more detail using
1334 // information from GStreamer, while we sync states where
1336 if (state == GST_STATE_PAUSED) {
1337 if (!m_webkitAudioSink)
1339 if (m_buffering && m_bufferingPercentage == 100) {
1340 m_buffering = false;
1341 m_bufferingPercentage = 0;
1342 m_readyState = MediaPlayer::HaveEnoughData;
1344 LOG_VERBOSE(Media, "[Buffering] Complete.");
1347 LOG_VERBOSE(Media, "[Buffering] Restarting playback.");
1348 gst_element_set_state(m_playBin, GST_STATE_PLAYING);
1350 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1351 if (m_audioSessionManager)
1352 m_audioSessionManager->setSoundState(ASM_STATE_PLAYING);
1355 } else if (!m_buffering && (currentTime() < duration())) {
1358 } else if (state == GST_STATE_PLAYING) {
1359 m_readyState = MediaPlayer::HaveEnoughData;
1363 m_readyState = MediaPlayer::HaveCurrentData;
1364 m_networkState = MediaPlayer::Loading;
1366 LOG_VERBOSE(Media, "[Buffering] Pausing stream for buffering.");
1368 gst_element_set_state(m_playBin, GST_STATE_PAUSED);
1370 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1371 if (m_audioSessionManager)
1372 m_audioSessionManager->setSoundState(ASM_STATE_PAUSE);
1378 // Is on-disk buffering in progress?
1379 if (m_fillTimer.isActive())
1380 m_networkState = MediaPlayer::Loading;
1382 if (m_changingRate) {
1383 m_player->rateChanged();
1384 m_changingRate = false;
1388 shouldUpdateAfterSeek = true;
1393 case GST_STATE_CHANGE_ASYNC:
1394 LOG_VERBOSE(Media, "Async: State: %s, pending: %s",
1395 gst_element_state_get_name(state),
1396 gst_element_state_get_name(pending));
1397 // Change in progress
1399 if (!m_isStreaming && !m_buffering)
1403 shouldUpdateAfterSeek = true;
1407 case GST_STATE_CHANGE_FAILURE:
1408 LOG_VERBOSE(Media, "Failure: State: %s, pending: %s",
1409 gst_element_state_get_name(state),
1410 gst_element_state_get_name(pending));
1413 case GST_STATE_CHANGE_NO_PREROLL:
1414 LOG_VERBOSE(Media, "No preroll: State: %s, pending: %s",
1415 gst_element_state_get_name(state),
1416 gst_element_state_get_name(pending));
1418 if (state == GST_STATE_READY)
1419 m_readyState = MediaPlayer::HaveNothing;
1420 else if (state == GST_STATE_PAUSED) {
1421 m_readyState = MediaPlayer::HaveEnoughData;
1423 // Live pipelines go in PAUSED without prerolling.
1424 m_isStreaming = true;
1425 } else if (state == GST_STATE_PLAYING)
1429 shouldUpdateAfterSeek = true;
1432 gst_element_set_state(m_playBin, GST_STATE_PLAYING);
1434 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1435 if (m_audioSessionManager)
1436 m_audioSessionManager->setSoundState(ASM_STATE_PLAYING);
1439 } else if (!m_paused) {
1440 gst_element_set_state(m_playBin, GST_STATE_PLAYING);
1442 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1443 if (m_audioSessionManager)
1444 m_audioSessionManager->setSoundState(ASM_STATE_PLAYING);
1448 m_networkState = MediaPlayer::Loading;
1451 LOG_VERBOSE(Media, "Else : %d", ret);
1456 m_readyState = MediaPlayer::HaveNothing;
1458 if (shouldUpdateAfterSeek)
1461 if (m_networkState != oldNetworkState) {
1462 LOG_VERBOSE(Media, "Network State Changed from %u to %u",
1463 oldNetworkState, m_networkState);
1464 m_player->networkStateChanged();
1466 if (m_readyState != oldReadyState) {
1467 LOG_VERBOSE(Media, "Ready State Changed from %u to %u",
1468 oldReadyState, m_readyState);
1469 m_player->readyStateChanged();
1470 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
1471 if (state == GST_STATE_PLAYING)
1472 m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
1477 void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message)
1479 if (m_mediaLocations)
1480 gst_structure_free(m_mediaLocations);
1482 const GstStructure* structure = gst_message_get_structure(message);
1484 // This structure can contain:
1485 // - both a new-location string and embedded locations structure
1486 // - or only a new-location string.
1487 m_mediaLocations = gst_structure_copy(structure);
1488 const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations");
1491 m_mediaLocationCurrentIndex = static_cast<int>(gst_value_list_get_size(locations)) -1;
1497 bool MediaPlayerPrivateGStreamer::loadNextLocation()
1499 if (!m_mediaLocations)
1502 const GValue* locations = gst_structure_get_value(m_mediaLocations, "locations");
1503 const gchar* newLocation = 0;
1506 // Fallback on new-location string.
1507 newLocation = gst_structure_get_string(m_mediaLocations, "new-location");
1513 if (m_mediaLocationCurrentIndex < 0) {
1514 m_mediaLocations = 0;
1518 const GValue* location = gst_value_list_get_value(locations,
1519 m_mediaLocationCurrentIndex);
1520 const GstStructure* structure = gst_value_get_structure(location);
1523 m_mediaLocationCurrentIndex--;
1527 newLocation = gst_structure_get_string(structure, "new-location");
1531 // Found a candidate. new-location is not always an absolute url
1532 // though. We need to take the base of the current url and
1533 // append the value of new-location to it.
1535 gchar* currentLocation = 0;
1536 g_object_get(m_playBin, "uri", ¤tLocation, NULL);
1538 KURL currentUrl(KURL(), currentLocation);
1539 g_free(currentLocation);
1543 if (gst_uri_is_valid(newLocation))
1544 newUrl = KURL(KURL(), newLocation);
1546 newUrl = KURL(KURL(), currentUrl.baseAsString() + newLocation);
1548 RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(currentUrl);
1549 if (securityOrigin->canRequest(newUrl)) {
1550 LOG_VERBOSE(Media, "New media url: %s", newUrl.string().utf8().data());
1552 // Reset player states.
1553 m_networkState = MediaPlayer::Loading;
1554 m_player->networkStateChanged();
1555 m_readyState = MediaPlayer::HaveNothing;
1556 m_player->readyStateChanged();
1558 // Reset pipeline state.
1559 m_resetPipeline = true;
1560 gst_element_set_state(m_playBin, GST_STATE_READY);
1562 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1563 if (m_audioSessionManager)
1564 m_audioSessionManager->setSoundState(ASM_STATE_NONE);
1567 gst_element_get_state(m_playBin, &state, 0, 0);
1568 if (state <= GST_STATE_READY) {
1569 // Set the new uri and start playing.
1570 g_object_set(m_playBin, "uri", newUrl.string().utf8().data(), NULL);
1571 gst_element_set_state(m_playBin, GST_STATE_PLAYING);
1573 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1574 if (m_audioSessionManager)
1575 m_audioSessionManager->setSoundState(ASM_STATE_PLAYING);
1581 m_mediaLocationCurrentIndex--;
1586 void MediaPlayerPrivateGStreamer::loadStateChanged()
1591 void MediaPlayerPrivateGStreamer::sizeChanged()
1596 void MediaPlayerPrivateGStreamer::timeChanged()
1599 m_player->timeChanged();
1602 void MediaPlayerPrivateGStreamer::didEnd()
1604 // Synchronize position and duration values to not confuse the
1605 // HTMLMediaElement. In some cases like reverse playback the
1606 // position is not always reported as 0 for instance.
1607 float now = currentTime();
1608 if (now > 0 && now <= duration() && m_mediaDuration != now) {
1609 m_mediaDurationKnown = true;
1610 m_mediaDuration = now;
1611 m_player->durationChanged();
1614 m_isEndReached = true;
1617 if (!m_player->mediaPlayerClient()->mediaPlayerIsLooping()) {
1619 gst_element_set_state(m_playBin, GST_STATE_NULL);
1621 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1622 if (m_audioSessionManager)
1623 m_audioSessionManager->setSoundState(ASM_STATE_STOP);
1628 void MediaPlayerPrivateGStreamer::cacheDuration()
1630 // Reset cached media duration
1631 m_mediaDuration = 0;
1633 // And re-cache it if possible.
1635 gst_element_get_state(m_playBin, &state, 0, 0);
1636 float newDuration = duration();
1638 if (state <= GST_STATE_READY) {
1639 // Don't set m_mediaDurationKnown yet if the pipeline is not
1640 // paused. This allows duration() query to fail at least once
1641 // before playback starts and duration becomes known.
1642 if (!isinf(newDuration))
1643 m_mediaDuration = newDuration;
1645 m_mediaDurationKnown = !isinf(newDuration);
1646 if (m_mediaDurationKnown)
1647 m_mediaDuration = newDuration;
1650 if (!isinf(newDuration))
1651 m_mediaDuration = newDuration;
1654 void MediaPlayerPrivateGStreamer::durationChanged()
1656 float previousDuration = m_mediaDuration;
1659 // Avoid emiting durationchanged in the case where the previous
1660 // duration was 0 because that case is already handled by the
1661 // HTMLMediaElement.
1662 if (previousDuration && m_mediaDuration != previousDuration)
1663 m_player->durationChanged();
1666 bool MediaPlayerPrivateGStreamer::supportsMuting() const
1671 void MediaPlayerPrivateGStreamer::setMuted(bool muted)
1676 g_object_set(m_playBin, "mute", muted, NULL);
1679 void MediaPlayerPrivateGStreamer::notifyPlayerOfMute()
1681 m_muteTimerHandler = 0;
1683 if (!m_player || !m_playBin)
1687 g_object_get(m_playBin, "mute", &muted, NULL);
1688 m_player->muteChanged(static_cast<bool>(muted));
1691 void MediaPlayerPrivateGStreamer::muteChanged()
1693 if (m_muteTimerHandler)
1694 g_source_remove(m_muteTimerHandler);
1695 m_muteTimerHandler = g_timeout_add(0, reinterpret_cast<GSourceFunc>(mediaPlayerPrivateMuteChangeTimeoutCallback), this);
1698 void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState error)
1700 m_errorOccured = true;
1701 if (m_networkState != error) {
1702 m_networkState = error;
1703 m_player->networkStateChanged();
1705 if (m_readyState != MediaPlayer::HaveNothing) {
1706 m_readyState = MediaPlayer::HaveNothing;
1707 m_player->readyStateChanged();
1711 void MediaPlayerPrivateGStreamer::setSize(const IntSize& size)
1716 void MediaPlayerPrivateGStreamer::setVisible(bool visible)
1720 void MediaPlayerPrivateGStreamer::triggerRepaint(GstBuffer* buffer)
1722 g_return_if_fail(GST_IS_BUFFER(buffer));
1723 gst_buffer_replace(&m_buffer, buffer);
1724 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && !ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
1725 if (m_videoSize.isEmpty())
1726 m_videoSize = naturalSize();
1727 m_videoLayer->paintVideoLayer(m_videoSize);
1729 m_player->repaint();
1733 void MediaPlayerPrivateGStreamer::paint(GraphicsContext* context, const IntRect& rect)
1735 if (context->paintingDisabled())
1738 if (!m_player->visible())
1744 GstCaps* caps = webkitGstGetPadCaps(m_videoSinkPad.get());
1748 RefPtr<ImageGStreamer> gstImage = ImageGStreamer::createImage(m_buffer, caps);
1752 context->drawImage(reinterpret_cast<Image*>(gstImage->image().get()), ColorSpaceSRGB,
1753 rect, gstImage->rect(), CompositeCopy, DoNotRespectImageOrientation, false);
1756 static HashSet<String> mimeTypeCache()
1758 initializeGStreamerAndRegisterWebKitElements();
1760 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1761 static bool typeListInitialized = false;
1763 if (typeListInitialized)
1765 const char* mimeTypes[] = {"application/ogg",
1766 "application/vnd.apple.mpegurl",
1767 "application/vnd.rn-realmedia",
1768 "application/x-3gp",
1769 "application/x-pn-realaudio",
1788 "audio/x-amr-nb-sh",
1789 "audio/x-amr-wb-sh",
1820 "audio/x-vorbis+ogg",
1824 "audio/x-wavpack-correction",
1850 for (unsigned i = 0; i < (sizeof(mimeTypes) / sizeof(*mimeTypes)); ++i)
1851 cache.add(String(mimeTypes[i]));
1853 typeListInitialized = true;
1857 void MediaPlayerPrivateGStreamer::getSupportedTypes(HashSet<String>& types)
1859 types = mimeTypeCache();
1862 MediaPlayer::SupportsType MediaPlayerPrivateGStreamer::supportsType(const String& type, const String& codecs, const KURL&)
1864 if (type.isNull() || type.isEmpty())
1865 return MediaPlayer::IsNotSupported;
1867 // spec says we should not return "probably" if the codecs string is empty
1868 if (mimeTypeCache().contains(type))
1869 return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
1870 return MediaPlayer::IsNotSupported;
1873 bool MediaPlayerPrivateGStreamer::hasSingleSecurityOrigin() const
1878 bool MediaPlayerPrivateGStreamer::supportsFullscreen() const
1880 #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1050
1881 // See <rdar://problem/7389945>
1888 PlatformMedia MediaPlayerPrivateGStreamer::platformMedia() const
1891 #ifndef GST_API_VERSION_1
1892 p.type = PlatformMedia::GStreamerGWorldType;
1893 p.media.gstreamerGWorld = m_gstGWorld.get();
1898 void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload)
1902 m_preload = preload;
1905 g_object_get(m_playBin, "flags", &flags, NULL);
1906 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
1907 flags = (GstPlayFlags)(flags | GST_PLAY_FLAG_NATIVE_VIDEO);
1909 if (preload == MediaPlayer::None)
1910 g_object_set(m_playBin, "flags", flags & ~GST_PLAY_FLAG_DOWNLOAD, NULL);
1912 g_object_set(m_playBin, "flags", flags | GST_PLAY_FLAG_DOWNLOAD, NULL);
1914 if (m_delayingLoad && m_preload != MediaPlayer::None) {
1915 m_delayingLoad = false;
1920 const char * MediaPlayerPrivateGStreamer::mediaClass() const
1922 static const gchar *default_media_class = "browser";
1924 return default_media_class;
1927 void MediaPlayerPrivateGStreamer::createGSTPlayBin()
1930 const gchar *media_class = mediaClass();
1933 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1934 if (m_audioSessionManager) {
1936 m_player_deferred_state = -1; // ignore
1937 m_audioSessionManager->registerAudioSessionManager(MM_SESSION_TYPE_SHARE, mediaPlayerPrivateAudioSessionNotifyCallback, player());
1938 asm_handle = m_audioSessionManager->getAsmHandle();
1942 m_playBin = gst_element_factory_make(gPlaybinName, "play");
1944 GstElement *sink = gst_element_factory_make("pulsesink", "audio-sink");
1946 GstStructure *props;
1950 gchar *fnam_str = g_strdup_printf("/tmp/murphy/mapping-file-%d", getpid());
1952 if ((f = fopen(fnam_str, "r"))) {
1953 if (fread((void *)ids, sizeof(guint32), 256, f) == 256)
1954 rsetid = ids[(asm_handle & 0xff)];
1961 gchar *prop_str = g_strdup_printf("props,resource.set.id=(string)%u,media.role=%s", rsetid, media_class);
1963 props = gst_structure_from_string(prop_str, NULL);
1964 g_object_set(sink, "stream-properties", props, NULL);
1965 gst_structure_free(props);
1967 g_object_set(m_playBin, "audio-sink", sink, NULL);
1972 #ifndef GST_API_VERSION_1
1973 m_gstGWorld = GStreamerGWorld::createGWorld(m_playBin);
1976 GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin));
1977 gst_bus_add_signal_watch(bus);
1978 g_signal_connect(bus, "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this);
1979 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
1980 gst_bus_set_sync_handler(bus, GstBusSyncHandler(mediaPlayerPrivateSyncHandler), this);
1982 gst_object_unref(bus);
1984 g_object_set(m_playBin, "mute", m_player->muted(), NULL);
1986 g_signal_connect(m_playBin, "notify::volume", G_CALLBACK(mediaPlayerPrivateVolumeChangedCallback), this);
1987 g_signal_connect(m_playBin, "notify::source", G_CALLBACK(mediaPlayerPrivateSourceChangedCallback), this);
1988 g_signal_connect(m_playBin, "notify::mute", G_CALLBACK(mediaPlayerPrivateMuteChangedCallback), this);
1989 g_signal_connect(m_playBin, "video-changed", G_CALLBACK(mediaPlayerPrivateVideoChangedCallback), this);
1990 g_signal_connect(m_playBin, "audio-changed", G_CALLBACK(mediaPlayerPrivateAudioChangedCallback), this);
1991 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
1992 g_signal_connect(m_playBin, "source-setup", G_CALLBACK(mediaPlayerPrivateSourceSetupCallback), this);
1995 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
1996 m_webkitVideoSink = m_videoLayer->createVideoSink();
1997 m_videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink, "sink"));
1998 #else // ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
1999 #ifndef GST_API_VERSION_1
2000 m_webkitVideoSink = webkitVideoSinkNew(m_gstGWorld.get());
2002 m_webkitVideoSink = webkitVideoSinkNew();
2004 m_videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink, "sink"));
2006 g_signal_connect(m_webkitVideoSink, "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this);
2007 #endif // ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER) && ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
2009 #ifndef GST_API_VERSION_1
2010 m_videoSinkBin = gst_bin_new("sink");
2012 GstElement* videoTee = gst_element_factory_make("tee", "videoTee");
2013 #if !ENABLE(TIZEN_GSTREAMER_VIDEO)
2014 GstElement* queue = gst_element_factory_make("queue", 0);
2017 gst_object_ref_sink(m_videoSinkBin);
2019 // Build a new video sink consisting of a bin containing a tee
2020 // (meant to distribute data to multiple video sinks) and our
2021 // internal video sink. For fullscreen we create an autovideosink
2022 // and initially block the data flow towards it and configure it
2023 #if !ENABLE(TIZEN_GSTREAMER_VIDEO)
2024 gst_bin_add_many(GST_BIN(m_videoSinkBin), videoTee, queue, NULL);
2026 gst_bin_add_many(GST_BIN(m_videoSinkBin), videoTee, NULL);
2029 #if !ENABLE(TIZEN_GSTREAMER_VIDEO)
2030 // Link a new src pad from tee to queue1.
2031 GRefPtr<GstPad> srcPad = adoptGRef(gst_element_get_request_pad(videoTee, "src%d"));
2032 GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(queue, "sink"));
2033 gst_pad_link(srcPad.get(), sinkPad.get());
2037 GstElement* actualVideoSink = 0;
2038 m_fpsSink = gst_element_factory_make("fpsdisplaysink", "sink");
2040 // The verbose property has been added in -bad 0.10.22. Making
2041 // this whole code depend on it because we don't want
2042 // fpsdiplaysink to spit data on stdout.
2043 GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(m_fpsSink)->elementfactory);
2044 if (gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 22)) {
2045 g_object_set(m_fpsSink, "silent", TRUE , NULL);
2047 // Turn off text overlay unless logging is enabled.
2049 g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
2051 WTFLogChannel* channel = getChannelFromName("Media");
2052 if (channel->state != WTFLogChannelOn)
2053 g_object_set(m_fpsSink, "text-overlay", FALSE , NULL);
2054 #endif // LOG_DISABLED
2056 if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_fpsSink), "video-sink")) {
2057 g_object_set(m_fpsSink, "video-sink", m_webkitVideoSink, NULL);
2058 #ifndef GST_API_VERSION_1
2059 gst_bin_add(GST_BIN(m_videoSinkBin), m_fpsSink);
2061 actualVideoSink = m_fpsSink;
2069 #ifndef GST_API_VERSION_1
2070 gst_bin_add(GST_BIN(m_videoSinkBin), m_webkitVideoSink);
2072 actualVideoSink = m_webkitVideoSink;
2075 ASSERT(actualVideoSink);
2077 #ifndef GST_API_VERSION_1
2078 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
2079 // Link a new src pad from tee to queue1.
2080 GRefPtr<GstPad> srcPad = adoptGRef(gst_element_get_request_pad(videoTee, "src%d"));
2081 GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(actualVideoSink, "sink"));
2082 gst_pad_link(srcPad.get(), sinkPad.get());
2084 // Faster elements linking.
2085 gst_element_link_pads_full(queue, "src", actualVideoSink, "sink", GST_PAD_LINK_CHECK_NOTHING);
2087 // Add a ghostpad to the bin so it can proxy to tee.
2088 GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(videoTee, "sink"));
2089 gst_element_add_pad(m_videoSinkBin, gst_ghost_pad_new("sink", pad.get()));
2091 // Set the bin as video sink of playbin.
2092 g_object_set(m_playBin, "video-sink", m_videoSinkBin, NULL);
2094 g_object_set(m_playBin, "video-sink", actualVideoSink, NULL);
2097 GRefPtr<GstPad> videoSinkPad = adoptGRef(gst_element_get_static_pad(m_webkitVideoSink, "sink"));
2099 g_signal_connect(videoSinkPad.get(), "notify::caps", G_CALLBACK(mediaPlayerPrivateVideoSinkCapsChangedCallback), this);
2101 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
2102 if (m_player_deferred_state == 1)
2104 else if (m_player_deferred_state == 0)
2109 #if ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
2110 bool MediaPlayerPrivateGStreamer::supportsAcceleratedRendering() const
2112 bool isEmbedVideo = false;
2113 if (m_player->mediaPlayerClient()) {
2114 Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument();
2115 if (document && document->settings()) {
2116 isEmbedVideo = document->settings()->acceleratedCompositingForVideoEnabled()
2117 && document->settings()->acceleratedCompositingEnabled();
2118 LOG_VERBOSE(Media, "isEmbedVideo: %d", isEmbedVideo);
2122 return isEmbedVideo;
2125 PlatformLayer* MediaPlayerPrivateGStreamer::platformLayer() const
2127 return m_videoLayer->platformLayer();
2130 #if ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
2131 void MediaPlayerPrivateGStreamer::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& rect)
2133 m_videoLayer->paintCurrentFrameInContext(context, rect);
2136 void MediaPlayerPrivateGStreamer::xWindowIdPrepared(GstMessage* message)
2140 gst_structure_get_int(message->structure, "video-width", &width);
2141 gst_structure_get_int(message->structure, "video-height", &height);
2143 IntSize size(width, height);
2145 m_videoLayer->setOverlay(size);
2147 #endif // ENABLE(TIZEN_WEBKIT2_TILED_AC_SHARED_PLATFORM_SURFACE)
2148 #endif // ENABLE(TIZEN_ACCELERATED_COMPOSITING) && USE(TIZEN_TEXTURE_MAPPER)
2150 #if ENABLE(TIZEN_WEBKIT2_PROXY)
2151 void MediaPlayerPrivateGStreamer::setProxy(GstElement* source)
2153 SoupSession* session = WebCore::ResourceHandle::defaultSession();
2157 SoupURI* proxyUri = 0;
2158 g_object_get(session, SOUP_SESSION_PROXY_URI, &proxyUri, NULL);
2162 char* proxy = soup_uri_to_string(proxyUri, false);
2163 g_object_set(source, "proxy", proxy, NULL);
2165 soup_uri_free(proxyUri);
2172 #endif // USE(GSTREAMER)