2011-05-02 Eric Carlson <eric.carlson@apple.com>
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 May 2011 17:21:34 +0000 (17:21 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 May 2011 17:21:34 +0000 (17:21 +0000)
        Reviewed by Brady Eidson.

        The preload attribute of the video tag is not completely implemented
        https://bugs.webkit.org/show_bug.cgi?id=43673
        <rdar://problem/7508322>

        Tested manually with manual-tests/media-elements/video-preload.html.

        * html/HTMLMediaElement.cpp:
        (WebCore::HTMLMediaElement::HTMLMediaElement): Initialize m_havePreparedToPlay.
        (WebCore::HTMLMediaElement::prepareForLoad): Ditto.
        (WebCore::HTMLMediaElement::prepareToPlay): New, tell player to prepare to play.
        (WebCore::HTMLMediaElement::seek): Call prepareToPlay when preload is less than 'metadata'
            because we need to have media data loaded to seek.
        (WebCore::HTMLMediaElement::updatePlayState): Call prepareToPlay.
        * html/HTMLMediaElement.h:

        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp:
        (WebCore::MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation):  Remove
            m_videoFrameHasDrawn and m_delayingLoad as they are no longer used.
        (WebCore::MediaPlayerPrivateAVFoundation::resumeLoad): Removed.
        (WebCore::MediaPlayerPrivateAVFoundation::load): Don't initialize m_videoFrameHasDrawn.
            Move all preload logic to setPreload, call it from here.
        (WebCore::MediaPlayerPrivateAVFoundation::prepareToPlay): Move all preload logic to
            setPreload, call it.
        (WebCore::MediaPlayerPrivateAVFoundation::duration): Don't cache duration = 0, it is
            unlikely to be correct and isn't worth caching.
        (WebCore::MediaPlayerPrivateAVFoundation::updateStates): Update for name change AVAssetStatus
            to AssetStatus. Create the AVPlayer once we know an asset is playable but preload is
            'metadata'. Set networkState to 'idle' when the playback buffer is full because that is
            a signal that AVFoundation won't do any more IO. Set readyState to 'HAVE_CURRENT_DATA'
            when the first frame is available.
        (WebCore::MediaPlayerPrivateAVFoundation::metadataLoaded): Call tracksChanged so we cache
            width, height, hasVideo, etc.
        (WebCore::MediaPlayerPrivateAVFoundation::repaint): Don't set m_videoFrameHasDrawn, it is done
            in derived classes.
        (WebCore::MediaPlayerPrivateAVFoundation::setPreload): Centralize all logic about when to create
            AVAsset and AVPlayerItem here.
        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h:

        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h:
        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm:
        (WebCore::MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC): Initialize
            m_videoFrameHasDrawn.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::hasAvailableVideoFrame): New, renamed from
            videoLayerIsReadyToDisplay. Return true if we have a layer with frames available or
            if we have painted a frame to the context.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVAssetForURL): New, create the AVAsset
            if necessary.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVAssetForCacheResource): Ditto.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer): Restructure logic.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem): New, create AVPlayerItem.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata): Correct logging.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatus): Return "buffer full" when
            the buffer is full.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformDuration): Get the duration from the
            AVAsset when we haven't allocated the AVPlayerItem yet so that we can return duration
            when we only have metadata.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::assetStatus): Update for name change.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::paint): Set m_videoFrameHasDrawn.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::tracksChanged): Get attributes from AVAsset
            when when we haven't allocated the AVPlayerItem yet so that we can report attributes
            when we only have metadata.
        (WebCore::MediaPlayerPrivateAVFoundationObjC::sizeChanged): Guard against being called before
            we have allocated the AVPlayerItem.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@85483 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp
Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h
Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h
Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm

index 4a975c0..08cf752 100644 (file)
@@ -1,3 +1,71 @@
+2011-05-02  Eric Carlson  <eric.carlson@apple.com>
+
+        Reviewed by Brady Eidson.
+
+        The preload attribute of the video tag is not completely implemented
+        https://bugs.webkit.org/show_bug.cgi?id=43673
+        <rdar://problem/7508322>
+
+        Tested manually with manual-tests/media-elements/video-preload.html.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::HTMLMediaElement): Initialize m_havePreparedToPlay.
+        (WebCore::HTMLMediaElement::prepareForLoad): Ditto.
+        (WebCore::HTMLMediaElement::prepareToPlay): New, tell player to prepare to play.
+        (WebCore::HTMLMediaElement::seek): Call prepareToPlay when preload is less than 'metadata'
+            because we need to have media data loaded to seek.
+        (WebCore::HTMLMediaElement::updatePlayState): Call prepareToPlay.
+        * html/HTMLMediaElement.h:
+
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp:
+        (WebCore::MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation):  Remove 
+            m_videoFrameHasDrawn and m_delayingLoad as they are no longer used.
+        (WebCore::MediaPlayerPrivateAVFoundation::resumeLoad): Removed.
+        (WebCore::MediaPlayerPrivateAVFoundation::load): Don't initialize m_videoFrameHasDrawn. 
+            Move all preload logic to setPreload, call it from here.
+        (WebCore::MediaPlayerPrivateAVFoundation::prepareToPlay): Move all preload logic to 
+            setPreload, call it.
+        (WebCore::MediaPlayerPrivateAVFoundation::duration): Don't cache duration = 0, it is
+            unlikely to be correct and isn't worth caching.
+        (WebCore::MediaPlayerPrivateAVFoundation::updateStates): Update for name change AVAssetStatus
+            to AssetStatus. Create the AVPlayer once we know an asset is playable but preload is
+            'metadata'. Set networkState to 'idle' when the playback buffer is full because that is
+            a signal that AVFoundation won't do any more IO. Set readyState to 'HAVE_CURRENT_DATA' 
+            when the first frame is available.
+        (WebCore::MediaPlayerPrivateAVFoundation::metadataLoaded): Call tracksChanged so we cache
+            width, height, hasVideo, etc.
+        (WebCore::MediaPlayerPrivateAVFoundation::repaint): Don't set m_videoFrameHasDrawn, it is done
+            in derived classes.
+        (WebCore::MediaPlayerPrivateAVFoundation::setPreload): Centralize all logic about when to create
+            AVAsset and AVPlayerItem here.
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h:
+
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC): Initialize
+            m_videoFrameHasDrawn.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::hasAvailableVideoFrame): New, renamed from
+            videoLayerIsReadyToDisplay. Return true if we have a layer with frames available or
+            if we have painted a frame to the context.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVAssetForURL): New, create the AVAsset
+            if necessary.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVAssetForCacheResource): Ditto.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer): Restructure logic.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem): New, create AVPlayerItem.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata): Correct logging.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatus): Return "buffer full" when
+            the buffer is full.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformDuration): Get the duration from the
+            AVAsset when we haven't allocated the AVPlayerItem yet so that we can return duration
+            when we only have metadata.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::assetStatus): Update for name change.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::paint): Set m_videoFrameHasDrawn.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::tracksChanged): Get attributes from AVAsset
+            when when we haven't allocated the AVPlayerItem yet so that we can report attributes
+            when we only have metadata.
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::sizeChanged): Guard against being called before
+            we have allocated the AVPlayerItem.
+
 2011-05-02  Philippe Normand  <pnormand@igalia.com>
 
         Reviewed by Martin Robinson.
index e581a2e..23aa400 100644 (file)
@@ -170,6 +170,7 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* docum
     , m_dispatchingCanPlayEvent(false)
     , m_loadInitiatedByUserGesture(false)
     , m_completelyLoaded(false)
+    , m_havePreparedToPlay(false)
 {
     LOG(Media, "HTMLMediaElement::HTMLMediaElement");
     document->registerForDocumentActivationCallbacks(this);
@@ -516,6 +517,7 @@ void HTMLMediaElement::prepareForLoad()
     m_sentStalledEvent = false;
     m_haveFiredLoadedData = false;
     m_completelyLoaded = false;
+    m_havePreparedToPlay = false;
     m_displayMode = Unknown;
 
     // 1 - Abort any already-running instance of the resource selection algorithm for this element.
@@ -1082,7 +1084,15 @@ bool HTMLMediaElement::supportsSave() const
 {
     return m_player ? m_player->supportsSave() : false;
 }
-    
+
+void HTMLMediaElement::prepareToPlay()
+{
+    if (m_havePreparedToPlay)
+        return;
+    m_havePreparedToPlay = true;
+    m_player->prepareToPlay();
+}
+
 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
 {
     LOG(Media, "HTMLMediaElement::seek(%f)", time);
@@ -1095,6 +1105,10 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec)
         return;
     }
 
+    // If the media engine has been told to postpone loading data, let it go ahead now.
+    if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
+        prepareToPlay();
+
     // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
     refreshCachedTime();
     float now = currentTime();
@@ -2196,7 +2210,7 @@ void HTMLMediaElement::updatePlayState()
             addPlayedRange(m_lastSeekTime, time);
 
         if (couldPlayIfEnoughData())
-            m_player->prepareToPlay();
+            prepareToPlay();
 
         if (hasMediaControls())
             mediaControls()->playbackStopped();
index 604cdf8..7d3d887 100644 (file)
@@ -286,6 +286,7 @@ private:
     void mediaEngineError(PassRefPtr<MediaError> err);
     void cancelPendingEventsAndCallbacks();
     void waitForSourceChange();
+    void prepareToPlay();
 
     enum InvalidSourceAction { DoNothing, Complain };
     bool isSafeToLoadURL(const KURL&, InvalidSourceAction);
@@ -417,6 +418,7 @@ private:
     bool m_dispatchingCanPlayEvent : 1;
     bool m_loadInitiatedByUserGesture : 1;
     bool m_completelyLoaded : 1;
+    bool m_havePreparedToPlay : 1;
 };
 
 } //namespace
index 096ebb2..c988ad6 100644 (file)
@@ -30,6 +30,7 @@
 #include "MediaPlayerPrivateAVFoundation.h"
 
 #include "ApplicationCacheHost.h"
+#include "ApplicationCacheResource.h"
 #include "DocumentLoader.h"
 #include "FrameView.h"
 #include "GraphicsContext.h"
@@ -45,30 +46,25 @@ 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_cachedDuration(invalidTime())
+    , m_reportedDuration(invalidTime())
+    , m_seekTo(invalidTime())
     , m_requestedRate(1)
-    , m_delayCallbacks(false)
-    , m_havePreparedToPlay(false)
+    , m_delayCallbacks(0)
+    , m_mainThreadCallPending(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)
@@ -167,17 +163,6 @@ 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);
@@ -191,20 +176,13 @@ void MediaPlayerPrivateAVFoundation::load(const String& url)
         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();
+    setPreload(m_preload);
 }
 
 void MediaPlayerPrivateAVFoundation::playabilityKnown()
@@ -232,22 +210,7 @@ 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();
+    setPreload(MediaPlayer::Auto);
 }
 
 void MediaPlayerPrivateAVFoundation::play()
@@ -269,22 +232,20 @@ void MediaPlayerPrivateAVFoundation::pause()
     platformPause();
 }
 
-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 (m_cachedDuration != invalidTime())
+        return m_cachedDuration;
+
     if (!metaDataAvailable())
         return 0;
 
-    if (m_cachedDuration == invalidTime) {
-        m_cachedDuration = platformDuration();
-        LOG(Media, "MediaPlayerPrivateAVFMac::duration(%p) - caching %f", this, m_cachedDuration);
-    }
+    float duration = platformDuration();
+    if (!duration || duration == invalidTime())
+        return 0;
 
+    m_cachedDuration = duration;
+    LOG(Media, "MediaPlayerPrivateAVFMac::duration(%p) - caching %f", this, m_cachedDuration);
     return m_cachedDuration;
 }
 
@@ -326,7 +287,7 @@ bool MediaPlayerPrivateAVFoundation::seeking() const
     if (!metaDataAvailable())
         return false;
 
-    return m_seekTo != invalidTime;
+    return m_seekTo != invalidTime();
 }
 
 IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
@@ -432,24 +393,28 @@ void MediaPlayerPrivateAVFoundation::updateStates()
         m_networkState = MediaPlayer::Loading;
     else {
         // -loadValuesAsynchronouslyForKeys:completionHandler: has invoked its handler; test status of keys and determine state.
-        AVAssetStatus avAssetStatus = assetStatus();
+        AssetStatus avAssetStatus = assetStatus();
         ItemStatus itemStatus = playerItemStatus();
         
         m_assetIsPlayable = (avAssetStatus == MediaPlayerAVAssetStatusPlayable);
         if (m_readyState < MediaPlayer::HaveMetadata && avAssetStatus > MediaPlayerAVAssetStatusLoading) {
             if (m_assetIsPlayable) {
-                if (itemStatus == MediaPlayerAVPlayerItemStatusUnknown) {
+                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
+                    } else {
+                        // The asset is playable, but we don't want to load media data yet so don't allocate
+                        // the player item. Even though we don't want to play yet, allocate a player so
+                        // we can create a layer as soon as possible.
+                        createAVPlayer();
                         m_networkState = MediaPlayer::Idle;
+                    }
                 }
-                if (avAssetStatus == MediaPlayerAVAssetStatusLoaded)
-                    m_readyState = MediaPlayer::HaveMetadata;
+                m_readyState = MediaPlayer::HaveMetadata;
             } else {
                 // FIX ME: fetch the error associated with the @"playable" key to distinguish between format 
                 // and network errors.
@@ -463,6 +428,7 @@ void MediaPlayerPrivateAVFoundation::updateStates()
             else {
                 float maxLoaded = maxTimeLoaded();
                 switch (itemStatus) {
+                case MediaPlayerAVPlayerItemStatusDoesNotExist:
                 case MediaPlayerAVPlayerItemStatusUnknown:
                     break;
                 case MediaPlayerAVPlayerItemStatusFailed:
@@ -471,8 +437,11 @@ void MediaPlayerPrivateAVFoundation::updateStates()
                 case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
                     m_readyState = MediaPlayer::HaveEnoughData;
                     break;
-                case MediaPlayerAVPlayerItemStatusReadyToPlay:
+
                 case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
+                    m_networkState = MediaPlayer::Idle;
+
+                case MediaPlayerAVPlayerItemStatusReadyToPlay:
                     // If the readyState is already HaveEnoughData, don't go lower because of this state change.
                     if (m_readyState == MediaPlayer::HaveEnoughData)
                         break;
@@ -485,7 +454,7 @@ void MediaPlayerPrivateAVFoundation::updateStates()
                     break;
                 }
 
-                if (itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
+                if (itemStatus != MediaPlayerAVPlayerItemStatusPlaybackBufferFull && itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
                     m_networkState = (maxLoaded == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
             }
         }
@@ -495,6 +464,8 @@ void MediaPlayerPrivateAVFoundation::updateStates()
         setUpVideoRendering();
 
     if (!m_haveReportedFirstVideoFrame && m_cachedHasVideo && hasAvailableVideoFrame()) {
+        if (m_readyState < MediaPlayer::HaveCurrentData)
+            m_readyState = MediaPlayer::HaveCurrentData;
         m_haveReportedFirstVideoFrame = true;
         m_player->firstVideoFrameAvailable();
     }
@@ -530,16 +501,6 @@ void MediaPlayerPrivateAVFoundation::setVisible(bool visible)
     platformSetVisible(visible);
 }
 
-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.
@@ -549,6 +510,7 @@ void MediaPlayerPrivateAVFoundation::acceleratedRenderingStateChanged()
 void MediaPlayerPrivateAVFoundation::metadataLoaded()
 {
     m_loadingMetadata = false;
+    tracksChanged();
     updateStates();
 }
 
@@ -575,7 +537,7 @@ void MediaPlayerPrivateAVFoundation::loadedTimeRangesChanged()
     // so report duration changed when the estimate is upated.
     float dur = duration();
     if (dur != m_reportedDuration) {
-        if (m_reportedDuration != invalidTime)
+        if (m_reportedDuration != invalidTime())
             m_player->durationChanged();
         m_reportedDuration = dur;
     }
@@ -590,7 +552,7 @@ void MediaPlayerPrivateAVFoundation::timeChanged(double time)
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::timeChanged(%p) - time = %f", this, time);
 
-    if (m_seekTo == invalidTime)
+    if (m_seekTo == invalidTime())
         return;
 
     // AVFoundation may call our observer more than once during a seek, and we can't currently tell
@@ -600,7 +562,7 @@ void MediaPlayerPrivateAVFoundation::timeChanged(double time)
 
     float currentRate = rate();
     if ((currentRate > 0 && time >= m_seekTo) || (currentRate < 0 && time <= m_seekTo) || (abs(m_seekTo - time) <= smallSeekDelta)) {
-        m_seekTo = invalidTime;
+        m_seekTo = invalidTime();
         updateStates();
         m_player->timeChanged();
     }
@@ -611,7 +573,7 @@ void MediaPlayerPrivateAVFoundation::seekCompleted(bool finished)
     LOG(Media, "MediaPlayerPrivateAVFoundation::seekCompleted(%p) - finished = %d", this, finished);
     
     if (finished)
-        m_seekTo = invalidTime;
+        m_seekTo = invalidTime();
 }
 
 void MediaPlayerPrivateAVFoundation::didEnd()
@@ -628,7 +590,6 @@ void MediaPlayerPrivateAVFoundation::didEnd()
 
 void MediaPlayerPrivateAVFoundation::repaint()
 {
-    m_videoFrameHasDrawn = true;
     m_player->repaint();
 }
 
@@ -646,8 +607,30 @@ MediaPlayer::MovieLoadType MediaPlayerPrivateAVFoundation::movieLoadType() const
 void MediaPlayerPrivateAVFoundation::setPreload(MediaPlayer::Preload preload)
 {
     m_preload = preload;
-    if (m_delayingLoad && m_preload != MediaPlayer::None)
-        resumeLoad();
+    if (!m_assetURL.length())
+        return;
+
+    if (m_preload >= MediaPlayer::MetaData && assetStatus() == MediaPlayerAVAssetStatusDoesNotExist) {
+#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;
+        if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(m_assetURL), resource) && 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())
+                createAVAssetForURL(resource->url());
+            else
+                createAVAssetForCacheResource(resource);
+        } else
+#endif    
+            createAVAssetForURL(m_assetURL);
+
+        checkPlayability();
+    }
+
+    if (m_preload == MediaPlayer::Auto && playerItemStatus() == MediaPlayerAVPlayerItemStatusDoesNotExist)
+        createAVPlayerItem();
 }
 
 void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay)
index 225f6cf..4a587b2 100644 (file)
@@ -142,10 +142,9 @@ protected:
     virtual PassRefPtr<TimeRanges> buffered() const;
     virtual unsigned bytesLoaded() const;
     virtual void setSize(const IntSize&);
-    virtual void paint(GraphicsContext*, const IntRect&);
+    virtual void paint(GraphicsContext*, const IntRect&) = 0;
     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;
@@ -159,12 +158,15 @@ protected:
     virtual bool supportsFullscreen() const;
 
     // Required interfaces for concrete derived classes.
-    virtual void createAVPlayerForURL(const String& url) = 0;
+    virtual void createAVAssetForURL(const String&) = 0;
+    virtual void createAVPlayer() = 0;
+    virtual void createAVPlayerItem() = 0;
 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
-    virtual void createAVPlayerForCacheResource(ApplicationCacheResource*) = 0;
+    virtual void createAVAssetForCacheResource(ApplicationCacheResource*) = 0;
 #endif
 
     enum ItemStatus {
+        MediaPlayerAVPlayerItemStatusDoesNotExist,
         MediaPlayerAVPlayerItemStatusUnknown,
         MediaPlayerAVPlayerItemStatusFailed,
         MediaPlayerAVPlayerItemStatusReadyToPlay,
@@ -174,7 +176,8 @@ protected:
     };
     virtual ItemStatus playerItemStatus() const = 0;
 
-    enum AVAssetStatus {
+    enum AssetStatus {
+        MediaPlayerAVAssetStatusDoesNotExist,
         MediaPlayerAVAssetStatusUnknown,
         MediaPlayerAVAssetStatusLoading,
         MediaPlayerAVAssetStatusFailed,
@@ -182,7 +185,7 @@ protected:
         MediaPlayerAVAssetStatusLoaded,
         MediaPlayerAVAssetStatusPlayable,
     };
-    virtual AVAssetStatus assetStatus() const = 0;
+    virtual AssetStatus assetStatus() const = 0;
 
     virtual void platformSetVisible(bool) = 0;
     virtual void platformPlay() = 0;
@@ -206,13 +209,13 @@ protected:
 
     virtual void createVideoLayer() = 0;
     virtual void destroyVideoLayer() = 0;
-    virtual bool videoLayerIsReadyToDisplay() const = 0;
+
+    virtual bool hasAvailableVideoFrame() const = 0;
 
     virtual bool hasContextRenderer() const = 0;
     virtual bool hasLayerRenderer() const = 0;
 
 protected:
-    void resumeLoad();
     void updateStates();
 
     void setHasVideo(bool b) { m_cachedHasVideo = b; };
@@ -236,14 +239,14 @@ protected:
     bool hasSetUpVideoRendering() const;
 
     static void mainThreadCallback(void*);
+    
+    float invalidTime() const { return -1.0f; }
 
 private:
-
     MediaPlayer* m_player;
 
     Vector<Notification> m_queuedNotifications;
     Mutex m_queueMutex;
-    bool m_mainThreadCallPending;
 
     mutable RefPtr<TimeRanges> m_cachedLoadedTimeRanges;
 
@@ -263,12 +266,10 @@ private:
     float m_seekTo;
     float m_requestedRate;
     int m_delayCallbacks;
-    bool m_havePreparedToPlay;
+    bool m_mainThreadCallPending;
     bool m_assetIsPlayable;
     bool m_visible;
-    bool m_videoFrameHasDrawn;
     bool m_loadingMetadata;
-    bool m_delayingLoad;
     bool m_isAllowedToRender;
     bool m_cachedHasAudio;
     bool m_cachedHasVideo;
index cebe2bc..78a9bc4 100644 (file)
@@ -86,12 +86,13 @@ private:
     virtual float mediaTimeForTimeValue(float) const;
 
     virtual void createAVPlayer();
-    virtual void createAVPlayerForURL(const String& url);
+    virtual void createAVPlayerItem();
+    virtual void createAVAssetForURL(const String& url);
 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
-    virtual void createAVPlayerForCacheResource(ApplicationCacheResource*);
+    virtual void createAVAssetForCacheResource(ApplicationCacheResource*);
 #endif
     virtual MediaPlayerPrivateAVFoundation::ItemStatus playerItemStatus() const;
-    virtual MediaPlayerPrivateAVFoundation::AVAssetStatus assetStatus() const;
+    virtual MediaPlayerPrivateAVFoundation::AssetStatus assetStatus() const;
 
     virtual void checkPlayability();
     virtual void updateRate();
@@ -105,12 +106,13 @@ private:
     virtual void beginLoadingMetadata();
     virtual void sizeChanged();
 
+    virtual bool hasAvailableVideoFrame() const;
+
     virtual void createContextVideoRenderer();
     virtual void destroyContextVideoRenderer();
 
     virtual void createVideoLayer();
     virtual void destroyVideoLayer();
-    virtual bool videoLayerIsReadyToDisplay() const;
 
     virtual bool hasContextRenderer() const;
     virtual bool hasLayerRenderer() const;
@@ -125,6 +127,7 @@ private:
     RetainPtr<WebCoreAVFMovieObserver> m_objcObserver;
     RetainPtr<AVAssetImageGenerator> m_imageGenerator;
     id m_timeObserver;
+    bool m_videoFrameHasDrawn;
 };
 
 }
index 542560a..60b2948 100644 (file)
@@ -138,6 +138,7 @@ MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlay
     : MediaPlayerPrivateAVFoundation(player)
     , m_objcObserver(AdoptNS, [[WebCoreAVFMovieObserver alloc] initWithCallback:this])
     , m_timeObserver(0)
+    , m_videoFrameHasDrawn(false)
 {
 }
 
@@ -234,71 +235,77 @@ void MediaPlayerPrivateAVFoundationObjC::destroyVideoLayer()
     m_videoLayer = 0;
 }
 
-bool MediaPlayerPrivateAVFoundationObjC::videoLayerIsReadyToDisplay() const
+bool MediaPlayerPrivateAVFoundationObjC::hasAvailableVideoFrame() const
 {
-    return (m_videoLayer && [m_videoLayer.get() isReadyForDisplay]);
+    return (m_videoFrameHasDrawn || (m_videoLayer && [m_videoLayer.get() isReadyForDisplay]));
 }
 
-void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForURL(const String& url)
+void MediaPlayerPrivateAVFoundationObjC::createAVAssetForURL(const String& url)
 {
-    setDelayCallbacks(true);
+    if (m_avAsset)
+        return;
 
-    if (!m_avAsset) {
-        NSURL *cocoaURL = KURL(ParsedURLString, url);
-        m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
-    }
-    
-    createAVPlayer();
+    setDelayCallbacks(true);
+    NSURL *cocoaURL = KURL(ParsedURLString, url);
+    m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
+    setDelayCallbacks(false);
 }
 
 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
-void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForCacheResource(ApplicationCacheResource* resource)
+void MediaPlayerPrivateAVFoundationObjC::createAVAssetForCacheResource(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());
+    if (m_avAsset)
         return;
-    }
+
+    // AVFoundation can't open arbitrary data pointers.
+    ASSERT(!resource->path().isEmpty());
     
     setDelayCallbacks(true);
 
-    if (!m_avAsset) {
-        NSURL* localURL = [NSURL fileURLWithPath:resource->path()];
-        m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:localURL options:nil]);
-    }
+    NSURL* localURL = [NSURL fileURLWithPath:resource->path()];
+    m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:localURL options:nil]);
 
-    createAVPlayer();
+    setDelayCallbacks(false);
 }
 #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_avPlayer)
+        return;
 
-    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()];
+    setDelayCallbacks(true);
 
-        for (NSString *keyName in itemKVOProperties())
-            [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
+    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)];
+    }];
 
-        [m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
-    }
+    setDelayCallbacks(false);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem()
+{
+    if (m_avPlayerItem)
+        return;
+
+    setDelayCallbacks(true);
+
+    // 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);
 }
@@ -314,7 +321,7 @@ void MediaPlayerPrivateAVFoundationObjC::checkPlayability()
 
 void MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata()
 {
-    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::playabilityKnown(%p) - requesting metadata loading", this);
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata(%p) - requesting metadata loading", this);
     [m_avAsset.get() loadValuesAsynchronouslyForKeys:[assetMetadataKeyNames() retain] completionHandler:^{
         [m_objcObserver.get() metadataLoaded];
     }];
@@ -323,7 +330,7 @@ void MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata()
 MediaPlayerPrivateAVFoundation::ItemStatus MediaPlayerPrivateAVFoundationObjC::playerItemStatus() const
 {
     if (!m_avPlayerItem)
-        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
+        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusDoesNotExist;
 
     AVPlayerItemStatus status = [m_avPlayerItem.get() status];
     if (status == AVPlayerItemStatusUnknown)
@@ -332,9 +339,9 @@ MediaPlayerPrivateAVFoundation::ItemStatus MediaPlayerPrivateAVFoundationObjC::p
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusFailed;
     if ([m_avPlayerItem.get() isPlaybackLikelyToKeepUp])
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp;
-    if (buffered()->contain(duration()))
+    if ([m_avPlayerItem.get() isPlaybackBufferFull])
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferFull;
-    if (buffered()->contain(currentTime()))
+    if ([m_avPlayerItem.get() isPlaybackBufferEmpty])
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty;
 
     return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusReadyToPlay;
@@ -385,21 +392,25 @@ void MediaPlayerPrivateAVFoundationObjC::platformPause()
 
 float MediaPlayerPrivateAVFoundationObjC::platformDuration() const
 {
-    if (!metaDataAvailable() || !m_avPlayerItem)
-        return 0;
+    if (!metaDataAvailable())
+        return invalidTime();
     
-    float duration;
-    CMTime cmDuration = [m_avPlayerItem.get() duration];
+    CMTime cmDuration;
+    
+    // Check the avitem if we have one, some assets never report duration.
+    if (m_avPlayerItem)
+        cmDuration = [m_avPlayerItem.get() duration];
+    else
+        cmDuration= [m_avAsset.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 narrowPrecisionToFloat(CMTimeGetSeconds(cmDuration));
+
+    if (CMTIME_IS_INDEFINITE(cmDuration))
+        return numeric_limits<float>::infinity();
 
-    return duration;
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::duration(%p) - invalid duration, returning -1", this);
+    return invalidTime();
 }
 
 float MediaPlayerPrivateAVFoundationObjC::currentTime() const
@@ -535,10 +546,10 @@ void MediaPlayerPrivateAVFoundationObjC::setAsset(id asset)
     m_avAsset = asset;
 }
 
-MediaPlayerPrivateAVFoundation::AVAssetStatus MediaPlayerPrivateAVFoundationObjC::assetStatus() const
+MediaPlayerPrivateAVFoundation::AssetStatus MediaPlayerPrivateAVFoundationObjC::assetStatus() const
 {
     if (!m_avAsset)
-        return MediaPlayerAVAssetStatusUnknown;
+        return MediaPlayerAVAssetStatusDoesNotExist;
 
     for (NSString *keyName in assetMetadataKeyNames()) {
         AVKeyValueStatus keyStatus = [m_avAsset.get() statusOfValueForKey:keyName error:nil];
@@ -587,7 +598,7 @@ void MediaPlayerPrivateAVFoundationObjC::paint(GraphicsContext* context, const I
     END_BLOCK_OBJC_EXCEPTIONS;
     setDelayCallbacks(false);
 
-    MediaPlayerPrivateAVFoundation::paint(context, rect);
+    m_videoFrameHasDrawn = true;
 }
 
 static HashSet<String> mimeTypeCache()
@@ -662,37 +673,51 @@ float MediaPlayerPrivateAVFoundationObjC::mediaTimeForTimeValue(float timeValue)
 
 void MediaPlayerPrivateAVFoundationObjC::tracksChanged()
 {
+    if (!m_avAsset)
+        return;
+
     // This is called whenever the tracks collection changes so cache hasVideo and hasAudio since we are
     // asked about those fairly fequently.
-    bool hasVideo = false;
-    bool hasAudio = false;
-    bool hasCaptions = false;
-    NSArray *tracks = [m_avPlayerItem.get() tracks];
-    for (AVPlayerItemTrack *track in tracks) {
-        if ([track isEnabled]) {
-            AVAssetTrack *assetTrack = [track assetTrack];
-            if ([[assetTrack mediaType] isEqualToString:AVMediaTypeVideo])
-                hasVideo = true;
-            else if ([[assetTrack mediaType] isEqualToString:AVMediaTypeAudio])
-                hasAudio = true;
-            else if ([[assetTrack mediaType] isEqualToString:AVMediaTypeClosedCaption])
-                hasCaptions = true;
+    if (!m_avPlayerItem) {
+        // We don't have a player item yet, so check with the asset because some assets support inspection
+        // prior to becoming ready to play.
+        setHasVideo([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicVisual] count]);
+        setHasAudio([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicAudible] count]);
+        setHasClosedCaptions([[m_avAsset.get() tracksWithMediaType:AVMediaTypeClosedCaption] count]);
+    } else {
+        bool hasVideo = false;
+        bool hasAudio = false;
+        bool hasCaptions = false;
+        NSArray *tracks = [m_avPlayerItem.get() tracks];
+        for (AVPlayerItemTrack *track in tracks) {
+            if ([track isEnabled]) {
+                AVAssetTrack *assetTrack = [track assetTrack];
+                if ([[assetTrack mediaType] isEqualToString:AVMediaTypeVideo])
+                    hasVideo = true;
+                else if ([[assetTrack mediaType] isEqualToString:AVMediaTypeAudio])
+                    hasAudio = true;
+                else if ([[assetTrack mediaType] isEqualToString:AVMediaTypeClosedCaption])
+                    hasCaptions = true;
+            }
         }
+        setHasVideo(hasVideo);
+        setHasAudio(hasAudio);
+        setHasClosedCaptions(hasCaptions);
     }
-    setHasVideo(hasVideo);
-    setHasAudio(hasAudio);
-    setHasClosedCaptions(hasCaptions);
 
     sizeChanged();
 }
 
 void MediaPlayerPrivateAVFoundationObjC::sizeChanged()
 {
+    if (!m_avAsset)
+        return;
+
     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]) {
+    if (m_avPlayerItem && ![tracks count]) {
         setNaturalSize(IntSize([m_avPlayerItem.get() presentationSize]));
         return;
     }