ASVideoPlayerNode.mm 30 KB

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