diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 2f2bc9500c..1801a03527 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -212,11 +212,6 @@ return stack; } -- (void)layoutDidFinish -{ - -} - - (void)layout { [super layout]; diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index d4200ade25..9147872b76 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -7,12 +7,13 @@ typedef NS_ENUM(NSUInteger, ASVideoGravity) { ASVideoGravityResize }; -@interface ASVideoNode : ASDisplayNode<_ASDisplayLayerDelegate> +@interface ASVideoNode : ASControlNode<_ASDisplayLayerDelegate> @property (atomic, strong, readwrite) AVAsset *asset; -@property (nonatomic, assign, readwrite) BOOL shouldAutoPlay; +@property (nonatomic, assign, readwrite) BOOL shouldAutoplay; @property (atomic) ASVideoGravity gravity; @property (atomic) BOOL autorepeat; @property (atomic) ASButtonNode *playButton; +@property (atomic) AVPlayer *player; - (void)play; - (void)pause; @@ -22,9 +23,9 @@ typedef NS_ENUM(NSUInteger, ASVideoGravity) { @protocol ASVideoNodeDelegate @end -@protocol ASVideoNodeDatasource +@protocol ASVideoNodeDataSource @optional -- (ASDisplayNode *)playButtonForVideoNode:(ASVideoNode *) videoNode; +- (ASButtonNode *)playButtonForVideoNode:(ASVideoNode *)videoNode; - (UIImage *)thumbnailForVideoNode:(ASVideoNode *) videoNode; - (NSURL *)thumbnailURLForVideoNode:(ASVideoNode *)videoNode; @end diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index a0555a6c09..148aa27ae8 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -2,10 +2,11 @@ #import "ASVideoNode.h" -@interface ASVideoNode () { +@interface ASVideoNode () +{ ASDN::RecursiveMutex _lock; - __weak id _datasource; + __weak id _dataSource; BOOL _shouldBePlaying; AVAsset *_asset; @@ -23,7 +24,8 @@ @implementation ASVideoNode -- (instancetype)init { +- (instancetype)init +{ if (!(self = [super init])) { return nil; } _playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ @@ -35,11 +37,14 @@ self.gravity = ASVideoGravityResizeAspect; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restartVideo:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; + [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; return self; } +// FIXME: Adopt interfaceStateDidChange API - (void)setInterfaceState:(ASInterfaceState)newState { [super setInterfaceState:newState]; @@ -51,9 +56,6 @@ if (_shouldBePlaying) { [self play]; } - if (_spinner) { - [self addSubnode:_spinner]; - } } } @@ -71,9 +73,9 @@ } } -- (void)restartVideo:(NSNotification *)notification +- (void)didPlayToEnd:(NSNotification *)notification { - if ( [[[notification object] asset] isEqual:_asset]) { + if (ASObjectIsEqual([[notification object] asset], _asset)) { [[((AVPlayerLayer *)_playerNode.layer) player] seekToTime:CMTimeMakeWithSeconds(0, 1)]; if (_autorepeat) { @@ -84,18 +86,12 @@ } } -- (void)layoutDidFinish +- (void)layout { + [super layout]; _playerNode.frame = self.bounds; } -- (void)didLoad { - [super didLoad]; - - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped)]; - [self.view addGestureRecognizer:tap]; -} - - (void)tapped { if (_shouldBePlaying) { @@ -134,7 +130,7 @@ } } - if (_shouldAutoPlay) { + if (_shouldAutoplay) { [self play]; } } @@ -189,7 +185,8 @@ return _asset; } -- (void)setGravity:(ASVideoGravity)gravity { +- (void)setGravity:(ASVideoGravity)gravity +{ ASDN::MutexLocker l(_lock); switch (gravity) { @@ -208,28 +205,25 @@ } } -- (ASVideoGravity)gravity; +- (ASVideoGravity)gravity { ASDN::MutexLocker l(_lock); - if ([((AVPlayerLayer *)_playerNode.layer).contentsGravity isEqualToString:AVLayerVideoGravityResize]) { + if (ASObjectIsEqual(((AVPlayerLayer *)_playerNode.layer).contentsGravity, AVLayerVideoGravityResize)) { return ASVideoGravityResize; } - if ([((AVPlayerLayer *)_playerNode.layer).contentsGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { + if (ASObjectIsEqual(((AVPlayerLayer *)_playerNode.layer).contentsGravity, AVLayerVideoGravityResizeAspectFill)) { return ASVideoGravityResizeAspectFill; } return ASVideoGravityResizeAspect; } -- (void)play; +- (void)play { ASDN::MutexLocker l(_lock); - [[((AVPlayerLayer *)_playerNode.layer) player] play]; - _shouldBePlaying = YES; - _playButton.alpha = 0.0; - if ([self ready] && ![self.subnodes containsObject:_spinner]) { + if (!_spinner) { _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] initWithFrame:_playButton.frame]; spinnnerView.color = [UIColor whiteColor]; @@ -237,21 +231,30 @@ return spinnnerView; }]; - + } + + if (![self ready]) { [self addSubnode:_spinner]; } + + [[((AVPlayerLayer *)_playerNode.layer) player] play]; + _shouldBePlaying = YES; + _playButton.alpha = 0.0; +// if ([self ready] && ![self.subnodes containsObject:_spinner]) { +// } } -- (BOOL)ready; +- (BOOL)ready { - return [((AVPlayerLayer *)_playerNode.layer) player].currentItem.status != AVPlayerItemStatusReadyToPlay; + return [((AVPlayerLayer *)_playerNode.layer) player].currentItem.status == AVPlayerItemStatusReadyToPlay; } -- (void)pause; +- (void)pause { ASDN::MutexLocker l(_lock); [[((AVPlayerLayer *)_playerNode.layer) player] pause]; + [((UIActivityIndicatorView *)_spinner.view) stopAnimating]; _shouldBePlaying = NO; _playButton.alpha = 1.0; } @@ -261,6 +264,16 @@ return _currentItem; } +- (ASDisplayNode *)spinner +{ + return _spinner; +} + +- (AVPlayerItem *)curentItem +{ + return _currentItem; +} + - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 8dbb7e2e5e..709b353b47 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -16,11 +16,17 @@ @interface ASVideoNode () @property (atomic, readonly) AVPlayerItem *currentItem; @property (atomic) ASInterfaceState interfaceState; +@property (atomic) ASDisplayNode *spinner; +@end + +@interface AVPlayerItem () +@property (nonatomic) AVPlayerItemStatus status; @end @implementation ASVideoNodeTests -- (void)testVideoNodeReplacesAVPlayerItemWhenNewURLIsSet { +- (void)testVideoNodeReplacesAVPlayerItemWhenNewURLIsSet +{ ASVideoNode *videoNode = [[ASVideoNode alloc] init]; videoNode.interfaceState = ASInterfaceStateFetchData; videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; @@ -33,7 +39,8 @@ XCTAssertNotEqualObjects(item, secondItem); } -- (void)testVideoNodeDoesNotReplaceAVPlayerItemWhenSameURLIsSet { +- (void)testVideoNodeDoesNotReplaceAVPlayerItemWhenSameURLIsSet +{ ASVideoNode *videoNode = [[ASVideoNode alloc] init]; videoNode.interfaceState = ASInterfaceStateFetchData; AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; @@ -47,4 +54,57 @@ XCTAssertEqualObjects(item, secondItem); } +//Touch Handling + +- (void)testSpinnerDefaultsToNil +{ + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + + XCTAssertNil(videoNode.spinner); +} + +- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnode +{ + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + videoNode.interfaceState = ASInterfaceStateFetchData; + AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + videoNode.asset = asset; + + [videoNode play]; + + XCTAssertNotNil(videoNode.spinner); +} + +- (void)testOnPauseSpinnerIsPausedIfPresent +{ + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + videoNode.interfaceState = ASInterfaceStateFetchData; + AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + videoNode.asset = asset; + + [videoNode play]; + + [videoNode pause]; + + XCTAssertFalse(((UIActivityIndicatorView *)videoNode.spinner.view).isAnimating); +} + +- (void)testOnVideoReadySpinnerIsStoppedAndRemoved +{ + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + videoNode.interfaceState = ASInterfaceStateFetchData; + AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + videoNode.asset = asset; + + [videoNode play]; + [videoNode observeValueForKeyPath:@"status" ofObject:[videoNode currentItem] change:@{@"new" : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; + + XCTAssertFalse(((UIActivityIndicatorView *)videoNode.spinner.view).isAnimating); +} + +- (void)testPlayButtonUserInteractionIsNotEnabled +{ + +} + @end diff --git a/examples/Videos/Sample.xcodeproj/project.pbxproj b/examples/Videos/Sample.xcodeproj/project.pbxproj index ff79c01752..958ebe008a 100644 --- a/examples/Videos/Sample.xcodeproj/project.pbxproj +++ b/examples/Videos/Sample.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ AE8E41191C228A4A00913AC4 /* bearacrat@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = AE8E41181C228A4A00913AC4 /* bearacrat@2x.jpg */; }; AE8E411B1C23634C00913AC4 /* simon.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = AE8E411A1C235A6000913AC4 /* simon.mp4 */; }; AED850671C22679200183ED3 /* playButton@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AED850661C22679200183ED3 /* playButton@2x.png */; }; + AEE1F2C01C293CF1005E0577 /* playButtonSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AEE1F2BF1C293CF1005E0577 /* playButtonSelected@2x.png */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,6 +39,7 @@ AE8E41181C228A4A00913AC4 /* bearacrat@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "bearacrat@2x.jpg"; sourceTree = ""; }; AE8E411A1C235A6000913AC4 /* simon.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = simon.mp4; sourceTree = ""; }; AED850661C22679200183ED3 /* playButton@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playButton@2x.png"; sourceTree = ""; }; + AEE1F2BF1C293CF1005E0577 /* playButtonSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playButtonSelected@2x.png"; sourceTree = ""; }; C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -78,6 +80,7 @@ isa = PBXGroup; children = ( AED850661C22679200183ED3 /* playButton@2x.png */, + AEE1F2BF1C293CF1005E0577 /* playButtonSelected@2x.png */, AE8E41181C228A4A00913AC4 /* bearacrat@2x.jpg */, AE8E411A1C235A6000913AC4 /* simon.mp4 */, 05E2128819D4DB510098F589 /* AppDelegate.h */, @@ -186,6 +189,7 @@ 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, AE8E411B1C23634C00913AC4 /* simon.mp4 in Resources */, 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + AEE1F2C01C293CF1005E0577 /* playButtonSelected@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index ced141e1f5..c941fbbd30 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -78,7 +78,7 @@ simonVideo.backgroundColor = [UIColor lightGrayColor]; simonVideo.autorepeat = YES; simonVideo.playButton = [self playButton]; - simonVideo.shouldAutoPlay = YES; + simonVideo.shouldAutoplay = YES; return simonVideo; } @@ -92,6 +92,7 @@ [playButton measure:CGSizeMake(50, 50)]; playButton.bounds = CGRectMake(0, 0, playButton.calculatedSize.width, playButton.calculatedSize.height); playButton.position = CGPointMake([UIScreen mainScreen].bounds.size.width/4, ([UIScreen mainScreen].bounds.size.height/3)/2); + [playButton setImage:[UIImage imageNamed:@"playButtonSelected@2x.png"] forState:ASButtonStateHighlighted]; return playButton; } diff --git a/examples/Videos/Sample/playButtonSelected@2x.png b/examples/Videos/Sample/playButtonSelected@2x.png new file mode 100644 index 0000000000..f22ebc0f81 Binary files /dev/null and b/examples/Videos/Sample/playButtonSelected@2x.png differ