mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 16:29:55 +00:00
[ASVideoPlayerNode] Fixes and improvements (#3135)
* Improvements for ASVideoPlayer, especially around asset handling and API * Change legacy URL of videos * Address comments
This commit is contained in:
parent
f5951d906e
commit
2df392261e
@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, assign) BOOL controlsDisabled;
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible;
|
||||
@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state. This flag does nothing.");
|
||||
|
||||
#pragma mark - ASVideoNode property proxy
|
||||
/**
|
||||
@ -52,6 +52,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, assign, readonly) ASVideoNodePlayerState playerState;
|
||||
@property (nonatomic, assign, readwrite) BOOL shouldAggressivelyRecoverFromStall;
|
||||
@property (nullable, nonatomic, strong, readwrite) NSURL *placeholderImageURL;
|
||||
|
||||
@property (nullable, nonatomic, strong, readwrite) AVAsset *asset;
|
||||
/**
|
||||
** @abstract The URL with which the asset was initialized.
|
||||
** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset.
|
||||
** @return Current URL the asset was initialized or nil if no URL was given.
|
||||
**/
|
||||
@property (nullable, nonatomic, strong, readwrite) NSURL *assetURL;
|
||||
|
||||
/// You should never set any value on the backing video node. Use exclusivively the video player node to set properties
|
||||
@property (nonatomic, strong, readonly) ASVideoNode *videoNode;
|
||||
|
||||
//! Defaults to 100
|
||||
@ -59,12 +69,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
//! Defaults to AVLayerVideoGravityResizeAspect
|
||||
@property (nonatomic, copy) NSString *gravity;
|
||||
|
||||
- (instancetype)initWithUrl:(NSURL*)url;
|
||||
- (instancetype)initWithAsset:(AVAsset*)asset;
|
||||
#pragma mark - Lifecycle
|
||||
- (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 Lifecycle Deprecated
|
||||
- (instancetype)initWithUrl:(NSURL *)url ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore.");
|
||||
- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore.");
|
||||
- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore.");
|
||||
- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore.");
|
||||
|
||||
#pragma mark - Public API
|
||||
- (void)seekToTime:(CGFloat)percentComplete;
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
|
||||
@interface ASVideoPlayerNode() <ASVideoNodeDelegate>
|
||||
@interface ASVideoPlayerNode() <ASVideoNodeDelegate, ASVideoPlayerNodeDelegate>
|
||||
{
|
||||
__weak id<ASVideoPlayerNodeDelegate> _delegate;
|
||||
|
||||
@ -53,11 +53,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1;
|
||||
} _delegateFlags;
|
||||
|
||||
NSURL *_url;
|
||||
AVAsset *_asset;
|
||||
AVVideoComposition *_videoComposition;
|
||||
AVAudioMix *_audioMix;
|
||||
// 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;
|
||||
@ -72,7 +74,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
ASStackLayoutSpec *_controlFlexGrowSpacerSpec;
|
||||
ASDisplayNode *_spinnerNode;
|
||||
|
||||
BOOL _loadAssetWhenNodeBecomesVisible;
|
||||
BOOL _isSeeking;
|
||||
CMTime _duration;
|
||||
|
||||
@ -95,122 +96,134 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
|
||||
@dynamic placeholderImageURL;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
[self _init];
|
||||
[self _initControlsAndVideoNode];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithUrl:(NSURL*)url
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_url = url;
|
||||
_asset = [AVAsset assetWithURL:_url];
|
||||
_loadAssetWhenNodeBecomesVisible = YES;
|
||||
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAsset:(AVAsset *)asset
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
if (!(self = [self init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_pendingAsset = asset;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
_asset = asset;
|
||||
_loadAssetWhenNodeBecomesVisible = YES;
|
||||
- (instancetype)initWithURL:(NSURL *)URL
|
||||
{
|
||||
return [self initWithAsset:[AVAsset assetWithURL:URL]];
|
||||
}
|
||||
|
||||
[self _init];
|
||||
- (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;
|
||||
}
|
||||
|
||||
-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix
|
||||
- (void)_initControlsAndVideoNode
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_defaultControlsColor = [UIColor whiteColor];
|
||||
_cachedControls = [[NSMutableDictionary alloc] init];
|
||||
|
||||
_videoNode = [[ASVideoNode alloc] init];
|
||||
_videoNode.delegate = self;
|
||||
[self addSubnode:_videoNode];
|
||||
}
|
||||
|
||||
_asset = asset;
|
||||
_videoComposition = videoComposition;
|
||||
_audioMix = audioMix;
|
||||
_loadAssetWhenNodeBecomesVisible = YES;
|
||||
#pragma mark Deprecated
|
||||
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
- (instancetype)initWithUrl:(NSURL *)url
|
||||
{
|
||||
return [self initWithURL:url];
|
||||
}
|
||||
|
||||
- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_url = url;
|
||||
_asset = [AVAsset assetWithURL:_url];
|
||||
_loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible;
|
||||
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
return [self initWithURL:url];
|
||||
}
|
||||
|
||||
- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_asset = asset;
|
||||
_loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible;
|
||||
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
return [self initWithAsset:asset];
|
||||
}
|
||||
|
||||
-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
|
||||
- (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;
|
||||
return [self initWithAsset:asset videoComposition:videoComposition audioMix:audioMix];
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
#pragma mark - Setter / Getter
|
||||
|
||||
- (void)setAssetURL:(NSURL *)assetURL
|
||||
{
|
||||
_defaultControlsColor = [UIColor whiteColor];
|
||||
_cachedControls = [[NSMutableDictionary alloc] init];
|
||||
|
||||
_videoNode = [[ASVideoNode alloc] init];
|
||||
_videoNode.delegate = self;
|
||||
if (_loadAssetWhenNodeBecomesVisible == NO) {
|
||||
_videoNode.asset = _asset;
|
||||
_videoNode.videoComposition = _videoComposition;
|
||||
_videoNode.audioMix = _audioMix;
|
||||
}
|
||||
[self addSubnode:_videoNode];
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
self.asset = [AVAsset assetWithURL:assetURL];
|
||||
}
|
||||
|
||||
- (NSURL *)assetURL
|
||||
{
|
||||
NSURL *url = nil;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if ([_pendingAsset isKindOfClass:AVURLAsset.class]) {
|
||||
url = ((AVURLAsset *)_pendingAsset).URL;
|
||||
}
|
||||
}
|
||||
|
||||
return url ?: _videoNode.assetURL;
|
||||
}
|
||||
|
||||
- (void)setAsset:(AVAsset *)asset
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
__instanceLock__.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
|
||||
__instanceLock__.unlock();
|
||||
_videoNode.asset = asset;
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingAsset = asset;
|
||||
__instanceLock__.unlock();
|
||||
}
|
||||
|
||||
- (AVAsset *)asset
|
||||
{
|
||||
AVAsset *asset = nil;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
asset = _pendingAsset;
|
||||
}
|
||||
return asset ?: _videoNode.asset;
|
||||
}
|
||||
|
||||
#pragma mark - ASDisplayNode
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
@ -220,38 +233,25 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didEnterVisibleState
|
||||
- (void)didEnterPreloadState
|
||||
{
|
||||
[super didEnterVisibleState];
|
||||
[super didEnterPreloadState];
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
if (_loadAssetWhenNodeBecomesVisible) {
|
||||
if (_asset != _videoNode.asset) {
|
||||
_videoNode.asset = _asset;
|
||||
}
|
||||
if (_videoComposition != _videoNode.videoComposition) {
|
||||
_videoNode.videoComposition = _videoComposition;
|
||||
}
|
||||
if (_audioMix != _videoNode.audioMix) {
|
||||
_videoNode.audioMix = _audioMix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)createDefaultControlElementArray
|
||||
{
|
||||
if (_delegateFlags.delegateNeededDefaultControls) {
|
||||
return [_delegate videoPlayerNodeNeededDefaultControls:self];
|
||||
AVAsset *pendingAsset = nil;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
pendingAsset = _pendingAsset;
|
||||
_pendingAsset = nil;
|
||||
}
|
||||
|
||||
return @[ @(ASVideoPlayerNodeControlTypePlaybackButton),
|
||||
@(ASVideoPlayerNodeControlTypeElapsedText),
|
||||
@(ASVideoPlayerNodeControlTypeScrubber),
|
||||
@(ASVideoPlayerNodeControlTypeDurationText) ];
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)createControls
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
@ -313,6 +313,18 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray *)createDefaultControlElementArray
|
||||
{
|
||||
if (_delegateFlags.delegateNeededDefaultControls) {
|
||||
return [_delegate videoPlayerNodeNeededDefaultControls:self];
|
||||
}
|
||||
|
||||
return @[ @(ASVideoPlayerNodeControlTypePlaybackButton),
|
||||
@(ASVideoPlayerNodeControlTypeElapsedText),
|
||||
@(ASVideoPlayerNodeControlTypeScrubber),
|
||||
@(ASVideoPlayerNodeControlTypeDurationText) ];
|
||||
}
|
||||
|
||||
- (void)removeControls
|
||||
{
|
||||
for (ASDisplayNode *node in [_cachedControls objectEnumerator]) {
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSString *videoUrlString = @"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov";
|
||||
NSString *videoUrlString = @"https://www.w3schools.com/html/mov_bbb.mp4";
|
||||
NSString *avatarUrlString = [NSString stringWithFormat:@"https://api.adorable.io/avatars/50/%@",[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||
|
||||
_title = @"Demo title";
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
_muteButtonNode.style.height = ASDimensionMakeWithPoints(22.0);
|
||||
[_muteButtonNode addTarget:self action:@selector(didTapMuteButton) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
|
||||
_videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url loadAssetWhenNodeBecomesVisible:YES];
|
||||
_videoPlayerNode = [[ASVideoPlayerNode alloc] initWithURL:_videoModel.url];
|
||||
_videoPlayerNode.delegate = self;
|
||||
_videoPlayerNode.backgroundColor = [UIColor blackColor];
|
||||
[self addSubnode:_videoPlayerNode];
|
||||
@ -142,7 +142,6 @@
|
||||
- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer
|
||||
{
|
||||
if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) {
|
||||
NSLog(@"TRANSITION");
|
||||
_videoPlayerNode.controlsDisabled = !_videoPlayerNode.controlsDisabled;
|
||||
[_videoPlayerNode pause];
|
||||
} else {
|
||||
|
||||
@ -87,9 +87,9 @@
|
||||
return _videoPlayerNode;
|
||||
}
|
||||
|
||||
NSURL *fileUrl = [NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"];
|
||||
NSURL *fileUrl = [NSURL URLWithString:@"https://www.w3schools.com/html/mov_bbb.mp4"];
|
||||
|
||||
_videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:fileUrl];
|
||||
_videoPlayerNode = [[ASVideoPlayerNode alloc] initWithURL:fileUrl];
|
||||
_videoPlayerNode.delegate = self;
|
||||
// _videoPlayerNode.disableControls = YES;
|
||||
//
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user