[ASVideoNode] Change superclass to ASNetworkImageNode so that it can be its own placeholder (#1710)

* [ASVideoNode] Change superclass to ASNetworkImageNode so that ASVideoNode can be its own placeholder
- remove _placeholderImageNode property of ASVideoNode (use self.image now instead)
- move layoutSpecThatFits: code to calculateSizeThatFits: & layout: methods as ASImageNode uses calculateSizeThatFits:

* [ASVideoNode] Tweaks to the definition of the delegate protocols to integrate with ASNetworkImageNode (superclass)
This commit is contained in:
Hannah Troisi
2016-06-05 18:18:52 -07:00
committed by appleguy
parent 4804f429b9
commit 52d58992da
4 changed files with 46 additions and 67 deletions

View File

@@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
* The delegate, which must conform to the <ASNetworkImageNodeDelegate> protocol. * The delegate, which must conform to the <ASNetworkImageNodeDelegate> protocol.
*/ */
@property (atomic, weak, readwrite) id<ASNetworkImageNodeDelegate> delegate; @property (nullable, atomic, weak, readwrite) id<ASNetworkImageNodeDelegate> delegate;
/** /**
* A placeholder image to display while the URL is loading. * A placeholder image to display while the URL is loading.
@@ -100,6 +100,7 @@ NS_ASSUME_NONNULL_BEGIN
* notifications such as finished decoding and downloading an image. * notifications such as finished decoding and downloading an image.
*/ */
@protocol ASNetworkImageNodeDelegate <NSObject> @protocol ASNetworkImageNodeDelegate <NSObject>
@optional
/** /**
* Notification that the image node finished downloading an image. * Notification that the image node finished downloading an image.
@@ -111,8 +112,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image; - (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image;
@optional
/** /**
* Notification that the image node started to load * Notification that the image node started to load
* *

View File

@@ -8,6 +8,7 @@
#if TARGET_OS_IOS #if TARGET_OS_IOS
#import <AsyncDisplayKit/ASButtonNode.h> #import <AsyncDisplayKit/ASButtonNode.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>
@class AVAsset, AVPlayer, AVPlayerItem; @class AVAsset, AVPlayer, AVPlayerItem;
@protocol ASVideoNodeDelegate; @protocol ASVideoNodeDelegate;
@@ -31,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
// there is room for further expansion and optimization. Please report any issues or requests // there is room for further expansion and optimization. Please report any issues or requests
// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues // in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues
@interface ASVideoNode : ASControlNode @interface ASVideoNode : ASNetworkImageNode
- (void)play; - (void)play;
- (void)pause; - (void)pause;
@@ -60,11 +61,11 @@ NS_ASSUME_NONNULL_BEGIN
//! Defaults to AVLayerVideoGravityResizeAspect //! Defaults to AVLayerVideoGravityResizeAspect
@property (atomic) NSString *gravity; @property (atomic) NSString *gravity;
@property (nullable, atomic, weak, readwrite) id<ASVideoNodeDelegate> delegate; @property (nullable, atomic, weak, readwrite) id<ASVideoNodeDelegate, ASNetworkImageNodeDelegate> delegate;
@end @end
@protocol ASVideoNodeDelegate <NSObject> @protocol ASVideoNodeDelegate <ASNetworkImageNodeDelegate>
@optional @optional
/** /**
* @abstract Delegate method invoked when the node's video has played to its end time. * @abstract Delegate method invoked when the node's video has played to its end time.

View File

@@ -71,8 +71,6 @@ static NSString * const kStatus = @"status";
int32_t _periodicTimeObserverTimescale; int32_t _periodicTimeObserverTimescale;
CMTime _timeObserverInterval; CMTime _timeObserverInterval;
ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this.
ASDisplayNode *_playerNode; ASDisplayNode *_playerNode;
NSString *_gravity; NSString *_gravity;
} }
@@ -151,7 +149,7 @@ static NSString * const kStatus = @"status";
self.player = [AVPlayer playerWithPlayerItem:playerItem]; self.player = [AVPlayer playerWithPlayerItem:playerItem];
} }
if (_placeholderImageNode.image == nil) { if (self.image == nil) {
[self generatePlaceholderImage]; [self generatePlaceholderImage];
} }
@@ -193,33 +191,34 @@ static NSString * const kStatus = @"status";
[notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
} }
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize - (void)layout
{ {
// All subnodes should taking the whole node frame [super layout];
CGSize maxSize = constrainedSize.max; // The _playerNode wraps AVPlayerLayer, and therefore should extend across the entire bounds.
if (!CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero)) { _playerNode.frame = self.bounds;
maxSize = self.preferredFrameSize; }
}
// Prevent crashes through if infinite width or height
if (isinf(maxSize.width) || isinf(maxSize.height)) {
ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode");
maxSize = CGSizeZero;
}
// Stretch out play button, placeholder image player node to the max size
NSMutableArray *children = [NSMutableArray array];
if (_placeholderImageNode) { - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
_placeholderImageNode.preferredFrameSize = maxSize; {
[children addObject:_placeholderImageNode]; ASDN::MutexLocker l(_videoLock);
} CGSize calculatedSize = constrainedSize;
if (_playerNode) {
_playerNode.preferredFrameSize = maxSize; // if a preferredFrameSize is set, call the superclass to return that instead of using the image size.
[children addObject:_playerNode]; if (CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero) == NO)
} calculatedSize = self.preferredFrameSize;
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children]; // Prevent crashes through if infinite width or height
if (isinf(calculatedSize.width) || isinf(calculatedSize.height)) {
ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode");
calculatedSize = CGSizeZero;
}
if (_playerNode) {
_playerNode.preferredFrameSize = calculatedSize;
[_playerNode measure:calculatedSize];
}
return calculatedSize;
} }
- (void)generatePlaceholderImage - (void)generatePlaceholderImage
@@ -265,23 +264,10 @@ static NSString * const kStatus = @"status";
- (void)setVideoPlaceholderImage:(UIImage *)image - (void)setVideoPlaceholderImage:(UIImage *)image
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
if (image != nil) {
if (_placeholderImageNode == nil && image != nil) { self.contentMode = ASContentModeFromVideoGravity(_gravity);
_placeholderImageNode = [[ASImageNode alloc] init];
_placeholderImageNode.layerBacked = YES;
_placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity);
} }
self.image = image;
_placeholderImageNode.image = image;
ASPerformBlockOnMainThread(^{
ASDN::MutexLocker l(_videoLock);
if (_placeholderImageNode != nil) {
[self insertSubnode:_placeholderImageNode atIndex:0];
[self setNeedsLayout];
}
});
} }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
@@ -296,7 +282,7 @@ static NSString * const kStatus = @"status";
if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
self.playerState = ASVideoNodePlayerStateReadyToPlay; self.playerState = ASVideoNodePlayerStateReadyToPlay;
// If we don't yet have a placeholder image update it now that we should have data available for it // 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 (self.image == nil) {
[self generatePlaceholderImage]; [self generatePlaceholderImage];
} }
} }
@@ -393,7 +379,6 @@ static NSString * const kStatus = @"status";
self.player = nil; self.player = nil;
self.currentItem = nil; self.currentItem = nil;
_placeholderImageNode.image = nil;
} }
} }
@@ -496,7 +481,7 @@ static NSString * const kStatus = @"status";
if (_playerNode.isNodeLoaded) { if (_playerNode.isNodeLoaded) {
((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity;
} }
_placeholderImageNode.contentMode = ASContentModeFromVideoGravity(gravity); self.contentMode = ASContentModeFromVideoGravity(gravity);
_gravity = gravity; _gravity = gravity;
} }
@@ -637,11 +622,6 @@ static NSString * const kStatus = @"status";
} }
#pragma mark - Internal Properties #pragma mark - Internal Properties
- (ASImageNode *)placeholderImageNode
{
ASDN::MutexLocker l(_videoLock);
return _placeholderImageNode;
}
- (AVPlayerItem *)currentItem - (AVPlayerItem *)currentItem
{ {

View File

@@ -29,7 +29,6 @@
} }
@property (atomic, readwrite) ASInterfaceState interfaceState; @property (atomic, readwrite) ASInterfaceState interfaceState;
@property (atomic, readonly) ASDisplayNode *spinner; @property (atomic, readonly) ASDisplayNode *spinner;
@property (atomic, readonly) ASImageNode *placeholderImageNode;
@property (atomic, readwrite) ASDisplayNode *playerNode; @property (atomic, readwrite) ASDisplayNode *playerNode;
@property (atomic, readwrite) AVPlayer *player; @property (atomic, readwrite) AVPlayer *player;
@property (atomic, readwrite) BOOL shouldBePlaying; @property (atomic, readwrite) BOOL shouldBePlaying;
@@ -355,16 +354,16 @@
- (void)testSettingVideoGravityChangesPlaceholderContentMode - (void)testSettingVideoGravityChangesPlaceholderContentMode
{ {
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode);
_videoNode.gravity = AVLayerVideoGravityResize; _videoNode.gravity = AVLayerVideoGravityResize;
XCTAssertEqual(UIViewContentModeScaleToFill, _videoNode.placeholderImageNode.contentMode); XCTAssertEqual(UIViewContentModeScaleToFill, _videoNode.contentMode);
_videoNode.gravity = AVLayerVideoGravityResizeAspect; _videoNode.gravity = AVLayerVideoGravityResizeAspect;
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode);
_videoNode.gravity = AVLayerVideoGravityResizeAspectFill; _videoNode.gravity = AVLayerVideoGravityResizeAspectFill;
XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.placeholderImageNode.contentMode); XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.contentMode);
} }
- (void)testChangingAssetsChangesPlaceholderImage - (void)testChangingAssetsChangesPlaceholderImage
@@ -373,10 +372,10 @@
_videoNode.asset = _firstAsset; _videoNode.asset = _firstAsset;
[_videoNode setVideoPlaceholderImage:firstImage]; [_videoNode setVideoPlaceholderImage:firstImage];
XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image); XCTAssertEqual(firstImage, _videoNode.image);
_videoNode.asset = _secondAsset; _videoNode.asset = _secondAsset;
XCTAssertNotEqual(firstImage, _videoNode.placeholderImageNode.image); XCTAssertNotEqual(firstImage, _videoNode.image);
} }
- (void)testClearingFetchedContentShouldClearAssetData - (void)testClearingFetchedContentShouldClearAssetData
@@ -397,12 +396,12 @@
XCTAssertNotNil(_videoNode.player); XCTAssertNotNil(_videoNode.player);
XCTAssertNotNil(_videoNode.currentItem); XCTAssertNotNil(_videoNode.currentItem);
XCTAssertNotNil(_videoNode.placeholderImageNode.image); XCTAssertNotNil(_videoNode.image);
[_videoNode clearFetchedData]; [_videoNode clearFetchedData];
XCTAssertNil(_videoNode.player); XCTAssertNil(_videoNode.player);
XCTAssertNil(_videoNode.currentItem); XCTAssertNil(_videoNode.currentItem);
XCTAssertNil(_videoNode.placeholderImageNode.image); XCTAssertNil(_videoNode.image);
} }
@end @end