⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 286984 in webkit


Ignore:
Timestamp:
Dec 13, 2021, 2:50:09 PM (5 years ago)
Author:
Alan Coon
Message:

Cherry-pick r286907. rdar://problem/86307593

[Cocoa] -[AVPlayerItem liveUpdateInterval] can hang the main thread for ~60ms
https://bugs.webkit.org/show_bug.cgi?id=234131

Reviewed by Eric Carlson.

Direct property access of AVFoundation objects can take tens of milliseconds to return
a value, even for simple properties. This impacts scrolling responsiveness.

-liveUpdateInterval is not KVO-observable, but only changes when -seekableTimeRanges does
as well. Query and cache that property during KVO of -seekableTimeRanges.

  • platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
  • platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm: (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesLastModifiedTime const): (WebCore::MediaPlayerPrivateAVFoundationObjC::liveUpdateInterval const): (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange): (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):

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

Location:
branches/safari-612-branch/Source/WebCore
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/safari-612-branch/Source/WebCore/ChangeLog

    r286977 r286984  
     12021-12-13  Alan Coon  <alancoon@apple.com>
     2
     3        Cherry-pick r286907. rdar://problem/86307593
     4
     5    [Cocoa] -[AVPlayerItem liveUpdateInterval] can hang the main thread for ~60ms
     6    https://bugs.webkit.org/show_bug.cgi?id=234131
     7   
     8    Reviewed by Eric Carlson.
     9   
     10    Direct property access of AVFoundation objects can take tens of milliseconds to return
     11    a value, even for simple properties. This impacts scrolling responsiveness.
     12   
     13    -liveUpdateInterval is not KVO-observable, but only changes when -seekableTimeRanges does
     14    as well. Query and cache that property during KVO of -seekableTimeRanges.
     15   
     16    * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
     17    * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
     18    (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesLastModifiedTime const):
     19    (WebCore::MediaPlayerPrivateAVFoundationObjC::liveUpdateInterval const):
     20    (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange):
     21    (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):
     22   
     23    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@286907 268f45cc-cd09-0410-ab3c-d52691b4dbfc
     24
     25    2021-12-11  Jer Noble  <jer.noble@apple.com>
     26
     27            [Cocoa] -[AVPlayerItem liveUpdateInterval] can hang the main thread for ~60ms
     28            https://bugs.webkit.org/show_bug.cgi?id=234131
     29
     30            Reviewed by Eric Carlson.
     31
     32            Direct property access of AVFoundation objects can take tens of milliseconds to return
     33            a value, even for simple properties. This impacts scrolling responsiveness.
     34
     35            -liveUpdateInterval is not KVO-observable, but only changes when -seekableTimeRanges does
     36            as well. Query and cache that property during KVO of -seekableTimeRanges.
     37
     38            * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
     39            * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
     40            (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesLastModifiedTime const):
     41            (WebCore::MediaPlayerPrivateAVFoundationObjC::liveUpdateInterval const):
     42            (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange):
     43            (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):
     44
    1452021-12-13  Alan Coon  <alancoon@apple.com>
    246
  • branches/safari-612-branch/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h

    r286237 r286984  
    5151typedef struct CGImage *CGImageRef;
    5252typedef struct __CVBuffer *CVPixelBufferRef;
     53typedef double NSTimeInterval;
    5354
    5455namespace WebCore {
     
    9798    void playbackBufferFullDidChange(bool);
    9899    void loadedTimeRangesDidChange(RetainPtr<NSArray>&&);
    99     void seekableTimeRangesDidChange(RetainPtr<NSArray>&&);
     100    void seekableTimeRangesDidChange(RetainPtr<NSArray>&&, NSTimeInterval, NSTimeInterval);
    100101    void tracksDidChange(const RetainPtr<NSArray>&);
    101102    void hasEnabledAudioDidChange(bool);
     
    442443    bool m_waitForVideoOutputMediaDataWillChangeTimedOut { false };
    443444    bool m_haveBeenAskedToPaint { false };
     445    mutable std::optional<NSTimeInterval> m_cachedSeekableTimeRangesLastModifiedTime;
     446    mutable std::optional<NSTimeInterval> m_cachedLiveUpdateInterval;
    444447};
    445448
  • branches/safari-612-branch/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm

    r286237 r286984  
    9898#import <wtf/OSObjectPtr.h>
    9999#import <wtf/URL.h>
     100#import <wtf/WorkQueue.h>
    100101#import <wtf/cocoa/VectorCocoa.h>
    101102#import <wtf/text/CString.h>
     
    175176    WeakPtr<MediaPlayerPrivateAVFoundationObjC> m_player;
    176177    int m_delayCallbacks;
     178    RefPtr<WorkQueue> m_backgroundQueue;
    177179}
    178180-(id)initWithPlayer:(WeakPtr<MediaPlayerPrivateAVFoundationObjC>&&)callback;
     
    16091611{
    16101612#if PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(MACCATALYST)
    1611     return [m_avPlayerItem seekableTimeRangesLastModifiedTime];
     1613    if (!m_cachedSeekableTimeRangesLastModifiedTime)
     1614        m_cachedSeekableTimeRangesLastModifiedTime = [m_avPlayerItem seekableTimeRangesLastModifiedTime];
     1615    return *m_cachedSeekableTimeRangesLastModifiedTime;
    16121616#else
    16131617    return 0;
     
    16181622{
    16191623#if PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(MACCATALYST)
    1620     return [m_avPlayerItem liveUpdateInterval];
     1624    if (!m_cachedLiveUpdateInterval)
     1625        m_cachedLiveUpdateInterval = [m_avPlayerItem liveUpdateInterval];
     1626    return *m_cachedLiveUpdateInterval;
    16211627#else
    16221628    return 0;
     
    32093215}
    32103216
    3211 void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(RetainPtr<NSArray>&& seekableRanges)
     3217void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(RetainPtr<NSArray>&& seekableRanges, NSTimeInterval seekableTimeRangesLastModifiedTime, NSTimeInterval liveUpdateInterval)
    32123218{
    32133219    m_cachedSeekableRanges = WTFMove(seekableRanges);
     3220    m_cachedSeekableTimeRangesLastModifiedTime = seekableTimeRangesLastModifiedTime;
     3221    m_cachedLiveUpdateInterval = liveUpdateInterval;
    32143222
    32153223    seekableTimeRangesChanged();
     
    36593667        return nil;
    36603668    m_player = WTFMove(player);
     3669    m_backgroundQueue = WorkQueue::create("WebCoreAVFMovieObserver Background Queue");
    36613670    return self;
    36623671}
     
    36963705- (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
    36973706{
    3698     ensureOnMainThread([self, strongSelf = retainPtr(self), keyPath = retainPtr(keyPath), change = retainPtr(change), object = retainPtr(object), context]() mutable {
    3699         if (!m_player)
    3700             return;
    3701 
    3702         m_player->queueTaskOnEventLoop([player = m_player, keyPath = WTFMove(keyPath), change = WTFMove(change), object = WTFMove(object), context] {
    3703             if (!player)
     3707    auto queueTaskOnEventLoopWithPlayer = [self, strongSelf = retainPtr(self)] (Function<void(MediaPlayerPrivateAVFoundationObjC&)>&& function) mutable {
     3708        ensureOnMainThread([self, strongSelf = WTFMove(strongSelf), function = WTFMove(function)] () mutable {
     3709            if (!m_player)
    37043710                return;
    37053711
    3706             ScriptDisallowedScope::InMainThread scriptDisallowedScope;
    3707 
    3708             id newValue = [change valueForKey:NSKeyValueChangeNewKey];
    3709             bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
    3710             bool shouldLogValue = !willChange;
    3711 
    3712             if (context == MediaPlayerAVFoundationObservationContextAVPlayerLayer) {
    3713                 if ([keyPath isEqualToString:@"readyForDisplay"])
    3714                     player->firstFrameAvailableDidChange([newValue boolValue]);
    3715             }
    3716 
    3717             if (context == MediaPlayerAVFoundationObservationContextPlayerItemTrack) {
    3718                 if ([keyPath isEqualToString:@"enabled"])
    3719                     player->trackEnabledDidChange([newValue boolValue]);
    3720             }
    3721 
    3722             if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
    3723                 if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
    3724                     player->playbackLikelyToKeepUpWillChange();
    3725                 else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
    3726                     player->playbackBufferEmptyWillChange();
    3727                 else if ([keyPath isEqualToString:@"playbackBufferFull"])
    3728                     player->playbackBufferFullWillChange();
    3729             }
    3730 
    3731             if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
    3732                 // A value changed for an AVPlayerItem
    3733                 if ([keyPath isEqualToString:@"status"])
    3734                     player->playerItemStatusDidChange([newValue intValue]);
    3735                 else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
    3736                     player->playbackLikelyToKeepUpDidChange([newValue boolValue]);
    3737                 else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
    3738                     player->playbackBufferEmptyDidChange([newValue boolValue]);
    3739                 else if ([keyPath isEqualToString:@"playbackBufferFull"])
    3740                     player->playbackBufferFullDidChange([newValue boolValue]);
    3741                 else if ([keyPath isEqualToString:@"asset"]) {
    3742                     player->setAsset(RetainPtr<id>(newValue));
    3743                     shouldLogValue = false;
    3744                 } else if ([keyPath isEqualToString:@"loadedTimeRanges"])
    3745                     player->loadedTimeRangesDidChange(RetainPtr<NSArray>(newValue));
    3746                 else if ([keyPath isEqualToString:@"seekableTimeRanges"])
    3747                     player->seekableTimeRangesDidChange(RetainPtr<NSArray>(newValue));
    3748                 else if ([keyPath isEqualToString:@"tracks"]) {
    3749                     player->tracksDidChange(RetainPtr<NSArray>(newValue));
    3750                     shouldLogValue = false;
    3751                 } else if ([keyPath isEqualToString:@"hasEnabledAudio"])
    3752                     player->hasEnabledAudioDidChange([newValue boolValue]);
    3753                 else if ([keyPath isEqualToString:@"presentationSize"])
    3754                     player->presentationSizeDidChange(FloatSize([newValue sizeValue]));
    3755                 else if ([keyPath isEqualToString:@"duration"])
    3756                     player->durationDidChange(PAL::toMediaTime([newValue CMTimeValue]));
    3757                 else if ([keyPath isEqualToString:@"canPlayFastReverse"])
    3758                     player->canPlayFastReverseDidChange([newValue boolValue]);
    3759                 else if ([keyPath isEqualToString:@"canPlayFastForward"])
    3760                     player->canPlayFastForwardDidChange([newValue boolValue]);
    3761             }
    3762 
    3763             if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
    3764                 // A value changed for an AVPlayer.
    3765                 if ([keyPath isEqualToString:@"rate"])
    3766                     player->rateDidChange([newValue doubleValue]);
    3767                 else if ([keyPath isEqualToString:@"timeControlStatus"])
    3768                     player->timeControlStatusDidChange([newValue intValue]);
     3712            m_player->queueTaskOnEventLoop([player = m_player, function = WTFMove(function)] {
     3713                if (!player)
     3714                    return;
     3715
     3716                ScriptDisallowedScope::InMainThread scriptDisallowedScope;
     3717                function(*player);
     3718            });
     3719        });
     3720    };
     3721
     3722    if (context == MediaPlayerAVFoundationObservationContextPlayerItem && [keyPath isEqualToString:@"seekableTimeRanges"]) {
     3723        // -liveUpdateInterval and -seekableTimeRangesLastModifiedTime are not KVO observable, but may also hang when queried.
     3724        // Query their values here on a background thread, and pass to the main thread for caching.
     3725        id newValue = [change valueForKey:NSKeyValueChangeNewKey];
     3726        auto seekableTimeRanges = RetainPtr<NSArray> { newValue };
     3727
     3728        m_backgroundQueue->dispatch([seekableTimeRanges = WTFMove(seekableTimeRanges), playerItem = RetainPtr<AVPlayerItem> { object }, queueTaskOnEventLoopWithPlayer] () mutable {
     3729            auto seekableTimeRangesLastModifiedTime = [playerItem seekableTimeRangesLastModifiedTime];
     3730            auto liveUpdateInterval = [playerItem liveUpdateInterval];
     3731            queueTaskOnEventLoopWithPlayer([seekableTimeRanges = WTFMove(seekableTimeRanges), seekableTimeRangesLastModifiedTime, liveUpdateInterval] (auto& player) mutable {
     3732                player.seekableTimeRangesDidChange(WTFMove(seekableTimeRanges), seekableTimeRangesLastModifiedTime, liveUpdateInterval);
     3733            });
     3734        });
     3735    }
     3736
     3737    queueTaskOnEventLoopWithPlayer([keyPath = RetainPtr { keyPath }, change = RetainPtr { change }, object = RetainPtr { object }, context] (auto& player) mutable {
     3738        id newValue = [change valueForKey:NSKeyValueChangeNewKey];
     3739        bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
     3740        bool shouldLogValue = !willChange;
     3741
     3742        if (context == MediaPlayerAVFoundationObservationContextAVPlayerLayer) {
     3743            if ([keyPath isEqualToString:@"readyForDisplay"])
     3744                player.firstFrameAvailableDidChange([newValue boolValue]);
     3745        }
     3746
     3747        if (context == MediaPlayerAVFoundationObservationContextPlayerItemTrack) {
     3748            if ([keyPath isEqualToString:@"enabled"])
     3749                player.trackEnabledDidChange([newValue boolValue]);
     3750        }
     3751
     3752        if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
     3753            if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
     3754                player.playbackLikelyToKeepUpWillChange();
     3755            else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
     3756                player.playbackBufferEmptyWillChange();
     3757            else if ([keyPath isEqualToString:@"playbackBufferFull"])
     3758                player.playbackBufferFullWillChange();
     3759        }
     3760
     3761        if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
     3762            // A value changed for an AVPlayerItem
     3763            if ([keyPath isEqualToString:@"status"])
     3764                player.playerItemStatusDidChange([newValue intValue]);
     3765            else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
     3766                player.playbackLikelyToKeepUpDidChange([newValue boolValue]);
     3767            else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
     3768                player.playbackBufferEmptyDidChange([newValue boolValue]);
     3769            else if ([keyPath isEqualToString:@"playbackBufferFull"])
     3770                player.playbackBufferFullDidChange([newValue boolValue]);
     3771            else if ([keyPath isEqualToString:@"asset"]) {
     3772                player.setAsset(RetainPtr<id>(newValue));
     3773                shouldLogValue = false;
     3774            } else if ([keyPath isEqualToString:@"loadedTimeRanges"])
     3775                player.loadedTimeRangesDidChange(RetainPtr<NSArray>(newValue));
     3776            else if ([keyPath isEqualToString:@"tracks"]) {
     3777                player.tracksDidChange(RetainPtr<NSArray>(newValue));
     3778                shouldLogValue = false;
     3779            } else if ([keyPath isEqualToString:@"hasEnabledAudio"])
     3780                player.hasEnabledAudioDidChange([newValue boolValue]);
     3781            else if ([keyPath isEqualToString:@"presentationSize"])
     3782                player.presentationSizeDidChange(FloatSize([newValue sizeValue]));
     3783            else if ([keyPath isEqualToString:@"duration"])
     3784                player.durationDidChange(PAL::toMediaTime([newValue CMTimeValue]));
     3785            else if ([keyPath isEqualToString:@"canPlayFastReverse"])
     3786                player.canPlayFastReverseDidChange([newValue boolValue]);
     3787            else if ([keyPath isEqualToString:@"canPlayFastForward"])
     3788                player.canPlayFastForwardDidChange([newValue boolValue]);
     3789        }
     3790
     3791        if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
     3792            // A value changed for an AVPlayer.
     3793            if ([keyPath isEqualToString:@"rate"])
     3794                player.rateDidChange([newValue doubleValue]);
     3795            else if ([keyPath isEqualToString:@"timeControlStatus"])
     3796                player.timeControlStatusDidChange([newValue intValue]);
    37693797#if ENABLE(WIRELESS_PLAYBACK_TARGET)
    3770                 else if ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"allowsExternalPlayback"])
    3771                     player->playbackTargetIsWirelessDidChange();
     3798            else if ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"allowsExternalPlayback"])
     3799                player.playbackTargetIsWirelessDidChange();
    37723800#endif
    37733801#if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
    3774                 else if ([keyPath isEqualToString:@"outputObscuredDueToInsufficientExternalProtection"])
    3775                     player->outputObscuredDueToInsufficientExternalProtectionChanged([newValue boolValue]);
    3776 #endif
    3777             }
    3778 
    3779             if (player->logger().willLog(player->logChannel(), WTFLogLevel::Debug) && !([keyPath isEqualToString:@"loadedTimeRanges"] || [keyPath isEqualToString:@"seekableTimeRanges"])) {
    3780                 auto identifier = Logger::LogSiteIdentifier("MediaPlayerPrivateAVFoundation", "observeValueForKeyPath", player->logIdentifier());
    3781 
    3782                 if (shouldLogValue) {
    3783                     if ([keyPath isEqualToString:@"duration"])
    3784                         player->logger().debug(player->logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", PAL::toMediaTime([newValue CMTimeValue]));
    3785                     else {
    3786                         RetainPtr<NSString> valueString = adoptNS([[NSString alloc] initWithFormat:@"%@", newValue]);
    3787                         player->logger().debug(player->logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", [valueString.get() UTF8String]);
    3788                     }
    3789                 } else
    3790                     player->logger().debug(player->logChannel(), identifier, willChange ? "will" : "did", " change '", [keyPath UTF8String], "'");
    3791             }
    3792         });
     3802            else if ([keyPath isEqualToString:@"outputObscuredDueToInsufficientExternalProtection"])
     3803                player.outputObscuredDueToInsufficientExternalProtectionChanged([newValue boolValue]);
     3804#endif
     3805        }
     3806
     3807        if (player.logger().willLog(player.logChannel(), WTFLogLevel::Debug) && !([keyPath isEqualToString:@"loadedTimeRanges"] || [keyPath isEqualToString:@"seekableTimeRanges"])) {
     3808            auto identifier = Logger::LogSiteIdentifier("MediaPlayerPrivateAVFoundation", "observeValueForKeyPath", player.logIdentifier());
     3809
     3810            if (shouldLogValue) {
     3811                if ([keyPath isEqualToString:@"duration"])
     3812                    player.logger().debug(player.logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", PAL::toMediaTime([newValue CMTimeValue]));
     3813                else {
     3814                    RetainPtr<NSString> valueString = adoptNS([[NSString alloc] initWithFormat:@"%@", [newValue description]]);
     3815                    player.logger().debug(player.logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", [valueString UTF8String]);
     3816                }
     3817            } else
     3818                player.logger().debug(player.logChannel(), identifier, willChange ? "will" : "did", " change '", [keyPath UTF8String], "'");
     3819        }
    37933820    });
    37943821}
Note: See TracChangeset for help on using the changeset viewer.