From 2e65339f33c0d69797b42ccffd02100d8a8d3ebd Mon Sep 17 00:00:00 2001 From: Flo Date: Sun, 10 Jul 2016 20:32:21 +0200 Subject: [PATCH] [ASVideoNode, ASVideoPlayerNode] Add video composition and audio mix capabilities (#1800) * [ASVideoNode] Add delegate method called when the currentItem is set. * [ASVideoNode] Add videoComposition and audioMix properties to the ASVideoNode. * [ASVideoPlayerNode] Add new initialiser with videoComposition and audioMix, and forward new delegate method. * [ASVideoPlayerNode] Forward missing ASVideoNodeDelegate methods. --- AsyncDisplayKit/ASVideoNode.h | 10 ++- AsyncDisplayKit/ASVideoNode.mm | 45 +++++++++++++- AsyncDisplayKit/ASVideoPlayerNode.h | 37 ++++++++++- AsyncDisplayKit/ASVideoPlayerNode.mm | 93 +++++++++++++++++++++++++++- 4 files changed, 178 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 1194266ef6..00f90ce80f 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -12,7 +12,7 @@ #import #import -@class AVAsset, AVPlayer, AVPlayerItem; +@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @protocol ASVideoNodeDelegate; typedef enum { @@ -41,6 +41,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isPlaying; @property (nullable, atomic, strong, readwrite) AVAsset *asset; +@property (nullable, atomic, strong, readwrite) AVVideoComposition *videoComposition; +@property (nullable, atomic, strong, readwrite) AVAudioMix *audioMix; @property (nullable, atomic, strong, readonly) AVPlayer *player; @property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; @@ -121,6 +123,12 @@ NS_ASSUME_NONNULL_BEGIN * @param videoNode The videoNode */ - (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode; +/** + * @abstract Delegate method invoked when the AVPlayerItem for the asset has been set up and can be accessed throught currentItem. + * @param videoNode The videoNode. + * @param currentItem The AVPlayerItem that was constructed from the asset. + */ +- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem; /** * @abstract Delegate method invoked when the video node has recovered from the stall * @param videoNode The videoNode diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 7e0c9050da..c0e0accf3a 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -49,6 +49,7 @@ static NSString * const kStatus = @"status"; unsigned int delegateVideoNodeDidPlayToTimeInterval:1; unsigned int delegateVideoNodeDidStartInitialLoading:1; unsigned int delegateVideoNodeDidFinishInitialLoading:1; + unsigned int delegateVideoNodeDidSetCurrentItem:1; unsigned int delegateVideoNodeDidStallAtTimeInterval:1; unsigned int delegateVideoNodeDidRecoverFromStall:1; @@ -68,6 +69,8 @@ static NSString * const kStatus = @"status"; ASVideoNodePlayerState _playerState; AVAsset *_asset; + AVVideoComposition *_videoComposition; + AVAudioMix *_audioMix; AVPlayerItem *_currentPlayerItem; AVPlayer *_player; @@ -124,7 +127,10 @@ static NSString * const kStatus = @"status"; ASDN::MutexLocker l(_propertyLock); if (_asset != nil) { - return [[AVPlayerItem alloc] initWithAsset:_asset]; + AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; + playerItem.videoComposition = _videoComposition; + playerItem.audioMix = _audioMix; + return playerItem; } return nil; @@ -153,7 +159,11 @@ static NSString * const kStatus = @"status"; } else { self.player = [AVPlayer playerWithPlayerItem:playerItem]; } - + + if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { + [_delegate videoNode:self didSetCurrentItem:playerItem]; + } + if (self.image == nil && self.URL == nil) { [self generatePlaceholderImage]; } @@ -448,6 +458,34 @@ static NSString * const kStatus = @"status"; return _asset; } +- (void)setVideoComposition:(AVVideoComposition *)videoComposition +{ + ASDN::MutexLocker l(_propertyLock); + + _videoComposition = videoComposition; + _currentPlayerItem.videoComposition = videoComposition; +} + +- (AVVideoComposition *)videoComposition +{ + ASDN::MutexLocker l(_propertyLock); + return _videoComposition; +} + +- (void)setAudioMix:(AVAudioMix *)audioMix +{ + ASDN::MutexLocker l(_propertyLock); + + _audioMix = audioMix; + _currentPlayerItem.audioMix = audioMix; +} + +- (AVAudioMix *)audioMix +{ + ASDN::MutexLocker l(_propertyLock); + return _audioMix; +} + - (AVPlayer *)player { ASDN::MutexLocker l(_propertyLock); @@ -473,6 +511,7 @@ static NSString * const kStatus = @"status"; _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; _delegateFlags.delegateVideoNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; _delegateFlags.delegateVideoNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; @@ -697,4 +736,4 @@ static NSString * const kStatus = @"status"; } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index c8f65fcb80..60318ce938 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -58,8 +58,10 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; - (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; - (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; #pragma mark - Public API - (void)seekToTime:(CGFloat)percentComplete; @@ -156,10 +158,43 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Delegate method invoked when the ASVideoNode has played to its end time. - * @param videoPlayerNode The video node has played to its end time. + * @param videoPlayer The video node has played to its end time. */ - (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; +/** + * @abstract Delegate method invoked when the ASVideoNode has constructed its AVPlayerItem for the asset. + * @param videoPlayer The video player node. + * @param currentItem The AVPlayerItem that was constructed from the asset. + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didSetCurrentItem:(AVPlayerItem *)currentItem; + +/** + * @abstract Delegate method invoked when the ASVideoNode stalls. + * @param videoPlayer The video player node that has experienced the stall + * @param second Current playback time when the stall happens + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; + +/** + * @abstract Delegate method invoked when the ASVideoNode starts the inital asset loading + * @param videoPlayer The videoPlayer + */ +- (void)videoPlayerNodeDidStartInitialLoading:(ASVideoPlayerNode *)videoPlayer; + +/** + * @abstract Delegate method invoked when the ASVideoNode is done loading the asset and can start the playback + * @param videoPlayer The videoPlayer + */ +- (void)videoPlayerNodeDidFinishInitialLoading:(ASVideoPlayerNode *)videoPlayer; + +/** + * @abstract Delegate method invoked when the ASVideoNode has recovered from the stall + * @param videoPlayer The videoplayer + */ +- (void)videoPlayerNodeDidRecoverFromStall:(ASVideoPlayerNode *)videoPlayer; + + @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 5e8d1bea7e..100485d539 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -37,10 +37,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; unsigned int delegateDidTapVideoPlayerNode:1; + unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; + unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; + unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; + unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1; + unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; } _delegateFlags; NSURL *_url; AVAsset *_asset; + AVVideoComposition *_videoComposition; + AVAudioMix *_audioMix; ASVideoNode *_videoNode; @@ -118,6 +125,22 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return self; } +-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _videoComposition = videoComposition; + _audioMix = audioMix; + _loadAssetWhenNodeBecomesVisible = YES; + + [self _init]; + + return self; +} + - (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible { if (!(self = [super init])) { @@ -147,6 +170,22 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return self; } +-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _videoComposition = videoComposition; + _audioMix = audioMix; + _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; + + [self _init]; + + return self; +} + - (void)_init { _defaultControlsColor = [UIColor whiteColor]; @@ -156,6 +195,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _videoNode.delegate = self; if (_loadAssetWhenNodeBecomesVisible == NO) { _videoNode.asset = _asset; + _videoNode.videoComposition = _videoComposition; + _videoNode.audioMix = _audioMix; } [self addSubnode:_videoNode]; } @@ -175,8 +216,16 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASDN::MutexLocker l(_propertyLock); - if (isVisible && _loadAssetWhenNodeBecomesVisible && _asset != _videoNode.asset) { - _videoNode.asset = _asset; + if (isVisible && _loadAssetWhenNodeBecomesVisible) { + if (_asset != _videoNode.asset) { + _videoNode.asset = _asset; + } + if (_videoComposition != _videoNode.videoComposition) { + _videoNode.videoComposition = _videoComposition; + } + if (_audioMix != _videoNode.audioMix) { + _videoNode.audioMix = _audioMix; + } } } @@ -480,6 +529,41 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem +{ + if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) { + [_delegate videoPlayerNode:self didSetCurrentItem:currentItem]; + } +} + +- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval +{ + if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) { + [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval]; + } +} + +- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) { + [_delegate videoPlayerNodeDidStartInitialLoading:self]; + } +} + +- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) { + [_delegate videoPlayerNodeDidFinishInitialLoading:self]; + } +} + +- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) { + [_delegate videoPlayerNodeDidRecoverFromStall:self]; + } +} + #pragma mark - Actions - (void)togglePlayPause { @@ -702,6 +786,11 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; + _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; + _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; + _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; + _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)]; } }