ASVideoPlayerNode.mm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. //
  2. // ASVideoPlayerNode.mm
  3. // AsyncDisplayKit
  4. //
  5. // Created by Erekle on 5/6/16.
  6. //
  7. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  8. // This source code is licensed under the BSD-style license found in the
  9. // LICENSE file in the root directory of this source tree. An additional grant
  10. // of patent rights can be found in the PATENTS file in the same directory.
  11. //
  12. #import "ASVideoPlayerNode.h"
  13. #import "ASDefaultPlaybackButton.h"
  14. #import "ASDisplayNode+FrameworkSubclasses.h"
  15. static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
  16. @interface ASVideoPlayerNode() <ASVideoNodeDelegate>
  17. {
  18. __weak id<ASVideoPlayerNodeDelegate> _delegate;
  19. struct {
  20. unsigned int delegateNeededDefaultControls:1;
  21. unsigned int delegateCustomControls:1;
  22. unsigned int delegateSpinnerTintColor:1;
  23. unsigned int delegateSpinnerStyle:1;
  24. unsigned int delegatePlaybackButtonTint:1;
  25. unsigned int delegateFullScreenButtonImage:1;
  26. unsigned int delegateScrubberMaximumTrackTintColor:1;
  27. unsigned int delegateScrubberMinimumTrackTintColor:1;
  28. unsigned int delegateScrubberThumbTintColor:1;
  29. unsigned int delegateScrubberThumbImage:1;
  30. unsigned int delegateTimeLabelAttributes:1;
  31. unsigned int delegateTimeLabelAttributedString:1;
  32. unsigned int delegateLayoutSpecForControls:1;
  33. unsigned int delegateVideoNodeDidPlayToTime:1;
  34. unsigned int delegateVideoNodeWillChangeState:1;
  35. unsigned int delegateVideoNodeShouldChangeState:1;
  36. unsigned int delegateVideoNodePlaybackDidFinish:1;
  37. unsigned int delegateDidTapVideoPlayerNode:1;
  38. unsigned int delegateDidTapFullScreenButtonNode:1;
  39. unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1;
  40. unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1;
  41. unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1;
  42. unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1;
  43. unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1;
  44. } _delegateFlags;
  45. NSURL *_url;
  46. AVAsset *_asset;
  47. AVVideoComposition *_videoComposition;
  48. AVAudioMix *_audioMix;
  49. ASVideoNode *_videoNode;
  50. NSArray *_neededDefaultControls;
  51. NSMutableDictionary *_cachedControls;
  52. ASDefaultPlaybackButton *_playbackButtonNode;
  53. ASButtonNode *_fullScreenButtonNode;
  54. ASTextNode *_elapsedTextNode;
  55. ASTextNode *_durationTextNode;
  56. ASDisplayNode *_scrubberNode;
  57. ASStackLayoutSpec *_controlFlexGrowSpacerSpec;
  58. ASDisplayNode *_spinnerNode;
  59. BOOL _loadAssetWhenNodeBecomesVisible;
  60. BOOL _isSeeking;
  61. CMTime _duration;
  62. BOOL _controlsDisabled;
  63. BOOL _shouldAutoPlay;
  64. BOOL _shouldAutoRepeat;
  65. BOOL _muted;
  66. int32_t _periodicTimeObserverTimescale;
  67. NSString *_gravity;
  68. BOOL _shouldAggressivelyRecoverFromStall;
  69. UIColor *_defaultControlsColor;
  70. }
  71. @end
  72. @implementation ASVideoPlayerNode
  73. @dynamic placeholderImageURL;
  74. - (instancetype)init
  75. {
  76. if (!(self = [super init])) {
  77. return nil;
  78. }
  79. [self _init];
  80. return self;
  81. }
  82. - (instancetype)initWithUrl:(NSURL*)url
  83. {
  84. if (!(self = [super init])) {
  85. return nil;
  86. }
  87. _url = url;
  88. _asset = [AVAsset assetWithURL:_url];
  89. _loadAssetWhenNodeBecomesVisible = YES;
  90. [self _init];
  91. return self;
  92. }
  93. - (instancetype)initWithAsset:(AVAsset *)asset
  94. {
  95. if (!(self = [super init])) {
  96. return nil;
  97. }
  98. _asset = asset;
  99. _loadAssetWhenNodeBecomesVisible = YES;
  100. [self _init];
  101. return self;
  102. }
  103. -(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix
  104. {
  105. if (!(self = [super init])) {
  106. return nil;
  107. }
  108. _asset = asset;
  109. _videoComposition = videoComposition;
  110. _audioMix = audioMix;
  111. _loadAssetWhenNodeBecomesVisible = YES;
  112. [self _init];
  113. return self;
  114. }
  115. - (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
  116. {
  117. if (!(self = [super init])) {
  118. return nil;
  119. }
  120. _url = url;
  121. _asset = [AVAsset assetWithURL:_url];
  122. _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible;
  123. [self _init];
  124. return self;
  125. }
  126. - (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
  127. {
  128. if (!(self = [super init])) {
  129. return nil;
  130. }
  131. _asset = asset;
  132. _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible;
  133. [self _init];
  134. return self;
  135. }
  136. -(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible
  137. {
  138. if (!(self = [super init])) {
  139. return nil;
  140. }
  141. _asset = asset;
  142. _videoComposition = videoComposition;
  143. _audioMix = audioMix;
  144. _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible;
  145. [self _init];
  146. return self;
  147. }
  148. - (void)_init
  149. {
  150. _defaultControlsColor = [UIColor whiteColor];
  151. _cachedControls = [[NSMutableDictionary alloc] init];
  152. _videoNode = [[ASVideoNode alloc] init];
  153. _videoNode.delegate = self;
  154. if (_loadAssetWhenNodeBecomesVisible == NO) {
  155. _videoNode.asset = _asset;
  156. _videoNode.videoComposition = _videoComposition;
  157. _videoNode.audioMix = _audioMix;
  158. }
  159. [self addSubnode:_videoNode];
  160. }
  161. - (void)didLoad
  162. {
  163. [super didLoad];
  164. {
  165. ASDN::MutexLocker l(__instanceLock__);
  166. [self createControls];
  167. }
  168. }
  169. - (void)didEnterVisibleState
  170. {
  171. [super didEnterVisibleState];
  172. ASDN::MutexLocker l(__instanceLock__);
  173. if (_loadAssetWhenNodeBecomesVisible) {
  174. if (_asset != _videoNode.asset) {
  175. _videoNode.asset = _asset;
  176. }
  177. if (_videoComposition != _videoNode.videoComposition) {
  178. _videoNode.videoComposition = _videoComposition;
  179. }
  180. if (_audioMix != _videoNode.audioMix) {
  181. _videoNode.audioMix = _audioMix;
  182. }
  183. }
  184. }
  185. - (NSArray *)createDefaultControlElementArray
  186. {
  187. if (_delegateFlags.delegateNeededDefaultControls) {
  188. return [_delegate videoPlayerNodeNeededDefaultControls:self];
  189. }
  190. return @[ @(ASVideoPlayerNodeControlTypePlaybackButton),
  191. @(ASVideoPlayerNodeControlTypeElapsedText),
  192. @(ASVideoPlayerNodeControlTypeScrubber),
  193. @(ASVideoPlayerNodeControlTypeDurationText) ];
  194. }
  195. #pragma mark - UI
  196. - (void)createControls
  197. {
  198. ASDN::MutexLocker l(__instanceLock__);
  199. if (_controlsDisabled) {
  200. return;
  201. }
  202. if (_neededDefaultControls == nil) {
  203. _neededDefaultControls = [self createDefaultControlElementArray];
  204. }
  205. if (_cachedControls == nil) {
  206. _cachedControls = [[NSMutableDictionary alloc] init];
  207. }
  208. for (id object in _neededDefaultControls) {
  209. ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue];
  210. switch (type) {
  211. case ASVideoPlayerNodeControlTypePlaybackButton:
  212. [self createPlaybackButton];
  213. break;
  214. case ASVideoPlayerNodeControlTypeElapsedText:
  215. [self createElapsedTextField];
  216. break;
  217. case ASVideoPlayerNodeControlTypeDurationText:
  218. [self createDurationTextField];
  219. break;
  220. case ASVideoPlayerNodeControlTypeScrubber:
  221. [self createScrubber];
  222. break;
  223. case ASVideoPlayerNodeControlTypeFullScreenButton:
  224. [self createFullScreenButton];
  225. break;
  226. case ASVideoPlayerNodeControlTypeFlexGrowSpacer:
  227. [self createControlFlexGrowSpacer];
  228. break;
  229. default:
  230. break;
  231. }
  232. }
  233. if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) {
  234. NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self];
  235. for (id key in customControls) {
  236. id node = customControls[key];
  237. if (![node isKindOfClass:[ASDisplayNode class]]) {
  238. continue;
  239. }
  240. [self addSubnode:node];
  241. [_cachedControls setObject:node forKey:key];
  242. }
  243. }
  244. ASPerformBlockOnMainThread(^{
  245. ASDN::MutexLocker l(__instanceLock__);
  246. [self setNeedsLayout];
  247. });
  248. }
  249. - (void)removeControls
  250. {
  251. for (ASDisplayNode *node in [_cachedControls objectEnumerator]) {
  252. [node removeFromSupernode];
  253. }
  254. [self cleanCachedControls];
  255. }
  256. - (void)cleanCachedControls
  257. {
  258. [_cachedControls removeAllObjects];
  259. _playbackButtonNode = nil;
  260. _fullScreenButtonNode = nil;
  261. _elapsedTextNode = nil;
  262. _durationTextNode = nil;
  263. _scrubberNode = nil;
  264. }
  265. - (void)createPlaybackButton
  266. {
  267. if (_playbackButtonNode == nil) {
  268. _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init];
  269. _playbackButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0);
  270. if (_delegateFlags.delegatePlaybackButtonTint) {
  271. _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self];
  272. } else {
  273. _playbackButtonNode.tintColor = _defaultControlsColor;
  274. }
  275. if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) {
  276. _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause;
  277. }
  278. [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside];
  279. [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)];
  280. }
  281. [self addSubnode:_playbackButtonNode];
  282. }
  283. - (void)createFullScreenButton
  284. {
  285. if (_fullScreenButtonNode == nil) {
  286. _fullScreenButtonNode = [[ASButtonNode alloc] init];
  287. _fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0);
  288. if (_delegateFlags.delegateFullScreenButtonImage) {
  289. [_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:ASControlStateNormal];
  290. }
  291. [_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside];
  292. [_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)];
  293. }
  294. [self addSubnode:_fullScreenButtonNode];
  295. }
  296. - (void)createElapsedTextField
  297. {
  298. if (_elapsedTextNode == nil) {
  299. _elapsedTextNode = [[ASTextNode alloc] init];
  300. _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00"
  301. forControlType:ASVideoPlayerNodeControlTypeElapsedText];
  302. _elapsedTextNode.truncationMode = NSLineBreakByClipping;
  303. [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)];
  304. }
  305. [self addSubnode:_elapsedTextNode];
  306. }
  307. - (void)createDurationTextField
  308. {
  309. if (_durationTextNode == nil) {
  310. _durationTextNode = [[ASTextNode alloc] init];
  311. _durationTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00"
  312. forControlType:ASVideoPlayerNodeControlTypeDurationText];
  313. _durationTextNode.truncationMode = NSLineBreakByClipping;
  314. [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)];
  315. }
  316. [self updateDurationTimeLabel];
  317. [self addSubnode:_durationTextNode];
  318. }
  319. - (void)createScrubber
  320. {
  321. if (_scrubberNode == nil) {
  322. __weak __typeof__(self) weakSelf = self;
  323. _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull {
  324. __typeof__(self) strongSelf = weakSelf;
  325. UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero];
  326. slider.minimumValue = 0.0;
  327. slider.maximumValue = 1.0;
  328. if (_delegateFlags.delegateScrubberMinimumTrackTintColor) {
  329. slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf];
  330. }
  331. if (_delegateFlags.delegateScrubberMaximumTrackTintColor) {
  332. slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf];
  333. }
  334. if (_delegateFlags.delegateScrubberThumbTintColor) {
  335. slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf];
  336. }
  337. if (_delegateFlags.delegateScrubberThumbImage) {
  338. UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf];
  339. [slider setThumbImage:thumbImage forState:UIControlStateNormal];
  340. }
  341. [slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown];
  342. [slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel];
  343. [slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged];
  344. return slider;
  345. }];
  346. _scrubberNode.style.flexShrink = 1;
  347. [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)];
  348. }
  349. [self addSubnode:_scrubberNode];
  350. }
  351. - (void)createControlFlexGrowSpacer
  352. {
  353. if (_controlFlexGrowSpacerSpec == nil) {
  354. _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init];
  355. _controlFlexGrowSpacerSpec.style.flexGrow = 1.0;
  356. }
  357. [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)];
  358. }
  359. - (void)updateDurationTimeLabel
  360. {
  361. if (!_durationTextNode) {
  362. return;
  363. }
  364. NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText];
  365. _durationTextNode.attributedText = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText];
  366. }
  367. - (void)updateElapsedTimeLabel:(NSTimeInterval)seconds
  368. {
  369. if (!_elapsedTextNode) {
  370. return;
  371. }
  372. NSString *formattedElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText];
  373. _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:formattedElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText];
  374. }
  375. - (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType
  376. {
  377. NSDictionary *options;
  378. if (_delegateFlags.delegateTimeLabelAttributes) {
  379. options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType];
  380. } else {
  381. options = @{
  382. NSFontAttributeName : [UIFont systemFontOfSize:12.0],
  383. NSForegroundColorAttributeName: _defaultControlsColor
  384. };
  385. }
  386. NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options];
  387. return attributedString;
  388. }
  389. #pragma mark - ASVideoNodeDelegate
  390. - (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState
  391. {
  392. if (_delegateFlags.delegateVideoNodeWillChangeState) {
  393. [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState];
  394. }
  395. if (toState == ASVideoNodePlayerStateReadyToPlay) {
  396. _duration = _videoNode.currentItem.duration;
  397. [self updateDurationTimeLabel];
  398. }
  399. if (toState == ASVideoNodePlayerStatePlaying) {
  400. _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause;
  401. [self removeSpinner];
  402. } else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) {
  403. _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay;
  404. }
  405. if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) {
  406. [self showSpinner];
  407. }
  408. if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) {
  409. [self removeSpinner];
  410. }
  411. }
  412. - (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state
  413. {
  414. if (_delegateFlags.delegateVideoNodeShouldChangeState) {
  415. return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state];
  416. }
  417. return YES;
  418. }
  419. - (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval
  420. {
  421. if (_delegateFlags.delegateVideoNodeDidPlayToTime) {
  422. [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime];
  423. }
  424. if (_isSeeking) {
  425. return;
  426. }
  427. if (_elapsedTextNode) {
  428. [self updateElapsedTimeLabel:timeInterval];
  429. }
  430. if (_scrubberNode) {
  431. [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO];
  432. }
  433. }
  434. - (void)videoDidPlayToEnd:(ASVideoNode *)videoNode
  435. {
  436. if (_delegateFlags.delegateVideoNodePlaybackDidFinish) {
  437. [_delegate videoPlayerNodeDidPlayToEnd:self];
  438. }
  439. }
  440. - (void)didTapVideoNode:(ASVideoNode *)videoNode
  441. {
  442. if (_delegateFlags.delegateDidTapVideoPlayerNode) {
  443. [_delegate didTapVideoPlayerNode:self];
  444. } else {
  445. [self togglePlayPause];
  446. }
  447. }
  448. - (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem
  449. {
  450. if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) {
  451. [_delegate videoPlayerNode:self didSetCurrentItem:currentItem];
  452. }
  453. }
  454. - (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval
  455. {
  456. if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) {
  457. [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval];
  458. }
  459. }
  460. - (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode
  461. {
  462. if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) {
  463. [_delegate videoPlayerNodeDidStartInitialLoading:self];
  464. }
  465. }
  466. - (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode
  467. {
  468. if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) {
  469. [_delegate videoPlayerNodeDidFinishInitialLoading:self];
  470. }
  471. }
  472. - (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode
  473. {
  474. if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) {
  475. [_delegate videoPlayerNodeDidRecoverFromStall:self];
  476. }
  477. }
  478. #pragma mark - Actions
  479. - (void)togglePlayPause
  480. {
  481. if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) {
  482. [_videoNode pause];
  483. } else {
  484. [_videoNode play];
  485. }
  486. }
  487. - (void)showSpinner
  488. {
  489. ASDN::MutexLocker l(__instanceLock__);
  490. if (!_spinnerNode) {
  491. __weak __typeof__(self) weakSelf = self;
  492. _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
  493. __typeof__(self) strongSelf = weakSelf;
  494. UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
  495. spinnnerView.backgroundColor = [UIColor clearColor];
  496. if (_delegateFlags.delegateSpinnerTintColor) {
  497. spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:strongSelf];
  498. } else {
  499. spinnnerView.color = _defaultControlsColor;
  500. }
  501. if (_delegateFlags.delegateSpinnerStyle) {
  502. spinnnerView.activityIndicatorViewStyle = [_delegate videoPlayerNodeSpinnerStyle:strongSelf];
  503. }
  504. return spinnnerView;
  505. }];
  506. _spinnerNode.style.preferredSize = CGSizeMake(44.0, 44.0);
  507. [self addSubnode:_spinnerNode];
  508. [self setNeedsLayout];
  509. }
  510. [(UIActivityIndicatorView *)_spinnerNode.view startAnimating];
  511. }
  512. - (void)removeSpinner
  513. {
  514. ASDN::MutexLocker l(__instanceLock__);
  515. if (!_spinnerNode) {
  516. return;
  517. }
  518. [_spinnerNode removeFromSupernode];
  519. _spinnerNode = nil;
  520. }
  521. - (void)didTapPlaybackButton:(ASControlNode*)node
  522. {
  523. [self togglePlayPause];
  524. }
  525. - (void)didTapFullScreenButton:(ASButtonNode*)node
  526. {
  527. [_delegate didTapFullScreenButtonNode:node];
  528. }
  529. - (void)beginSeek
  530. {
  531. _isSeeking = YES;
  532. }
  533. - (void)endSeek
  534. {
  535. _isSeeking = NO;
  536. }
  537. - (void)seekTimeDidChange:(UISlider*)slider
  538. {
  539. CGFloat percentage = slider.value * 100;
  540. [self seekToTime:percentage];
  541. }
  542. #pragma mark - Public API
  543. - (void)seekToTime:(CGFloat)percentComplete
  544. {
  545. CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100;
  546. [self updateElapsedTimeLabel:seconds];
  547. [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)];
  548. if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) {
  549. [self togglePlayPause];
  550. }
  551. }
  552. - (void)play
  553. {
  554. [_videoNode play];
  555. }
  556. - (void)pause
  557. {
  558. [_videoNode pause];
  559. }
  560. - (BOOL)isPlaying
  561. {
  562. return [_videoNode isPlaying];
  563. }
  564. - (void)resetToPlaceholder
  565. {
  566. [_videoNode resetToPlaceholder];
  567. }
  568. - (NSArray *)controlsForLayoutSpec
  569. {
  570. NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count];
  571. if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) {
  572. [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]];
  573. }
  574. if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) {
  575. [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]];
  576. }
  577. if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) {
  578. [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]];
  579. }
  580. if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) {
  581. [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]];
  582. }
  583. if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) {
  584. [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]];
  585. }
  586. return controls;
  587. }
  588. #pragma mark - Layout
  589. - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
  590. {
  591. CGSize maxSize = constrainedSize.max;
  592. // Prevent crashes through if infinite width or height
  593. if (isinf(maxSize.width) || isinf(maxSize.height)) {
  594. ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode");
  595. maxSize = CGSizeZero;
  596. }
  597. _videoNode.style.preferredSize = maxSize;
  598. ASLayoutSpec *layoutSpec;
  599. if (_delegateFlags.delegateLayoutSpecForControls) {
  600. layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize];
  601. } else {
  602. layoutSpec = [self defaultLayoutSpecThatFits:maxSize];
  603. }
  604. NSMutableArray *children = [[NSMutableArray alloc] init];
  605. if (_spinnerNode) {
  606. ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode];
  607. centerLayoutSpec.style.preferredSize = maxSize;
  608. [children addObject:centerLayoutSpec];
  609. }
  610. ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec];
  611. overlaySpec.style.preferredSize = maxSize;
  612. [children addObject:overlaySpec];
  613. return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:children];
  614. }
  615. - (ASLayoutSpec *)defaultLayoutSpecThatFits:(CGSize)maxSize
  616. {
  617. _scrubberNode.style.preferredSize = CGSizeMake(maxSize.width, 44.0);
  618. ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
  619. spacer.style.flexGrow = 1.0;
  620. ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
  621. spacing:10.0
  622. justifyContent:ASStackLayoutJustifyContentStart
  623. alignItems:ASStackLayoutAlignItemsCenter
  624. children: [self controlsForLayoutSpec] ];
  625. controlbarSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
  626. UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
  627. ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec];
  628. controlbarInsetSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
  629. ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
  630. spacing:0.0
  631. justifyContent:ASStackLayoutJustifyContentStart
  632. alignItems:ASStackLayoutAlignItemsStart
  633. children:@[spacer,controlbarInsetSpec]];
  634. return mainVerticalStack;
  635. }
  636. #pragma mark - Properties
  637. - (id<ASVideoPlayerNodeDelegate>)delegate
  638. {
  639. return _delegate;
  640. }
  641. - (void)setDelegate:(id<ASVideoPlayerNodeDelegate>)delegate
  642. {
  643. if (delegate == _delegate) {
  644. return;
  645. }
  646. _delegate = delegate;
  647. if (_delegate == nil) {
  648. memset(&_delegateFlags, 0, sizeof(_delegateFlags));
  649. } else {
  650. _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)];
  651. _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)];
  652. _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)];
  653. _delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)];
  654. _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)];
  655. _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)];
  656. _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)];
  657. _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)];
  658. _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)];
  659. _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)];
  660. _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)];
  661. _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)];
  662. _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)];
  663. _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)];
  664. _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)];
  665. _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)];
  666. _delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)];
  667. _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)];
  668. _delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)];
  669. _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)];
  670. _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)];
  671. _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)];
  672. _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)];
  673. _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)];
  674. }
  675. }
  676. - (void)setControlsDisabled:(BOOL)controlsDisabled
  677. {
  678. if (_controlsDisabled == controlsDisabled) {
  679. return;
  680. }
  681. _controlsDisabled = controlsDisabled;
  682. if (_controlsDisabled && _cachedControls.count > 0) {
  683. [self removeControls];
  684. } else if (!_controlsDisabled) {
  685. [self createControls];
  686. }
  687. }
  688. - (void)setShouldAutoPlay:(BOOL)shouldAutoPlay
  689. {
  690. if (_shouldAutoPlay == shouldAutoPlay) {
  691. return;
  692. }
  693. _shouldAutoPlay = shouldAutoPlay;
  694. _videoNode.shouldAutoplay = _shouldAutoPlay;
  695. }
  696. - (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat
  697. {
  698. if (_shouldAutoRepeat == shouldAutoRepeat) {
  699. return;
  700. }
  701. _shouldAutoRepeat = shouldAutoRepeat;
  702. _videoNode.shouldAutorepeat = _shouldAutoRepeat;
  703. }
  704. - (void)setMuted:(BOOL)muted
  705. {
  706. if (_muted == muted) {
  707. return;
  708. }
  709. _muted = muted;
  710. _videoNode.muted = _muted;
  711. }
  712. - (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale
  713. {
  714. if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) {
  715. return;
  716. }
  717. _periodicTimeObserverTimescale = periodicTimeObserverTimescale;
  718. _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale;
  719. }
  720. - (NSString *)gravity
  721. {
  722. if (_gravity == nil) {
  723. _gravity = _videoNode.gravity;
  724. }
  725. return _gravity;
  726. }
  727. - (void)setGravity:(NSString *)gravity
  728. {
  729. if (_gravity == gravity) {
  730. return;
  731. }
  732. _gravity = gravity;
  733. _videoNode.gravity = _gravity;
  734. }
  735. - (ASVideoNodePlayerState)playerState
  736. {
  737. return _videoNode.playerState;
  738. }
  739. - (BOOL)shouldAggressivelyRecoverFromStall
  740. {
  741. return _videoNode.shouldAggressivelyRecoverFromStall;
  742. }
  743. - (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL
  744. {
  745. _videoNode.URL = placeholderImageURL;
  746. }
  747. - (NSURL*) placeholderImageURL
  748. {
  749. return _videoNode.URL;
  750. }
  751. - (ASVideoNode*)videoNode
  752. {
  753. return _videoNode;
  754. }
  755. - (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall
  756. {
  757. if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) {
  758. return;
  759. }
  760. _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall;
  761. _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall;
  762. }
  763. #pragma mark - Helpers
  764. - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type
  765. {
  766. if (!CMTIME_IS_VALID(time)) {
  767. return @"00:00";
  768. }
  769. if (_delegateFlags.delegateTimeLabelAttributedString) {
  770. return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time];
  771. }
  772. NSUInteger dTotalSeconds = CMTimeGetSeconds(time);
  773. NSUInteger dHours = floor(dTotalSeconds / 3600);
  774. NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60);
  775. NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60);
  776. NSString *videoDurationText;
  777. if (dHours > 0) {
  778. videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds];
  779. } else {
  780. videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds];
  781. }
  782. return videoDurationText;
  783. }
  784. @end