Title: [158745] trunk/Source/WebCore
Revision
158745
Author
[email protected]
Date
2013-11-06 08:15:16 -0800 (Wed, 06 Nov 2013)

Log Message

Playing many sounds with HTML5 Audio makes WebKit unresponsive
https://bugs.webkit.org/show_bug.cgi?id=116145

Reviewed by Eric Carlson.

Cache as much information as possible from AVPlayerItem to eliminate unneccesary
calls into AVFoundation.

Add WillChange/DidChange functions to handle the results of KVO notifications
from AVPlayerItem and AVPlayer:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformPlay):
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformPause):
(WebCore::MediaPlayerPrivateAVFoundationObjC::updateRate):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::tracksDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::durationDidChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::rateDidChange):
(WebCore::itemKVOProperties):
(-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):

Instruct the HTMLMediaElement to cache the currentTime value for 5 seconds:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
(WebCore::MediaPlayerPrivateAVFoundationObjC::maximumDurationToCacheMediaTime):

Add and initialize member variables to hold these cached values:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC):
(WebCore::MediaPlayerPrivateAVFoundationObjC::cancelLoad):

Add a new Notification type which can take (and call) a Function object:
* platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp:
(WebCore::MediaPlayerPrivateAVFoundation::dispatchNotification):
* platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h:
(WebCore::MediaPlayerPrivateAVFoundation::Notification::Notification):
(WebCore::MediaPlayerPrivateAVFoundation::Notification::function):

Implement queries in terms of the cached values of AVPlayerItem and AVPlayer
properties:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer):
(WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem):
(WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatus):
(WebCore::MediaPlayerPrivateAVFoundationObjC::rate):
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges):
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformMinTimeSeekable):
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable):
(WebCore::MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded):
(WebCore::MediaPlayerPrivateAVFoundationObjC::totalBytes):
(WebCore::MediaPlayerPrivateAVFoundationObjC::tracksChanged):
(WebCore::MediaPlayerPrivateAVFoundationObjC::updateAudioTracks):
(WebCore::MediaPlayerPrivateAVFoundationObjC::updateVideoTracks):
(WebCore::MediaPlayerPrivateAVFoundationObjC::sizeChanged):
(WebCore::MediaPlayerPrivateAVFoundationObjC::processLegacyClosedCaptionsTracks):

Invalidate the cached currentTime before calling scheduleTimeUpdate so that the
correct movieTime is saved in m_clockTimeAtLastUpdateEvent:
* html/HTMLMediaElement.cpp:
(HTMLMediaElement::setReadyState):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (158744 => 158745)


--- trunk/Source/WebCore/ChangeLog	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/ChangeLog	2013-11-06 16:15:16 UTC (rev 158745)
@@ -1,3 +1,77 @@
+2013-11-04  Jer Noble  <[email protected]>
+
+        Playing many sounds with HTML5 Audio makes WebKit unresponsive
+        https://bugs.webkit.org/show_bug.cgi?id=116145
+
+        Reviewed by Eric Carlson.
+
+        Cache as much information as possible from AVPlayerItem to eliminate unneccesary
+        calls into AVFoundation.
+
+        Add WillChange/DidChange functions to handle the results of KVO notifications
+        from AVPlayerItem and AVPlayer:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformPlay):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformPause):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::updateRate):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::tracksDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::durationDidChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::rateDidChange):
+        (WebCore::itemKVOProperties):
+        (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):
+
+        Instruct the HTMLMediaElement to cache the currentTime value for 5 seconds:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::maximumDurationToCacheMediaTime):
+
+        Add and initialize member variables to hold these cached values:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::cancelLoad):
+
+        Add a new Notification type which can take (and call) a Function object:
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp:
+        (WebCore::MediaPlayerPrivateAVFoundation::dispatchNotification):
+        * platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h:
+        (WebCore::MediaPlayerPrivateAVFoundation::Notification::Notification):
+        (WebCore::MediaPlayerPrivateAVFoundation::Notification::function):
+
+        Implement queries in terms of the cached values of AVPlayerItem and AVPlayer
+        properties:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayerItem):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::playerItemStatus):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::rate):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformMinTimeSeekable):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::totalBytes):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::tracksChanged):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::updateAudioTracks):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::updateVideoTracks):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::sizeChanged):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::processLegacyClosedCaptionsTracks):
+
+        Invalidate the cached currentTime before calling scheduleTimeUpdate so that the
+        correct movieTime is saved in m_clockTimeAtLastUpdateEvent:
+        * html/HTMLMediaElement.cpp:
+        (HTMLMediaElement::setReadyState):
+
 2013-10-24  Sergio Villar Senin  <[email protected]>
 
         [CSS Grid Layout] Add support for named grid areas

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (158744 => 158745)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2013-11-06 16:15:16 UTC (rev 158745)
@@ -1889,6 +1889,7 @@
     } else {
         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
             // 4.8.10.8
+            invalidateCachedTime();
             scheduleTimeupdateEvent(false);
             scheduleEvent(eventNames().waitingEvent);
         }

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp (158744 => 158745)


--- trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp	2013-11-06 16:15:16 UTC (rev 158745)
@@ -808,7 +808,7 @@
         
         if (!m_queuedNotifications.isEmpty() && !m_mainThreadCallPending)
             callOnMainThread(mainThreadCallback, this);
-        
+
         if (!notification.isValid())
             return;
     }
@@ -875,6 +875,9 @@
         m_inbandTrackConfigurationPending = false;
         configureInbandTracks();
         break;
+    case Notification::FunctionType:
+        notification.function()();
+        break;
 
     case Notification::None:
         ASSERT_NOT_REACHED();

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h (158744 => 158745)


--- trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.h	2013-11-06 16:15:16 UTC (rev 158745)
@@ -32,6 +32,7 @@
 #include "InbandTextTrackPrivateAVF.h"
 #include "MediaPlayerPrivate.h"
 #include "Timer.h"
+#include <wtf/Functional.h>
 #include <wtf/RetainPtr.h>
 
 namespace WebCore {
@@ -83,6 +84,7 @@
 #define DEFINE_TYPE_ENUM(type) type,
             FOR_EACH_MEDIAPLAYERPRIVATEAVFOUNDATION_NOTIFICATION_TYPE(DEFINE_TYPE_ENUM)
 #undef DEFINE_TYPE_ENUM
+            FunctionType,
         };
         
         Notification()
@@ -105,16 +107,26 @@
             , m_finished(finished)
         {
         }
+
+        Notification(WTF::Function<void ()> function)
+            : m_type(FunctionType)
+            , m_time(0)
+            , m_finished(false)
+            , m_function(function)
+        {
+        }
         
         Type type() { return m_type; }
         bool isValid() { return m_type != None; }
         double time() { return m_time; }
         bool finished() { return m_finished; }
+        Function<void ()>& function() { return m_function; }
         
     private:
         Type m_type;
         double m_time;
         bool m_finished;
+        Function<void ()> m_function;
     };
 
     void scheduleMainThreadNotification(Notification);

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h (158744 => 158745)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2013-11-06 16:15:16 UTC (rev 158745)
@@ -91,6 +91,21 @@
     static RetainPtr<AVAssetResourceLoadingRequest> takeRequestForPlayerAndKeyURI(MediaPlayer*, const String&);
 #endif
 
+    void playerItemStatusDidChange(int);
+    void playbackLikelyToKeepUpWillChange();
+    void playbackLikelyToKeepUpDidChange(bool);
+    void playbackBufferEmptyWillChange();
+    void playbackBufferEmptyDidChange(bool);
+    void playbackBufferFullWillChange();
+    void playbackBufferFullDidChange(bool);
+    void loadedTimeRangesDidChange(NSArray*);
+    void seekableTimeRangesDidChange(NSArray*);
+    void tracksDidChange(NSArray*);
+    void hasEnabledAudioDidChange(bool);
+    void presentationSizeDidChange(FloatSize);
+    void durationDidChange(double);
+    void rateDidChange(double);
+
 private:
     MediaPlayerPrivateAVFoundationObjC(MediaPlayer*);
 
@@ -116,6 +131,7 @@
     virtual PlatformLayer* platformLayer() const;
     virtual bool supportsAcceleratedRendering() const { return true; }
     virtual float mediaTimeForTimeValue(float) const;
+    virtual double maximumDurationToCacheMediaTime() const { return 5; }
 
     virtual void createAVPlayer();
     virtual void createAVPlayerItem();
@@ -227,6 +243,19 @@
 #endif
 
     InbandTextTrackPrivateAVF* m_currentTrack;
+
+    mutable RetainPtr<NSArray> m_cachedSeekableRanges;
+    mutable RetainPtr<NSArray> m_cachedLoadedRanges;
+    RetainPtr<NSArray> m_cachedTracks;
+    FloatSize m_cachedPresentationSize;
+    double m_cachedDuration;
+    double m_cachedRate;
+    unsigned m_pendingStatusChanges;
+    int m_cachedItemStatus;
+    bool m_cachedLikelyToKeepUp;
+    bool m_cachedBufferEmpty;
+    bool m_cachedBufferFull;
+    bool m_cachedHasEnabledAudio;
 };
 
 }

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm (158744 => 158745)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2013-11-06 16:15:16 UTC (rev 158745)
@@ -57,6 +57,7 @@
 #import <runtime/Uint32Array.h>
 #import <runtime/Uint8Array.h>
 #import <wtf/CurrentTime.h>
+#import <wtf/Functional.h>
 #import <wtf/text/CString.h>
 
 #import <AVFoundation/AVFoundation.h>
@@ -248,6 +249,14 @@
     , m_loaderDelegate(adoptNS([[WebCoreAVFLoaderDelegate alloc] initWithCallback:this]))
 #endif
     , m_currentTrack(0)
+    , m_cachedDuration(MediaPlayer::invalidTime())
+    , m_cachedRate(0)
+    , m_pendingStatusChanges(0)
+    , m_cachedItemStatus(MediaPlayerAVPlayerItemStatusDoesNotExist)
+    , m_cachedLikelyToKeepUp(false)
+    , m_cachedBufferEmpty(false)
+    , m_cachedBufferFull(false)
+    , m_cachedHasEnabledAudio(false)
 {
 #if ENABLE(ENCRYPTED_MEDIA_V2)
     playerToPrivateMap().set(player, this);
@@ -304,6 +313,17 @@
         [m_avPlayer.get() removeObserver:m_objcObserver.get() forKeyPath:@"rate"];
         m_avPlayer = nil;
     }
+
+    // Reset cached properties
+    m_pendingStatusChanges = 0;
+    m_cachedItemStatus = MediaPlayerAVPlayerItemStatusDoesNotExist;
+    m_cachedSeekableRanges = nullptr;
+    m_cachedLoadedRanges = nullptr;
+    m_cachedTracks = nullptr;
+    m_cachedHasEnabledAudio = false;
+    m_cachedPresentationSize = FloatSize();
+    m_cachedDuration = 0;
+
     setIgnoreLoadStateChanges(false);
 }
 
@@ -452,7 +472,7 @@
     setDelayCallbacks(true);
 
     m_avPlayer = adoptNS([[AVPlayer alloc] init]);
-    [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
+    [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
 
 #if HAVE(AVFOUNDATION_MEDIA_SELECTION_GROUP) && HAVE(AVFOUNDATION_LEGIBLE_OUTPUT_SUPPORT)
     [m_avPlayer.get() setAppliesMediaSelectionCriteriaAutomatically:YES];
@@ -478,8 +498,9 @@
 
     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(didEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:m_avPlayerItem.get()];
 
+    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionPrior;
     for (NSString *keyName in itemKVOProperties())
-        [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
+        [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:options context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
 
     if (m_avPlayer)
         [m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
@@ -525,16 +546,15 @@
     if (!m_avPlayerItem)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusDoesNotExist;
 
-    AVPlayerItemStatus status = [m_avPlayerItem.get() status];
-    if (status == AVPlayerItemStatusUnknown)
+    if (m_cachedItemStatus == AVPlayerItemStatusUnknown)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
-    if (status == AVPlayerItemStatusFailed)
+    if (m_cachedItemStatus == AVPlayerItemStatusFailed)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusFailed;
-    if ([m_avPlayerItem.get() isPlaybackLikelyToKeepUp])
+    if (m_cachedLikelyToKeepUp)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp;
-    if ([m_avPlayerItem.get() isPlaybackBufferFull])
+    if (m_cachedBufferFull)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferFull;
-    if ([m_avPlayerItem.get() isPlaybackBufferEmpty])
+    if (m_cachedBufferEmpty)
         return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty;
 
     return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusReadyToPlay;
@@ -570,6 +590,7 @@
         return;
 
     setDelayCallbacks(true);
+    m_cachedRate = requestedRate();
     [m_avPlayer.get() setRate:requestedRate()];
     setDelayCallbacks(false);
 }
@@ -581,6 +602,7 @@
         return;
 
     setDelayCallbacks(true);
+    m_cachedRate = 0;
     [m_avPlayer.get() setRate:nil];
     setDelayCallbacks(false);
 }
@@ -658,6 +680,7 @@
 void MediaPlayerPrivateAVFoundationObjC::updateRate()
 {
     setDelayCallbacks(true);
+    m_cachedRate = requestedRate();
     [m_avPlayer.get() setRate:requestedRate()];
     setDelayCallbacks(false);
 }
@@ -667,7 +690,7 @@
     if (!metaDataAvailable())
         return 0;
 
-    return [m_avPlayer.get() rate];
+    return m_cachedRate;
 }
 
 PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges() const
@@ -677,8 +700,7 @@
     if (!m_avPlayerItem)
         return timeRanges.release();
 
-    NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
-    for (NSValue *thisRangeValue in loadedRanges) {
+    for (NSValue *thisRangeValue in m_cachedLoadedRanges.get()) {
         CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
         if (CMTIMERANGE_IS_VALID(timeRange) && !CMTIMERANGE_IS_EMPTY(timeRange)) {
             float rangeStart = narrowPrecisionToFloat(CMTimeGetSeconds(timeRange.start));
@@ -691,13 +713,12 @@
 
 double MediaPlayerPrivateAVFoundationObjC::platformMinTimeSeekable() const
 {
-    NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
-    if (!seekableRanges || ![seekableRanges count])
+    if (!m_cachedSeekableRanges || ![m_cachedSeekableRanges count])
         return 0;
 
     double minTimeSeekable = std::numeric_limits<double>::infinity();
     bool hasValidRange = false;
-    for (NSValue *thisRangeValue in seekableRanges) {
+    for (NSValue *thisRangeValue in m_cachedSeekableRanges.get()) {
         CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
         if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
             continue;
@@ -712,12 +733,11 @@
 
 double MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable() const
 {
-    NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
-    if (!seekableRanges)
-        return 0;
+    if (!m_cachedSeekableRanges)
+        m_cachedSeekableRanges = [m_avPlayerItem seekableTimeRanges];
 
     double maxTimeSeekable = 0;
-    for (NSValue *thisRangeValue in seekableRanges) {
+    for (NSValue *thisRangeValue in m_cachedSeekableRanges.get()) {
         CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
         if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
             continue;
@@ -731,12 +751,19 @@
 
 float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded() const
 {
-    NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
-    if (!loadedRanges)
+#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 1080
+    // AVFoundation on Mountain Lion will occasionally not send a KVO notification
+    // when loadedTimeRanges changes when there is no video output. In that case
+    // update the cached value explicitly.
+    if (!hasLayerRenderer() && !hasContextRenderer())
+        m_cachedLoadedRanges = [m_avPlayerItem loadedTimeRanges];
+#endif
+
+    if (!m_cachedLoadedRanges)
         return 0;
 
     float maxTimeLoaded = 0;
-    for (NSValue *thisRangeValue in loadedRanges) {
+    for (NSValue *thisRangeValue in m_cachedLoadedRanges.get()) {
         CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
         if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
             continue;
@@ -755,9 +782,8 @@
         return 0;
 
     long long totalMediaSize = 0;
-    NSArray *tracks = [m_avAsset.get() tracks];
-    for (AVAssetTrack *thisTrack in tracks)
-        totalMediaSize += [thisTrack totalSampleDataLength];
+    for (AVPlayerItemTrack *thisTrack in m_cachedTracks.get())
+        totalMediaSize += [[thisTrack assetTrack] totalSampleDataLength];
 
     return static_cast<unsigned>(totalMediaSize);
 }
@@ -1032,8 +1058,7 @@
     } else {
         bool hasVideo = false;
         bool hasAudio = false;
-        NSArray *tracks = [m_avPlayerItem.get() tracks];
-        for (AVPlayerItemTrack *track in tracks) {
+        for (AVPlayerItemTrack *track in m_cachedTracks.get()) {
             if ([track isEnabled]) {
                 AVAssetTrack *assetTrack = [track assetTrack];
                 if ([[assetTrack mediaType] isEqualToString:AVMediaTypeVideo])
@@ -1131,12 +1156,12 @@
 
 void MediaPlayerPrivateAVFoundationObjC::updateAudioTracks()
 {
-    determineChangedTracksFromNewTracksAndOldItems([m_avPlayerItem tracks], AVMediaTypeAudio, m_audioTracks, &AudioTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeAudioTrack, &MediaPlayer::addAudioTrack);
+    determineChangedTracksFromNewTracksAndOldItems(m_cachedTracks.get(), AVMediaTypeAudio, m_audioTracks, &AudioTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeAudioTrack, &MediaPlayer::addAudioTrack);
 }
 
 void MediaPlayerPrivateAVFoundationObjC::updateVideoTracks()
 {
-    determineChangedTracksFromNewTracksAndOldItems([m_avPlayerItem tracks], AVMediaTypeVideo, m_videoTracks, &VideoTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeVideoTrack, &MediaPlayer::addVideoTrack);
+    determineChangedTracksFromNewTracksAndOldItems(m_cachedTracks.get(), AVMediaTypeVideo, m_videoTracks, &VideoTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeVideoTrack, &MediaPlayer::addVideoTrack);
 }
 #endif // ENABLE(VIDEO_TRACK)
 
@@ -1145,22 +1170,21 @@
     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 (m_avPlayerItem && ![tracks count]) {
-        setNaturalSize(IntSize([m_avPlayerItem.get() presentationSize]));
+    if (m_avPlayerItem && ![m_cachedTracks count]) {
+        setNaturalSize(IntSize(m_cachedPresentationSize));
         return;
     }
 
     // AVAsset's 'naturalSize' property only considers the movie's first video track, so we need to compute
     // the union of all visual track rects.
     CGRect trackUnionRect = CGRectZero;
-    for (AVAssetTrack *track in tracks) {
-        CGSize trackSize = [track naturalSize];
+    for (AVPlayerItemTrack *track in m_cachedTracks.get()) {
+        AVAssetTrack* assetTrack = [track assetTrack];
+        CGSize trackSize = [assetTrack naturalSize];
         CGRect trackRect = CGRectMake(0, 0, trackSize.width, trackSize.height);
-        trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [track preferredTransform]));
+        trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [assetTrack preferredTransform]));
     }
 
     // The movie is always displayed at 0,0 so move the track rect to the origin before using width and height.
@@ -1462,8 +1486,7 @@
 #endif
 
     Vector<RefPtr<InbandTextTrackPrivateAVF>> removedTextTracks = m_textTracks;
-    NSArray *tracks = [m_avPlayerItem.get() tracks];
-    for (AVPlayerItemTrack *playerItemTrack in tracks) {
+    for (AVPlayerItemTrack *playerItemTrack in m_cachedTracks.get()) {
 
         AVAssetTrack *assetTrack = [playerItemTrack assetTrack];
         if (![[assetTrack mediaType] isEqualToString:AVMediaTypeClosedCaption])
@@ -1627,6 +1650,110 @@
     return m_languageOfPrimaryAudioTrack;
 }
 
+void MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange(int status)
+{
+    m_cachedItemStatus = status;
+
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange()
+{
+    m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange(bool likelyToKeepUp)
+{
+    m_cachedLikelyToKeepUp = likelyToKeepUp;
+
+    ASSERT(m_pendingStatusChanges);
+    if (!--m_pendingStatusChanges)
+        updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange()
+{
+    m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange(bool bufferEmpty)
+{
+    m_cachedBufferEmpty = bufferEmpty;
+
+    ASSERT(m_pendingStatusChanges);
+    if (!--m_pendingStatusChanges)
+        updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange()
+{
+    m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange(bool bufferFull)
+{
+    m_cachedBufferFull = bufferFull;
+
+    ASSERT(m_pendingStatusChanges);
+    if (!--m_pendingStatusChanges)
+        updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(NSArray* seekableRanges)
+{
+    m_cachedSeekableRanges = seekableRanges;
+
+    seekableTimeRangesChanged();
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange(NSArray* loadedRanges)
+{
+    m_cachedLoadedRanges = loadedRanges;
+
+    loadedTimeRangesChanged();
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::tracksDidChange(NSArray* tracks)
+{
+    m_cachedTracks = tracks;
+
+    tracksChanged();
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange(bool hasEnabledAudio)
+{
+    m_cachedHasEnabledAudio = hasEnabledAudio;
+
+    tracksChanged();
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange(FloatSize size)
+{
+    m_cachedPresentationSize = size;
+
+    sizeChanged();
+    updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::durationDidChange(double duration)
+{
+    m_cachedDuration = duration;
+
+    invalidateCachedDuration();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::rateDidChange(double rate)
+{
+    m_cachedRate = rate;
+
+    updateStates();
+    rateChanged();
+}
+
 NSArray* assetMetadataKeyNames()
 {
     static NSArray* keys;
@@ -1717,46 +1844,63 @@
 
 - (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
 {
-    UNUSED_PARAM(change);
+    UNUSED_PARAM(object);
+    id newValue = [change valueForKey:NSKeyValueChangeNewKey];
 
     LOG(Media, "WebCoreAVFMovieObserver:observeValueForKeyPath(%p) - keyPath = %s", self, [keyPath UTF8String]);
 
     if (!m_callback)
         return;
 
-    if (context == MediaPlayerAVFoundationObservationContextPlayerItem) {
+    bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
+
+    WTF::Function<void ()> function;
+
+    if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
+        if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange, m_callback);
+        else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange, m_callback);
+        else if ([keyPath isEqualToString:@"playbackBufferFull"])
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange, m_callback);
+    }
+
+    if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
         // A value changed for an AVPlayerItem
         if ([keyPath isEqualToString:@"status"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemStatusChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange, m_callback, [newValue intValue]);
         else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackLikelyToKeepUpChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange, m_callback, [newValue boolValue]);
         else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferEmptyChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange, m_callback, [newValue boolValue]);
         else if ([keyPath isEqualToString:@"playbackBufferFull"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferFullChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange, m_callback, [newValue boolValue]);
         else if ([keyPath isEqualToString:@"asset"])
-            m_callback->setAsset([object asset]);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::setAsset, m_callback, newValue);
         else if ([keyPath isEqualToString:@"loadedTimeRanges"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemLoadedTimeRangesChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange, m_callback, newValue);
         else if ([keyPath isEqualToString:@"seekableTimeRanges"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemSeekableTimeRangesChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange, m_callback, newValue);
         else if ([keyPath isEqualToString:@"tracks"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::tracksDidChange, m_callback, newValue);
         else if ([keyPath isEqualToString:@"hasEnabledAudio"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange, m_callback, [newValue boolValue]);
         else if ([keyPath isEqualToString:@"presentationSize"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemPresentationSizeChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange, m_callback, FloatSize([newValue sizeValue]));
         else if ([keyPath isEqualToString:@"duration"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::DurationChanged);
-
-        return;
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::durationDidChange, m_callback, [newValue doubleValue]);
     }
 
-    if (context == MediaPlayerAVFoundationObservationContextPlayer) {
+    if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
         // A value changed for an AVPlayer.
         if ([keyPath isEqualToString:@"rate"])
-            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerRateChanged);
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::rateDidChange, m_callback, [newValue doubleValue]);
     }
+    
+    if (function.isNull())
+        return;
+
+    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification(function));
 }
 
 #if HAVE(AVFOUNDATION_MEDIA_SELECTION_GROUP)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to