2011-05-05 Eric Carlson <eric.carlson@apple.com>
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 May 2011 18:17:23 +0000 (18:17 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 May 2011 18:17:23 +0000 (18:17 +0000)
        Reviewed by Adam Roben.

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

        This change implements "preload=metadata" for the AVFoundation backend.
        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 'auto'
            because we need to have media data loaded to seek.
        (WebCore::HTMLMediaElement::updatePlayState): Call prepareToPlay.
        * html/HTMLMediaElement.h:

        * manual-tests/media-elements/video-preload.html: Make changing urls work.

        * 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. Use invalidTime() function.
        (WebCore::MediaPlayerPrivateAVFoundation::seeking): Use invalidTime() function.
        (WebCore::MediaPlayerPrivateAVFoundation::setNaturalSize): Add logging.
        (WebCore::MediaPlayerPrivateAVFoundation::updateStates): Update for name change AVAssetStatus
            to AssetStatus. Always create a AVPlayerItem for live streams because they can't be inspected
            without one. 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::loadedTimeRangesChanged): Use invalidTime() function.
        (WebCore::MediaPlayerPrivateAVFoundation::timeChanged): Ditto.
        (WebCore::MediaPlayerPrivateAVFoundation::seekCompleted): Ditto.
        (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@85865 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/manual-tests/media-elements/video-preload.html
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 dcd7d15..c5203cc 100644 (file)
@@ -1,3 +1,79 @@
+2011-05-05  Eric Carlson  <eric.carlson@apple.com>
+
+        Reviewed by Adam Roben.
+
+        The preload attribute of the video tag is not completely implemented
+        https://bugs.webkit.org/show_bug.cgi?id=43673
+        <rdar://problem/9369746>
+
+        This change implements "preload=metadata" for the AVFoundation backend.
+        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 'auto'
+            because we need to have media data loaded to seek.
+        (WebCore::HTMLMediaElement::updatePlayState): Call prepareToPlay.
+        * html/HTMLMediaElement.h:
+
+        * manual-tests/media-elements/video-preload.html: Make changing urls work. 
+
+        * 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. Use invalidTime() function.
+        (WebCore::MediaPlayerPrivateAVFoundation::seeking): Use invalidTime() function.
+        (WebCore::MediaPlayerPrivateAVFoundation::setNaturalSize): Add logging.
+        (WebCore::MediaPlayerPrivateAVFoundation::updateStates): Update for name change AVAssetStatus
+            to AssetStatus. Always create a AVPlayerItem for live streams because they can't be inspected
+            without one. 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::loadedTimeRangesChanged): Use invalidTime() function.
+        (WebCore::MediaPlayerPrivateAVFoundation::timeChanged): Ditto.
+        (WebCore::MediaPlayerPrivateAVFoundation::seekCompleted): Ditto.
+        (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-05  Ryosuke Niwa  <rniwa@webkit.org>
 
         Reviewed by Eric Seidel.
index fe82976..2368b65 100644 (file)
@@ -171,6 +171,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);
@@ -517,6 +518,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 89e1add..a4f3335 100644 (file)
@@ -288,6 +288,7 @@ private:
     void mediaEngineError(PassRefPtr<MediaError> err);
     void cancelPendingEventsAndCallbacks();
     void waitForSourceChange();
+    void prepareToPlay();
 
     enum InvalidSourceAction { DoNothing, Complain };
     bool isSafeToLoadURL(const KURL&, InvalidSourceAction);
@@ -419,6 +420,7 @@ private:
     bool m_dispatchingCanPlayEvent : 1;
     bool m_loadInitiatedByUserGesture : 1;
     bool m_completelyLoaded : 1;
+    bool m_havePreparedToPlay : 1;
 };
 
 } //namespace
index c267f7c..ddc324f 100644 (file)
@@ -95,9 +95,9 @@
             }
         }
 
-        function setURL(url, vidID)
+        function setURL(url)
         {
-            var vid = document.getElementById(vidID);
+            var vid = document.getElementById("vid");
 
             logMsg(vid, "###############");
             logMsg(vid, "##### setting url to " + url);
         <table>
             <tbody>
                 <tr><td>Preload</td><td id="Preload"></td></tr>
+                <tr><td>Error</td><td id="Error"></td></tr>
                 <tr><td>Duration</td> <td id="Duration"></td></tr>
                 <tr><td>Video Width</td><td id="Video Width"></td></tr>
                 <tr><td>Video Height</td><td id="Video Height"></td></tr>
 
     <br>
     <div class="info">
-        Enter a url:<input  type="text" size="90" maxlength="2048" onchange="setURL(this.value, 'vid-1')" >
+        Enter a url:<input  type="text" size="90" maxlength="2048" onchange="setURL(this.value)" >
         <br>
         <input id="clear_log" type="button" value="Clear" onclick="clearlog()"> 
         <input type="checkbox" id="log-progress"> log 'progress' events
index 096ebb2..2c2b2a1 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,17 @@ 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 (!metaDataAvailable())
-        return 0;
+    if (m_cachedDuration != invalidTime())
+        return m_cachedDuration;
 
-    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, "MediaPlayerPrivateAVFoundation::duration(%p) - caching %f", this, m_cachedDuration);
     return m_cachedDuration;
 }
 
@@ -326,7 +284,7 @@ bool MediaPlayerPrivateAVFoundation::seeking() const
     if (!metaDataAvailable())
         return false;
 
-    return m_seekTo != invalidTime;
+    return m_seekTo != invalidTime();
 }
 
 IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
@@ -346,6 +304,8 @@ IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
 
 void MediaPlayerPrivateAVFoundation::setNaturalSize(IntSize size)
 {
+    LOG(Media, "MediaPlayerPrivateAVFoundation:sizeChanged(%p) - size = %d x %d", this, size.width(), size.height());
+
     IntSize oldSize = m_cachedNaturalSize;
     m_cachedNaturalSize = size;
     if (oldSize != m_cachedNaturalSize)
@@ -432,24 +392,23 @@ 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 assetStatus = this->assetStatus();
         ItemStatus itemStatus = playerItemStatus();
         
-        m_assetIsPlayable = (avAssetStatus == MediaPlayerAVAssetStatusPlayable);
-        if (m_readyState < MediaPlayer::HaveMetadata && avAssetStatus > MediaPlayerAVAssetStatusLoading) {
+        m_assetIsPlayable = (assetStatus == MediaPlayerAVAssetStatusPlayable);
+        if (m_readyState < MediaPlayer::HaveMetadata && assetStatus > 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.
+                if (assetStatus >= MediaPlayerAVAssetStatusLoaded)
+                    m_readyState = MediaPlayer::HaveMetadata;
+                if (itemStatus <= MediaPlayerAVPlayerItemStatusUnknown) {
+                    if (assetStatus == MediaPlayerAVAssetStatusFailed || m_preload > MediaPlayer::MetaData || isLiveStream()) {
+                        // The asset is playable but doesn't support inspection prior to playback (eg. streaming files),
+                        // or we are supposed to prepare for playback immediately, so create the player item now.
                         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.
@@ -457,36 +416,40 @@ void MediaPlayerPrivateAVFoundation::updateStates()
             }
         }
         
-        if (avAssetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
+        if (assetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
             if (seeking())
                 m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
             else {
-                float maxLoaded = maxTimeLoaded();
                 switch (itemStatus) {
+                case MediaPlayerAVPlayerItemStatusDoesNotExist:
                 case MediaPlayerAVPlayerItemStatusUnknown:
-                    break;
                 case MediaPlayerAVPlayerItemStatusFailed:
-                    m_networkState = MediaPlayer::DecodeError;
                     break;
+
                 case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
                     m_readyState = MediaPlayer::HaveEnoughData;
                     break;
-                case MediaPlayerAVPlayerItemStatusReadyToPlay:
+
                 case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
+                case MediaPlayerAVPlayerItemStatusReadyToPlay:
                     // If the readyState is already HaveEnoughData, don't go lower because of this state change.
                     if (m_readyState == MediaPlayer::HaveEnoughData)
                         break;
 
                 case MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty:
-                    if (maxLoaded > currentTime())
+                    if (maxTimeLoaded() > currentTime())
                         m_readyState = MediaPlayer::HaveFutureData;
                     else
                         m_readyState = MediaPlayer::HaveCurrentData;
                     break;
                 }
 
-                if (itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
-                    m_networkState = (maxLoaded == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
+                if (itemStatus == MediaPlayerAVPlayerItemStatusPlaybackBufferFull)
+                    m_networkState = MediaPlayer::Idle;
+                else if (itemStatus == MediaPlayerAVPlayerItemStatusFailed)
+                    m_networkState = MediaPlayer::DecodeError;
+                else if (itemStatus != MediaPlayerAVPlayerItemStatusPlaybackBufferFull && itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
+                    m_networkState = (maxTimeLoaded() == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
             }
         }
     }
@@ -495,6 +458,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 +495,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 +504,7 @@ void MediaPlayerPrivateAVFoundation::acceleratedRenderingStateChanged()
 void MediaPlayerPrivateAVFoundation::metadataLoaded()
 {
     m_loadingMetadata = false;
+    tracksChanged();
     updateStates();
 }
 
@@ -575,7 +531,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 +546,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 +556,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 +567,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 +584,6 @@ void MediaPlayerPrivateAVFoundation::didEnd()
 
 void MediaPlayerPrivateAVFoundation::repaint()
 {
-    m_videoFrameHasDrawn = true;
     m_player->repaint();
 }
 
@@ -646,8 +601,38 @@ 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;
+
+    setDelayCallbacks(true);
+
+    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);
+
+        createAVPlayer();
+
+        checkPlayability();
+    }
+
+    // Don't force creation of the player item unless we already know that the asset is playable. If we aren't
+    // there yet, or if we already know it is not playable, creating it now won't help.
+    if (m_preload == MediaPlayer::Auto && m_assetIsPlayable)
+        createAVPlayerItem();
+
+    setDelayCallbacks(false);
 }
 
 void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay)
@@ -742,6 +727,7 @@ void MediaPlayerPrivateAVFoundation::dispatchNotification()
         break;
     case Notification::ItemTracksChanged:
         tracksChanged();
+        updateStates();
         break;
     case Notification::ItemStatusChanged:
         loadStateChanged();
@@ -756,6 +742,7 @@ void MediaPlayerPrivateAVFoundation::dispatchNotification()
         break;
     case Notification::ItemPresentationSizeChanged:
         sizeChanged();
+        updateStates();
         break;
     case Notification::ItemIsPlaybackLikelyToKeepUpChanged:
         loadStateChanged();
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 51fdb72..684e1f8 100644 (file)
@@ -88,12 +88,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();
@@ -107,12 +108,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;
@@ -127,6 +129,8 @@ private:
     RetainPtr<WebCoreAVFMovieObserver> m_objcObserver;
     RetainPtr<AVAssetImageGenerator> m_imageGenerator;
     id m_timeObserver;
+    bool m_videoFrameHasDrawn;
+    bool m_haveCheckedPlayability;
 };
 
 }
index 542560a..fbeab8e 100644 (file)
@@ -138,6 +138,8 @@ MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlay
     : MediaPlayerPrivateAVFoundation(player)
     , m_objcObserver(AdoptNS, [[WebCoreAVFMovieObserver alloc] initWithCallback:this])
     , m_timeObserver(0)
+    , m_videoFrameHasDrawn(false)
+    , m_haveCheckedPlayability(false)
 {
 }
 
@@ -234,77 +236,101 @@ 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)
 {
+    if (m_avAsset)
+        return;
+
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createAVAssetForURL(%p)", this);
+
     setDelayCallbacks(true);
 
-    if (!m_avAsset) {
-        NSURL *cocoaURL = KURL(ParsedURLString, url);
-        m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
-    }
-    
-    createAVPlayer();
+    NSURL *cocoaURL = KURL(ParsedURLString, url);
+    m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
+    m_haveCheckedPlayability = false;
+
+    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;
-    }
+
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createAVAssetForCacheResource(%p)", this);
+
+    // 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]);
+    m_haveCheckedPlayability = false;
 
-    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()];
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createAVPlayer(%p)", this);
 
-        for (NSString *keyName in itemKVOProperties())
-            [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
+    setDelayCallbacks(true);
 
-        [m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
-    }
+    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 * 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)];
+    }];
+
+    setDelayCallbacks(false);
+}
+
+void MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem()
+{
+    if (m_avPlayerItem)
+        return;
+
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem(%p)", this);
+
+    ASSERT(m_avPlayer);
+
+    setDelayCallbacks(true);
+
+    // Create the player item so we can load 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()
 {
+    if (m_haveCheckedPlayability)
+        return;
+    m_haveCheckedPlayability = true;
+
     LOG(Media, "MediaPlayerPrivateAVFoundationObjC::checkPlayability(%p)", this);
 
     [m_avAsset.get() loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"playable"] completionHandler:^{
@@ -314,7 +340,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 +349,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 +358,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 +411,25 @@ void MediaPlayerPrivateAVFoundationObjC::platformPause()
 
 float MediaPlayerPrivateAVFoundationObjC::platformDuration() const
 {
-    if (!metaDataAvailable() || !m_avPlayerItem)
-        return 0;
+    if (!m_avAsset)
+        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));
 
-    return duration;
+    if (CMTIME_IS_INDEFINITE(cmDuration))
+        return numeric_limits<float>::infinity();
+
+    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::duration(%p) - invalid duration, returning %.0f", this, invalidTime());
+    return invalidTime();
 }
 
 float MediaPlayerPrivateAVFoundationObjC::currentTime() const
@@ -535,10 +565,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];
@@ -547,6 +577,7 @@ MediaPlayerPrivateAVFoundation::AVAssetStatus MediaPlayerPrivateAVFoundationObjC
         
         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.
     }
@@ -587,7 +618,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 +693,54 @@ 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);
+
+    LOG(Media, "WebCoreAVFMovieObserver:tracksChanged(%p) - hasVideo = %s, hasAudio = %s, hasCaptions = %s", 
+        this, boolString(hasVideo()), boolString(hasAudio()), boolString(hasClosedCaptions()));
 
     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;
     }