diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index e6f44993ea..4fd05f06ac 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -37,6 +37,7 @@ static void *ASVideoNodeContext = &ASVideoNodeContext; static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; static NSString * const kStatus = @"status"; +static NSString * const kRate = @"rate"; @interface ASVideoNode () { @@ -208,6 +209,25 @@ static NSString * const kStatus = @"status"; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; } +- (void)addPlayerObservers:(AVPlayer *)player +{ + if (player == nil) { + return; + } + + [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; +} + +- (void) removePlayerObservers:(AVPlayer *)player +{ + @try { + [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext]; + } + @catch (NSException * __unused exception) { + NSLog(@"Unnecessary KVO removal"); + } +} + - (void)layout { [super layout]; @@ -267,6 +287,7 @@ static NSString * const kStatus = @"status"; AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; previewImageGenerator.appliesPreferredTrackTransform = YES; + previewImageGenerator.videoComposition = _videoComposition; [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { @@ -291,29 +312,47 @@ static NSString * const kStatus = @"status"; { ASDN::MutexLocker l(__instanceLock__); - if (object != _currentPlayerItem) { - return; - } - - if ([keyPath isEqualToString:kStatus]) { - if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - self.playerState = ASVideoNodePlayerStateReadyToPlay; - // If we don't yet have a placeholder image update it now that we should have data available for it - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; + if (object == _currentPlayerItem) { + if ([keyPath isEqualToString:kStatus]) { + if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + if (self.playerState != ASVideoNodePlayerStatePlaying) { + self.playerState = ASVideoNodePlayerStateReadyToPlay; + } + // If we don't yet have a placeholder image update it now that we should have data available for it + if (self.image == nil && self.URL == nil) { + [self generatePlaceholderImage]; + } + } + } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue]; + if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) { + return; + } + if (!likelyToKeepUp) { + self.playerState = ASVideoNodePlayerStateLoading; + } else if (self.playerState != ASVideoNodePlayerStateFinished) { + self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; + } + if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { + if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { + [_delegate videoNodeDidRecoverFromStall:self]; + } + [self play]; // autoresume after buffer catches up + } + } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { + if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { + self.playerState = ASVideoNodePlayerStateLoading; } } - } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [_delegate videoNodeDidRecoverFromStall:self]; + } else if (object == _player) { + if ([keyPath isEqualToString:kRate]) { + if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { + if (self.playerState == ASVideoNodePlayerStatePlaying) { + self.playerState = ASVideoNodePlayerStatePaused; + } + } else { + self.playerState = ASVideoNodePlayerStatePlaying; } - [self play]; // autoresume after buffer catches up - } - } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { - self.playerState = ASVideoNodePlayerStateLoading; } } } @@ -345,14 +384,11 @@ static NSString * const kStatus = @"status"; ASDN::MutexLocker l(__instanceLock__); AVAsset *asset = self.asset; // Return immediately if the asset is nil; - if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) { + if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { return; } - // FIXME: Nothing appears to prevent this method from sending the delegate notification / calling load on the asset - // multiple times, even after the asset is fully loaded and ready to play. There should probably be a playerState - // for NotLoaded or such, besides Unknown, so this can be easily checked before proceeding. - self.playerState = ASVideoNodePlayerStateInitialLoading; + self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { [_delegate videoNodeDidStartInitialLoading:self]; } @@ -396,6 +432,7 @@ static NSString * const kStatus = @"status"; self.player = nil; self.currentItem = nil; + self.playerState = ASVideoNodePlayerStateUnknown; } } @@ -603,12 +640,6 @@ static NSString * const kStatus = @"status"; [_player play]; _shouldBePlaying = YES; - - if (![self ready]) { - self.playerState = ASVideoNodePlayerStateLoading; - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } } - (BOOL)ready @@ -622,7 +653,6 @@ static NSString * const kStatus = @"status"; if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { return; } - self.playerState = ASVideoNodePlayerStatePaused; [_player pause]; _shouldBePlaying = NO; } @@ -731,9 +761,16 @@ static NSString * const kStatus = @"status"; - (void)setPlayer:(AVPlayer *)player { ASDN::MutexLocker l(__instanceLock__); + + [self removePlayerObservers:_player]; + _player = player; player.muted = _muted; ((AVPlayerLayer *)_playerNode.layer).player = player; + + if (player != nil) { + [self addPlayerObservers:player]; + } } - (BOOL)shouldBePlaying @@ -755,6 +792,7 @@ static NSString * const kStatus = @"status"; [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; [self removePlayerItemObservers:_currentPlayerItem]; + [self removePlayerObservers:_player]; } @end