ASVideoNode.mm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. //
  2. // ASVideoNode.mm
  3. // AsyncDisplayKit
  4. //
  5. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  6. // This source code is licensed under the BSD-style license found in the
  7. // LICENSE file in the root directory of this source tree. An additional grant
  8. // of patent rights can be found in the PATENTS file in the same directory.
  9. //
  10. #if TARGET_OS_IOS
  11. #import <AVFoundation/AVFoundation.h>
  12. #import "ASDisplayNode+FrameworkSubclasses.h"
  13. #import "ASVideoNode.h"
  14. #import "ASEqualityHelpers.h"
  15. #import "ASInternalHelpers.h"
  16. #import "ASDisplayNodeExtras.h"
  17. static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) {
  18. return ASObjectIsEqual(asset1, asset2)
  19. || ([asset1 isKindOfClass:[AVURLAsset class]]
  20. && [asset2 isKindOfClass:[AVURLAsset class]]
  21. && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL));
  22. }
  23. static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
  24. if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
  25. return UIViewContentModeScaleAspectFill;
  26. } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) {
  27. return UIViewContentModeScaleToFill;
  28. } else {
  29. return UIViewContentModeScaleAspectFit;
  30. }
  31. }
  32. static void *ASVideoNodeContext = &ASVideoNodeContext;
  33. static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
  34. static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty";
  35. static NSString * const kStatus = @"status";
  36. static NSString * const kRate = @"rate";
  37. @interface ASVideoNode ()
  38. {
  39. struct {
  40. unsigned int delegateVideNodeShouldChangePlayerStateTo:1;
  41. unsigned int delegateVideoDidPlayToEnd:1;
  42. unsigned int delegateDidTapVideoNode:1;
  43. unsigned int delegateVideoNodeWillChangePlayerStateToState:1;
  44. unsigned int delegateVideoNodeDidPlayToTimeInterval:1;
  45. unsigned int delegateVideoNodeDidStartInitialLoading:1;
  46. unsigned int delegateVideoNodeDidFinishInitialLoading:1;
  47. unsigned int delegateVideoNodeDidSetCurrentItem:1;
  48. unsigned int delegateVideoNodeDidStallAtTimeInterval:1;
  49. unsigned int delegateVideoNodeDidRecoverFromStall:1;
  50. } _delegateFlags;
  51. BOOL _shouldBePlaying;
  52. BOOL _shouldAutorepeat;
  53. BOOL _shouldAutoplay;
  54. BOOL _shouldAggressivelyRecoverFromStall;
  55. BOOL _muted;
  56. ASVideoNodePlayerState _playerState;
  57. AVAsset *_asset;
  58. NSURL *_assetURL;
  59. AVVideoComposition *_videoComposition;
  60. AVAudioMix *_audioMix;
  61. AVPlayerItem *_currentPlayerItem;
  62. AVPlayer *_player;
  63. id _timeObserver;
  64. int32_t _periodicTimeObserverTimescale;
  65. CMTime _timeObserverInterval;
  66. CMTime _lastPlaybackTime;
  67. ASDisplayNode *_playerNode;
  68. NSString *_gravity;
  69. }
  70. @end
  71. @implementation ASVideoNode
  72. @dynamic delegate;
  73. // TODO: Support preview images with HTTP Live Streaming videos.
  74. #pragma mark - Construction and Layout
  75. - (instancetype)init
  76. {
  77. if (!(self = [super init])) {
  78. return nil;
  79. }
  80. self.gravity = AVLayerVideoGravityResizeAspect;
  81. _periodicTimeObserverTimescale = 10000;
  82. [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
  83. _lastPlaybackTime = kCMTimeZero;
  84. return self;
  85. }
  86. - (ASDisplayNode *)constructPlayerNode
  87. {
  88. ASVideoNode * __weak weakSelf = self;
  89. return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
  90. AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init];
  91. playerLayer.player = weakSelf.player;
  92. playerLayer.videoGravity = weakSelf.gravity;
  93. return playerLayer;
  94. }];
  95. }
  96. - (AVPlayerItem *)constructPlayerItem
  97. {
  98. ASDisplayNodeAssertMainThread();
  99. ASDN::MutexLocker l(__instanceLock__);
  100. AVPlayerItem *playerItem = nil;
  101. if (_assetURL != nil) {
  102. playerItem = [[AVPlayerItem alloc] initWithURL:_assetURL];
  103. _asset = [playerItem asset];
  104. } else {
  105. playerItem = [[AVPlayerItem alloc] initWithAsset:_asset];
  106. }
  107. playerItem.videoComposition = _videoComposition;
  108. playerItem.audioMix = _audioMix;
  109. return playerItem;
  110. }
  111. - (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray<NSString *> *)requestedKeys
  112. {
  113. ASDisplayNodeAssertMainThread();
  114. for (NSString *key in requestedKeys) {
  115. NSError *error = nil;
  116. AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error];
  117. if (keyStatus == AVKeyValueStatusFailed) {
  118. NSLog(@"Asset loading failed with error: %@", error);
  119. }
  120. }
  121. if ([asset isPlayable] == NO) {
  122. NSLog(@"Asset is not playable.");
  123. return;
  124. }
  125. AVPlayerItem *playerItem = [self constructPlayerItem];
  126. [self setCurrentItem:playerItem];
  127. if (_player != nil) {
  128. [_player replaceCurrentItemWithPlayerItem:playerItem];
  129. } else {
  130. self.player = [AVPlayer playerWithPlayerItem:playerItem];
  131. }
  132. if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) {
  133. [self.delegate videoNode:self didSetCurrentItem:playerItem];
  134. }
  135. if (self.image == nil && self.URL == nil) {
  136. [self generatePlaceholderImage];
  137. }
  138. __weak __typeof(self) weakSelf = self;
  139. _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale);
  140. _timeObserver = [_player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){
  141. [weakSelf periodicTimeObserver:time];
  142. }];
  143. }
  144. - (void)addPlayerItemObservers:(AVPlayerItem *)playerItem
  145. {
  146. if (playerItem == nil) {
  147. return;
  148. }
  149. [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext];
  150. [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
  151. [playerItem addObserver:self forKeyPath:kplaybackBufferEmpty options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
  152. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  153. [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
  154. [notificationCenter addObserver:self selector:@selector(videoNodeDidStall:) name:AVPlayerItemPlaybackStalledNotification object:playerItem];
  155. [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
  156. [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
  157. }
  158. - (void)removePlayerItemObservers:(AVPlayerItem *)playerItem
  159. {
  160. @try {
  161. [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext];
  162. [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext];
  163. [playerItem removeObserver:self forKeyPath:kplaybackBufferEmpty context:ASVideoNodeContext];
  164. }
  165. @catch (NSException * __unused exception) {
  166. NSLog(@"Unnecessary KVO removal");
  167. }
  168. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  169. [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
  170. [notificationCenter removeObserver:self name: AVPlayerItemPlaybackStalledNotification object:playerItem];
  171. [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
  172. [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
  173. }
  174. - (void)addPlayerObservers:(AVPlayer *)player
  175. {
  176. if (player == nil) {
  177. return;
  178. }
  179. [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
  180. }
  181. - (void) removePlayerObservers:(AVPlayer *)player
  182. {
  183. @try {
  184. [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext];
  185. }
  186. @catch (NSException * __unused exception) {
  187. NSLog(@"Unnecessary KVO removal");
  188. }
  189. }
  190. - (void)layout
  191. {
  192. [super layout];
  193. // The _playerNode wraps AVPlayerLayer, and therefore should extend across the entire bounds.
  194. _playerNode.frame = self.bounds;
  195. }
  196. - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
  197. {
  198. ASDN::MutexLocker l(__instanceLock__);
  199. CGSize calculatedSize = constrainedSize;
  200. // Prevent crashes through if infinite width or height
  201. if (isinf(calculatedSize.width) || isinf(calculatedSize.height)) {
  202. ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode");
  203. calculatedSize = CGSizeZero;
  204. }
  205. if (_playerNode) {
  206. _playerNode.style.preferredSize = calculatedSize;
  207. [_playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)];
  208. }
  209. return calculatedSize;
  210. }
  211. - (void)generatePlaceholderImage
  212. {
  213. ASVideoNode * __weak weakSelf = self;
  214. AVAsset *asset = self.asset;
  215. [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) {
  216. ASPerformBlockOnMainThread(^{
  217. // Ensure the asset hasn't changed since the image request was made
  218. if (ASAssetIsEqual(weakSelf.asset, asset)) {
  219. [weakSelf setVideoPlaceholderImage:image];
  220. }
  221. });
  222. }];
  223. }
  224. - (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler
  225. {
  226. ASPerformBlockOnBackgroundThread(^{
  227. AVAsset *asset = self.asset;
  228. // Skip the asset image generation if we don't have any tracks available that are capable of supporting it
  229. NSArray<AVAssetTrack *>* visualAssetArray = [asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual];
  230. if (visualAssetArray.count == 0) {
  231. completionHandler(nil);
  232. return;
  233. }
  234. AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
  235. previewImageGenerator.appliesPreferredTrackTransform = YES;
  236. previewImageGenerator.videoComposition = _videoComposition;
  237. [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]]
  238. completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
  239. if (error != nil && result != AVAssetImageGeneratorCancelled) {
  240. NSLog(@"Asset preview image generation failed with error: %@", error);
  241. }
  242. completionHandler(image ? [UIImage imageWithCGImage:image] : nil);
  243. }];
  244. });
  245. }
  246. - (void)setVideoPlaceholderImage:(UIImage *)image
  247. {
  248. ASDN::MutexLocker l(__instanceLock__);
  249. if (image != nil) {
  250. self.contentMode = ASContentModeFromVideoGravity(_gravity);
  251. }
  252. self.image = image;
  253. }
  254. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  255. {
  256. ASDN::MutexLocker l(__instanceLock__);
  257. if (object == _currentPlayerItem) {
  258. if ([keyPath isEqualToString:kStatus]) {
  259. if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
  260. if (self.playerState != ASVideoNodePlayerStatePlaying) {
  261. self.playerState = ASVideoNodePlayerStateReadyToPlay;
  262. if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) {
  263. [self play];
  264. }
  265. }
  266. // If we don't yet have a placeholder image update it now that we should have data available for it
  267. if (self.image == nil && self.URL == nil) {
  268. [self generatePlaceholderImage];
  269. }
  270. }
  271. } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) {
  272. BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue];
  273. if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) {
  274. return;
  275. }
  276. if (!likelyToKeepUp) {
  277. self.playerState = ASVideoNodePlayerStateLoading;
  278. } else if (self.playerState != ASVideoNodePlayerStateFinished) {
  279. self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying;
  280. }
  281. if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) {
  282. if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) {
  283. [self.delegate videoNodeDidRecoverFromStall:self];
  284. }
  285. [self play]; // autoresume after buffer catches up
  286. }
  287. } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) {
  288. if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == YES && ASInterfaceStateIncludesVisible(self.interfaceState)) {
  289. self.playerState = ASVideoNodePlayerStateLoading;
  290. }
  291. }
  292. } else if (object == _player) {
  293. if ([keyPath isEqualToString:kRate]) {
  294. if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) {
  295. if (self.playerState == ASVideoNodePlayerStatePlaying) {
  296. self.playerState = ASVideoNodePlayerStatePaused;
  297. }
  298. } else {
  299. self.playerState = ASVideoNodePlayerStatePlaying;
  300. }
  301. }
  302. }
  303. }
  304. - (void)tapped
  305. {
  306. if (_delegateFlags.delegateDidTapVideoNode) {
  307. [self.delegate didTapVideoNode:self];
  308. } else {
  309. if (_shouldBePlaying) {
  310. [self pause];
  311. } else {
  312. [self play];
  313. }
  314. }
  315. }
  316. - (void)didEnterPreloadState
  317. {
  318. [super didEnterPreloadState];
  319. ASDN::MutexLocker l(__instanceLock__);
  320. AVAsset *asset = self.asset;
  321. // Return immediately if the asset is nil;
  322. if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) {
  323. return;
  324. }
  325. self.playerState = ASVideoNodePlayerStateLoading;
  326. if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) {
  327. [self.delegate videoNodeDidStartInitialLoading:self];
  328. }
  329. NSArray<NSString *> *requestedKeys = @[@"playable"];
  330. [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{
  331. ASPerformBlockOnMainThread(^{
  332. if (_delegateFlags.delegateVideoNodeDidFinishInitialLoading) {
  333. [self.delegate videoNodeDidFinishInitialLoading:self];
  334. }
  335. [self prepareToPlayAsset:asset withKeys:requestedKeys];
  336. });
  337. }];
  338. }
  339. - (void)periodicTimeObserver:(CMTime)time
  340. {
  341. NSTimeInterval timeInSeconds = CMTimeGetSeconds(time);
  342. if (timeInSeconds <= 0) {
  343. return;
  344. }
  345. if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) {
  346. [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds];
  347. }
  348. }
  349. - (void)didExitPreloadState
  350. {
  351. [super didExitPreloadState];
  352. {
  353. ASDN::MutexLocker l(__instanceLock__);
  354. self.player = nil;
  355. self.currentItem = nil;
  356. self.playerState = ASVideoNodePlayerStateUnknown;
  357. }
  358. }
  359. - (void)didEnterVisibleState
  360. {
  361. [super didEnterVisibleState];
  362. ASDN::MutexLocker l(__instanceLock__);
  363. if (_shouldBePlaying || _shouldAutoplay) {
  364. if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) {
  365. [_player seekToTime:_lastPlaybackTime];
  366. }
  367. [self play];
  368. }
  369. }
  370. - (void)didExitVisibleState
  371. {
  372. [super didExitVisibleState];
  373. ASDN::MutexLocker l(__instanceLock__);
  374. if (_shouldBePlaying) {
  375. [self pause];
  376. if (_player != nil && CMTIME_IS_VALID(_player.currentTime)) {
  377. _lastPlaybackTime = _player.currentTime;
  378. }
  379. _shouldBePlaying = YES;
  380. }
  381. }
  382. #pragma mark - Video Properties
  383. - (void)setPlayerState:(ASVideoNodePlayerState)playerState
  384. {
  385. ASDN::MutexLocker l(__instanceLock__);
  386. ASVideoNodePlayerState oldState = _playerState;
  387. if (oldState == playerState) {
  388. return;
  389. }
  390. if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) {
  391. [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState];
  392. }
  393. _playerState = playerState;
  394. }
  395. - (void)setAssetURL:(NSURL *)assetURL
  396. {
  397. ASDisplayNodeAssertMainThread();
  398. if (ASObjectIsEqual(assetURL, self.assetURL) == NO) {
  399. [self setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL];
  400. }
  401. }
  402. - (NSURL *)assetURL
  403. {
  404. ASDN::MutexLocker l(__instanceLock__);
  405. if (_assetURL != nil) {
  406. return _assetURL;
  407. } else if ([_asset isKindOfClass:AVURLAsset.class]) {
  408. return ((AVURLAsset *)_asset).URL;
  409. }
  410. return nil;
  411. }
  412. - (void)setAsset:(AVAsset *)asset
  413. {
  414. ASDisplayNodeAssertMainThread();
  415. if (ASAssetIsEqual(asset, self.asset) == NO) {
  416. [self setAndFetchAsset:asset url:nil];
  417. }
  418. }
  419. - (AVAsset *)asset
  420. {
  421. ASDN::MutexLocker l(__instanceLock__);
  422. return _asset;
  423. }
  424. - (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL
  425. {
  426. ASDisplayNodeAssertMainThread();
  427. [self didExitPreloadState];
  428. {
  429. ASDN::MutexLocker l(__instanceLock__);
  430. self.videoPlaceholderImage = nil;
  431. _asset = asset;
  432. _assetURL = assetURL;
  433. }
  434. [self setNeedsPreload];
  435. }
  436. - (void)setVideoComposition:(AVVideoComposition *)videoComposition
  437. {
  438. ASDN::MutexLocker l(__instanceLock__);
  439. _videoComposition = videoComposition;
  440. _currentPlayerItem.videoComposition = videoComposition;
  441. }
  442. - (AVVideoComposition *)videoComposition
  443. {
  444. ASDN::MutexLocker l(__instanceLock__);
  445. return _videoComposition;
  446. }
  447. - (void)setAudioMix:(AVAudioMix *)audioMix
  448. {
  449. ASDN::MutexLocker l(__instanceLock__);
  450. _audioMix = audioMix;
  451. _currentPlayerItem.audioMix = audioMix;
  452. }
  453. - (AVAudioMix *)audioMix
  454. {
  455. ASDN::MutexLocker l(__instanceLock__);
  456. return _audioMix;
  457. }
  458. - (AVPlayer *)player
  459. {
  460. ASDN::MutexLocker l(__instanceLock__);
  461. return _player;
  462. }
  463. - (AVPlayerLayer *)playerLayer
  464. {
  465. ASDN::MutexLocker l(__instanceLock__);
  466. return (AVPlayerLayer *)_playerNode.layer;
  467. }
  468. - (void)setDelegate:(id<ASVideoNodeDelegate>)delegate
  469. {
  470. [super setDelegate:delegate];
  471. if (delegate == nil) {
  472. memset(&_delegateFlags, 0, sizeof(_delegateFlags));
  473. } else {
  474. _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)];
  475. _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)];
  476. _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)];
  477. _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)];
  478. _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)];
  479. _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)];
  480. _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)];
  481. _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)];
  482. _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)];
  483. _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)];
  484. }
  485. }
  486. - (void)setGravity:(NSString *)gravity
  487. {
  488. ASDN::MutexLocker l(__instanceLock__);
  489. if (_playerNode.isNodeLoaded) {
  490. ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity;
  491. }
  492. self.contentMode = ASContentModeFromVideoGravity(gravity);
  493. _gravity = gravity;
  494. }
  495. - (NSString *)gravity
  496. {
  497. ASDN::MutexLocker l(__instanceLock__);
  498. return _gravity;
  499. }
  500. - (BOOL)muted
  501. {
  502. ASDN::MutexLocker l(__instanceLock__);
  503. return _muted;
  504. }
  505. - (void)setMuted:(BOOL)muted
  506. {
  507. ASDN::MutexLocker l(__instanceLock__);
  508. _player.muted = muted;
  509. _muted = muted;
  510. }
  511. #pragma mark - Video Playback
  512. - (void)play
  513. {
  514. __instanceLock__.lock();
  515. if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) {
  516. __instanceLock__.unlock();
  517. return;
  518. }
  519. if (_player == nil) {
  520. __instanceLock__.unlock();
  521. [self setNeedsPreload];
  522. __instanceLock__.lock();
  523. }
  524. if (_playerNode == nil) {
  525. _playerNode = [self constructPlayerNode];
  526. __instanceLock__.unlock();
  527. [self addSubnode:_playerNode];
  528. __instanceLock__.lock();
  529. [self setNeedsLayout];
  530. }
  531. [_player play];
  532. _shouldBePlaying = YES;
  533. __instanceLock__.unlock();
  534. }
  535. - (BOOL)ready
  536. {
  537. return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay;
  538. }
  539. - (void)pause
  540. {
  541. ASDN::MutexLocker l(__instanceLock__);
  542. if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) {
  543. return;
  544. }
  545. [_player pause];
  546. _shouldBePlaying = NO;
  547. }
  548. - (BOOL)isPlaying
  549. {
  550. ASDN::MutexLocker l(__instanceLock__);
  551. return (_player.rate > 0 && !_player.error);
  552. }
  553. - (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state
  554. {
  555. if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) {
  556. if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) {
  557. return NO;
  558. }
  559. }
  560. return YES;
  561. }
  562. - (void)resetToPlaceholder
  563. {
  564. ASDN::MutexLocker l(__instanceLock__);
  565. if (_playerNode != nil) {
  566. [_playerNode removeFromSupernode];
  567. _playerNode = nil;
  568. }
  569. [_player seekToTime:kCMTimeZero];
  570. [self pause];
  571. }
  572. #pragma mark - Playback observers
  573. - (void)didPlayToEnd:(NSNotification *)notification
  574. {
  575. self.playerState = ASVideoNodePlayerStateFinished;
  576. if (_delegateFlags.delegateVideoDidPlayToEnd) {
  577. [self.delegate videoDidPlayToEnd:self];
  578. }
  579. if (_shouldAutorepeat) {
  580. [_player seekToTime:kCMTimeZero];
  581. [self play];
  582. } else {
  583. [self pause];
  584. }
  585. }
  586. - (void)videoNodeDidStall:(NSNotification *)notification
  587. {
  588. self.playerState = ASVideoNodePlayerStateLoading;
  589. if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) {
  590. [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)];
  591. }
  592. }
  593. - (void)errorWhilePlaying:(NSNotification *)notification
  594. {
  595. if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) {
  596. NSLog(@"Failed to play video");
  597. } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) {
  598. AVPlayerItem *item = (AVPlayerItem *)notification.object;
  599. AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject;
  600. NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error,
  601. (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status]));
  602. NSLog(@"Item is %@", item);
  603. if (logEvent) {
  604. NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment);
  605. }
  606. }
  607. }
  608. #pragma mark - Internal Properties
  609. - (AVPlayerItem *)currentItem
  610. {
  611. ASDN::MutexLocker l(__instanceLock__);
  612. return _currentPlayerItem;
  613. }
  614. - (void)setCurrentItem:(AVPlayerItem *)currentItem
  615. {
  616. ASDN::MutexLocker l(__instanceLock__);
  617. [self removePlayerItemObservers:_currentPlayerItem];
  618. _currentPlayerItem = currentItem;
  619. if (currentItem != nil) {
  620. [self addPlayerItemObservers:currentItem];
  621. }
  622. }
  623. - (ASDisplayNode *)playerNode
  624. {
  625. ASDN::MutexLocker l(__instanceLock__);
  626. return _playerNode;
  627. }
  628. - (void)setPlayerNode:(ASDisplayNode *)playerNode
  629. {
  630. ASDN::MutexLocker l(__instanceLock__);
  631. _playerNode = playerNode;
  632. [self setNeedsLayout];
  633. }
  634. - (void)setPlayer:(AVPlayer *)player
  635. {
  636. ASDN::MutexLocker l(__instanceLock__);
  637. [self removePlayerObservers:_player];
  638. _player = player;
  639. player.muted = _muted;
  640. ((AVPlayerLayer *)_playerNode.layer).player = player;
  641. if (player != nil) {
  642. [self addPlayerObservers:player];
  643. }
  644. }
  645. - (BOOL)shouldBePlaying
  646. {
  647. ASDN::MutexLocker l(__instanceLock__);
  648. return _shouldBePlaying;
  649. }
  650. - (void)setShouldBePlaying:(BOOL)shouldBePlaying
  651. {
  652. ASDN::MutexLocker l(__instanceLock__);
  653. _shouldBePlaying = shouldBePlaying;
  654. }
  655. #pragma mark - Lifecycle
  656. - (void)dealloc
  657. {
  658. [_player removeTimeObserver:_timeObserver];
  659. _timeObserver = nil;
  660. [self removePlayerItemObservers:_currentPlayerItem];
  661. [self removePlayerObservers:_player];
  662. }
  663. @end
  664. #endif