diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/avfoundation')
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 |