summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/platform/graphics/avfoundation
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/platform/graphics/avfoundation')
-rw-r--r--Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp731
-rw-r--r--Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h262
-rw-r--r--Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h131
-rw-r--r--Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm811
4 files changed, 1935 insertions, 0 deletions
diff --git a/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp
new file mode 100644
index 0000000..eb96532
--- /dev/null
+++ b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(VIDEO) && USE(AVFOUNDATION)
+
+#include "MediaPlayerPrivateAVFoundation.h"
+
+#include "ApplicationCacheHost.h"
+#include "DocumentLoader.h"
+#include "FrameView.h"
+#include "GraphicsContext.h"
+#include "GraphicsLayer.h"
+#include "KURL.h"
+#include "Logging.h"
+#include "SoftLinking.h"
+#include "TimeRanges.h"
+#include <CoreMedia/CoreMedia.h>
+#include <wtf/UnusedParam.h>
+
+using namespace std;
+
+namespace WebCore {
+
+static const float invalidTime = -1.0f;
+
+MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(MediaPlayer* player)
+ : m_player(player)
+ , m_queuedNotifications()
+ , m_queueMutex()
+ , m_mainThreadCallPending(false)
+ , m_networkState(MediaPlayer::Empty)
+ , m_readyState(MediaPlayer::HaveNothing)
+ , m_preload(MediaPlayer::Auto)
+ , m_scaleFactor(1, 1)
+ , m_cachedMaxTimeLoaded(0)
+ , m_cachedMaxTimeSeekable(0)
+ , m_cachedDuration(invalidTime)
+ , m_reportedDuration(invalidTime)
+ , m_seekTo(invalidTime)
+ , m_requestedRate(1)
+ , m_delayCallbacks(false)
+ , m_havePreparedToPlay(false)
+ , m_assetIsPlayable(false)
+ , m_visible(false)
+ , m_videoFrameHasDrawn(false)
+ , m_loadingMetadata(false)
+ , m_delayingLoad(false)
+ , m_isAllowedToRender(false)
+ , m_cachedHasAudio(false)
+ , m_cachedHasVideo(false)
+ , m_cachedHasCaptions(false)
+ , m_ignoreLoadStateChanges(false)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(%p)", this);
+}
+
+MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation(%p)", this);
+ cancelCallOnMainThread(mainThreadCallback, this);
+}
+
+MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::currentRenderingMode() const
+{
+#if USE(ACCELERATED_COMPOSITING)
+ if (platformLayer())
+ return MediaRenderingToLayer;
+#endif
+
+ if (hasContextRenderer())
+ return MediaRenderingToContext;
+
+ return MediaRenderingNone;
+}
+
+MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::preferredRenderingMode() const
+{
+ if (!m_player->visible() || !m_player->frameView() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
+ return MediaRenderingNone;
+
+#if USE(ACCELERATED_COMPOSITING)
+ if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
+ return MediaRenderingToLayer;
+#endif
+
+ return MediaRenderingToContext;
+}
+
+void MediaPlayerPrivateAVFoundation::setUpVideoRendering()
+{
+ if (!isReadyForVideoSetup())
+ return;
+
+ MediaRenderingMode currentMode = currentRenderingMode();
+ MediaRenderingMode preferredMode = preferredRenderingMode();
+ if (currentMode == preferredMode && currentMode != MediaRenderingNone)
+ return;
+
+ LOG(Media, "MediaPlayerPrivateAVFoundation::setUpVideoRendering(%p) - current mode = %d, preferred mode = %d",
+ this, static_cast<int>(currentMode), static_cast<int>(preferredMode));
+
+ if (currentMode != MediaRenderingNone)
+ tearDownVideoRendering();
+
+ switch (preferredMode) {
+ case MediaRenderingNone:
+ case MediaRenderingToContext:
+ createContextVideoRenderer();
+ break;
+
+#if USE(ACCELERATED_COMPOSITING)
+ case MediaRenderingToLayer:
+ createVideoLayer();
+ break;
+#endif
+ }
+
+#if USE(ACCELERATED_COMPOSITING)
+ // If using a movie layer, inform the client so the compositing tree is updated.
+ if (currentMode == MediaRenderingToLayer || preferredMode == MediaRenderingToLayer) {
+ LOG(Media, "MediaPlayerPrivateAVFoundation::setUpVideoRendering(%p) - calling mediaPlayerRenderingModeChanged()", this);
+ m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
+ }
+#endif
+}
+
+void MediaPlayerPrivateAVFoundation::tearDownVideoRendering()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::tearDownVideoRendering(%p)", this);
+
+ destroyContextVideoRenderer();
+
+#if USE(ACCELERATED_COMPOSITING)
+ if (platformLayer())
+ destroyVideoLayer();
+#endif
+}
+
+bool MediaPlayerPrivateAVFoundation::hasSetUpVideoRendering() const
+{
+ return hasLayerRenderer() || hasContextRenderer();
+}
+
+void MediaPlayerPrivateAVFoundation::resumeLoad()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::resumeLoad(%p)", this);
+
+ ASSERT(m_delayingLoad);
+ m_delayingLoad = false;
+
+ if (m_assetURL.length())
+ prepareToPlay();
+}
+
+void MediaPlayerPrivateAVFoundation::load(const String& url)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p)", this);
+
+ if (m_networkState != MediaPlayer::Loading) {
+ m_networkState = MediaPlayer::Loading;
+ m_player->networkStateChanged();
+ }
+ if (m_readyState != MediaPlayer::HaveNothing) {
+ m_readyState = MediaPlayer::HaveNothing;
+ m_player->readyStateChanged();
+ }
+
+ m_videoFrameHasDrawn = false;
+ m_assetURL = url;
+
+ // Don't do any more work if the url is empty.
+ if (!url.length())
+ return;
+
+ if (m_preload == MediaPlayer::None) {
+ LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p) - preload==none so returning", this);
+ m_delayingLoad = true;
+ return;
+ }
+
+ prepareToPlay();
+}
+
+void MediaPlayerPrivateAVFoundation::playabilityKnown()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::playabilityKnown(%p)", this);
+
+ updateStates();
+ if (m_assetIsPlayable)
+ return;
+
+ // Nothing more to do if we already have all of the item's metadata.
+ if (assetStatus() > MediaPlayerAVAssetStatusLoading) {
+ LOG(Media, "MediaPlayerPrivateAVFoundation::playabilityKnown(%p) - all metadata loaded", this);
+ return;
+ }
+
+ // At this point we are supposed to load metadata. It is OK to ask the asset to load the same
+ // information multiple times, because if it has already been loaded the completion handler
+ // will just be called synchronously.
+ m_loadingMetadata = true;
+ beginLoadingMetadata();
+}
+
+void MediaPlayerPrivateAVFoundation::prepareToPlay()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::prepareToPlay(%p)", this);
+
+ m_preload = MediaPlayer::Auto;
+ if (m_havePreparedToPlay)
+ return;
+ m_havePreparedToPlay = true;
+
+ m_delayingLoad = false;
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+ Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0;
+ ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : 0;
+ ApplicationCacheResource* resource = 0;
+ if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(m_assetURL), resource) && resource)
+ createAVPlayerForCacheResource(resource);
+ else
+#endif
+ createAVPlayerForURL(m_assetURL);
+ checkPlayability();
+}
+
+void MediaPlayerPrivateAVFoundation::paint(GraphicsContext*, const IntRect&)
+{
+ // This is the base class, only need to remember that a frame has been drawn.
+ m_videoFrameHasDrawn = true;
+}
+
+float MediaPlayerPrivateAVFoundation::duration() const
+{
+ if (!metaDataAvailable())
+ return 0;
+
+ if (m_cachedDuration == invalidTime) {
+ m_cachedDuration = platformDuration();
+ LOG(Media, "MediaPlayerPrivateAVFMac::duration(%p) - caching %f", this, m_cachedDuration);
+ }
+
+ return m_cachedDuration;
+}
+
+void MediaPlayerPrivateAVFoundation::seek(float time)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::seek(%p) - seeking to %f", this, time);
+ if (!metaDataAvailable())
+ return;
+
+ if (time > duration())
+ time = duration();
+
+ m_seekTo = time;
+
+ seekToTime(time);
+}
+
+void MediaPlayerPrivateAVFoundation::setRate(float rate)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::setRate(%p) - seting to %f", this, rate);
+ m_requestedRate = rate;
+}
+
+bool MediaPlayerPrivateAVFoundation::paused() const
+{
+ if (!metaDataAvailable())
+ return true;
+
+ return rate() == 0;
+}
+
+bool MediaPlayerPrivateAVFoundation::seeking() const
+{
+ if (!metaDataAvailable())
+ return false;
+
+ return m_seekTo != invalidTime;
+}
+
+IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
+{
+ if (!metaDataAvailable())
+ return IntSize();
+
+ // In spite of the name of this method, return the natural size transformed by the
+ // initial movie scale because the spec says intrinsic size is:
+ //
+ // ... the dimensions of the resource in CSS pixels after taking into account the resource's
+ // dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the
+ // format used by the resource
+
+ return m_cachedNaturalSize;
+}
+
+void MediaPlayerPrivateAVFoundation::setNaturalSize(IntSize size)
+{
+ IntSize oldSize = m_cachedNaturalSize;
+ m_cachedNaturalSize = size;
+ if (oldSize != m_cachedNaturalSize)
+ m_player->sizeChanged();
+}
+
+PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundation::buffered() const
+{
+ if (!m_cachedLoadedTimeRanges)
+ m_cachedLoadedTimeRanges = platformBufferedTimeRanges();
+
+ return m_cachedLoadedTimeRanges->copy();
+}
+
+float MediaPlayerPrivateAVFoundation::maxTimeSeekable() const
+{
+ if (!metaDataAvailable())
+ return 0;
+
+ if (!m_cachedMaxTimeSeekable)
+ m_cachedMaxTimeSeekable = platformMaxTimeSeekable();
+
+ LOG(Media, "MediaPlayerPrivateAVFoundation::maxTimeSeekable(%p) - returning %f", this, m_cachedMaxTimeSeekable);
+ return m_cachedMaxTimeSeekable;
+}
+
+float MediaPlayerPrivateAVFoundation::maxTimeLoaded() const
+{
+ if (!metaDataAvailable())
+ return 0;
+
+ if (!m_cachedMaxTimeLoaded)
+ m_cachedMaxTimeLoaded = platformMaxTimeLoaded();
+
+ return m_cachedMaxTimeLoaded;
+}
+
+unsigned MediaPlayerPrivateAVFoundation::bytesLoaded() const
+{
+ float dur = duration();
+ if (!dur)
+ return 0;
+ unsigned loaded = totalBytes() * maxTimeLoaded() / dur;
+ LOG(Media, "MediaPlayerPrivateAVFoundation::bytesLoaded(%p) - returning %i", this, loaded);
+ return loaded;
+}
+
+bool MediaPlayerPrivateAVFoundation::isReadyForVideoSetup() const
+{
+ return m_isAllowedToRender && m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
+}
+
+void MediaPlayerPrivateAVFoundation::prepareForRendering()
+{
+ if (m_isAllowedToRender)
+ return;
+ m_isAllowedToRender = true;
+
+ setUpVideoRendering();
+
+ if (currentRenderingMode() == MediaRenderingToLayer || preferredRenderingMode() == MediaRenderingToLayer)
+ m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
+}
+
+bool MediaPlayerPrivateAVFoundation::supportsFullscreen() const
+{
+#if ENABLE(FULLSCREEN_API)
+ return true;
+#else
+ // FIXME: WebVideoFullscreenController assumes a QTKit/QuickTime media engine
+ return false;
+#endif
+}
+
+void MediaPlayerPrivateAVFoundation::updateStates()
+{
+ MediaPlayer::NetworkState oldNetworkState = m_networkState;
+ MediaPlayer::ReadyState oldReadyState = m_readyState;
+
+ LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - entering with networkState = %i, readyState = %i",
+ this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
+
+ if (m_loadingMetadata)
+ m_networkState = MediaPlayer::Loading;
+ else {
+ // -loadValuesAsynchronouslyForKeys:completionHandler: has invoked its handler; test status of keys and determine state.
+ AVAssetStatus avAssetStatus = assetStatus();
+ ItemStatus itemStatus = playerItemStatus();
+
+ m_assetIsPlayable = (avAssetStatus == MediaPlayerAVAssetStatusPlayable);
+ if (m_readyState < MediaPlayer::HaveMetadata && avAssetStatus > MediaPlayerAVAssetStatusLoading) {
+ if (m_assetIsPlayable) {
+ if (itemStatus == MediaPlayerAVPlayerItemStatusUnknown) {
+ if (avAssetStatus == MediaPlayerAVAssetStatusFailed || m_preload > MediaPlayer::MetaData) {
+ // We may have a playable asset that doesn't support inspection prior to playback; go ahead
+ // and create the AVPlayerItem now. When the AVPlayerItem becomes ready to play, we will
+ // have access to its metadata. Or we may have been asked to become ready to play immediately.
+ m_networkState = MediaPlayer::Loading;
+ prepareToPlay();
+ } else
+ m_networkState = MediaPlayer::Idle;
+ }
+ if (avAssetStatus == MediaPlayerAVAssetStatusLoaded)
+ m_readyState = MediaPlayer::HaveMetadata;
+ } else {
+ // FIX ME: fetch the error associated with the @"playable" key to distinguish between format
+ // and network errors.
+ m_networkState = MediaPlayer::FormatError;
+ }
+ }
+
+ if (avAssetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
+ if (seeking())
+ m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
+ else {
+ float maxLoaded = maxTimeLoaded();
+ switch (itemStatus) {
+ case MediaPlayerAVPlayerItemStatusUnknown:
+ break;
+ case MediaPlayerAVPlayerItemStatusFailed:
+ m_networkState = MediaPlayer::DecodeError;
+ break;
+ case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
+ m_readyState = MediaPlayer::HaveEnoughData;
+ break;
+ case MediaPlayerAVPlayerItemStatusReadyToPlay:
+ case MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty:
+ case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
+ if (maxLoaded > currentTime())
+ m_readyState = MediaPlayer::HaveFutureData;
+ else
+ m_readyState = MediaPlayer::HaveCurrentData;
+ break;
+ }
+
+ if (itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
+ m_networkState = (maxLoaded == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
+ }
+ }
+ }
+
+ if (isReadyForVideoSetup() && currentRenderingMode() != preferredRenderingMode())
+ setUpVideoRendering();
+
+ if (m_networkState != oldNetworkState)
+ m_player->networkStateChanged();
+
+ if (m_readyState != oldReadyState)
+ m_player->readyStateChanged();
+
+ LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - exiting with networkState = %i, readyState = %i",
+ this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
+}
+
+void MediaPlayerPrivateAVFoundation::setSize(const IntSize&)
+{
+}
+
+void MediaPlayerPrivateAVFoundation::setVisible(bool visible)
+{
+ if (m_visible == visible)
+ return;
+
+ m_visible = visible;
+ if (visible)
+ setUpVideoRendering();
+ else
+ tearDownVideoRendering();
+}
+
+bool MediaPlayerPrivateAVFoundation::hasAvailableVideoFrame() const
+{
+ if (currentRenderingMode() == MediaRenderingToLayer)
+ return videoLayerIsReadyToDisplay();
+
+ // When using the software renderer we hope someone will signal that a frame is available so we might as well
+ // wait until we know that a frame has been drawn.
+ return m_videoFrameHasDrawn;
+}
+
+void MediaPlayerPrivateAVFoundation::acceleratedRenderingStateChanged()
+{
+ // Set up or change the rendering path if necessary.
+ setUpVideoRendering();
+}
+
+void MediaPlayerPrivateAVFoundation::metadataLoaded()
+{
+ m_loadingMetadata = false;
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundation::loadStateChanged()
+{
+ if (m_ignoreLoadStateChanges)
+ return;
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundation::rateChanged()
+{
+ updateStates();
+ m_player->rateChanged();
+}
+
+void MediaPlayerPrivateAVFoundation::loadedTimeRangesChanged()
+{
+ m_cachedLoadedTimeRanges = 0;
+ m_cachedMaxTimeLoaded = 0;
+ updateStates();
+
+ // For some media files, reported duration is estimated and updated as media is loaded
+ // so report duration changed when the estimate is upated.
+ float dur = duration();
+ if (dur != m_reportedDuration) {
+ if (m_reportedDuration != invalidTime)
+ m_player->durationChanged();
+ m_reportedDuration = dur;
+ }
+}
+
+void MediaPlayerPrivateAVFoundation::seekableTimeRangesChanged()
+{
+ m_cachedMaxTimeSeekable = 0;
+}
+
+void MediaPlayerPrivateAVFoundation::timeChanged(double time)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::timeChanged(%p) - time = %f", this, time);
+
+ if (m_seekTo == invalidTime)
+ return;
+
+ // AVFoundation may call our observer more than once during a seek, and we can't currently tell
+ // if we will be able to seek to an exact time, so assume that we are done seeking if we are
+ // "close enough" to the seek time.
+ const double smallSeekDelta = 1.0 / 100;
+
+ float currentRate = rate();
+ if ((currentRate > 0 && time >= m_seekTo) || (currentRate < 0 && time <= m_seekTo) || (abs(m_seekTo - time) <= smallSeekDelta)) {
+ m_seekTo = invalidTime;
+ updateStates();
+ m_player->timeChanged();
+ }
+}
+
+void MediaPlayerPrivateAVFoundation::didEnd()
+{
+ // Hang onto the current time and use it as duration from now on since we are definitely at
+ // the end of the movie. Do this because the initial duration is sometimes an estimate.
+ float now = currentTime();
+ if (now > 0)
+ m_cachedDuration = now;
+
+ updateStates();
+ m_player->timeChanged();
+}
+
+void MediaPlayerPrivateAVFoundation::repaint()
+{
+ m_videoFrameHasDrawn = true;
+ m_player->repaint();
+}
+
+MediaPlayer::MovieLoadType MediaPlayerPrivateAVFoundation::movieLoadType() const
+{
+ if (!metaDataAvailable() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
+ return MediaPlayer::Unknown;
+
+ if (isinf(duration()))
+ return MediaPlayer::LiveStream;
+
+ return MediaPlayer::Download;
+}
+
+void MediaPlayerPrivateAVFoundation::setPreload(MediaPlayer::Preload preload)
+{
+ m_preload = preload;
+ if (m_delayingLoad && m_preload != MediaPlayer::None)
+ resumeLoad();
+}
+
+void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay)
+{
+ MutexLocker lock(m_queueMutex);
+ if (delay)
+ ++m_delayCallbacks;
+ else {
+ ASSERT(m_delayCallbacks);
+ --m_delayCallbacks;
+ }
+}
+
+void MediaPlayerPrivateAVFoundation::mainThreadCallback(void* context)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::mainThreadCallback(%p)", context);
+ MediaPlayerPrivateAVFoundation* player = static_cast<MediaPlayerPrivateAVFoundation*>(context);
+ player->clearMainThreadPendingFlag();
+ player->dispatchNotification();
+}
+
+void MediaPlayerPrivateAVFoundation::clearMainThreadPendingFlag()
+{
+ MutexLocker lock(m_queueMutex);
+ m_mainThreadCallPending = false;
+}
+
+void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification::Type type, double time)
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - notification %d", this, static_cast<int>(type));
+ m_queueMutex.lock();
+
+ // It is important to always process the properties in the order that we are notified,
+ // so always go through the queue because notifications happen on different threads.
+ m_queuedNotifications.append(Notification(type, time));
+
+ bool delayDispatch = m_delayCallbacks || !isMainThread();
+ if (delayDispatch && !m_mainThreadCallPending) {
+ m_mainThreadCallPending = true;
+ callOnMainThread(mainThreadCallback, this);
+ }
+
+ m_queueMutex.unlock();
+
+ if (delayDispatch) {
+ LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - early return", this);
+ return;
+ }
+
+ dispatchNotification();
+}
+
+void MediaPlayerPrivateAVFoundation::dispatchNotification()
+{
+ ASSERT(isMainThread());
+
+ Notification notification = Notification();
+ {
+ MutexLocker lock(m_queueMutex);
+
+ if (m_queuedNotifications.isEmpty())
+ return;
+
+ if (!m_delayCallbacks) {
+ // Only dispatch one notification callback per invocation because they can cause recursion.
+ notification = m_queuedNotifications.first();
+ m_queuedNotifications.remove(0);
+ }
+
+ if (!m_queuedNotifications.isEmpty() && !m_mainThreadCallPending)
+ callOnMainThread(mainThreadCallback, this);
+
+ if (!notification.isValid())
+ return;
+ }
+
+ LOG(Media, "MediaPlayerPrivateAVFoundation::dispatchNotification(%p) - dispatching %d", this, static_cast<int>(notification.type()));
+
+ switch (notification.type()) {
+ case Notification::ItemDidPlayToEndTime:
+ didEnd();
+ break;
+ case Notification::ItemTracksChanged:
+ tracksChanged();
+ break;
+ case Notification::ItemStatusChanged:
+ loadStateChanged();
+ break;
+ case Notification::ItemSeekableTimeRangesChanged:
+ seekableTimeRangesChanged();
+ loadStateChanged();
+ break;
+ case Notification::ItemLoadedTimeRangesChanged:
+ loadedTimeRangesChanged();
+ loadStateChanged();
+ break;
+ case Notification::ItemPresentationSizeChanged:
+ sizeChanged();
+ break;
+ case Notification::ItemIsPlaybackLikelyToKeepUpChanged:
+ loadStateChanged();
+ break;
+ case Notification::ItemIsPlaybackBufferEmptyChanged:
+ loadStateChanged();
+ break;
+ case Notification::ItemIsPlaybackBufferFullChanged:
+ loadStateChanged();
+ break;
+ case Notification::PlayerRateChanged:
+ rateChanged();
+ break;
+ case Notification::PlayerTimeChanged:
+ timeChanged(notification.time());
+ break;
+ case Notification::AssetMetadataLoaded:
+ metadataLoaded();
+ break;
+ case Notification::AssetPlayabilityKnown:
+ playabilityKnown();
+ break;
+ case Notification::None:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+}
+
+} // namespace WebCore
+
+#endif
diff --git a/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h
new file mode 100644
index 0000000..a768ab4
--- /dev/null
+++ b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MediaPlayerPrivateAVFoundation_h
+#define MediaPlayerPrivateAVFoundation_h
+
+#if ENABLE(VIDEO) && USE(AVFOUNDATION)
+
+#include "FloatSize.h"
+#include "MediaPlayerPrivate.h"
+#include "Timer.h"
+#include <wtf/RetainPtr.h>
+
+namespace WebCore {
+
+class ApplicationCacheResource;
+
+class MediaPlayerPrivateAVFoundation : public MediaPlayerPrivateInterface {
+public:
+
+ virtual void repaint();
+ virtual void metadataLoaded();
+ virtual void loadStateChanged();
+ virtual void playabilityKnown();
+ virtual void rateChanged();
+ virtual void loadedTimeRangesChanged();
+ virtual void seekableTimeRangesChanged();
+ virtual void timeChanged(double);
+ virtual void didEnd();
+
+ class Notification {
+ public:
+ enum Type {
+ None,
+ ItemDidPlayToEndTime,
+ ItemTracksChanged,
+ ItemStatusChanged,
+ ItemSeekableTimeRangesChanged,
+ ItemLoadedTimeRangesChanged,
+ ItemPresentationSizeChanged,
+ ItemIsPlaybackLikelyToKeepUpChanged,
+ ItemIsPlaybackBufferEmptyChanged,
+ ItemIsPlaybackBufferFullChanged,
+ AssetMetadataLoaded,
+ AssetPlayabilityKnown,
+ PlayerRateChanged,
+ PlayerTimeChanged
+ };
+
+ Notification()
+ : m_type(None)
+ , m_time(0)
+ {
+ }
+
+ Notification(Type type, double time)
+ : m_type(type)
+ , m_time(time)
+ {
+ }
+
+ Type type() { return m_type; }
+ bool isValid() { return m_type != None; }
+ double time() { return m_time; }
+
+ private:
+ Type m_type;
+ double m_time;
+ };
+
+ void scheduleMainThreadNotification(Notification::Type, double time = 0);
+ void dispatchNotification();
+ void clearMainThreadPendingFlag();
+
+protected:
+ MediaPlayerPrivateAVFoundation(MediaPlayer*);
+ virtual ~MediaPlayerPrivateAVFoundation();
+
+ // MediaPlayerPrivatePrivateInterface overrides.
+ virtual void load(const String& url);
+ virtual void cancelLoad() = 0;
+
+ virtual void prepareToPlay();
+ virtual PlatformMedia platformMedia() const = 0;
+
+ virtual void play() = 0;
+ virtual void pause() = 0;
+
+ virtual IntSize naturalSize() const;
+ virtual bool hasVideo() const { return m_cachedHasVideo; }
+ virtual bool hasAudio() const { return m_cachedHasAudio; }
+ virtual void setVisible(bool);
+ virtual float duration() const;
+ virtual float currentTime() const = 0;
+ virtual void seek(float);
+ virtual bool seeking() const;
+ virtual void setRate(float);
+ virtual bool paused() const;
+ virtual void setVolume(float) = 0;
+ virtual bool hasClosedCaptions() const { return m_cachedHasCaptions; }
+ virtual void setClosedCaptionsVisible(bool) = 0;
+ virtual MediaPlayer::NetworkState networkState() const { return m_networkState; }
+ virtual MediaPlayer::ReadyState readyState() const { return m_readyState; }
+ virtual float maxTimeSeekable() const;
+ virtual PassRefPtr<TimeRanges> buffered() const;
+ virtual unsigned bytesLoaded() const;
+ virtual void setSize(const IntSize&);
+ virtual void paint(GraphicsContext*, const IntRect&);
+ virtual void paintCurrentFrameInContext(GraphicsContext*, const IntRect&) = 0;
+ virtual void setPreload(MediaPlayer::Preload);
+ virtual bool hasAvailableVideoFrame() const;
+#if USE(ACCELERATED_COMPOSITING)
+ virtual PlatformLayer* platformLayer() const { return 0; }
+ virtual bool supportsAcceleratedRendering() const = 0;
+ virtual void acceleratedRenderingStateChanged();
+#endif
+ virtual bool hasSingleSecurityOrigin() const { return true; }
+ virtual MediaPlayer::MovieLoadType movieLoadType() const;
+ virtual void prepareForRendering();
+ virtual float mediaTimeForTimeValue(float) const = 0;
+
+ virtual bool supportsFullscreen() const;
+
+ // Required interfaces for concrete derived classes.
+ virtual void createAVPlayerForURL(const String& url) = 0;
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+ virtual void createAVPlayerForCacheResource(ApplicationCacheResource*) = 0;
+#endif
+
+ enum ItemStatus {
+ MediaPlayerAVPlayerItemStatusUnknown,
+ MediaPlayerAVPlayerItemStatusFailed,
+ MediaPlayerAVPlayerItemStatusReadyToPlay,
+ MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty,
+ MediaPlayerAVPlayerItemStatusPlaybackBufferFull,
+ MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp,
+ };
+ virtual ItemStatus playerItemStatus() const = 0;
+
+ enum AVAssetStatus {
+ MediaPlayerAVAssetStatusUnknown,
+ MediaPlayerAVAssetStatusLoading,
+ MediaPlayerAVAssetStatusFailed,
+ MediaPlayerAVAssetStatusCancelled,
+ MediaPlayerAVAssetStatusLoaded,
+ MediaPlayerAVAssetStatusPlayable,
+ };
+ virtual AVAssetStatus assetStatus() const = 0;
+
+ virtual void checkPlayability() = 0;
+ virtual float rate() const = 0;
+ virtual void seekToTime(float time) = 0;
+ virtual unsigned totalBytes() const = 0;
+ virtual PassRefPtr<TimeRanges> platformBufferedTimeRanges() const = 0;
+ virtual float platformMaxTimeSeekable() const = 0;
+ virtual float platformMaxTimeLoaded() const = 0;
+ virtual float platformDuration() const = 0;
+
+ virtual void beginLoadingMetadata() = 0;
+ virtual void tracksChanged() = 0;
+ virtual void sizeChanged() = 0;
+
+ virtual void createContextVideoRenderer() = 0;
+ virtual void destroyContextVideoRenderer() = 0;
+
+ virtual void createVideoLayer() = 0;
+ virtual void destroyVideoLayer() = 0;
+ virtual bool videoLayerIsReadyToDisplay() const = 0;
+
+ virtual bool hasContextRenderer() const = 0;
+ virtual bool hasLayerRenderer() const = 0;
+
+protected:
+ void resumeLoad();
+ void updateStates();
+
+ void setHasVideo(bool b) { m_cachedHasVideo = b; };
+ void setHasAudio(bool b) { m_cachedHasAudio = b; }
+ void setHasClosedCaptions(bool b) { m_cachedHasCaptions = b; }
+ void setDelayCallbacks(bool);
+ void setIgnoreLoadStateChanges(bool delay) { m_ignoreLoadStateChanges = delay; }
+ void setNaturalSize(IntSize);
+
+ enum MediaRenderingMode { MediaRenderingNone, MediaRenderingToContext, MediaRenderingToLayer };
+ MediaRenderingMode currentRenderingMode() const;
+ MediaRenderingMode preferredRenderingMode() const;
+
+ bool metaDataAvailable() const { return m_readyState >= MediaPlayer::HaveMetadata; }
+ float requestedRate() const { return m_requestedRate; }
+ float maxTimeLoaded() const;
+ bool isReadyForVideoSetup() const;
+ virtual void setUpVideoRendering();
+ virtual void tearDownVideoRendering();
+ bool hasSetUpVideoRendering() const;
+
+ static void mainThreadCallback(void*);
+
+private:
+
+ MediaPlayer* m_player;
+
+ Vector<Notification> m_queuedNotifications;
+ Mutex m_queueMutex;
+ bool m_mainThreadCallPending;
+
+ mutable RefPtr<TimeRanges> m_cachedLoadedTimeRanges;
+
+ MediaPlayer::NetworkState m_networkState;
+ MediaPlayer::ReadyState m_readyState;
+
+ String m_assetURL;
+ MediaPlayer::Preload m_preload;
+ FloatSize m_scaleFactor;
+
+ IntSize m_cachedNaturalSize;
+ mutable float m_cachedMaxTimeLoaded;
+ mutable float m_cachedMaxTimeSeekable;
+ mutable float m_cachedDuration;
+ float m_reportedDuration;
+
+ float m_seekTo;
+ float m_requestedRate;
+ int m_delayCallbacks;
+ bool m_havePreparedToPlay;
+ bool m_assetIsPlayable;
+ bool m_visible;
+ bool m_videoFrameHasDrawn;
+ bool m_loadingMetadata;
+ bool m_delayingLoad;
+ bool m_isAllowedToRender;
+ bool m_cachedHasAudio;
+ bool m_cachedHasVideo;
+ bool m_cachedHasCaptions;
+ bool m_ignoreLoadStateChanges;
+};
+
+}
+
+#endif
+#endif
diff --git a/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h
new file mode 100644
index 0000000..cc00c15
--- /dev/null
+++ b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MediaPlayerPrivateAVFoundationObjC_h
+#define MediaPlayerPrivateAVFoundationObjC_h
+
+#if ENABLE(VIDEO) && USE(AVFOUNDATION)
+
+#include "MediaPlayerPrivateAVFoundation.h"
+
+#ifdef __OBJC__
+@class AVAsset;
+@class AVPlayer;
+@class AVPlayerItem;
+@class AVPlayerLayer;
+@class AVAssetImageGenerator;
+@class WebCoreAVFMovieObserver;
+#else
+class AVAsset;
+class AVPlayer;
+class AVPlayerItem;
+class AVPlayerLayer;
+class AVAssetImageGenerator;
+class WebCoreAVFMovieObserver;
+typedef struct objc_object *id;
+#endif
+
+namespace WebCore {
+
+class ApplicationCacheResource;
+
+class MediaPlayerPrivateAVFoundationObjC : public MediaPlayerPrivateAVFoundation {
+public:
+
+ static void registerMediaEngine(MediaEngineRegistrar);
+
+ void setAsset(id);
+ virtual void tracksChanged();
+
+private:
+ MediaPlayerPrivateAVFoundationObjC(MediaPlayer*);
+ ~MediaPlayerPrivateAVFoundationObjC();
+
+ // engine support
+ static MediaPlayerPrivateInterface* create(MediaPlayer* player);
+ static void getSupportedTypes(HashSet<String>& types);
+ static MediaPlayer::SupportsType supportsType(const String& type, const String& codecs);
+ static bool isAvailable();
+
+ virtual void cancelLoad();
+
+ virtual PlatformMedia platformMedia() const;
+
+ virtual void play();
+ virtual void pause();
+ virtual float currentTime() const;
+ virtual void setVolume(float);
+ virtual void setClosedCaptionsVisible(bool);
+ virtual void paint(GraphicsContext*, const IntRect&);
+ virtual void paintCurrentFrameInContext(GraphicsContext*, const IntRect&);
+ virtual PlatformLayer* platformLayer() const;
+ virtual bool supportsAcceleratedRendering() const { return true; }
+ virtual float mediaTimeForTimeValue(float) const;
+
+ virtual void createAVPlayer();
+ virtual void createAVPlayerForURL(const String& url);
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+ virtual void createAVPlayerForCacheResource(ApplicationCacheResource*);
+#endif
+ virtual MediaPlayerPrivateAVFoundation::ItemStatus playerItemStatus() const;
+ virtual MediaPlayerPrivateAVFoundation::AVAssetStatus assetStatus() const;
+
+ virtual void checkPlayability();
+ virtual float rate() const;
+ virtual void seekToTime(float time);
+ virtual unsigned totalBytes() const;
+ virtual PassRefPtr<TimeRanges> platformBufferedTimeRanges() const;
+ virtual float platformMaxTimeSeekable() const;
+ virtual float platformDuration() const;
+ virtual float platformMaxTimeLoaded() const;
+ virtual void beginLoadingMetadata();
+ virtual void sizeChanged();
+
+ virtual void createContextVideoRenderer();
+ virtual void destroyContextVideoRenderer();
+
+ virtual void createVideoLayer();
+ virtual void destroyVideoLayer();
+ virtual bool videoLayerIsReadyToDisplay() const;
+
+ virtual bool hasContextRenderer() const;
+ virtual bool hasLayerRenderer() const;
+
+ RetainPtr<CGImageRef> createImageForTimeInRect(float, const IntRect&);
+
+ MediaPlayer* m_player;
+ RetainPtr<AVAsset> m_avAsset;
+ RetainPtr<AVPlayer> m_avPlayer;
+ RetainPtr<AVPlayerItem> m_avPlayerItem;
+ RetainPtr<AVPlayerLayer> m_videoLayer;
+ RetainPtr<WebCoreAVFMovieObserver> m_objcObserver;
+ RetainPtr<AVAssetImageGenerator> m_imageGenerator;
+ id m_timeObserver;
+};
+
+}
+
+#endif
+#endif
diff --git a/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm
new file mode 100644
index 0000000..55eb433
--- /dev/null
+++ b/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#if ENABLE(VIDEO) && USE(AVFOUNDATION)
+
+#import "MediaPlayerPrivateAVFoundationObjC.h"
+
+#import "ApplicationCacheResource.h"
+#import "BlockExceptions.h"
+#import "FloatConversion.h"
+#import "FrameView.h"
+#import "FloatConversion.h"
+#import "GraphicsContext.h"
+#import "KURL.h"
+#import "Logging.h"
+#import "SoftLinking.h"
+#import "TimeRanges.h"
+#import "WebCoreSystemInterface.h"
+#import <objc/objc-runtime.h>
+#import <wtf/UnusedParam.h>
+
+#import <CoreMedia/CoreMedia.h>
+#import <AVFoundation/AVFoundation.h>
+
+SOFT_LINK_FRAMEWORK(AVFoundation)
+SOFT_LINK_FRAMEWORK(CoreMedia)
+
+SOFT_LINK(CoreMedia, CMTimeCompare, int32_t, (CMTime time1, CMTime time2), (time1, time2))
+SOFT_LINK(CoreMedia, CMTimeMakeWithSeconds, CMTime, (Float64 seconds, int32_t preferredTimeScale), (seconds, preferredTimeScale))
+SOFT_LINK(CoreMedia, CMTimeGetSeconds, Float64, (CMTime time), (time))
+SOFT_LINK(CoreMedia, CMTimeRangeGetEnd, CMTime, (CMTimeRange range), (range))
+
+SOFT_LINK_CLASS(AVFoundation, AVPlayer)
+SOFT_LINK_CLASS(AVFoundation, AVPlayerItem)
+SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
+SOFT_LINK_CLASS(AVFoundation, AVURLAsset)
+SOFT_LINK_CLASS(AVFoundation, AVAssetImageGenerator)
+
+SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicVisual, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicAudible, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVMediaTypeClosedCaption, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVPlayerItemDidPlayToEndTimeNotification, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVAssetImageGeneratorApertureModeCleanAperture, NSString *)
+
+SOFT_LINK_CONSTANT(CoreMedia, kCMTimeZero, CMTime)
+
+#define AVPlayer getAVPlayerClass()
+#define AVPlayerItem getAVPlayerItemClass()
+#define AVPlayerLayer getAVPlayerLayerClass()
+#define AVURLAsset getAVURLAssetClass()
+#define AVAssetImageGenerator getAVAssetImageGeneratorClass()
+
+#define AVMediaCharacteristicVisual getAVMediaCharacteristicVisual()
+#define AVMediaCharacteristicAudible getAVMediaCharacteristicAudible()
+#define AVMediaTypeClosedCaption getAVMediaTypeClosedCaption()
+#define AVPlayerItemDidPlayToEndTimeNotification getAVPlayerItemDidPlayToEndTimeNotification()
+#define AVAssetImageGeneratorApertureModeCleanAperture getAVAssetImageGeneratorApertureModeCleanAperture()
+
+#define kCMTimeZero getkCMTimeZero()
+
+using namespace WebCore;
+using namespace std;
+
+enum MediaPlayerAVFoundationObservationContext {
+ MediaPlayerAVFoundationObservationContextPlayerItem,
+ MediaPlayerAVFoundationObservationContextPlayer
+};
+
+@interface WebCoreAVFMovieObserver : NSObject
+{
+ MediaPlayerPrivateAVFoundationObjC* m_callback;
+ int m_delayCallbacks;
+}
+-(id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback;
+-(void)disconnect;
+-(void)playableKnown;
+-(void)metadataLoaded;
+-(void)timeChanged:(double)time;
+-(void)didEnd:(NSNotification *)notification;
+-(void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context;
+@end
+
+namespace WebCore {
+
+static NSArray *assetMetadataKeyNames();
+static NSArray *itemKVOProperties();
+
+#if !LOG_DISABLED
+static const char *boolString(bool val)
+{
+ return val ? "true" : "false";
+}
+#endif
+
+static const float invalidTime = -1.0f;
+
+MediaPlayerPrivateInterface* MediaPlayerPrivateAVFoundationObjC::create(MediaPlayer* player)
+{
+ return new MediaPlayerPrivateAVFoundationObjC(player);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::registerMediaEngine(MediaEngineRegistrar registrar)
+{
+ if (isAvailable())
+ registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
+}
+
+MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlayer* player)
+ : MediaPlayerPrivateAVFoundation(player)
+ , m_objcObserver(AdoptNS, [[WebCoreAVFMovieObserver alloc] initWithCallback:this])
+ , m_timeObserver(0)
+{
+}
+
+MediaPlayerPrivateAVFoundationObjC::~MediaPlayerPrivateAVFoundationObjC()
+{
+ cancelLoad();
+ [m_objcObserver.get() disconnect];
+}
+
+void MediaPlayerPrivateAVFoundationObjC::cancelLoad()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::cancelLoad(%p)", this);
+ tearDownVideoRendering();
+
+ [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
+
+ // Tell our observer to do nothing when our cancellation of pending loading calls its completion handler.
+ setIgnoreLoadStateChanges(true);
+ if (m_avAsset) {
+ [m_avAsset.get() cancelLoading];
+ m_avAsset = nil;
+ }
+ if (m_avPlayerItem) {
+ for (NSString *keyName in itemKVOProperties())
+ [m_avPlayerItem.get() removeObserver:m_objcObserver.get() forKeyPath:keyName];
+
+ m_avPlayerItem = nil;
+ }
+ if (m_avPlayer) {
+ if (m_timeObserver)
+ [m_avPlayer.get() removeTimeObserver:m_timeObserver];
+ [m_avPlayer.get() removeObserver:m_objcObserver.get() forKeyPath:@"rate"];
+ m_avPlayer = nil;
+ }
+ setIgnoreLoadStateChanges(false);
+}
+
+bool MediaPlayerPrivateAVFoundationObjC::hasLayerRenderer() const
+{
+ return m_videoLayer;
+}
+
+bool MediaPlayerPrivateAVFoundationObjC::hasContextRenderer() const
+{
+ return m_imageGenerator;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::createContextVideoRenderer()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createContextVideoRenderer(%p)", this);
+
+ if (!m_avAsset || m_imageGenerator)
+ return;
+
+ m_imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:m_avAsset.get()];
+
+ [m_imageGenerator.get() setApertureMode:AVAssetImageGeneratorApertureModeCleanAperture];
+ [m_imageGenerator.get() setAppliesPreferredTrackTransform:YES];
+
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createImageGenerator(%p) - returning %p", this, m_imageGenerator.get());
+}
+
+void MediaPlayerPrivateAVFoundationObjC::destroyContextVideoRenderer()
+{
+ if (!m_imageGenerator)
+ return;
+
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::destroyContextVideoRenderer(%p) - destroying", this, m_imageGenerator.get());
+
+ m_imageGenerator = 0;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::createVideoLayer()
+{
+ if (!m_avPlayer)
+ return;
+
+ if (!m_videoLayer) {
+ m_videoLayer.adoptNS([[AVPlayerLayer alloc] init]);
+ [m_videoLayer.get() setPlayer:m_avPlayer.get()];
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createVideoLayer(%p) - returning", this, m_videoLayer.get());
+ }
+}
+
+void MediaPlayerPrivateAVFoundationObjC::destroyVideoLayer()
+{
+ if (!m_videoLayer)
+ return;
+
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::destroyVideoLayer(%p) - destroying", this, m_videoLayer.get());
+
+ [m_videoLayer.get() setPlayer:nil];
+
+ m_videoLayer = 0;
+}
+
+bool MediaPlayerPrivateAVFoundationObjC::videoLayerIsReadyToDisplay() const
+{
+ return (m_videoLayer && [m_videoLayer.get() isReadyForDisplay]);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForURL(const String& url)
+{
+ setDelayCallbacks(true);
+
+ if (!m_avAsset) {
+ NSURL *cocoaURL = KURL(ParsedURLString, url);
+ m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
+ }
+
+ createAVPlayer();
+}
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForCacheResource(ApplicationCacheResource* resource)
+{
+ // AVFoundation can't open arbitrary data pointers, so if this ApplicationCacheResource doesn't
+ // have a valid local path, just open the resource's original URL.
+ if (resource->path().isEmpty()) {
+ createAVPlayerForURL(resource->url());
+ return;
+ }
+
+ setDelayCallbacks(true);
+
+ if (!m_avAsset) {
+ NSURL* localURL = [NSURL fileURLWithPath:resource->path()];
+ m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:localURL options:nil]);
+ }
+
+ createAVPlayer();
+}
+#endif
+
+void MediaPlayerPrivateAVFoundationObjC::createAVPlayer()
+{
+ if (!m_avPlayer) {
+ m_avPlayer.adoptNS([[AVPlayer alloc] init]);
+
+ [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
+
+ // Add a time observer, ask to be called infrequently because we don't really want periodic callbacks but
+ // our observer will also be called whenever a seek happens.
+ const double veryLongInterval = 60*60*60*24*30;
+ WebCoreAVFMovieObserver *observer = m_objcObserver.get();
+ m_timeObserver = [m_avPlayer.get() addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(veryLongInterval, 10) queue:nil usingBlock:^(CMTime time){
+ [observer timeChanged:CMTimeGetSeconds(time)];
+ }];
+ }
+
+ if (!m_avPlayerItem) {
+ // Create the player item so we can media data.
+ m_avPlayerItem.adoptNS([[AVPlayerItem alloc] initWithAsset:m_avAsset.get()]);
+
+ [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()selector:@selector(didEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:m_avPlayerItem.get()];
+
+ for (NSString *keyName in itemKVOProperties())
+ [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
+
+ [m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
+ }
+
+ setDelayCallbacks(false);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::checkPlayability()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::checkPlayability(%p)", this);
+
+ [m_avAsset.get() loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"playable"] completionHandler:^{
+ [m_objcObserver.get() playableKnown];
+ }];
+}
+
+void MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::playabilityKnown(%p) - requesting metadata loading", this);
+ [m_avAsset.get() loadValuesAsynchronouslyForKeys:[assetMetadataKeyNames() retain] completionHandler:^{
+ [m_objcObserver.get() metadataLoaded];
+ }];
+}
+
+MediaPlayerPrivateAVFoundation::ItemStatus MediaPlayerPrivateAVFoundationObjC::playerItemStatus() const
+{
+ if (!m_avPlayerItem)
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
+
+ AVPlayerItemStatus status = [m_avPlayerItem.get() status];
+ if (status == AVPlayerItemStatusUnknown)
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
+ if (status == AVPlayerItemStatusFailed)
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusFailed;
+ if ([m_avPlayerItem.get() isPlaybackLikelyToKeepUp])
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp;
+ if ([m_avPlayerItem.get() isPlaybackBufferFull])
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferFull;
+ if ([m_avPlayerItem.get() isPlaybackBufferEmpty])
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty;
+
+ return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusReadyToPlay;
+}
+
+PlatformMedia MediaPlayerPrivateAVFoundationObjC::platformMedia() const
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformMedia(%p)", this);
+ PlatformMedia pm;
+ pm.type = PlatformMedia::AVFoundationMediaPlayerType;
+ pm.media.avfMediaPlayer = m_avPlayer.get();
+ return pm;
+}
+
+PlatformLayer* MediaPlayerPrivateAVFoundationObjC::platformLayer() const
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformLayer(%p)", this);
+ return m_videoLayer.get();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::play()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::play(%p)", this);
+ if (!metaDataAvailable())
+ return;
+
+ setDelayCallbacks(true);
+ [m_avPlayer.get() setRate:requestedRate()];
+ setDelayCallbacks(false);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::pause()
+{
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::pause(%p)", this);
+ if (!metaDataAvailable())
+ return;
+
+ setDelayCallbacks(true);
+ [m_avPlayer.get() setRate:nil];
+ setDelayCallbacks(false);
+}
+
+float MediaPlayerPrivateAVFoundationObjC::platformDuration() const
+{
+ if (!metaDataAvailable() || !m_avPlayerItem)
+ return 0;
+
+ float duration;
+ CMTime cmDuration = [m_avPlayerItem.get() duration];
+ if (CMTIME_IS_NUMERIC(cmDuration))
+ duration = narrowPrecisionToFloat(CMTimeGetSeconds(cmDuration));
+ else if (CMTIME_IS_INDEFINITE(cmDuration))
+ duration = numeric_limits<float>::infinity();
+ else {
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::duration(%p) - invalid duration, returning 0", this);
+ return 0;
+ }
+
+ return duration;
+}
+
+float MediaPlayerPrivateAVFoundationObjC::currentTime() const
+{
+ if (!metaDataAvailable() || !m_avPlayerItem)
+ return 0;
+
+ CMTime itemTime = [m_avPlayerItem.get() currentTime];
+ if (CMTIME_IS_NUMERIC(itemTime))
+ return narrowPrecisionToFloat(CMTimeGetSeconds(itemTime));
+
+ return 0;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::seekToTime(float time)
+{
+ // setCurrentTime generates several event callbacks, update afterwards.
+ setDelayCallbacks(true);
+
+ float now = currentTime();
+ if (time != now)
+ [m_avPlayerItem.get() seekToTime:CMTimeMakeWithSeconds(time, 600) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
+ else {
+ // Force a call to the "time changed" notifier manually because a seek to the current time is a noop
+ // so the seek will never seem to complete.
+ [m_objcObserver.get() timeChanged:now];
+ }
+
+ setDelayCallbacks(false);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::setVolume(float volume)
+{
+ if (!metaDataAvailable())
+ return;
+
+ [m_avPlayer.get() setVolume:volume];
+}
+
+void MediaPlayerPrivateAVFoundationObjC::setClosedCaptionsVisible(bool closedCaptionsVisible)
+{
+ if (!metaDataAvailable())
+ return;
+
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::setClosedCaptionsVisible(%p) - setting to %s", this, boolString(closedCaptionsVisible));
+ [m_avPlayer.get() setClosedCaptionDisplayEnabled:closedCaptionsVisible];
+}
+
+float MediaPlayerPrivateAVFoundationObjC::rate() const
+{
+ if (!metaDataAvailable())
+ return 0;
+
+ return [m_avPlayer.get() rate];
+}
+
+PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges() const
+{
+ RefPtr<TimeRanges> timeRanges = TimeRanges::create();
+
+ if (!m_avPlayerItem)
+ return timeRanges.release();
+
+ NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
+ for (NSValue *thisRangeValue in loadedRanges) {
+ CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
+ if (CMTIMERANGE_IS_VALID(timeRange) && !CMTIMERANGE_IS_EMPTY(timeRange)) {
+ float rangeStart = narrowPrecisionToFloat(CMTimeGetSeconds(timeRange.start));
+ float rangeEnd = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
+ timeRanges->add(rangeStart, rangeEnd);
+ }
+ }
+ return timeRanges.release();
+}
+
+float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable() const
+{
+ NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
+ if (!seekableRanges)
+ return 0;
+
+ float maxTimeSeekable = 0;
+ for (NSValue *thisRangeValue in seekableRanges) {
+ CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
+ if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
+ continue;
+
+ float endOfRange = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
+ if (maxTimeSeekable < endOfRange)
+ maxTimeSeekable = endOfRange;
+ }
+ return maxTimeSeekable;
+}
+
+float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded() const
+{
+ NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
+ if (!loadedRanges)
+ return 0;
+
+ float maxTimeLoaded = 0;
+ for (NSValue *thisRangeValue in loadedRanges) {
+ CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
+ if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
+ continue;
+
+ float endOfRange = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
+ if (maxTimeLoaded < endOfRange)
+ maxTimeLoaded = endOfRange;
+ }
+
+ return maxTimeLoaded;
+}
+
+unsigned MediaPlayerPrivateAVFoundationObjC::totalBytes() const
+{
+ if (!metaDataAvailable())
+ return 0;
+
+ long long totalMediaSize = 0;
+ NSArray *tracks = [m_avAsset.get() tracks];
+ for (AVAssetTrack *thisTrack in tracks)
+ totalMediaSize += [thisTrack totalSampleDataLength];
+
+ return static_cast<unsigned>(totalMediaSize);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::setAsset(id asset)
+{
+ m_avAsset = asset;
+}
+
+MediaPlayerPrivateAVFoundation::AVAssetStatus MediaPlayerPrivateAVFoundationObjC::assetStatus() const
+{
+ if (!m_avAsset)
+ return MediaPlayerAVAssetStatusUnknown;
+
+ for (NSString *keyName in assetMetadataKeyNames()) {
+ AVKeyValueStatus keyStatus = [m_avAsset.get() statusOfValueForKey:keyName error:nil];
+ if (keyStatus < AVKeyValueStatusLoaded)
+ return MediaPlayerAVAssetStatusLoading;// At least one key is not loaded yet.
+
+ if (keyStatus == AVKeyValueStatusFailed)
+ return MediaPlayerAVAssetStatusFailed; // At least one key could not be loaded.
+ if (keyStatus == AVKeyValueStatusCancelled)
+ return MediaPlayerAVAssetStatusCancelled; // Loading of at least one key was cancelled.
+ }
+
+ if ([[m_avAsset.get() valueForKey:@"playable"] boolValue])
+ return MediaPlayerAVAssetStatusPlayable;
+
+ return MediaPlayerAVAssetStatusLoaded;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& rect)
+{
+ if (!metaDataAvailable() || context->paintingDisabled())
+ return;
+
+ paint(context, rect);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::paint(GraphicsContext* context, const IntRect& rect)
+{
+ if (!metaDataAvailable() || context->paintingDisabled())
+ return;
+
+ setDelayCallbacks(true);
+ BEGIN_BLOCK_OBJC_EXCEPTIONS;
+
+ RetainPtr<CGImageRef> image = createImageForTimeInRect(currentTime(), rect);
+ if (image) {
+ context->save();
+ context->translate(rect.x(), rect.y() + rect.height());
+ context->scale(FloatSize(1.0f, -1.0f));
+ context->setImageInterpolationQuality(InterpolationLow);
+ IntRect paintRect(IntPoint(0, 0), IntSize(rect.width(), rect.height()));
+ CGContextDrawImage(context->platformContext(), CGRectMake(0, 0, paintRect.width(), paintRect.height()), image.get());
+ context->restore();
+ image = 0;
+ }
+
+ END_BLOCK_OBJC_EXCEPTIONS;
+ setDelayCallbacks(false);
+
+ MediaPlayerPrivateAVFoundation::paint(context, rect);
+}
+
+static HashSet<String> mimeTypeCache()
+{
+ DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
+ static bool typeListInitialized = false;
+
+ if (typeListInitialized)
+ return cache;
+ typeListInitialized = true;
+
+ NSArray *types = [AVURLAsset audiovisualMIMETypes];
+ for (NSString *mimeType in types)
+ cache.add(mimeType);
+
+ return cache;
+}
+
+RetainPtr<CGImageRef> MediaPlayerPrivateAVFoundationObjC::createImageForTimeInRect(float time, const IntRect& rect)
+{
+ if (!m_imageGenerator)
+ createContextVideoRenderer();
+ ASSERT(m_imageGenerator);
+
+#if !LOG_DISABLED
+ double start = WTF::currentTime();
+#endif
+
+ [m_imageGenerator.get() setMaximumSize:CGSize(rect.size())];
+ CGImageRef image = [m_imageGenerator.get() copyCGImageAtTime:CMTimeMakeWithSeconds(time, 600) actualTime:nil error:nil];
+
+#if !LOG_DISABLED
+ double duration = WTF::currentTime() - start;
+ LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createImageForTimeInRect(%p) - creating image took %.4f", this, narrowPrecisionToFloat(duration));
+#endif
+
+ return image;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::getSupportedTypes(HashSet<String>& supportedTypes)
+{
+ supportedTypes = mimeTypeCache();
+}
+
+MediaPlayer::SupportsType MediaPlayerPrivateAVFoundationObjC::supportsType(const String& type, const String& codecs)
+{
+ // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask if it supports an
+ // extended MIME type until rdar://6220037 is fixed.
+ if (mimeTypeCache().contains(type))
+ return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
+
+ return MediaPlayer::IsNotSupported;
+}
+
+bool MediaPlayerPrivateAVFoundationObjC::isAvailable()
+{
+ return true;
+}
+
+float MediaPlayerPrivateAVFoundationObjC::mediaTimeForTimeValue(float timeValue) const
+{
+ if (!metaDataAvailable())
+ return timeValue;
+
+ // FIXME - impossible to implement until rdar://8721510 is fixed.
+ return timeValue;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::tracksChanged()
+{
+ // This is called whenever the tracks collection changes so cache hasVideo and hasAudio since we get
+ // asked about those fairly fequently.
+ setHasVideo([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicVisual] count]);
+ setHasAudio([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicAudible] count]);
+ setHasClosedCaptions([[m_avAsset.get() tracksWithMediaType:AVMediaTypeClosedCaption] count]);
+
+ sizeChanged();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::sizeChanged()
+{
+ NSArray *tracks = [m_avAsset.get() tracks];
+
+ // Some assets don't report track properties until they are completely ready to play, but we
+ // want to report a size as early as possible so use presentationSize when an asset has no tracks.
+ if (![tracks count]) {
+ setNaturalSize(IntSize([m_avPlayerItem.get() presentationSize]));
+ return;
+ }
+
+ // AVAsset's 'naturalSize' property only considers the movie's first video track, so we need to compute
+ // the union of all visual track rects.
+ CGRect trackUnionRect = CGRectZero;
+ for (AVAssetTrack *track in tracks) {
+ CGSize trackSize = [track naturalSize];
+ CGRect trackRect = CGRectMake(0, 0, trackSize.width, trackSize.height);
+ trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [track preferredTransform]));
+ }
+
+ // The movie is always displayed at 0,0 so move the track rect to the origin before using width and height.
+ trackUnionRect = CGRectOffset(trackUnionRect, trackUnionRect.origin.x, trackUnionRect.origin.y);
+
+ // Also look at the asset's preferred transform so we account for a movie matrix.
+ CGSize naturalSize = CGSizeApplyAffineTransform(trackUnionRect.size, [m_avAsset.get() preferredTransform]);
+
+ // Cache the natural size (setNaturalSize will notify the player if it has changed).
+ setNaturalSize(IntSize(naturalSize));
+}
+
+NSArray* assetMetadataKeyNames()
+{
+ static NSArray* keys;
+ if (!keys) {
+ keys = [[NSArray alloc] initWithObjects:@"duration",
+ @"naturalSize",
+ @"preferredTransform",
+ @"preferredVolume",
+ @"preferredRate",
+ @"playable",
+ @"tracks",
+ nil];
+ }
+ return keys;
+}
+
+NSArray* itemKVOProperties()
+{
+ static NSArray* keys;
+ if (!keys) {
+ keys = [[NSArray alloc] initWithObjects:@"presentationSize",
+ @"status",
+ @"asset",
+ @"tracks",
+ @"seekableTimeRanges",
+ @"loadedTimeRanges",
+ @"playbackLikelyToKeepUp",
+ @"playbackBufferFull",
+ @"playbackBufferEmpty",
+ nil];
+ }
+ return keys;
+}
+
+} // namespace WebCore
+
+@implementation WebCoreAVFMovieObserver
+
+- (id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback
+{
+ m_callback = callback;
+ return [super init];
+}
+
+- (void)disconnect
+{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ m_callback = 0;
+}
+
+- (void)metadataLoaded
+{
+ if (!m_callback)
+ return;
+
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::AssetMetadataLoaded);
+}
+
+- (void)playableKnown
+{
+ if (!m_callback)
+ return;
+
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::AssetPlayabilityKnown);
+}
+
+- (void)timeChanged:(double)time
+{
+ if (!m_callback)
+ return;
+
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerTimeChanged, time);
+}
+
+- (void)didEnd:(NSNotification *)unusedNotification
+{
+ UNUSED_PARAM(unusedNotification);
+ if (!m_callback)
+ return;
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemDidPlayToEndTime);
+}
+
+- (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
+{
+ UNUSED_PARAM(change);
+
+ LOG(Media, "WebCoreAVFMovieObserver:observeValueForKeyPath(%p) - keyPath = %s", self, [keyPath UTF8String]);
+
+ if (!m_callback)
+ return;
+
+ if (context == MediaPlayerAVFoundationObservationContextPlayerItem) {
+ // A value changed for an AVPlayerItem
+ if ([keyPath isEqualToString:@"status"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemStatusChanged);
+ else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackLikelyToKeepUpChanged);
+ else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferEmptyChanged);
+ else if ([keyPath isEqualToString:@"playbackBufferFull"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferFullChanged);
+ else if ([keyPath isEqualToString:@"asset"])
+ m_callback->setAsset([object asset]);
+ else if ([keyPath isEqualToString:@"loadedTimeRanges"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemLoadedTimeRangesChanged);
+ else if ([keyPath isEqualToString:@"seekableTimeRanges"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemSeekableTimeRangesChanged);
+ else if ([keyPath isEqualToString:@"tracks"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
+ else if ([keyPath isEqualToString:@"presentationSize"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemPresentationSizeChanged);
+
+ return;
+ }
+
+ if (context == MediaPlayerAVFoundationObservationContextPlayer) {
+ // A value changed for an AVPlayer.
+ if ([keyPath isEqualToString:@"rate"])
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerRateChanged);
+}
+}
+
+@end
+
+#endif