From e65d63a5024bf2cdbcfedc012f2a3e51c395fbf0 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 16:20:47 -0700 Subject: [PATCH 1/5] Simplified and improved video node's handling of state changes: - Simplified visibility state change handling. - Placeholder image is now updated after switching assets. - Improves memory usage by clearing the placeholder image in clearFetchedData. --- AsyncDisplayKit/ASVideoNode.mm | 317 +++++++++++------------- AsyncDisplayKitTests/ASVideoNodeTests.m | 27 ++ 2 files changed, 174 insertions(+), 170 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 1a345b5f3c..b4cd2e9dca 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -26,6 +26,9 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } } +static void *ASVideoNodeContext = &ASVideoNodeContext; +static NSString * const kStatus = @"status"; + @interface ASVideoNode () { ASDN::RecursiveMutex _videoLock; @@ -81,72 +84,62 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (ASDisplayNode *)constructPlayerNode { - ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - ASDN::MutexLocker l(_videoLock); - + ASVideoNode * __weak weakSelf = self; + + return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - playerLayer.player = _player; - playerLayer.videoGravity = [self gravity]; + playerLayer.player = weakSelf.player; + playerLayer.videoGravity = weakSelf.gravity; return playerLayer; }]; - - return playerNode; } -- (void)constructCurrentPlayerItemFromInitData +- (AVPlayerItem *)constructPlayerItem { ASDN::MutexLocker l(_videoLock); ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); - [self removePlayerItemObservers]; + + AVPlayerItem *playerItem = nil; - if (_asset) { - if ([_asset.tracks count]) { - _currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } else { - _currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; + if (_asset != nil) { + if (_asset.tracks.count > 0) { + playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; + } else if ([_asset isKindOfClass:[AVURLAsset class]]) { + playerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; } } - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - } + + return playerItem; } -- (void)removePlayerItemObservers +- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem { - ASDN::MutexLocker l(_videoLock); - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - } + [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; } -- (void)didLoad +- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem { - [super didLoad]; - - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - _playerNode = [self constructPlayerNode]; - [self insertSubnode:_playerNode atIndex:0]; - } else if (_asset) { - [self setPlaceholderImagefromAsset:_asset]; + @try { + [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; } + @catch (NSException * __unused exception) { + NSLog(@"Unnecessary KVO removal"); + } + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; + [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; + [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; + [notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)layout @@ -169,25 +162,43 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); } -- (void)setPlaceholderImagefromAsset:(AVAsset*)asset +- (void)generatePlaceholderImage +{ + ASVideoNode * __weak weakSelf = self; + AVAsset * __weak asset = self.asset; + + [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) { + ASPerformBlockOnMainThread(^{ + // Ensure the asset hasn't changed since the image request was made + if (ASAssetIsEqual(weakSelf.asset, asset)) { + [weakSelf setPlaceholderImage:image]; + } + }); + }]; +} + +- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler { ASPerformBlockOnBackgroundThread(^{ ASDN::MutexLocker l(_videoLock); - - AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; - imageGenerator.appliesPreferredTrackTransform = YES; - NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; - - [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { - - ASDN::MutexLocker l(_videoLock); - - // Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here - // http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error - if (image && _placeholderImageNode.image == nil) { - [self setPlaceholderImage:[UIImage imageWithCGImage:image]]; - } - }]; + + // Skip the asset image generation if we don't have any tracks available that are capable of supporting it + NSArray* visualAssetArray = [_asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]; + if (visualAssetArray.count == 0) { + completionHandler(nil); + return; + } + + AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset]; + previewImageGenerator.appliesPreferredTrackTransform = YES; + + [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] + completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { + if (error != nil && result != AVAssetImageGeneratorCancelled) { + NSLog(@"Asset preview image generation failed with error: %@", error); + } + completionHandler(image ? [UIImage imageWithCGImage:image] : nil); + }]; }); } @@ -195,7 +206,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); - if (_placeholderImageNode == nil) { + if (_placeholderImageNode == nil && image != nil) { _placeholderImageNode = [[ASImageNode alloc] init]; _placeholderImageNode.layerBacked = YES; _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); @@ -206,61 +217,30 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { dispatch_async(dispatch_get_main_queue(), ^{ ASDN::MutexLocker l(_videoLock); - [self insertSubnode:_placeholderImageNode atIndex:0]; - [self setNeedsLayout]; + if (_placeholderImageNode != nil) { + [self insertSubnode:_placeholderImageNode atIndex:0]; + [self setNeedsLayout]; + } }); } -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - ASDN::MutexLocker l(_videoLock); - - if (!nowVisible) { - if (wasVisible) { - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } - [(UIActivityIndicatorView *)_spinner.view stopAnimating]; - [_spinner removeFromSupernode]; - } - } else { - if (_shouldBePlaying) { - [self play]; - } - } -} - - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { ASDN::MutexLocker l(_videoLock); - - if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { - if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { - if ([self.subnodes containsObject:_spinner]) { - [_spinner removeFromSupernode]; - _spinner = nil; - } - + + if (object != _currentPlayerItem) { + return; + } + + if ([keyPath isEqualToString:kStatus]) { + if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + [_spinner removeFromSupernode]; + _spinner = nil; + // If we don't yet have a placeholder image update it now that we should have data available for it if (_placeholderImageNode.image == nil) { - if (_currentPlayerItem && - _currentPlayerItem.tracks.count > 0 && - _currentPlayerItem.tracks[0].assetTrack && - _currentPlayerItem.tracks[0].assetTrack.asset) { - _asset = _currentPlayerItem.tracks[0].assetTrack.asset; - [self setPlaceholderImagefromAsset:_asset]; - [self setNeedsLayout]; - } + [self generatePlaceholderImage]; } - - } else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { - } } } @@ -282,23 +262,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { [super fetchData]; - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in fetch data"); - } - { ASDN::MutexLocker l(_videoLock); - [self constructCurrentPlayerItemFromInitData]; - [_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; - - if (_player) { - [_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; - } else { - _player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; + + AVPlayerItem *playerItem = [self constructPlayerItem]; + self.currentItem = playerItem; + self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; + + if (_placeholderImageNode.image == nil) { + [self generatePlaceholderImage]; } } } @@ -309,8 +281,10 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); - ((AVPlayerLayer *)_playerNode.layer).player = nil; - _player = nil; + + self.player = nil; + self.currentItem = nil; + _placeholderImageNode.image = nil; } } @@ -320,24 +294,13 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { ASDN::MutexLocker l(_videoLock); - if (_shouldAutoplay && _playerNode.isNodeLoaded) { - [self play]; - } else if (_shouldAutoplay) { - _shouldBePlaying = YES; - } if (isVisible) { - if (_playerNode.isNodeLoaded) { - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - ((AVPlayerLayer *)_playerNode.layer).player = _player; - } - - if (_shouldBePlaying) { + if (_shouldBePlaying || _shouldAutoplay) { [self play]; } + } else if (_shouldBePlaying) { + [self pause]; + _shouldBePlaying = YES; } } @@ -373,11 +336,14 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { return; } + [self clearFetchedData]; + _asset = asset; - - // FIXME: Adopt -setNeedsFetchData when it is available - if (self.interfaceState & ASInterfaceStateFetchData) { - [self fetchData]; + + [self setNeedsDataFetch]; + + if (_shouldAutoplay) { + [self play]; } } @@ -430,20 +396,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (void)play { ASDN::MutexLocker l(_videoLock); - - if (!_spinner) { - _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.color = [UIColor whiteColor]; - - return spinnnerView; - }]; + + if (_player == nil) { + [self setNeedsDataFetch]; } - - if (!_playerNode) { + + if (_playerNode == nil) { _playerNode = [self constructPlayerNode]; - - if ([self.subnodes containsObject:_playButton]) { + + if (_playButton.supernode == self) { [self insertSubnode:_playerNode belowSubnode:_playButton]; } else { [self addSubnode:_playerNode]; @@ -456,9 +417,19 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { [UIView animateWithDuration:0.15 animations:^{ _playButton.alpha = 0.0; }]; - - if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [self addSubnode:_spinner]; + + if (![self ready]) { + if (!_spinner) { + _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; + spinnnerView.color = [UIColor whiteColor]; + + return spinnnerView; + }]; + + [self addSubnode:_spinner]; + } + [(UIActivityIndicatorView *)_spinner.view startAnimating]; } } @@ -540,7 +511,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } } -#pragma mark - Property Accessors for Tests +#pragma mark - Internal Properties - (ASDisplayNode *)spinner { @@ -563,7 +534,12 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (void)setCurrentItem:(AVPlayerItem *)currentItem { ASDN::MutexLocker l(_videoLock); + + [self removePlayerItemObservers:_currentPlayerItem]; + _currentPlayerItem = currentItem; + + [self addPlayerItemObservers:currentItem]; } - (ASDisplayNode *)playerNode @@ -582,6 +558,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); _player = player; + player.muted = _muted; + ((AVPlayerLayer *)_playerNode.layer).player = player; } - (BOOL)shouldBePlaying @@ -590,19 +568,18 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { return _shouldBePlaying; } +- (void)setShouldBePlaying:(BOOL)shouldBePlaying +{ + ASDN::MutexLocker l(_videoLock); + _shouldBePlaying = shouldBePlaying; +} + #pragma mark - Lifecycle - (void)dealloc { [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - [self removePlayerItemObservers]; - - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in dealloc"); - } + [self removePlayerItemObservers:_currentPlayerItem]; } @end diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index f9f3acd9d9..81224550fa 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -362,4 +362,31 @@ XCTAssertNotEqual(_videoNode, firstButton.supernode); } +- (void)testChangingAssetsChangesPlaceholderImage +{ + UIImage *firstImage = [[UIImage alloc] init]; + + _videoNode.asset = _firstAsset; + [_videoNode setPlaceholderImage:firstImage]; + XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image); + + _videoNode.asset = _secondAsset; + XCTAssertNotEqual(firstImage, _videoNode.placeholderImageNode.image); +} + +- (void)testClearingFetchedContentShouldClearAssetData +{ + _videoNode.asset = _firstAsset; + [_videoNode fetchData]; + [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + XCTAssertNotNil(_videoNode.player); + XCTAssertNotNil(_videoNode.currentItem); + XCTAssertNotNil(_videoNode.placeholderImageNode.image); + + [_videoNode clearFetchedData]; + XCTAssertNil(_videoNode.player); + XCTAssertNil(_videoNode.currentItem); + XCTAssertNil(_videoNode.placeholderImageNode.image); +} + @end From b5c3b15069ce9cfbd426d6e5dc758687cf3f6310 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 16:22:27 -0700 Subject: [PATCH 2/5] Observe playbackLikelyToKeepUp in order to handle player buffer filling and emptying --- AsyncDisplayKit/ASVideoNode.mm | 30 ++++++------------------- AsyncDisplayKitTests/ASVideoNodeTests.m | 14 +++++------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index b4cd2e9dca..5a5895f303 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -27,6 +27,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } static void *ASVideoNodeContext = &ASVideoNodeContext; +static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kStatus = @"status"; @interface ASVideoNode () @@ -116,19 +117,19 @@ static NSString * const kStatus = @"status"; - (void)addPlayerItemObservers:(AVPlayerItem *)playerItem { [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; + [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)removePlayerItemObservers:(AVPlayerItem *)playerItem { @try { [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; + [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext]; } @catch (NSException * __unused exception) { NSLog(@"Unnecessary KVO removal"); @@ -138,8 +139,6 @@ static NSString * const kStatus = @"status"; [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; - [notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)layout @@ -242,6 +241,10 @@ static NSString * const kStatus = @"status"; [self generatePlaceholderImage]; } } + } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true) { + [self play]; // autoresume after buffer catches up + } } } @@ -492,25 +495,6 @@ static NSString * const kStatus = @"status"; } } -- (void)willEnterForeground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self play]; - } -} - -- (void)didEnterBackground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } -} - #pragma mark - Internal Properties - (ASDisplayNode *)spinner diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 81224550fa..4d6b471c9a 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -29,7 +29,7 @@ @property (atomic, readonly) ASImageNode *placeholderImageNode; @property (atomic, readwrite) ASDisplayNode *playerNode; @property (atomic, readwrite) AVPlayer *player; -@property (atomic, readonly) BOOL shouldBePlaying; +@property (atomic, readwrite) BOOL shouldBePlaying; - (void)setPlaceholderImage:(UIImage *)image; @@ -313,21 +313,17 @@ XCTAssertTrue(_videoNode.isPlaying); } -- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume +- (void)testVideoResumedWhenBufferIsLikelyToKeepUp { _videoNode.asset = _firstAsset; - [_videoNode didLoad]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; - [_videoNode play]; - - XCTAssertTrue(_videoNode.isPlaying); - - [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + [_videoNode pause]; + _videoNode.shouldBePlaying = YES; XCTAssertFalse(_videoNode.isPlaying); - [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL]; XCTAssertTrue(_videoNode.isPlaying); } From a2b03d6e902fea8e5b0ac18150b14f5a1c8e243e Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 22:06:03 -0700 Subject: [PATCH 3/5] Change ASContentModeFromVideoGravity's default to UIViewContentModeScaleAspectFit --- AsyncDisplayKit/ASVideoNode.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 5a5895f303..c1fecba3c7 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -17,12 +17,12 @@ static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { } static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { - return UIViewContentModeScaleAspectFit; - } else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { + if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { return UIViewContentModeScaleAspectFill; - } else { + } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) { return UIViewContentModeScaleToFill; + } else { + return UIViewContentModeScaleAspectFit; } } From 6a4bc3fd5b808bb670627ac1afcf5d63ff4baa79 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 22:30:32 -0700 Subject: [PATCH 4/5] Rename setPlaceholderImage to setVideoPlaceholderImage in order to prevent super class setter conflict --- AsyncDisplayKit/ASVideoNode.mm | 4 ++-- AsyncDisplayKitTests/ASVideoNodeTests.m | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index c1fecba3c7..63b00830bc 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -170,7 +170,7 @@ static NSString * const kStatus = @"status"; ASPerformBlockOnMainThread(^{ // Ensure the asset hasn't changed since the image request was made if (ASAssetIsEqual(weakSelf.asset, asset)) { - [weakSelf setPlaceholderImage:image]; + [weakSelf setVideoPlaceholderImage:image]; } }); }]; @@ -201,7 +201,7 @@ static NSString * const kStatus = @"status"; }); } -- (void)setPlaceholderImage:(UIImage *)image +- (void)setVideoPlaceholderImage:(UIImage *)image { ASDN::MutexLocker l(_videoLock); diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 4d6b471c9a..3effa2431a 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -31,7 +31,7 @@ @property (atomic, readwrite) AVPlayer *player; @property (atomic, readwrite) BOOL shouldBePlaying; -- (void)setPlaceholderImage:(UIImage *)image; +- (void)setVideoPlaceholderImage:(UIImage *)image; @end @@ -330,7 +330,7 @@ - (void)testSettingVideoGravityChangesPlaceholderContentMode { - [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); _videoNode.gravity = AVLayerVideoGravityResize; @@ -363,7 +363,7 @@ UIImage *firstImage = [[UIImage alloc] init]; _videoNode.asset = _firstAsset; - [_videoNode setPlaceholderImage:firstImage]; + [_videoNode setVideoPlaceholderImage:firstImage]; XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image); _videoNode.asset = _secondAsset; @@ -374,7 +374,7 @@ { _videoNode.asset = _firstAsset; [_videoNode fetchData]; - [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; XCTAssertNotNil(_videoNode.player); XCTAssertNotNil(_videoNode.currentItem); XCTAssertNotNil(_videoNode.placeholderImageNode.image); From a49d620771d039a165e66937e4723d79223431af Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 23:08:31 -0700 Subject: [PATCH 5/5] Replace dispatch_async(dispatch_get_main_queue... with ASPerformBlockOnMainThread --- AsyncDisplayKit/ASVideoNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 63b00830bc..5170e2e19f 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -213,7 +213,7 @@ static NSString * const kStatus = @"status"; _placeholderImageNode.image = image; - dispatch_async(dispatch_get_main_queue(), ^{ + ASPerformBlockOnMainThread(^{ ASDN::MutexLocker l(_videoLock); if (_placeholderImageNode != nil) {