From c1c1358baf8ac8bb4942d08e6ed6ae0f50ca9ccf Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 11:10:45 -0700 Subject: [PATCH] Fix video stalling by pausing the video after backgrounding the application --- AsyncDisplayKit/ASVideoNode.mm | 36 +++++++++++++---- AsyncDisplayKitTests/ASVideoNodeTests.m | 52 ++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 73951d52..8d30ef12 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -113,6 +113,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { [[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]; } } @@ -124,6 +126,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { [[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]; } } @@ -487,8 +491,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { if ([_delegate respondsToSelector:@selector(videoPlaybackDidFinish:)]) { [_delegate videoPlaybackDidFinish:self]; } - [_player seekToTime:CMTimeMakeWithSeconds(0, 1)]; - + [_player seekToTime:kCMTimeZero]; + if (_shouldAutorepeat) { [self play]; } else { @@ -500,19 +504,37 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { NSLog(@"Failed to play video"); - } - else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { - AVPlayerItem* item = (AVPlayerItem*)notification.object; - AVPlayerItemErrorLogEvent* logEvent = item.errorLog.events.lastObject; + } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { + AVPlayerItem *item = (AVPlayerItem *)notification.object; + AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); NSLog(@"Item is %@", item); - if (logEvent) + if (logEvent) { NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); + } } } +- (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 - Property Accessors for Tests diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 74a42452..f9f3acd9 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -113,7 +113,7 @@ _videoNode.interfaceState = ASInterfaceStateFetchData; [_videoNode play]; - [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{@"new" : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; + [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; XCTAssertFalse(((UIActivityIndicatorView *)_videoNode.spinner.view).isAnimating); } @@ -282,6 +282,56 @@ XCTAssertFalse(_videoNode.player.muted); } +- (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd +{ + _videoNode.asset = _firstAsset; + _videoNode.shouldAutorepeat = NO; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + XCTAssertTrue(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; + + XCTAssertFalse(_videoNode.isPlaying); + XCTAssertEqual(0, CMTimeGetSeconds(_videoNode.player.currentTime)); +} + +- (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd +{ + _videoNode.asset = _firstAsset; + _videoNode.shouldAutorepeat = YES; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; + + XCTAssertTrue(_videoNode.isPlaying); +} + +- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume +{ + _videoNode.asset = _firstAsset; + + [_videoNode didLoad]; + [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; + [_videoNode play]; + + XCTAssertTrue(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + + XCTAssertFalse(_videoNode.isPlaying); + + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + + XCTAssertTrue(_videoNode.isPlaying); +} + - (void)testSettingVideoGravityChangesPlaceholderContentMode { [_videoNode setPlaceholderImage:[[UIImage alloc] init]];