| 1 | /* |
|---|
| 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 | * |
|---|
| 7 | * This library is free software; you can redistribute it and/or |
|---|
| 8 | * modify it under the terms of the GNU Library General Public |
|---|
| 9 | * License as published by the Free Software Foundation; either |
|---|
| 10 | * version 2 of the License, or (at your option) any later version. |
|---|
| 11 | * |
|---|
| 12 | * This library is distributed in the hope that it will be useful, |
|---|
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 15 | * Library General Public License for more details. |
|---|
| 16 | * |
|---|
| 17 | * You should have received a copy of the GNU Library General Public License |
|---|
| 18 | * aint with this library; see the file COPYING.LIB. If not, write to |
|---|
| 19 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|---|
| 20 | * Boston, MA 02110-1301, USA. |
|---|
| 21 | */ |
|---|
| 22 | |
|---|
| 23 | #include "config.h" |
|---|
| 24 | |
|---|
| 25 | #if ENABLE(VIDEO) |
|---|
| 26 | |
|---|
| 27 | #include "MediaPlayerPrivateGStreamer.h" |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | #include "CString.h" |
|---|
| 31 | #include "DataSourceGStreamer.h" |
|---|
| 32 | #include "GraphicsContext.h" |
|---|
| 33 | #include "IntRect.h" |
|---|
| 34 | #include "KURL.h" |
|---|
| 35 | #include "MIMETypeRegistry.h" |
|---|
| 36 | #include "MediaPlayer.h" |
|---|
| 37 | #include "NotImplemented.h" |
|---|
| 38 | #include "ScrollView.h" |
|---|
| 39 | #include "TimeRanges.h" |
|---|
| 40 | #include "VideoSinkGStreamer.h" |
|---|
| 41 | #include "Widget.h" |
|---|
| 42 | |
|---|
| 43 | #include <gst/gst.h> |
|---|
| 44 | #include <gst/interfaces/mixer.h> |
|---|
| 45 | #include <gst/interfaces/xoverlay.h> |
|---|
| 46 | #include <gst/video/video.h> |
|---|
| 47 | #include <limits> |
|---|
| 48 | #include <math.h> |
|---|
| 49 | #include <wtf/GOwnPtr.h> |
|---|
| 50 | |
|---|
| 51 | using namespace std; |
|---|
| 52 | |
|---|
| 53 | namespace WebCore { |
|---|
| 54 | |
|---|
| 55 | gboolean mediaPlayerPrivateMessageCallback(GstBus* bus, GstMessage* message, gpointer data) |
|---|
| 56 | { |
|---|
| 57 | GOwnPtr<GError> err; |
|---|
| 58 | GOwnPtr<gchar> debug; |
|---|
| 59 | MediaPlayer::NetworkState error; |
|---|
| 60 | MediaPlayerPrivate* mp = reinterpret_cast<MediaPlayerPrivate*>(data); |
|---|
| 61 | gint percent = 0; |
|---|
| 62 | |
|---|
| 63 | switch (GST_MESSAGE_TYPE(message)) { |
|---|
| 64 | case GST_MESSAGE_ERROR: |
|---|
| 65 | gst_message_parse_error(message, &err.outPtr(), &debug.outPtr()); |
|---|
| 66 | LOG_VERBOSE(Media, "Error: %d, %s", err->code, err->message); |
|---|
| 67 | |
|---|
| 68 | error = MediaPlayer::Empty; |
|---|
| 69 | if (err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND || |
|---|
| 70 | err->code == GST_STREAM_ERROR_WRONG_TYPE || |
|---|
| 71 | err->code == GST_STREAM_ERROR_FAILED || |
|---|
| 72 | err->code == GST_CORE_ERROR_MISSING_PLUGIN || |
|---|
| 73 | err->code == GST_RESOURCE_ERROR_NOT_FOUND) |
|---|
| 74 | error = MediaPlayer::FormatError; |
|---|
| 75 | else if (err->domain == GST_STREAM_ERROR) |
|---|
| 76 | error = MediaPlayer::DecodeError; |
|---|
| 77 | else if (err->domain == GST_RESOURCE_ERROR) |
|---|
| 78 | error = MediaPlayer::NetworkError; |
|---|
| 79 | |
|---|
| 80 | if (mp) |
|---|
| 81 | mp->loadingFailed(error); |
|---|
| 82 | break; |
|---|
| 83 | case GST_MESSAGE_EOS: |
|---|
| 84 | LOG_VERBOSE(Media, "End of Stream"); |
|---|
| 85 | mp->didEnd(); |
|---|
| 86 | break; |
|---|
| 87 | case GST_MESSAGE_STATE_CHANGED: |
|---|
| 88 | mp->updateStates(); |
|---|
| 89 | break; |
|---|
| 90 | case GST_MESSAGE_BUFFERING: |
|---|
| 91 | gst_message_parse_buffering(message, &percent); |
|---|
| 92 | LOG_VERBOSE(Media, "Buffering %d", percent); |
|---|
| 93 | break; |
|---|
| 94 | default: |
|---|
| 95 | LOG_VERBOSE(Media, "Unhandled GStreamer message type: %s", |
|---|
| 96 | GST_MESSAGE_TYPE_NAME(message)); |
|---|
| 97 | break; |
|---|
| 98 | } |
|---|
| 99 | return true; |
|---|
| 100 | } |
|---|
| 101 | |
|---|
| 102 | void mediaPlayerPrivateRepaintCallback(WebKitVideoSink*, GstBuffer *buffer, MediaPlayerPrivate* playerPrivate) |
|---|
| 103 | { |
|---|
| 104 | g_return_if_fail(GST_IS_BUFFER(buffer)); |
|---|
| 105 | gst_buffer_replace(&playerPrivate->m_buffer, buffer); |
|---|
| 106 | playerPrivate->repaint(); |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) |
|---|
| 110 | { |
|---|
| 111 | return new MediaPlayerPrivate(player); |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) |
|---|
| 115 | { |
|---|
| 116 | if (isAvailable()) |
|---|
| 117 | registrar(create, getSupportedTypes, supportsType); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | static bool gstInitialized = false; |
|---|
| 121 | |
|---|
| 122 | static void do_gst_init() |
|---|
| 123 | { |
|---|
| 124 | // FIXME: We should pass the arguments from the command line |
|---|
| 125 | if (!gstInitialized) { |
|---|
| 126 | gst_init(0, 0); |
|---|
| 127 | gstInitialized = true; |
|---|
| 128 | gst_element_register(0, "webkitmediasrc", GST_RANK_PRIMARY, |
|---|
| 129 | WEBKIT_TYPE_DATA_SRC); |
|---|
| 130 | |
|---|
| 131 | } |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) |
|---|
| 135 | : m_player(player) |
|---|
| 136 | , m_playBin(0) |
|---|
| 137 | , m_videoSink(0) |
|---|
| 138 | , m_source(0) |
|---|
| 139 | , m_seekTime(0) |
|---|
| 140 | , m_endTime(numeric_limits<float>::infinity()) |
|---|
| 141 | , m_networkState(MediaPlayer::Empty) |
|---|
| 142 | , m_readyState(MediaPlayer::HaveNothing) |
|---|
| 143 | , m_startedPlaying(false) |
|---|
| 144 | , m_isStreaming(false) |
|---|
| 145 | , m_size(IntSize()) |
|---|
| 146 | , m_buffer(0) |
|---|
| 147 | , m_paused(true) |
|---|
| 148 | , m_seeking(false) |
|---|
| 149 | , m_errorOccured(false) |
|---|
| 150 | { |
|---|
| 151 | do_gst_init(); |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | MediaPlayerPrivate::~MediaPlayerPrivate() |
|---|
| 155 | { |
|---|
| 156 | if (m_buffer) |
|---|
| 157 | gst_buffer_unref(m_buffer); |
|---|
| 158 | m_buffer = 0; |
|---|
| 159 | |
|---|
| 160 | if (m_playBin) { |
|---|
| 161 | gst_element_set_state(m_playBin, GST_STATE_NULL); |
|---|
| 162 | gst_object_unref(GST_OBJECT(m_playBin)); |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | if (m_videoSink) { |
|---|
| 166 | g_object_unref(m_videoSink); |
|---|
| 167 | m_videoSink = 0; |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | void MediaPlayerPrivate::load(const String& url) |
|---|
| 172 | { |
|---|
| 173 | LOG_VERBOSE(Media, "Load %s", url.utf8().data()); |
|---|
| 174 | if (m_networkState != MediaPlayer::Loading) { |
|---|
| 175 | m_networkState = MediaPlayer::Loading; |
|---|
| 176 | m_player->networkStateChanged(); |
|---|
| 177 | } |
|---|
| 178 | if (m_readyState != MediaPlayer::HaveNothing) { |
|---|
| 179 | m_readyState = MediaPlayer::HaveNothing; |
|---|
| 180 | m_player->readyStateChanged(); |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | createGSTPlayBin(url); |
|---|
| 184 | pause(); |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | void MediaPlayerPrivate::play() |
|---|
| 188 | { |
|---|
| 189 | LOG_VERBOSE(Media, "Play"); |
|---|
| 190 | gst_element_set_state(m_playBin, GST_STATE_PLAYING); |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | void MediaPlayerPrivate::pause() |
|---|
| 194 | { |
|---|
| 195 | LOG_VERBOSE(Media, "Pause"); |
|---|
| 196 | gst_element_set_state(m_playBin, GST_STATE_PAUSED); |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | float MediaPlayerPrivate::duration() const |
|---|
| 200 | { |
|---|
| 201 | if (!m_playBin) |
|---|
| 202 | return 0.0; |
|---|
| 203 | |
|---|
| 204 | if (m_errorOccured) |
|---|
| 205 | return 0.0; |
|---|
| 206 | |
|---|
| 207 | GstFormat timeFormat = GST_FORMAT_TIME; |
|---|
| 208 | gint64 timeLength = 0; |
|---|
| 209 | |
|---|
| 210 | if (!gst_element_query_duration(m_playBin, &timeFormat, &timeLength) || timeFormat != GST_FORMAT_TIME || static_cast<guint64>(timeLength) == GST_CLOCK_TIME_NONE) { |
|---|
| 211 | LOG_VERBOSE(Media, "Time duration query failed."); |
|---|
| 212 | return numeric_limits<float>::infinity(); |
|---|
| 213 | } |
|---|
| 214 | |
|---|
| 215 | LOG_VERBOSE(Media, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(timeLength)); |
|---|
| 216 | |
|---|
| 217 | return (float) ((guint64) timeLength / 1000000000.0); |
|---|
| 218 | // FIXME: handle 3.14.9.5 properly |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | float MediaPlayerPrivate::currentTime() const |
|---|
| 222 | { |
|---|
| 223 | if (!m_playBin) |
|---|
| 224 | return 0; |
|---|
| 225 | |
|---|
| 226 | if (m_errorOccured) |
|---|
| 227 | return 0; |
|---|
| 228 | |
|---|
| 229 | if (m_seeking) |
|---|
| 230 | return m_seekTime; |
|---|
| 231 | |
|---|
| 232 | float ret = 0.0; |
|---|
| 233 | |
|---|
| 234 | GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); |
|---|
| 235 | if (!gst_element_query(m_playBin, query)) { |
|---|
| 236 | LOG_VERBOSE(Media, "Position query failed..."); |
|---|
| 237 | gst_query_unref(query); |
|---|
| 238 | return ret; |
|---|
| 239 | } |
|---|
| 240 | |
|---|
| 241 | gint64 position; |
|---|
| 242 | gst_query_parse_position(query, 0, &position); |
|---|
| 243 | ret = (float) (position / 1000000000.0); |
|---|
| 244 | LOG_VERBOSE(Media, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); |
|---|
| 245 | |
|---|
| 246 | gst_query_unref(query); |
|---|
| 247 | |
|---|
| 248 | return ret; |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | void MediaPlayerPrivate::seek(float time) |
|---|
| 252 | { |
|---|
| 253 | GstClockTime sec = (GstClockTime)(time * GST_SECOND); |
|---|
| 254 | |
|---|
| 255 | if (!m_playBin) |
|---|
| 256 | return; |
|---|
| 257 | |
|---|
| 258 | if (m_isStreaming) |
|---|
| 259 | return; |
|---|
| 260 | |
|---|
| 261 | if (m_errorOccured) |
|---|
| 262 | return; |
|---|
| 263 | |
|---|
| 264 | LOG_VERBOSE(Media, "Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(sec)); |
|---|
| 265 | if (!gst_element_seek(m_playBin, m_player->rate(), |
|---|
| 266 | GST_FORMAT_TIME, |
|---|
| 267 | (GstSeekFlags)(GST_SEEK_FLAG_FLUSH), |
|---|
| 268 | GST_SEEK_TYPE_SET, sec, |
|---|
| 269 | GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) |
|---|
| 270 | LOG_VERBOSE(Media, "Seek to %f failed", time); |
|---|
| 271 | else { |
|---|
| 272 | m_seeking = true; |
|---|
| 273 | m_seekTime = sec; |
|---|
| 274 | } |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | void MediaPlayerPrivate::setEndTime(float time) |
|---|
| 278 | { |
|---|
| 279 | notImplemented(); |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | void MediaPlayerPrivate::startEndPointTimerIfNeeded() |
|---|
| 283 | { |
|---|
| 284 | notImplemented(); |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | void MediaPlayerPrivate::cancelSeek() |
|---|
| 288 | { |
|---|
| 289 | notImplemented(); |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*) |
|---|
| 293 | { |
|---|
| 294 | notImplemented(); |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | bool MediaPlayerPrivate::paused() const |
|---|
| 298 | { |
|---|
| 299 | return m_paused; |
|---|
| 300 | } |
|---|
| 301 | |
|---|
| 302 | bool MediaPlayerPrivate::seeking() const |
|---|
| 303 | { |
|---|
| 304 | return m_seeking; |
|---|
| 305 | } |
|---|
| 306 | |
|---|
| 307 | // Returns the size of the video |
|---|
| 308 | IntSize MediaPlayerPrivate::naturalSize() const |
|---|
| 309 | { |
|---|
| 310 | if (!hasVideo()) |
|---|
| 311 | return IntSize(); |
|---|
| 312 | |
|---|
| 313 | // TODO: handle possible clean aperture data. See |
|---|
| 314 | // https://bugzilla.gnome.org/show_bug.cgi?id=596571 |
|---|
| 315 | // TODO: handle possible transformation matrix. See |
|---|
| 316 | // https://bugzilla.gnome.org/show_bug.cgi?id=596326 |
|---|
| 317 | int width = 0, height = 0; |
|---|
| 318 | if (GstPad* pad = gst_element_get_static_pad(m_videoSink, "sink")) { |
|---|
| 319 | GstCaps* caps = GST_PAD_CAPS(pad); |
|---|
| 320 | gfloat pixelAspectRatio; |
|---|
| 321 | gint pixelAspectRatioNumerator, pixelAspectRatioDenominator; |
|---|
| 322 | |
|---|
| 323 | if (!GST_IS_CAPS(caps) || !gst_caps_is_fixed(caps) || |
|---|
| 324 | !gst_video_format_parse_caps(caps, NULL, &width, &height) || |
|---|
| 325 | !gst_video_parse_caps_pixel_aspect_ratio(caps, &pixelAspectRatioNumerator, |
|---|
| 326 | &pixelAspectRatioDenominator)) { |
|---|
| 327 | gst_object_unref(GST_OBJECT(pad)); |
|---|
| 328 | return IntSize(); |
|---|
| 329 | } |
|---|
| 330 | |
|---|
| 331 | pixelAspectRatio = (gfloat) pixelAspectRatioNumerator / (gfloat) pixelAspectRatioDenominator; |
|---|
| 332 | width *= pixelAspectRatio; |
|---|
| 333 | height /= pixelAspectRatio; |
|---|
| 334 | gst_object_unref(GST_OBJECT(pad)); |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | return IntSize(width, height); |
|---|
| 338 | } |
|---|
| 339 | |
|---|
| 340 | bool MediaPlayerPrivate::hasVideo() const |
|---|
| 341 | { |
|---|
| 342 | gint currentVideo = -1; |
|---|
| 343 | if (m_playBin) |
|---|
| 344 | g_object_get(G_OBJECT(m_playBin), "current-video", ¤tVideo, NULL); |
|---|
| 345 | return currentVideo > -1; |
|---|
| 346 | } |
|---|
| 347 | |
|---|
| 348 | bool MediaPlayerPrivate::hasAudio() const |
|---|
| 349 | { |
|---|
| 350 | gint currentAudio = -1; |
|---|
| 351 | if (m_playBin) |
|---|
| 352 | g_object_get(G_OBJECT(m_playBin), "current-audio", ¤tAudio, NULL); |
|---|
| 353 | return currentAudio > -1; |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | void MediaPlayerPrivate::setVolume(float volume) |
|---|
| 357 | { |
|---|
| 358 | if (!m_playBin) |
|---|
| 359 | return; |
|---|
| 360 | |
|---|
| 361 | g_object_set(G_OBJECT(m_playBin), "volume", static_cast<double>(volume), NULL); |
|---|
| 362 | } |
|---|
| 363 | |
|---|
| 364 | void MediaPlayerPrivate::setRate(float rate) |
|---|
| 365 | { |
|---|
| 366 | if (rate == 0.0) { |
|---|
| 367 | gst_element_set_state(m_playBin, GST_STATE_PAUSED); |
|---|
| 368 | return; |
|---|
| 369 | } |
|---|
| 370 | |
|---|
| 371 | if (m_isStreaming) |
|---|
| 372 | return; |
|---|
| 373 | |
|---|
| 374 | LOG_VERBOSE(Media, "Set Rate to %f", rate); |
|---|
| 375 | seek(currentTime()); |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | int MediaPlayerPrivate::dataRate() const |
|---|
| 379 | { |
|---|
| 380 | notImplemented(); |
|---|
| 381 | return 1; |
|---|
| 382 | } |
|---|
| 383 | |
|---|
| 384 | MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const |
|---|
| 385 | { |
|---|
| 386 | return m_networkState; |
|---|
| 387 | } |
|---|
| 388 | |
|---|
| 389 | MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const |
|---|
| 390 | { |
|---|
| 391 | return m_readyState; |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const |
|---|
| 395 | { |
|---|
| 396 | RefPtr<TimeRanges> timeRanges = TimeRanges::create(); |
|---|
| 397 | float loaded = maxTimeLoaded(); |
|---|
| 398 | if (!m_errorOccured && !m_isStreaming && loaded > 0) |
|---|
| 399 | timeRanges->add(0, loaded); |
|---|
| 400 | return timeRanges.release(); |
|---|
| 401 | } |
|---|
| 402 | |
|---|
| 403 | float MediaPlayerPrivate::maxTimeSeekable() const |
|---|
| 404 | { |
|---|
| 405 | if (m_errorOccured) |
|---|
| 406 | return 0.0; |
|---|
| 407 | |
|---|
| 408 | // TODO |
|---|
| 409 | LOG_VERBOSE(Media, "maxTimeSeekable"); |
|---|
| 410 | if (m_isStreaming) |
|---|
| 411 | return numeric_limits<float>::infinity(); |
|---|
| 412 | // infinite duration means live stream |
|---|
| 413 | return maxTimeLoaded(); |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | float MediaPlayerPrivate::maxTimeLoaded() const |
|---|
| 417 | { |
|---|
| 418 | if (m_errorOccured) |
|---|
| 419 | return 0.0; |
|---|
| 420 | |
|---|
| 421 | // TODO |
|---|
| 422 | LOG_VERBOSE(Media, "maxTimeLoaded"); |
|---|
| 423 | notImplemented(); |
|---|
| 424 | return duration(); |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | unsigned MediaPlayerPrivate::bytesLoaded() const |
|---|
| 428 | { |
|---|
| 429 | notImplemented(); |
|---|
| 430 | LOG_VERBOSE(Media, "bytesLoaded"); |
|---|
| 431 | /*if (!m_playBin) |
|---|
| 432 | return 0; |
|---|
| 433 | float dur = duration(); |
|---|
| 434 | float maxTime = maxTimeLoaded(); |
|---|
| 435 | if (!dur) |
|---|
| 436 | return 0;*/ |
|---|
| 437 | |
|---|
| 438 | return 1;//totalBytes() * maxTime / dur; |
|---|
| 439 | } |
|---|
| 440 | |
|---|
| 441 | bool MediaPlayerPrivate::totalBytesKnown() const |
|---|
| 442 | { |
|---|
| 443 | LOG_VERBOSE(Media, "totalBytesKnown"); |
|---|
| 444 | return totalBytes() > 0; |
|---|
| 445 | } |
|---|
| 446 | |
|---|
| 447 | unsigned MediaPlayerPrivate::totalBytes() const |
|---|
| 448 | { |
|---|
| 449 | LOG_VERBOSE(Media, "totalBytes"); |
|---|
| 450 | if (!m_source) |
|---|
| 451 | return 0; |
|---|
| 452 | |
|---|
| 453 | if (m_errorOccured) |
|---|
| 454 | return 0; |
|---|
| 455 | |
|---|
| 456 | GstFormat fmt = GST_FORMAT_BYTES; |
|---|
| 457 | gint64 length = 0; |
|---|
| 458 | gst_element_query_duration(m_source, &fmt, &length); |
|---|
| 459 | |
|---|
| 460 | return length; |
|---|
| 461 | } |
|---|
| 462 | |
|---|
| 463 | void MediaPlayerPrivate::cancelLoad() |
|---|
| 464 | { |
|---|
| 465 | if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) |
|---|
| 466 | return; |
|---|
| 467 | |
|---|
| 468 | if (m_playBin) |
|---|
| 469 | gst_element_set_state(m_playBin, GST_STATE_NULL); |
|---|
| 470 | } |
|---|
| 471 | |
|---|
| 472 | void MediaPlayerPrivate::updateStates() |
|---|
| 473 | { |
|---|
| 474 | // There is no (known) way to get such level of information about |
|---|
| 475 | // the state of GStreamer, therefore, when in PAUSED state, |
|---|
| 476 | // we are sure we can display the first frame and go to play |
|---|
| 477 | |
|---|
| 478 | if (!m_playBin) |
|---|
| 479 | return; |
|---|
| 480 | |
|---|
| 481 | if (m_errorOccured) |
|---|
| 482 | return; |
|---|
| 483 | |
|---|
| 484 | MediaPlayer::NetworkState oldNetworkState = m_networkState; |
|---|
| 485 | MediaPlayer::ReadyState oldReadyState = m_readyState; |
|---|
| 486 | GstState state; |
|---|
| 487 | GstState pending; |
|---|
| 488 | |
|---|
| 489 | GstStateChangeReturn ret = gst_element_get_state(m_playBin, |
|---|
| 490 | &state, &pending, 250 * GST_NSECOND); |
|---|
| 491 | |
|---|
| 492 | bool shouldUpdateAfterSeek = false; |
|---|
| 493 | switch (ret) { |
|---|
| 494 | case GST_STATE_CHANGE_SUCCESS: |
|---|
| 495 | LOG_VERBOSE(Media, "State: %s, pending: %s", |
|---|
| 496 | gst_element_state_get_name(state), |
|---|
| 497 | gst_element_state_get_name(pending)); |
|---|
| 498 | |
|---|
| 499 | if (state == GST_STATE_READY) |
|---|
| 500 | m_readyState = MediaPlayer::HaveNothing; |
|---|
| 501 | else if (state == GST_STATE_PAUSED) |
|---|
| 502 | m_readyState = MediaPlayer::HaveEnoughData; |
|---|
| 503 | |
|---|
| 504 | if (state == GST_STATE_PLAYING) { |
|---|
| 505 | m_readyState = MediaPlayer::HaveEnoughData; |
|---|
| 506 | m_paused = false; |
|---|
| 507 | } else |
|---|
| 508 | m_paused = true; |
|---|
| 509 | |
|---|
| 510 | if (m_seeking) { |
|---|
| 511 | shouldUpdateAfterSeek = true; |
|---|
| 512 | m_seeking = false; |
|---|
| 513 | } |
|---|
| 514 | |
|---|
| 515 | m_networkState = MediaPlayer::Loaded; |
|---|
| 516 | |
|---|
| 517 | g_object_get(m_playBin, "source", &m_source, NULL); |
|---|
| 518 | if (!m_source) |
|---|
| 519 | LOG_VERBOSE(Media, "m_source is 0"); |
|---|
| 520 | break; |
|---|
| 521 | case GST_STATE_CHANGE_ASYNC: |
|---|
| 522 | LOG_VERBOSE(Media, "Async: State: %s, pending: %s", |
|---|
| 523 | gst_element_state_get_name(state), |
|---|
| 524 | gst_element_state_get_name(pending)); |
|---|
| 525 | // Change in progress |
|---|
| 526 | return; |
|---|
| 527 | case GST_STATE_CHANGE_FAILURE: |
|---|
| 528 | LOG_VERBOSE(Media, "Failure: State: %s, pending: %s", |
|---|
| 529 | gst_element_state_get_name(state), |
|---|
| 530 | gst_element_state_get_name(pending)); |
|---|
| 531 | // Change failed |
|---|
| 532 | return; |
|---|
| 533 | case GST_STATE_CHANGE_NO_PREROLL: |
|---|
| 534 | LOG_VERBOSE(Media, "No preroll: State: %s, pending: %s", |
|---|
| 535 | gst_element_state_get_name(state), |
|---|
| 536 | gst_element_state_get_name(pending)); |
|---|
| 537 | |
|---|
| 538 | if (state == GST_STATE_READY) |
|---|
| 539 | m_readyState = MediaPlayer::HaveNothing; |
|---|
| 540 | else if (state == GST_STATE_PAUSED) |
|---|
| 541 | m_readyState = MediaPlayer::HaveCurrentData; |
|---|
| 542 | |
|---|
| 543 | m_networkState = MediaPlayer::Loading; |
|---|
| 544 | break; |
|---|
| 545 | default: |
|---|
| 546 | LOG_VERBOSE(Media, "Else : %d", ret); |
|---|
| 547 | break; |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | if (seeking()) |
|---|
| 551 | m_readyState = MediaPlayer::HaveNothing; |
|---|
| 552 | |
|---|
| 553 | if (shouldUpdateAfterSeek) |
|---|
| 554 | timeChanged(); |
|---|
| 555 | |
|---|
| 556 | if (m_networkState != oldNetworkState) { |
|---|
| 557 | LOG_VERBOSE(Media, "Network State Changed from %u to %u", |
|---|
| 558 | oldNetworkState, m_networkState); |
|---|
| 559 | m_player->networkStateChanged(); |
|---|
| 560 | } |
|---|
| 561 | if (m_readyState != oldReadyState) { |
|---|
| 562 | LOG_VERBOSE(Media, "Ready State Changed from %u to %u", |
|---|
| 563 | oldReadyState, m_readyState); |
|---|
| 564 | m_player->readyStateChanged(); |
|---|
| 565 | } |
|---|
| 566 | } |
|---|
| 567 | |
|---|
| 568 | void MediaPlayerPrivate::loadStateChanged() |
|---|
| 569 | { |
|---|
| 570 | updateStates(); |
|---|
| 571 | } |
|---|
| 572 | |
|---|
| 573 | void MediaPlayerPrivate::rateChanged() |
|---|
| 574 | { |
|---|
| 575 | updateStates(); |
|---|
| 576 | } |
|---|
| 577 | |
|---|
| 578 | void MediaPlayerPrivate::sizeChanged() |
|---|
| 579 | { |
|---|
| 580 | notImplemented(); |
|---|
| 581 | } |
|---|
| 582 | |
|---|
| 583 | void MediaPlayerPrivate::timeChanged() |
|---|
| 584 | { |
|---|
| 585 | updateStates(); |
|---|
| 586 | m_player->timeChanged(); |
|---|
| 587 | } |
|---|
| 588 | |
|---|
| 589 | void MediaPlayerPrivate::volumeChanged() |
|---|
| 590 | { |
|---|
| 591 | m_player->volumeChanged(); |
|---|
| 592 | } |
|---|
| 593 | |
|---|
| 594 | void MediaPlayerPrivate::didEnd() |
|---|
| 595 | { |
|---|
| 596 | timeChanged(); |
|---|
| 597 | } |
|---|
| 598 | |
|---|
| 599 | void MediaPlayerPrivate::loadingFailed(MediaPlayer::NetworkState error) |
|---|
| 600 | { |
|---|
| 601 | m_errorOccured = true; |
|---|
| 602 | if (m_networkState != error) { |
|---|
| 603 | m_networkState = error; |
|---|
| 604 | m_player->networkStateChanged(); |
|---|
| 605 | } |
|---|
| 606 | if (m_readyState != MediaPlayer::HaveNothing) { |
|---|
| 607 | m_readyState = MediaPlayer::HaveNothing; |
|---|
| 608 | m_player->readyStateChanged(); |
|---|
| 609 | } |
|---|
| 610 | } |
|---|
| 611 | |
|---|
| 612 | void MediaPlayerPrivate::setSize(const IntSize& size) |
|---|
| 613 | { |
|---|
| 614 | m_size = size; |
|---|
| 615 | } |
|---|
| 616 | |
|---|
| 617 | void MediaPlayerPrivate::setVisible(bool visible) |
|---|
| 618 | { |
|---|
| 619 | } |
|---|
| 620 | |
|---|
| 621 | void MediaPlayerPrivate::repaint() |
|---|
| 622 | { |
|---|
| 623 | m_player->repaint(); |
|---|
| 624 | } |
|---|
| 625 | |
|---|
| 626 | void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& rect) |
|---|
| 627 | { |
|---|
| 628 | if (context->paintingDisabled()) |
|---|
| 629 | return; |
|---|
| 630 | |
|---|
| 631 | if (!m_player->visible()) |
|---|
| 632 | return; |
|---|
| 633 | if (!m_buffer) |
|---|
| 634 | return; |
|---|
| 635 | |
|---|
| 636 | int width = 0, height = 0; |
|---|
| 637 | int pixelAspectRatioNumerator = 0; |
|---|
| 638 | int pixelAspectRatioDenominator = 0; |
|---|
| 639 | double doublePixelAspectRatioNumerator = 0; |
|---|
| 640 | double doublePixelAspectRatioDenominator = 0; |
|---|
| 641 | double displayWidth; |
|---|
| 642 | double displayHeight; |
|---|
| 643 | double scale, gapHeight, gapWidth; |
|---|
| 644 | GstVideoFormat format; |
|---|
| 645 | |
|---|
| 646 | GstCaps *caps = gst_buffer_get_caps(m_buffer); |
|---|
| 647 | |
|---|
| 648 | if (G_UNLIKELY(!gst_video_format_parse_caps(caps, &format, &width, &height) || |
|---|
| 649 | !gst_video_parse_caps_pixel_aspect_ratio(caps, &pixelAspectRatioNumerator, &pixelAspectRatioDenominator))) { |
|---|
| 650 | gst_caps_unref(caps); |
|---|
| 651 | return; |
|---|
| 652 | } |
|---|
| 653 | |
|---|
| 654 | displayWidth = width; |
|---|
| 655 | displayHeight = height; |
|---|
| 656 | doublePixelAspectRatioNumerator = pixelAspectRatioNumerator; |
|---|
| 657 | doublePixelAspectRatioDenominator = pixelAspectRatioDenominator; |
|---|
| 658 | |
|---|
| 659 | cairo_format_t cairoFormat; |
|---|
| 660 | if (format == GST_VIDEO_FORMAT_ARGB || format == GST_VIDEO_FORMAT_BGRA) |
|---|
| 661 | cairoFormat = CAIRO_FORMAT_ARGB32; |
|---|
| 662 | else |
|---|
| 663 | cairoFormat = CAIRO_FORMAT_RGB24; |
|---|
| 664 | |
|---|
| 665 | cairo_t* cr = context->platformContext(); |
|---|
| 666 | cairo_surface_t* src = cairo_image_surface_create_for_data(GST_BUFFER_DATA(m_buffer), |
|---|
| 667 | cairoFormat, |
|---|
| 668 | width, height, |
|---|
| 669 | 4 * width); |
|---|
| 670 | |
|---|
| 671 | cairo_save(cr); |
|---|
| 672 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
|---|
| 673 | |
|---|
| 674 | // Calculate the display width/height from the storage width/height and the pixel aspect ratio |
|---|
| 675 | displayWidth *= doublePixelAspectRatioNumerator / doublePixelAspectRatioDenominator; |
|---|
| 676 | displayHeight *= doublePixelAspectRatioDenominator / doublePixelAspectRatioNumerator; |
|---|
| 677 | |
|---|
| 678 | // Calculate the largest scale factor that would fill the target surface |
|---|
| 679 | scale = MIN(rect.width() / displayWidth, rect.height() / displayHeight); |
|---|
| 680 | // And calculate the new display width/height |
|---|
| 681 | displayWidth *= scale; |
|---|
| 682 | displayHeight *= scale; |
|---|
| 683 | |
|---|
| 684 | // Calculate gap between border an picture on every side |
|---|
| 685 | gapWidth = (rect.width() - displayWidth) / 2.0; |
|---|
| 686 | gapHeight = (rect.height() - displayHeight) / 2.0; |
|---|
| 687 | |
|---|
| 688 | // Paint the rectangle on the context and draw the buffer inside the rectangle |
|---|
| 689 | |
|---|
| 690 | // Go to the new origin and center the video frame. |
|---|
| 691 | cairo_translate(cr, rect.x() + gapWidth, rect.y() + gapHeight); |
|---|
| 692 | cairo_rectangle(cr, 0, 0, rect.width(), rect.height()); |
|---|
| 693 | // Scale the video frame according to the pixel aspect ratio. |
|---|
| 694 | cairo_scale(cr, doublePixelAspectRatioNumerator / doublePixelAspectRatioDenominator, |
|---|
| 695 | doublePixelAspectRatioDenominator / doublePixelAspectRatioNumerator); |
|---|
| 696 | // Scale the video frame to fill the target surface as good as possible. |
|---|
| 697 | cairo_scale(cr, scale, scale); |
|---|
| 698 | // And paint it. |
|---|
| 699 | cairo_set_source_surface(cr, src, 0, 0); |
|---|
| 700 | cairo_fill(cr); |
|---|
| 701 | cairo_restore(cr); |
|---|
| 702 | |
|---|
| 703 | cairo_surface_destroy(src); |
|---|
| 704 | gst_caps_unref(caps); |
|---|
| 705 | } |
|---|
| 706 | |
|---|
| 707 | static HashSet<String> mimeTypeCache() |
|---|
| 708 | { |
|---|
| 709 | |
|---|
| 710 | do_gst_init(); |
|---|
| 711 | |
|---|
| 712 | static HashSet<String> cache; |
|---|
| 713 | static bool typeListInitialized = false; |
|---|
| 714 | |
|---|
| 715 | if (!typeListInitialized) { |
|---|
| 716 | // These subtypes are already beeing supported by WebKit itself |
|---|
| 717 | HashSet<String> ignoredApplicationSubtypes; |
|---|
| 718 | ignoredApplicationSubtypes.add(String("javascript")); |
|---|
| 719 | ignoredApplicationSubtypes.add(String("ecmascript")); |
|---|
| 720 | ignoredApplicationSubtypes.add(String("x-javascript")); |
|---|
| 721 | ignoredApplicationSubtypes.add(String("xml")); |
|---|
| 722 | ignoredApplicationSubtypes.add(String("xhtml+xml")); |
|---|
| 723 | ignoredApplicationSubtypes.add(String("rss+xml")); |
|---|
| 724 | ignoredApplicationSubtypes.add(String("atom+xml")); |
|---|
| 725 | ignoredApplicationSubtypes.add(String("x-ftp-directory")); |
|---|
| 726 | ignoredApplicationSubtypes.add(String("x-java-applet")); |
|---|
| 727 | ignoredApplicationSubtypes.add(String("x-java-bean")); |
|---|
| 728 | ignoredApplicationSubtypes.add(String("x-java-vm")); |
|---|
| 729 | ignoredApplicationSubtypes.add(String("x-shockwave-flash")); |
|---|
| 730 | |
|---|
| 731 | GList* factories = gst_type_find_factory_get_list(); |
|---|
| 732 | for (GList* iterator = factories; iterator; iterator = iterator->next) { |
|---|
| 733 | GstTypeFindFactory* factory = GST_TYPE_FIND_FACTORY(iterator->data); |
|---|
| 734 | GstCaps* caps = gst_type_find_factory_get_caps(factory); |
|---|
| 735 | |
|---|
| 736 | // Splitting the capability by comma and taking the first part |
|---|
| 737 | // as capability can be something like "audio/x-wavpack, framed=(boolean)false" |
|---|
| 738 | GOwnPtr<gchar> capabilityString(gst_caps_to_string(caps)); |
|---|
| 739 | gchar** capability = g_strsplit(capabilityString.get(), ",", 2); |
|---|
| 740 | gchar** mimetype = g_strsplit(capability[0], "/", 2); |
|---|
| 741 | |
|---|
| 742 | // GStreamer plugins can be capable of supporting types which WebKit supports |
|---|
| 743 | // by default. In that case, we should not consider these types supportable by GStreamer. |
|---|
| 744 | // Examples of what GStreamer can support but should not be added: |
|---|
| 745 | // text/plain, text/html, image/jpeg, application/xml |
|---|
| 746 | if (g_str_equal(mimetype[0], "audio") || |
|---|
| 747 | g_str_equal(mimetype[0], "video") || |
|---|
| 748 | (g_str_equal(mimetype[0], "application") && |
|---|
| 749 | !ignoredApplicationSubtypes.contains(String(mimetype[1])))) { |
|---|
| 750 | cache.add(String(capability[0])); |
|---|
| 751 | |
|---|
| 752 | // These formats are supported by GStreamer, but not correctly advertised |
|---|
| 753 | if (g_str_equal(capability[0], "video/x-h264") || |
|---|
| 754 | g_str_equal(capability[0], "audio/x-m4a")) { |
|---|
| 755 | cache.add(String("video/mp4")); |
|---|
| 756 | cache.add(String("audio/aac")); |
|---|
| 757 | } |
|---|
| 758 | |
|---|
| 759 | if (g_str_equal(capability[0], "video/x-theora")) |
|---|
| 760 | cache.add(String("video/ogg")); |
|---|
| 761 | |
|---|
| 762 | if (g_str_equal(capability[0], "audio/x-wav")) |
|---|
| 763 | cache.add(String("audio/wav")); |
|---|
| 764 | |
|---|
| 765 | if (g_str_equal(capability[0], "audio/mpeg")) { |
|---|
| 766 | // This is what we are handling: mpegversion=(int)1, layer=(int)[ 1, 3 ] |
|---|
| 767 | gchar** versionAndLayer = g_strsplit(capability[1], ",", 2); |
|---|
| 768 | |
|---|
| 769 | if (g_str_has_suffix (versionAndLayer[0], "(int)1")) { |
|---|
| 770 | for (int i = 0; versionAndLayer[1][i] != '\0'; i++) { |
|---|
| 771 | if (versionAndLayer[1][i] == '1') |
|---|
| 772 | cache.add(String("audio/mp1")); |
|---|
| 773 | else if (versionAndLayer[1][i] == '2') |
|---|
| 774 | cache.add(String("audio/mp2")); |
|---|
| 775 | else if (versionAndLayer[1][i] == '3') |
|---|
| 776 | cache.add(String("audio/mp3")); |
|---|
| 777 | } |
|---|
| 778 | } |
|---|
| 779 | |
|---|
| 780 | g_strfreev(versionAndLayer); |
|---|
| 781 | } |
|---|
| 782 | } |
|---|
| 783 | |
|---|
| 784 | g_strfreev(capability); |
|---|
| 785 | g_strfreev(mimetype); |
|---|
| 786 | } |
|---|
| 787 | |
|---|
| 788 | gst_plugin_feature_list_free(factories); |
|---|
| 789 | typeListInitialized = true; |
|---|
| 790 | } |
|---|
| 791 | |
|---|
| 792 | return cache; |
|---|
| 793 | } |
|---|
| 794 | |
|---|
| 795 | void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) |
|---|
| 796 | { |
|---|
| 797 | types = mimeTypeCache(); |
|---|
| 798 | } |
|---|
| 799 | |
|---|
| 800 | MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) |
|---|
| 801 | { |
|---|
| 802 | if (type.isNull() || type.isEmpty()) |
|---|
| 803 | return MediaPlayer::IsNotSupported; |
|---|
| 804 | |
|---|
| 805 | // spec says we should not return "probably" if the codecs string is empty |
|---|
| 806 | if (mimeTypeCache().contains(type)) |
|---|
| 807 | return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported; |
|---|
| 808 | return MediaPlayer::IsNotSupported; |
|---|
| 809 | } |
|---|
| 810 | |
|---|
| 811 | bool MediaPlayerPrivate::hasSingleSecurityOrigin() const |
|---|
| 812 | { |
|---|
| 813 | return true; |
|---|
| 814 | } |
|---|
| 815 | |
|---|
| 816 | bool MediaPlayerPrivate::supportsFullscreen() const |
|---|
| 817 | { |
|---|
| 818 | return true; |
|---|
| 819 | } |
|---|
| 820 | |
|---|
| 821 | void MediaPlayerPrivate::createGSTPlayBin(String url) |
|---|
| 822 | { |
|---|
| 823 | ASSERT(!m_playBin); |
|---|
| 824 | m_playBin = gst_element_factory_make("playbin2", "play"); |
|---|
| 825 | |
|---|
| 826 | GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(m_playBin)); |
|---|
| 827 | gst_bus_add_signal_watch(bus); |
|---|
| 828 | g_signal_connect(bus, "message", G_CALLBACK(mediaPlayerPrivateMessageCallback), this); |
|---|
| 829 | gst_object_unref(bus); |
|---|
| 830 | |
|---|
| 831 | g_object_set(G_OBJECT(m_playBin), "uri", url.utf8().data(), |
|---|
| 832 | "volume", static_cast<double>(m_player->volume()), NULL); |
|---|
| 833 | |
|---|
| 834 | m_videoSink = webkit_video_sink_new(); |
|---|
| 835 | |
|---|
| 836 | g_object_ref_sink(m_videoSink); |
|---|
| 837 | g_object_set(m_playBin, "video-sink", m_videoSink, NULL); |
|---|
| 838 | |
|---|
| 839 | g_signal_connect(m_videoSink, "repaint-requested", G_CALLBACK(mediaPlayerPrivateRepaintCallback), this); |
|---|
| 840 | } |
|---|
| 841 | |
|---|
| 842 | } |
|---|
| 843 | |
|---|
| 844 | #endif |
|---|