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

Changeset 286907 in webkit


Ignore:
Timestamp:
Dec 11, 2021, 12:47:27 AM (5 years ago)
Author:
jer.noble@apple.com
Message:

[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:]):

Location:
trunk/Source/WebCore
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r286905 r286907  
     12021-12-11  Jer Noble  <jer.noble@apple.com>
     2
     3        [Cocoa] -[AVPlayerItem liveUpdateInterval] can hang the main thread for ~60ms
     4        https://bugs.webkit.org/show_bug.cgi?id=234131
     5
     6        Reviewed by Eric Carlson.
     7
     8        Direct property access of AVFoundation objects can take tens of milliseconds to return
     9        a value, even for simple properties. This impacts scrolling responsiveness.
     10
     11        -liveUpdateInterval is not KVO-observable, but only changes when -seekableTimeRanges does
     12        as well. Query and cache that property during KVO of -seekableTimeRanges.
     13
     14        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
     15        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
     16        (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesLastModifiedTime const):
     17        (WebCore::MediaPlayerPrivateAVFoundationObjC::liveUpdateInterval const):
     18        (WebCore::MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange):
     19        (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):
     20
    1212021-12-10  Simon Fraser  <simon.fraser@apple.com>
    222
  • trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h

    r285993 r286907  
    5454typedef struct __CVBuffer *CVPixelBufferRef;
    5555typedef NSString *AVMediaCharacteristic;
     56typedef double NSTimeInterval;
    5657
    5758namespace WebCore {
     
    102103    void playbackBufferFullDidChange(bool);
    103104    void loadedTimeRangesDidChange(RetainPtr<NSArray>&&);
    104     void seekableTimeRangesDidChange(RetainPtr<NSArray>&&);
     105    void seekableTimeRangesDidChange(RetainPtr<NSArray>&&, NSTimeInterval, NSTimeInterval);
    105106    void tracksDidChange(const RetainPtr<NSArray>&);
    106107    void hasEnabledAudioDidChange(bool);
     
    462463    bool m_isGatheringVideoFrameMetadata { false };
    463464    std::optional<VideoFrameMetadata> m_videoFrameMetadata;
     465    mutable std::optional<NSTimeInterval> m_cachedSeekableTimeRangesLastModifiedTime;
     466    mutable std::optional<NSTimeInterval> m_cachedLiveUpdateInterval;
    464467};
    465468
  • trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm

    r286772 r286907  
    103103#import <wtf/OSObjectPtr.h>
    104104#import <wtf/URL.h>
     105#import <wtf/WorkQueue.h>
    105106#import <wtf/cocoa/VectorCocoa.h>
    106107#import <wtf/text/CString.h>
     
    184185    WeakPtr<MediaPlayerPrivateAVFoundationObjC> m_player;
    185186    int m_delayCallbacks;
     187    RefPtr<WorkQueue> m_backgroundQueue;
    186188}
    187189-(id)initWithPlayer:(WeakPtr<MediaPlayerPrivateAVFoundationObjC>&&)callback;
     
    16781680{
    16791681#if PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(MACCATALYST)
    1680     return [m_avPlayerItem seekableTimeRangesLastModifiedTime];
     1682    if (!m_cachedSeekableTimeRangesLastModifiedTime)
     1683        m_cachedSeekableTimeRangesLastModifiedTime = [m_avPlayerItem seekableTimeRangesLastModifiedTime];
     1684    return *m_cachedSeekableTimeRangesLastModifiedTime;
    16811685#else
    16821686    return 0;
     
    16871691{
    16881692#if PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(MACCATALYST)
    1689     return [m_avPlayerItem liveUpdateInterval];
     1693    if (!m_cachedLiveUpdateInterval)
     1694        m_cachedLiveUpdateInterval = [m_avPlayerItem liveUpdateInterval];
     1695    return *m_cachedLiveUpdateInterval;
    16901696#else
    16911697    return 0;
     
    33283334}
    33293335
    3330 void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(RetainPtr<NSArray>&& seekableRanges)
     3336void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(RetainPtr<NSArray>&& seekableRanges, NSTimeInterval seekableTimeRangesLastModifiedTime, NSTimeInterval liveUpdateInterval)
    33313337{
    33323338    m_cachedSeekableRanges = WTFMove(seekableRanges);
     3339    m_cachedSeekableTimeRangesLastModifiedTime = seekableTimeRangesLastModifiedTime;
     3340    m_cachedLiveUpdateInterval = liveUpdateInterval;
    33333341
    33343342    seekableTimeRangesChanged();
     
    37803788        return nil;
    37813789    m_player = WTFMove(player);
     3790    m_backgroundQueue = WorkQueue::create("WebCoreAVFMovieObserver Background Queue");
    37823791    return self;
    37833792}
     
    38313840- (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
    38323841{
    3833     ensureOnMainThread([self, strongSelf = retainPtr(self), keyPath = retainPtr(keyPath), change = retainPtr(change), object = retainPtr(object), context]() mutable {
    3834         if (!m_player)
    3835             return;
    3836 
    3837         m_player->queueTaskOnEventLoop([player = m_player, keyPath = WTFMove(keyPath), change = WTFMove(change), object = WTFMove(object), context] {
    3838             if (!player)
     3842    auto queueTaskOnEventLoopWithPlayer = [self, strongSelf = retainPtr(self)] (Function<void(MediaPlayerPrivateAVFoundationObjC&)>&& function) mutable {
     3843        ensureOnMainThread([self, strongSelf = WTFMove(strongSelf), function = WTFMove(function)] () mutable {
     3844            if (!m_player)
    38393845                return;
    38403846
    3841             ScriptDisallowedScope::InMainThread scriptDisallowedScope;
    3842 
    3843             id newValue = [change valueForKey:NSKeyValueChangeNewKey];
    3844             bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
    3845             bool shouldLogValue = !willChange;
    3846 
    3847             if (context == MediaPlayerAVFoundationObservationContextAVPlayerLayer) {
    3848                 if ([keyPath isEqualToString:@"readyForDisplay"])
    3849                     player->firstFrameAvailableDidChange([newValue boolValue]);
    3850             }
    3851 
    3852             if (context == MediaPlayerAVFoundationObservationContextPlayerItemTrack) {
    3853                 if ([keyPath isEqualToString:@"enabled"])
    3854                     player->trackEnabledDidChange([newValue boolValue]);
    3855             }
    3856 
    3857             if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
    3858                 if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
    3859                     player->playbackLikelyToKeepUpWillChange();
    3860                 else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
    3861                     player->playbackBufferEmptyWillChange();
    3862                 else if ([keyPath isEqualToString:@"playbackBufferFull"])
    3863                     player->playbackBufferFullWillChange();
    3864             }
    3865 
    3866             if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
    3867                 // A value changed for an AVPlayerItem
    3868                 if ([keyPath isEqualToString:@"status"])
    3869                     player->playerItemStatusDidChange([newValue intValue]);
    3870                 else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
    3871                     player->playbackLikelyToKeepUpDidChange([newValue boolValue]);
    3872                 else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
    3873                     player->playbackBufferEmptyDidChange([newValue boolValue]);
    3874                 else if ([keyPath isEqualToString:@"playbackBufferFull"])
    3875                     player->playbackBufferFullDidChange([newValue boolValue]);
    3876                 else if ([keyPath isEqualToString:@"asset"]) {
    3877                     player->setAsset(RetainPtr<id>(newValue));
    3878                     shouldLogValue = false;
    3879                 } else if ([keyPath isEqualToString:@"loadedTimeRanges"])
    3880                     player->loadedTimeRangesDidChange(RetainPtr<NSArray>(newValue));
    3881                 else if ([keyPath isEqualToString:@"seekableTimeRanges"])
    3882                     player->seekableTimeRangesDidChange(RetainPtr<NSArray>(newValue));
    3883                 else if ([keyPath isEqualToString:@"tracks"]) {
    3884                     player->tracksDidChange(RetainPtr<NSArray>(newValue));
    3885                     shouldLogValue = false;
    3886                 } else if ([keyPath isEqualToString:@"hasEnabledAudio"])
    3887                     player->hasEnabledAudioDidChange([newValue boolValue]);
    3888                 else if ([keyPath isEqualToString:@"presentationSize"])
    3889                     player->presentationSizeDidChange(FloatSize([newValue sizeValue]));
    3890                 else if ([keyPath isEqualToString:@"duration"])
    3891                     player->durationDidChange(PAL::toMediaTime([newValue CMTimeValue]));
    3892                 else if ([keyPath isEqualToString:@"canPlayFastReverse"])
    3893                     player->canPlayFastReverseDidChange([newValue boolValue]);
    3894                 else if ([keyPath isEqualToString:@"canPlayFastForward"])
    3895                     player->canPlayFastForwardDidChange([newValue boolValue]);
    3896             }
    3897 
    3898             if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
    3899                 // A value changed for an AVPlayer.
    3900                 if ([keyPath isEqualToString:@"rate"])
    3901                     player->rateDidChange([newValue doubleValue]);
    3902                 else if ([keyPath isEqualToString:@"timeControlStatus"])
    3903                     player->timeControlStatusDidChange([newValue intValue]);
     3847            m_player->queueTaskOnEventLoop([player = m_player, function = WTFMove(function)] {
     3848                if (!player)
     3849                    return;
     3850
     3851                ScriptDisallowedScope::InMainThread scriptDisallowedScope;
     3852                function(*player);
     3853            });
     3854        });
     3855    };
     3856
     3857    if (context == MediaPlayerAVFoundationObservationContextPlayerItem && [keyPath isEqualToString:@"seekableTimeRanges"]) {
     3858        // -liveUpdateInterval and -seekableTimeRangesLastModifiedTime are not KVO observable, but may also hang when queried.
     3859        // Query their values here on a background thread, and pass to the main thread for caching.
     3860        id newValue = [change valueForKey:NSKeyValueChangeNewKey];
     3861        auto seekableTimeRanges = RetainPtr<NSArray> { newValue };
     3862
     3863        m_backgroundQueue->dispatch([seekableTimeRanges = WTFMove(seekableTimeRanges), playerItem = RetainPtr<AVPlayerItem> { object }, queueTaskOnEventLoopWithPlayer] () mutable {
     3864            auto seekableTimeRangesLastModifiedTime = [playerItem seekableTimeRangesLastModifiedTime];
     3865            auto liveUpdateInterval = [playerItem liveUpdateInterval];
     3866            queueTaskOnEventLoopWithPlayer([seekableTimeRanges = WTFMove(seekableTimeRanges), seekableTimeRangesLastModifiedTime, liveUpdateInterval] (auto& player) mutable {
     3867                player.seekableTimeRangesDidChange(WTFMove(seekableTimeRanges), seekableTimeRangesLastModifiedTime, liveUpdateInterval);
     3868            });
     3869        });
     3870    }
     3871
     3872    queueTaskOnEventLoopWithPlayer([keyPath = RetainPtr { keyPath }, change = RetainPtr { change }, object = RetainPtr { object }, context] (auto& player) mutable {
     3873        id newValue = [change valueForKey:NSKeyValueChangeNewKey];
     3874        bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
     3875        bool shouldLogValue = !willChange;
     3876
     3877        if (context == MediaPlayerAVFoundationObservationContextAVPlayerLayer) {
     3878            if ([keyPath isEqualToString:@"readyForDisplay"])
     3879                player.firstFrameAvailableDidChange([newValue boolValue]);
     3880        }
     3881
     3882        if (context == MediaPlayerAVFoundationObservationContextPlayerItemTrack) {
     3883            if ([keyPath isEqualToString:@"enabled"])
     3884                player.trackEnabledDidChange([newValue boolValue]);
     3885        }
     3886
     3887        if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
     3888            if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
     3889                player.playbackLikelyToKeepUpWillChange();
     3890            else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
     3891                player.playbackBufferEmptyWillChange();
     3892            else if ([keyPath isEqualToString:@"playbackBufferFull"])
     3893                player.playbackBufferFullWillChange();
     3894        }
     3895
     3896        if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
     3897            // A value changed for an AVPlayerItem
     3898            if ([keyPath isEqualToString:@"status"])
     3899                player.playerItemStatusDidChange([newValue intValue]);
     3900            else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
     3901                player.playbackLikelyToKeepUpDidChange([newValue boolValue]);
     3902            else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
     3903                player.playbackBufferEmptyDidChange([newValue boolValue]);
     3904            else if ([keyPath isEqualToString:@"playbackBufferFull"])
     3905                player.playbackBufferFullDidChange([newValue boolValue]);
     3906            else if ([keyPath isEqualToString:@"asset"]) {
     3907                player.setAsset(RetainPtr<id>(newValue));
     3908                shouldLogValue = false;
     3909            } else if ([keyPath isEqualToString:@"loadedTimeRanges"])
     3910                player.loadedTimeRangesDidChange(RetainPtr<NSArray>(newValue));
     3911            else if ([keyPath isEqualToString:@"tracks"]) {
     3912                player.tracksDidChange(RetainPtr<NSArray>(newValue));
     3913                shouldLogValue = false;
     3914            } else if ([keyPath isEqualToString:@"hasEnabledAudio"])
     3915                player.hasEnabledAudioDidChange([newValue boolValue]);
     3916            else if ([keyPath isEqualToString:@"presentationSize"])
     3917                player.presentationSizeDidChange(FloatSize([newValue sizeValue]));
     3918            else if ([keyPath isEqualToString:@"duration"])
     3919                player.durationDidChange(PAL::toMediaTime([newValue CMTimeValue]));
     3920            else if ([keyPath isEqualToString:@"canPlayFastReverse"])
     3921                player.canPlayFastReverseDidChange([newValue boolValue]);
     3922            else if ([keyPath isEqualToString:@"canPlayFastForward"])
     3923                player.canPlayFastForwardDidChange([newValue boolValue]);
     3924        }
     3925
     3926        if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
     3927            // A value changed for an AVPlayer.
     3928            if ([keyPath isEqualToString:@"rate"])
     3929                player.rateDidChange([newValue doubleValue]);
     3930            else if ([keyPath isEqualToString:@"timeControlStatus"])
     3931                player.timeControlStatusDidChange([newValue intValue]);
    39043932#if ENABLE(WIRELESS_PLAYBACK_TARGET)
    3905                 else if ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"allowsExternalPlayback"])
    3906                     player->playbackTargetIsWirelessDidChange();
     3933            else if ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"allowsExternalPlayback"])
     3934                player.playbackTargetIsWirelessDidChange();
    39073935#endif
    39083936#if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
    3909                 else if ([keyPath isEqualToString:@"outputObscuredDueToInsufficientExternalProtection"])
    3910                     player->outputObscuredDueToInsufficientExternalProtectionChanged([newValue boolValue]);
    3911 #endif
    3912             }
    3913 
    3914             if (player->logger().willLog(player->logChannel(), WTFLogLevel::Debug) && !([keyPath isEqualToString:@"loadedTimeRanges"] || [keyPath isEqualToString:@"seekableTimeRanges"])) {
    3915                 auto identifier = Logger::LogSiteIdentifier("MediaPlayerPrivateAVFoundation", "observeValueForKeyPath", player->logIdentifier());
    3916 
    3917                 if (shouldLogValue) {
    3918                     if ([keyPath isEqualToString:@"duration"])
    3919                         player->logger().debug(player->logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", PAL::toMediaTime([newValue CMTimeValue]));
    3920                     else {
    3921                         RetainPtr<NSString> valueString = adoptNS([[NSString alloc] initWithFormat:@"%@", newValue]);
    3922                         player->logger().debug(player->logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", [valueString UTF8String]);
    3923                     }
    3924                 } else
    3925                     player->logger().debug(player->logChannel(), identifier, willChange ? "will" : "did", " change '", [keyPath UTF8String], "'");
    3926             }
    3927         });
     3937            else if ([keyPath isEqualToString:@"outputObscuredDueToInsufficientExternalProtection"])
     3938                player.outputObscuredDueToInsufficientExternalProtectionChanged([newValue boolValue]);
     3939#endif
     3940        }
     3941
     3942        if (player.logger().willLog(player.logChannel(), WTFLogLevel::Debug) && !([keyPath isEqualToString:@"loadedTimeRanges"] || [keyPath isEqualToString:@"seekableTimeRanges"])) {
     3943            auto identifier = Logger::LogSiteIdentifier("MediaPlayerPrivateAVFoundation", "observeValueForKeyPath", player.logIdentifier());
     3944
     3945            if (shouldLogValue) {
     3946                if ([keyPath isEqualToString:@"duration"])
     3947                    player.logger().debug(player.logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", PAL::toMediaTime([newValue CMTimeValue]));
     3948                else {
     3949                    RetainPtr<NSString> valueString = adoptNS([[NSString alloc] initWithFormat:@"%@", [newValue description]]);
     3950                    player.logger().debug(player.logChannel(), identifier, "did change '", [keyPath UTF8String], "' to ", [valueString UTF8String]);
     3951                }
     3952            } else
     3953                player.logger().debug(player.logChannel(), identifier, willChange ? "will" : "did", " change '", [keyPath UTF8String], "'");
     3954        }
    39283955    });
    39293956}
Note: See TracChangeset for help on using the changeset viewer.