Peter 9bc996374f Add 'submodules/AsyncDisplayKit/' from commit '02bedc12816e251ad71777f9d2578329b6d2bef6'
git-subtree-dir: submodules/AsyncDisplayKit
git-subtree-mainline: d06f423e0ed3df1fed9bd10d79ee312a9179b632
git-subtree-split: 02bedc12816e251ad71777f9d2578329b6d2bef6
2019-06-11 18:42:43 +01:00

1025 lines
31 KiB
Plaintext

//
// ASVideoPlayerNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASVideoPlayerNode.h>
#if AS_USE_VIDEO
#if TARGET_OS_IOS
#import <AVFoundation/AVFoundation.h>
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <AsyncDisplayKit/ASDefaultPlaybackButton.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASThread.h>
static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
@interface ASVideoPlayerNode() <ASVideoNodeDelegate, ASVideoPlayerNodeDelegate>
{
__weak id<ASVideoPlayerNodeDelegate> _delegate;
struct {
unsigned int delegateNeededDefaultControls:1;
unsigned int delegateCustomControls:1;
unsigned int delegateSpinnerTintColor:1;
unsigned int delegateSpinnerStyle:1;
unsigned int delegatePlaybackButtonTint:1;
unsigned int delegateFullScreenButtonImage:1;
unsigned int delegateScrubberMaximumTrackTintColor:1;
unsigned int delegateScrubberMinimumTrackTintColor:1;
unsigned int delegateScrubberThumbTintColor:1;
unsigned int delegateScrubberThumbImage:1;
unsigned int delegateTimeLabelAttributes:1;
unsigned int delegateTimeLabelAttributedString:1;
unsigned int delegateLayoutSpecForControls:1;
unsigned int delegateVideoNodeDidPlayToTime:1;
unsigned int delegateVideoNodeWillChangeState:1;
unsigned int delegateVideoNodeShouldChangeState:1;
unsigned int delegateVideoNodePlaybackDidFinish:1;
unsigned int delegateDidTapVideoPlayerNode:1;
unsigned int delegateDidTapFullScreenButtonNode:1;
unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1;
unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1;
unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1;
unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1;
unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1;
} _delegateFlags;
// The asset passed in the initializer will be assigned as pending asset. As soon as the first
// preload state happened all further asset handling is made by using the asset of the backing
// video node
AVAsset *_pendingAsset;
// The backing video node. Ideally this is the source of truth and the video player node should
// not handle anything related to asset management
ASVideoNode *_videoNode;
NSArray *_neededDefaultControls;
NSMutableDictionary *_cachedControls;
ASDefaultPlaybackButton *_playbackButtonNode;
ASButtonNode *_fullScreenButtonNode;
ASTextNode *_elapsedTextNode;
ASTextNode *_durationTextNode;
ASDisplayNode *_scrubberNode;
ASStackLayoutSpec *_controlFlexGrowSpacerSpec;
ASDisplayNode *_spinnerNode;
BOOL _isSeeking;
CMTime _duration;
BOOL _controlsDisabled;
BOOL _shouldAutoPlay;
BOOL _shouldAutoRepeat;
BOOL _muted;
int32_t _periodicTimeObserverTimescale;
NSString *_gravity;
BOOL _shouldAggressivelyRecoverFromStall;
UIColor *_defaultControlsColor;
}
@end
@implementation ASVideoPlayerNode
@dynamic placeholderImageURL;
#pragma mark - Lifecycle
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
[self _initControlsAndVideoNode];
return self;
}
- (instancetype)initWithAsset:(AVAsset *)asset
{
if (!(self = [self init])) {
return nil;
}
_pendingAsset = asset;
return self;
}
- (instancetype)initWithURL:(NSURL *)URL
{
return [self initWithAsset:[AVAsset assetWithURL:URL]];
}
- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix
{
if (!(self = [self initWithAsset:asset])) {
return nil;
}
_videoNode.videoComposition = videoComposition;
_videoNode.audioMix = audioMix;
return self;
}
- (void)_initControlsAndVideoNode
{
_defaultControlsColor = [UIColor whiteColor];
_cachedControls = [[NSMutableDictionary alloc] init];
_videoNode = [[ASVideoNode alloc] init];
_videoNode.delegate = self;
[self addSubnode:_videoNode];
}
#pragma mark - Setter / Getter
- (void)setAssetURL:(NSURL *)assetURL
{
ASDisplayNodeAssertMainThread();
self.asset = [AVAsset assetWithURL:assetURL];
}
- (NSURL *)assetURL
{
NSURL *url = nil;
{
ASLockScopeSelf();
if ([_pendingAsset isKindOfClass:AVURLAsset.class]) {
url = ((AVURLAsset *)_pendingAsset).URL;
}
}
return url ?: _videoNode.assetURL;
}
- (void)setAsset:(AVAsset *)asset
{
ASDisplayNodeAssertMainThread();
[self lock];
// Clean out pending asset
_pendingAsset = nil;
// Set asset based on interface state
if ((ASInterfaceStateIncludesPreload(self.interfaceState))) {
// Don't hold the lock while accessing the subnode
[self unlock];
_videoNode.asset = asset;
return;
}
_pendingAsset = asset;
[self unlock];
}
- (AVAsset *)asset
{
return ASLockedSelf(_pendingAsset) ?: _videoNode.asset;
}
#pragma mark - ASDisplayNode
- (void)didLoad
{
[super didLoad];
[self createControls];
}
- (void)didEnterPreloadState
{
[super didEnterPreloadState];
AVAsset *pendingAsset = nil;
{
ASLockScopeSelf();
pendingAsset = _pendingAsset;
_pendingAsset = nil;
}
// If we enter preload state we apply the pending asset to load to the video node so it can start and fetch the asset
if (pendingAsset != nil && _videoNode.asset != pendingAsset) {
_videoNode.asset = pendingAsset;
}
}
- (BOOL)supportsLayerBacking
{
return NO;
}
#pragma mark - UI
- (void)createControls
{
{
ASLockScopeSelf();
if (_controlsDisabled) {
return;
}
if (_neededDefaultControls == nil) {
_neededDefaultControls = [self createDefaultControlElementArray];
}
if (_cachedControls == nil) {
_cachedControls = [[NSMutableDictionary alloc] init];
}
for (id object in _neededDefaultControls) {
ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue];
switch (type) {
case ASVideoPlayerNodeControlTypePlaybackButton:
[self _locked_createPlaybackButton];
break;
case ASVideoPlayerNodeControlTypeElapsedText:
[self _locked_createElapsedTextField];
break;
case ASVideoPlayerNodeControlTypeDurationText:
[self _locked_createDurationTextField];
break;
case ASVideoPlayerNodeControlTypeScrubber:
[self _locked_createScrubber];
break;
case ASVideoPlayerNodeControlTypeFullScreenButton:
[self _locked_createFullScreenButton];
break;
case ASVideoPlayerNodeControlTypeFlexGrowSpacer:
[self _locked_createControlFlexGrowSpacer];
break;
default:
break;
}
}
if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) {
NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self];
std::vector<ASDisplayNode *> subnodes;
for (id key in customControls) {
id node = customControls[key];
if (![node isKindOfClass:[ASDisplayNode class]]) {
continue;
}
subnodes.push_back(node);
[_cachedControls setObject:node forKey:key];
}
{
ASUnlockScope(self);
for (ASDisplayNode *subnode : subnodes) {
[self addSubnode:subnode];
}
}
}
}
ASPerformBlockOnMainThread(^{
[self setNeedsLayout];
});
}
- (NSArray *)createDefaultControlElementArray
{
if (_delegateFlags.delegateNeededDefaultControls) {
return [_delegate videoPlayerNodeNeededDefaultControls:self];
}
return @[ @(ASVideoPlayerNodeControlTypePlaybackButton),
@(ASVideoPlayerNodeControlTypeElapsedText),
@(ASVideoPlayerNodeControlTypeScrubber),
@(ASVideoPlayerNodeControlTypeDurationText) ];
}
- (void)removeControls
{
NSMutableDictionary *cachedControls = nil;
{
ASLockScope(self);
// Grab the cached controls for removing it
cachedControls = [_cachedControls copy];
[self _locked_cleanCachedControls];
}
for (ASDisplayNode *node in [cachedControls objectEnumerator]) {
[node removeFromSupernode];
}
}
- (void)_locked_cleanCachedControls
{
[_cachedControls removeAllObjects];
_playbackButtonNode = nil;
_fullScreenButtonNode = nil;
_elapsedTextNode = nil;
_durationTextNode = nil;
_scrubberNode = nil;
}
- (void)_locked_createPlaybackButton
{
ASAssertLocked(__instanceLock__);
if (_playbackButtonNode == nil) {
_playbackButtonNode = [[ASDefaultPlaybackButton alloc] init];
_playbackButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0);
if (_delegateFlags.delegatePlaybackButtonTint) {
_playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self];
} else {
_playbackButtonNode.tintColor = _defaultControlsColor;
}
if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) {
_playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause;
}
[_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside];
[_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)];
}
{
ASUnlockScope(self);
[self addSubnode:_playbackButtonNode];
}
}
- (void)_locked_createFullScreenButton
{
ASAssertLocked(__instanceLock__);
if (_fullScreenButtonNode == nil) {
_fullScreenButtonNode = [[ASButtonNode alloc] init];
_fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0);
if (_delegateFlags.delegateFullScreenButtonImage) {
[_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:UIControlStateNormal];
}
[_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside];
[_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)];
}
{
ASUnlockScope(self);
[self addSubnode:_fullScreenButtonNode];
}
}
- (void)_locked_createElapsedTextField
{
ASAssertLocked(__instanceLock__);
if (_elapsedTextNode == nil) {
_elapsedTextNode = [[ASTextNode alloc] init];
_elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00"
forControlType:ASVideoPlayerNodeControlTypeElapsedText];
_elapsedTextNode.truncationMode = NSLineBreakByClipping;
[_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)];
}
{
ASUnlockScope(self);
[self addSubnode:_elapsedTextNode];
}
}
- (void)_locked_createDurationTextField
{
ASAssertLocked(__instanceLock__);
if (_durationTextNode == nil) {
_durationTextNode = [[ASTextNode alloc] init];
_durationTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00"
forControlType:ASVideoPlayerNodeControlTypeDurationText];
_durationTextNode.truncationMode = NSLineBreakByClipping;
[_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)];
}
[self updateDurationTimeLabel];
{
ASUnlockScope(self);
[self addSubnode:_durationTextNode];
}
}
- (void)_locked_createScrubber
{
ASAssertLocked(__instanceLock__);
if (_scrubberNode == nil) {
__weak __typeof__(self) weakSelf = self;
_scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull {
__typeof__(self) strongSelf = weakSelf;
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero];
slider.minimumValue = 0.0;
slider.maximumValue = 1.0;
if (_delegateFlags.delegateScrubberMinimumTrackTintColor) {
slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf];
}
if (_delegateFlags.delegateScrubberMaximumTrackTintColor) {
slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf];
}
if (_delegateFlags.delegateScrubberThumbTintColor) {
slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf];
}
if (_delegateFlags.delegateScrubberThumbImage) {
UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf];
[slider setThumbImage:thumbImage forState:UIControlStateNormal];
}
[slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown];
[slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel];
[slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged];
return slider;
}];
_scrubberNode.style.flexShrink = 1;
[_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)];
}
{
ASUnlockScope(self);
[self addSubnode:_scrubberNode];
}
}
- (void)_locked_createControlFlexGrowSpacer
{
ASAssertLocked(__instanceLock__);
if (_controlFlexGrowSpacerSpec == nil) {
_controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init];
_controlFlexGrowSpacerSpec.style.flexGrow = 1.0;
}
[_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)];
}
- (void)updateDurationTimeLabel
{
if (!_durationTextNode) {
return;
}
NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText];
_durationTextNode.attributedText = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText];
}
- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds
{
if (!_elapsedTextNode) {
return;
}
NSString *formattedElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText];
_elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:formattedElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText];
}
- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType
{
NSDictionary *options;
if (_delegateFlags.delegateTimeLabelAttributes) {
options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType];
} else {
options = @{
NSFontAttributeName : [UIFont systemFontOfSize:12.0],
NSForegroundColorAttributeName: _defaultControlsColor
};
}
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options];
return attributedString;
}
#pragma mark - ASVideoNodeDelegate
- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState
{
if (_delegateFlags.delegateVideoNodeWillChangeState) {
[_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState];
}
if (toState == ASVideoNodePlayerStateReadyToPlay) {
_duration = _videoNode.currentItem.duration;
[self updateDurationTimeLabel];
}
if (toState == ASVideoNodePlayerStatePlaying) {
_playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause;
[self removeSpinner];
} else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) {
_playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay;
}
if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) {
[self showSpinner];
}
if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) {
[self removeSpinner];
}
}
- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state
{
if (_delegateFlags.delegateVideoNodeShouldChangeState) {
return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state];
}
return YES;
}
- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval
{
if (_delegateFlags.delegateVideoNodeDidPlayToTime) {
[_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime];
}
if (_isSeeking) {
return;
}
if (_elapsedTextNode) {
[self updateElapsedTimeLabel:timeInterval];
}
if (_scrubberNode) {
[(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO];
}
}
- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode
{
if (_delegateFlags.delegateVideoNodePlaybackDidFinish) {
[_delegate videoPlayerNodeDidPlayToEnd:self];
}
}
- (void)didTapVideoNode:(ASVideoNode *)videoNode
{
if (_delegateFlags.delegateDidTapVideoPlayerNode) {
[_delegate didTapVideoPlayerNode:self];
} else {
[self togglePlayPause];
}
}
- (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
{
if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) {
[_videoNode pause];
} else {
[_videoNode play];
}
}
- (void)showSpinner
{
ASLockScopeSelf();
if (!_spinnerNode) {
__weak __typeof__(self) weakSelf = self;
_spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
__typeof__(self) strongSelf = weakSelf;
UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
spinnnerView.backgroundColor = [UIColor clearColor];
if (_delegateFlags.delegateSpinnerTintColor) {
spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:strongSelf];
} else {
spinnnerView.color = _defaultControlsColor;
}
if (_delegateFlags.delegateSpinnerStyle) {
spinnnerView.activityIndicatorViewStyle = [_delegate videoPlayerNodeSpinnerStyle:strongSelf];
}
return spinnnerView;
}];
_spinnerNode.style.preferredSize = CGSizeMake(44.0, 44.0);
const auto spinnerNode = _spinnerNode;
{
ASUnlockScope(self);
[self addSubnode:spinnerNode];
[self setNeedsLayout];
}
}
[(UIActivityIndicatorView *)_spinnerNode.view startAnimating];
}
- (void)removeSpinner
{
ASDisplayNode *spinnerNode = nil;
{
ASLockScopeSelf();
if (!_spinnerNode) {
return;
}
spinnerNode = _spinnerNode;
_spinnerNode = nil;
}
[spinnerNode removeFromSupernode];
}
- (void)didTapPlaybackButton:(ASControlNode*)node
{
[self togglePlayPause];
}
- (void)didTapFullScreenButton:(ASButtonNode*)node
{
if (_delegateFlags.delegateDidTapFullScreenButtonNode) {
[_delegate didTapFullScreenButtonNode:node];
}
}
- (void)beginSeek
{
_isSeeking = YES;
}
- (void)endSeek
{
_isSeeking = NO;
}
- (void)seekTimeDidChange:(UISlider*)slider
{
CGFloat percentage = slider.value * 100;
[self seekToTime:percentage];
}
#pragma mark - Public API
- (void)seekToTime:(CGFloat)percentComplete
{
CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100;
[self updateElapsedTimeLabel:seconds];
[_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)];
if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) {
[self togglePlayPause];
}
}
- (void)play
{
[_videoNode play];
}
- (void)pause
{
[_videoNode pause];
}
- (BOOL)isPlaying
{
return [_videoNode isPlaying];
}
- (void)resetToPlaceholder
{
[_videoNode resetToPlaceholder];
}
- (NSArray *)controlsForLayoutSpec
{
NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count];
if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) {
[controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]];
}
if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) {
[controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]];
}
if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) {
[controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]];
}
if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) {
[controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]];
}
if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) {
[controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]];
}
return controls;
}
#pragma mark - Layout
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
CGSize maxSize = constrainedSize.max;
// Prevent crashes through if infinite width or height
if (isinf(maxSize.width) || isinf(maxSize.height)) {
ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode");
maxSize = CGSizeZero;
}
_videoNode.style.preferredSize = maxSize;
ASLayoutSpec *layoutSpec;
if (_delegateFlags.delegateLayoutSpecForControls) {
layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize];
} else {
layoutSpec = [self defaultLayoutSpecThatFits:maxSize];
}
NSMutableArray *children = [[NSMutableArray alloc] init];
if (_spinnerNode) {
ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode];
centerLayoutSpec.style.preferredSize = maxSize;
[children addObject:centerLayoutSpec];
}
ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec];
overlaySpec.style.preferredSize = maxSize;
[children addObject:overlaySpec];
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:children];
}
- (ASLayoutSpec *)defaultLayoutSpecThatFits:(CGSize)maxSize
{
_scrubberNode.style.preferredSize = CGSizeMake(maxSize.width, 44.0);
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.style.flexGrow = 1.0;
ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:10.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsCenter
children: [self controlsForLayoutSpec] ];
controlbarSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec];
controlbarInsetSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:0.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
children:@[spacer,controlbarInsetSpec]];
return mainVerticalStack;
}
#pragma mark - Properties
- (id<ASVideoPlayerNodeDelegate>)delegate
{
return _delegate;
}
- (void)setDelegate:(id<ASVideoPlayerNodeDelegate>)delegate
{
if (delegate == _delegate) {
return;
}
_delegate = delegate;
if (_delegate == nil) {
memset(&_delegateFlags, 0, sizeof(_delegateFlags));
} else {
_delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)];
_delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)];
_delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)];
_delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)];
_delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)];
_delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)];
_delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)];
_delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)];
_delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)];
_delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)];
_delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)];
_delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)];
_delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)];
_delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)];
_delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)];
_delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)];
_delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)];
_delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)];
_delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)];
_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:)];
}
}
- (void)setControlsDisabled:(BOOL)controlsDisabled
{
if (_controlsDisabled == controlsDisabled) {
return;
}
_controlsDisabled = controlsDisabled;
if (_controlsDisabled && _cachedControls.count > 0) {
[self removeControls];
} else if (!_controlsDisabled) {
[self createControls];
}
}
- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay
{
if (_shouldAutoPlay == shouldAutoPlay) {
return;
}
_shouldAutoPlay = shouldAutoPlay;
_videoNode.shouldAutoplay = _shouldAutoPlay;
}
- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat
{
if (_shouldAutoRepeat == shouldAutoRepeat) {
return;
}
_shouldAutoRepeat = shouldAutoRepeat;
_videoNode.shouldAutorepeat = _shouldAutoRepeat;
}
- (void)setMuted:(BOOL)muted
{
if (_muted == muted) {
return;
}
_muted = muted;
_videoNode.muted = _muted;
}
- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale
{
if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) {
return;
}
_periodicTimeObserverTimescale = periodicTimeObserverTimescale;
_videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale;
}
- (NSString *)gravity
{
if (_gravity == nil) {
_gravity = _videoNode.gravity;
}
return _gravity;
}
- (void)setGravity:(NSString *)gravity
{
if (_gravity == gravity) {
return;
}
_gravity = gravity;
_videoNode.gravity = _gravity;
}
- (ASVideoNodePlayerState)playerState
{
return _videoNode.playerState;
}
- (BOOL)shouldAggressivelyRecoverFromStall
{
return _videoNode.shouldAggressivelyRecoverFromStall;
}
- (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL
{
_videoNode.URL = placeholderImageURL;
}
- (NSURL*) placeholderImageURL
{
return _videoNode.URL;
}
- (ASVideoNode*)videoNode
{
return _videoNode;
}
- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall
{
if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) {
return;
}
_shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall;
_videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall;
}
#pragma mark - Helpers
- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type
{
if (!CMTIME_IS_VALID(time)) {
return @"00:00";
}
if (_delegateFlags.delegateTimeLabelAttributedString) {
return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time];
}
NSUInteger dTotalSeconds = CMTimeGetSeconds(time);
NSUInteger dHours = floor(dTotalSeconds / 3600);
NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60);
NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60);
NSString *videoDurationText;
if (dHours > 0) {
videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds];
} else {
videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds];
}
return videoDurationText;
}
@end
#endif // TARGET_OS_IOS
#endif
#endif