Merge remote-tracking branch

# Conflicts:
#	AsyncDisplayKit/ASVideoNode.mm
This commit is contained in:
Erekle
2016-05-03 22:59:54 +04:00
5 changed files with 184 additions and 81 deletions

19
.editorconfig Normal file
View File

@@ -0,0 +1,19 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[**.{h,cc,mm,m}]
indent_style = space
indent_size = 2
[*.{md,markdown}]
trim_trailing_whitespace = false
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab

View File

@@ -5,7 +5,7 @@ Pod::Spec.new do |spec|
spec.homepage = 'http://asyncdisplaykit.org'
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.7.3' }
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.73' }
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'
@@ -69,5 +69,6 @@ Pod::Spec.new do |spec|
}
spec.ios.deployment_target = '7.0'
spec.tvos.deployment_target = '9.0'
# Uncomment when fixed: The platform of the target `Pods` (tvOS 9.0) is not compatible with `PINRemoteImage/iOS (2.1.4)`, which does not support `tvos`.) during validation
# spec.tvos.deployment_target = '9.0'
end

View File

@@ -122,10 +122,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_URL = URL;
if (reset || _URL == nil) {
BOOL hasURL = _URL == nil;
if (reset || hasURL) {
self.image = _defaultImage;
ASPerformBlockOnMainThread(^{
self.currentImageQuality = 1.0;
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
so always use a dispatch_async to ensure that we queue the operations in the correct order.
(see comment in displayDidFinish) */
dispatch_async(dispatch_get_main_queue(), ^{
self.currentImageQuality = hasURL ? 0.0 : 1.0;
});
}
@@ -151,8 +155,12 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_defaultImage = defaultImage;
if (!_imageLoaded) {
ASPerformBlockOnMainThread(^{
self.currentImageQuality = 0.0;
BOOL hasURL = _URL == nil;
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
so always use a dispatch_async to ensure that we queue the operations in the correct order.
(see comment in displayDidFinish) */
dispatch_async(dispatch_get_main_queue(), ^{
self.currentImageQuality = hasURL ? 0.0 : 1.0;
});
_lock.unlock();
// Locking: it is important to release _lock before entering setImage:, as it needs to release the lock before -invalidateCalculatedLayout.
@@ -229,7 +237,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (result) {
self.image = result;
_imageLoaded = YES;
_currentImageQuality = 1.0;
dispatch_async(dispatch_get_main_queue(), ^{
_currentImageQuality = 1.0;
});
}
}
}
@@ -322,7 +332,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
return;
}
strongSelf.image = progressImage;
ASPerformBlockOnMainThread(^{
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf->_currentImageQuality = progress;
});
};
@@ -347,7 +357,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
self.animatedImage = nil;
self.image = _defaultImage;
_imageLoaded = NO;
ASPerformBlockOnMainThread(^{
dispatch_async(dispatch_get_main_queue(), ^{
self.currentImageQuality = 0.0;
});
}
@@ -431,9 +441,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
}
}
}
_imageLoaded = YES;
self.currentImageQuality = 1.0;
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
so always use a dispatch_async to ensure that we queue the operations in the correct order.
(see comment in displayDidFinish) */
dispatch_async(dispatch_get_main_queue(), ^{
self.currentImageQuality = 1.0;
});
[_delegate imageNode:self didLoadImage:self.image];
});
}
@@ -459,7 +474,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
} else {
strongSelf.image = [imageContainer asdk_image];
}
strongSelf->_currentImageQuality = 1.0;
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf->_currentImageQuality = 1.0;
});
}
strongSelf->_downloadIdentifier = nil;

View File

@@ -101,7 +101,7 @@ static NSString * const kStatus = @"status";
- (ASDisplayNode *)constructPlayerNode
{
ASVideoNode * __weak weakSelf = self;
return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init];
playerLayer.player = weakSelf.player;
@@ -113,16 +113,47 @@ static NSString * const kStatus = @"status";
- (AVPlayerItem *)constructPlayerItem
{
ASDN::MutexLocker l(_videoLock);
if (_asset != nil) {
if ([_asset isKindOfClass:[AVURLAsset class]]) {
return [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL];
} else {
return [[AVPlayerItem alloc] initWithAsset:_asset];
return [[AVPlayerItem alloc] initWithAsset:_asset];
}
return nil;
}
- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray<NSString *> *)requestedKeys
{
for (NSString *key in requestedKeys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
NSLog(@"Asset loading failed with error: %@", error);
}
}
return nil;
if (![asset isPlayable]) {
NSLog(@"Asset is not playable.");
return;
}
AVPlayerItem *playerItem = [self constructPlayerItem];
[self setCurrentItem:playerItem];
if (_player != nil) {
[_player replaceCurrentItemWithPlayerItem:playerItem];
} else {
self.player = [AVPlayer playerWithPlayerItem:playerItem];
}
if (_placeholderImageNode.image == nil) {
[self generatePlaceholderImage];
}
__weak __typeof(self) weakSelf = self;
_timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale);
_timeObserver = [_player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){
[weakSelf periodicTimeObserver:time];
}];
}
- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem
@@ -130,7 +161,7 @@ static NSString * const kStatus = @"status";
[playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext];
[playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
[playerItem addObserver:self forKeyPath:kplaybackBufferEmpty 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];
@@ -147,7 +178,7 @@ static NSString * const kStatus = @"status";
@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];
@@ -178,7 +209,7 @@ static NSString * const kStatus = @"status";
{
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
@@ -193,17 +224,17 @@ static NSString * const kStatus = @"status";
{
ASPerformBlockOnBackgroundThread(^{
AVAsset *asset = self.asset;
// Skip the asset image generation if we don't have any tracks available that are capable of supporting it
NSArray<AVAssetTrack *>* 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) {
@@ -217,18 +248,18 @@ static NSString * const kStatus = @"status";
- (void)setVideoPlaceholderImage:(UIImage *)image
{
ASDN::MutexLocker l(_videoLock);
if (_placeholderImageNode == nil && image != nil) {
_placeholderImageNode = [[ASImageNode alloc] init];
_placeholderImageNode.layerBacked = YES;
_placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity);
}
_placeholderImageNode.image = image;
ASPerformBlockOnMainThread(^{
ASDN::MutexLocker l(_videoLock);
if (_placeholderImageNode != nil) {
[self insertSubnode:_placeholderImageNode atIndex:0];
[self setNeedsLayout];
@@ -239,11 +270,11 @@ static NSString * const kStatus = @"status";
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
ASDN::MutexLocker l(_videoLock);
if (object != _currentPlayerItem) {
return;
}
if ([keyPath isEqualToString:kStatus]) {
if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
[self removeSpinner];
@@ -285,26 +316,14 @@ static NSString * const kStatus = @"status";
[super fetchData];
{
ASDN::MutexLocker l(_videoLock);
AVPlayerItem *playerItem = [self constructPlayerItem];
self.currentItem = playerItem;
if (_player != nil) {
[_player replaceCurrentItemWithPlayerItem:playerItem];
} else {
self.player = [AVPlayer playerWithPlayerItem:playerItem];
}
if (_placeholderImageNode.image == nil) {
[self generatePlaceholderImage];
}
__weak __typeof(self) weakSelf = self;
_timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale);
_timeObserver = [_player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){
[weakSelf periodicTimeObserver:time];
}];
ASDN::MutexLocker l(_videoLock);
AVAsset *asset = self.asset;
NSArray<NSString *> *requestedKeys = @[ @"playable" ];
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{
ASPerformBlockOnMainThread(^{
[self prepareToPlayAsset:asset withKeys:requestedKeys];
});
}];
}
}
@@ -325,11 +344,11 @@ static NSString * const kStatus = @"status";
[super clearFetchedData];
{
ASDN::MutexLocker l(_videoLock);
self.player = nil;
self.currentItem = nil;
_placeholderImageNode.image = nil;
ASDN::MutexLocker l(_videoLock);
self.player = nil;
self.currentItem = nil;
_placeholderImageNode.image = nil;
}
}
@@ -372,10 +391,10 @@ static NSString * const kStatus = @"status";
- (void)setPlayButton:(ASButtonNode *)playButton
{
ASDN::MutexLocker l(_videoLock);
[_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[_playButton removeFromSupernode];
_playButton = playButton;
[self addSubnode:playButton];
@@ -397,13 +416,13 @@ static NSString * const kStatus = @"status";
if (ASAssetIsEqual(asset, _asset)) {
return;
}
[self clearFetchedData];
_asset = asset;
[self setNeedsDataFetch];
if (_shouldAutoplay) {
[self play];
}
@@ -480,14 +499,14 @@ static NSString * const kStatus = @"status";
if(![self isStateChangeValid:ASVideoNodePlayerStatePlaying]){
return;
}
if (_player == nil) {
[self setNeedsDataFetch];
}
if (_playerNode == nil) {
_playerNode = [self constructPlayerNode];
if (_playButton.supernode == self) {
[self insertSubnode:_playerNode belowSubnode:_playButton];
} else {
@@ -585,7 +604,7 @@ static NSString * const kStatus = @"status";
[_delegate videoPlaybackDidFinish:self];
}
[_player seekToTime:kCMTimeZero];
if (_shouldAutorepeat) {
[self play];
} else {
@@ -633,11 +652,11 @@ static NSString * const kStatus = @"status";
- (void)setCurrentItem:(AVPlayerItem *)currentItem
{
ASDN::MutexLocker l(_videoLock);
[self removePlayerItemObservers:_currentPlayerItem];
_currentPlayerItem = currentItem;
[self addPlayerItemObservers:currentItem];
}

View File

@@ -6,6 +6,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <OCMock/OCMock.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <AVFoundation/AVFoundation.h>
@@ -17,6 +19,7 @@
AVURLAsset *_firstAsset;
AVAsset *_secondAsset;
NSURL *_url;
NSArray *_requestedKeys;
}
@end
@@ -32,6 +35,7 @@
@property (atomic, readwrite) BOOL shouldBePlaying;
- (void)setVideoPlaceholderImage:(UIImage *)image;
- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys;
@end
@@ -43,6 +47,7 @@
_firstAsset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"firstURL"]];
_secondAsset = [AVAsset assetWithURL:[NSURL URLWithString:@"secondURL"]];
_url = [NSURL URLWithString:@"testURL"];
_requestedKeys = @[ @"playable" ];
}
@@ -131,23 +136,42 @@
XCTAssertNil(_videoNode.player);
}
- (void)testPlayerIsCreatedInFetchData
- (void)testPlayerIsCreatedAsynchronouslyInFetchData
{
_videoNode.asset = _firstAsset;
AVAsset *asset = _firstAsset;
id assetMock = [OCMockObject partialMockForObject:asset];
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
_videoNode.asset = assetMock;
_videoNode.interfaceState = ASInterfaceStateFetchData;
[videoNodeMock verifyWithDelay:1.0f];
XCTAssertNotNil(_videoNode.player);
}
- (void)testPlayerIsCreatedInFetchDataWithURL
- (void)testPlayerIsCreatedAsynchronouslyInFetchDataWithURL
{
_videoNode.asset = [AVAsset assetWithURL:_url];
AVAsset *asset = [AVAsset assetWithURL:_url];
id assetMock = [OCMockObject partialMockForObject:asset];
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
_videoNode.asset = assetMock;
_videoNode.interfaceState = ASInterfaceStateFetchData;
[videoNodeMock verifyWithDelay:1.0f];
XCTAssertNotNil(_videoNode.player);
}
- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlaying
{
_videoNode.asset = _firstAsset;
@@ -284,13 +308,17 @@
- (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd
{
_videoNode.asset = _firstAsset;
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
_videoNode.asset = assetMock;
_videoNode.shouldAutorepeat = NO;
[_videoNode didLoad];
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData];
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
[_videoNode play];
XCTAssertTrue(_videoNode.isPlaying);
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem];
@@ -301,11 +329,15 @@
- (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd
{
_videoNode.asset = _firstAsset;
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
_videoNode.asset = assetMock;
_videoNode.shouldAutorepeat = YES;
[_videoNode didLoad];
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData];
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
[_videoNode play];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem];
@@ -315,9 +347,13 @@
- (void)testVideoResumedWhenBufferIsLikelyToKeepUp
{
_videoNode.asset = _firstAsset;
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
_videoNode.asset = assetMock;
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData];
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
[_videoNode pause];
_videoNode.shouldBePlaying = YES;
@@ -372,9 +408,20 @@
- (void)testClearingFetchedContentShouldClearAssetData
{
_videoNode.asset = _firstAsset;
AVAsset *asset = _firstAsset;
id assetMock = [OCMockObject partialMockForObject:asset];
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
[[[assetMock stub] andReturnValue:@YES] isPlayable];
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
_videoNode.asset = assetMock;
[_videoNode fetchData];
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
[videoNodeMock verifyWithDelay:1.0f];
XCTAssertNotNil(_videoNode.player);
XCTAssertNotNil(_videoNode.currentItem);
XCTAssertNotNil(_videoNode.placeholderImageNode.image);