CTAssetScrollView.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. /*
  2. MIT License (MIT)
  3. Copyright (c) 2015 Clement CN Tsang
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. #import <PureLayout/PureLayout.h>
  21. #import "CTAssetScrollView.h"
  22. #import "CTAssetPlayButton.h"
  23. #import "PHAsset+CTAssetsPickerController.h"
  24. #import "NSBundle+CTAssetsPickerController.h"
  25. #import "UIImage+CTAssetsPickerController.h"
  26. NSString * const CTAssetScrollViewDidTapNotification = @"CTAssetScrollViewDidTapNotification";
  27. NSString * const CTAssetScrollViewPlayerWillPlayNotification = @"CTAssetScrollViewPlayerWillPlayNotification";
  28. NSString * const CTAssetScrollViewPlayerWillPauseNotification = @"CTAssetScrollViewPlayerWillPauseNotification";
  29. @interface CTAssetScrollView ()
  30. <UIScrollViewDelegate, UIGestureRecognizerDelegate>
  31. @property (nonatomic, strong) PHAsset *asset;
  32. @property (nonatomic, strong) UIImage *image;
  33. @property (nonatomic, strong) AVPlayer *player;
  34. @property (nonatomic, assign) BOOL didLoadPlayerItem;
  35. @property (nonatomic, assign) CGFloat perspectiveZoomScale;
  36. @property (nonatomic, strong) UIImageView *imageView;
  37. @property (nonatomic, strong) UIProgressView *progressView;
  38. @property (nonatomic, strong) UIActivityIndicatorView *activityView;
  39. @property (nonatomic, strong) CTAssetPlayButton *playButton;
  40. @property (nonatomic, strong) CTAssetSelectionButton *selectionButton;
  41. @property (nonatomic, assign) BOOL shouldUpdateConstraints;
  42. @property (nonatomic, assign) BOOL didSetupConstraints;
  43. @end
  44. @implementation CTAssetScrollView
  45. - (instancetype)initWithFrame:(CGRect)frame
  46. {
  47. self = [super initWithFrame:frame];
  48. if (self)
  49. {
  50. _shouldUpdateConstraints = YES;
  51. self.allowsSelection = NO;
  52. self.showsVerticalScrollIndicator = NO;
  53. self.showsHorizontalScrollIndicator = NO;
  54. self.bouncesZoom = YES;
  55. self.decelerationRate = UIScrollViewDecelerationRateFast;
  56. self.delegate = self;
  57. [self setupViews];
  58. [self addGestureRecognizers];
  59. }
  60. return self;
  61. }
  62. - (void)dealloc
  63. {
  64. [self removePlayerNotificationObserver];
  65. [self removePlayerLoadedTimeRangesObserver];
  66. [self removePlayerRateObserver];
  67. }
  68. #pragma mark - Setup
  69. - (void)setupViews
  70. {
  71. UIImageView *imageView = [UIImageView new];
  72. imageView.isAccessibilityElement = YES;
  73. imageView.accessibilityTraits = UIAccessibilityTraitImage;
  74. self.imageView = imageView;
  75. [self addSubview:self.imageView];
  76. UIProgressView *progressView =
  77. [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
  78. self.progressView = progressView;
  79. [self addSubview:self.progressView];
  80. UIActivityIndicatorView *activityView =
  81. [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  82. self.activityView = activityView;
  83. [self addSubview:self.activityView];
  84. CTAssetPlayButton *playButton = [CTAssetPlayButton newAutoLayoutView];
  85. self.playButton = playButton;
  86. [self addSubview:self.playButton];
  87. CTAssetSelectionButton *selectionButton = [CTAssetSelectionButton newAutoLayoutView];
  88. self.selectionButton = selectionButton;
  89. [self addSubview:self.selectionButton];
  90. }
  91. #pragma mark - Update auto layout constraints
  92. - (void)updateConstraints
  93. {
  94. if (!self.didSetupConstraints)
  95. {
  96. [self updateSelectionButtonIfNeeded];
  97. [self autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
  98. [self updateProgressConstraints];
  99. [self updateActivityConstraints];
  100. [self updateButtonsConstraints];
  101. self.didSetupConstraints = YES;
  102. }
  103. [self updateContentFrame];
  104. [super updateConstraints];
  105. }
  106. - (void)updateSelectionButtonIfNeeded
  107. {
  108. if (!self.allowsSelection)
  109. {
  110. [self.selectionButton removeFromSuperview];
  111. self.selectionButton = nil;
  112. }
  113. }
  114. - (void)updateProgressConstraints
  115. {
  116. [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow forConstraints:^{
  117. [self.progressView autoConstrainAttribute:ALAttributeLeading toAttribute:ALAttributeLeading ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
  118. [self.progressView autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
  119. [self.progressView autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.superview withMultiplier:1 relation:NSLayoutRelationEqual];
  120. }];
  121. [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
  122. [self.progressView autoConstrainAttribute:ALAttributeLeading toAttribute:ALAttributeLeading ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationGreaterThanOrEqual];
  123. [self.progressView autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationLessThanOrEqual];
  124. [self.progressView autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.imageView withMultiplier:1 relation:NSLayoutRelationLessThanOrEqual];
  125. }];
  126. }
  127. - (void)updateActivityConstraints
  128. {
  129. [self.activityView autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
  130. [self.activityView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
  131. }
  132. - (void)updateButtonsConstraints
  133. {
  134. [self.playButton autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
  135. [self.playButton autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
  136. [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow forConstraints:^{
  137. [self.selectionButton autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.superview withOffset:-self.layoutMargins.right relation:NSLayoutRelationEqual];
  138. [self.selectionButton autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.superview withOffset:-self.layoutMargins.bottom relation:NSLayoutRelationEqual];
  139. }];
  140. [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
  141. [self.selectionButton autoConstrainAttribute:ALAttributeTrailing toAttribute:ALAttributeTrailing ofView:self.imageView withOffset:-self.layoutMargins.right relation:NSLayoutRelationLessThanOrEqual];
  142. [self.selectionButton autoConstrainAttribute:ALAttributeBottom toAttribute:ALAttributeBottom ofView:self.imageView withOffset:-self.layoutMargins.bottom relation:NSLayoutRelationLessThanOrEqual];
  143. }];
  144. }
  145. - (void)updateContentFrame
  146. {
  147. CGSize boundsSize = self.bounds.size;
  148. CGFloat w = self.zoomScale * self.asset.pixelWidth;
  149. CGFloat h = self.zoomScale * self.asset.pixelHeight;
  150. CGFloat dx = (boundsSize.width - w) / 2.0;
  151. CGFloat dy = (boundsSize.height - h) / 2.0;
  152. self.contentOffset = CGPointZero;
  153. self.imageView.frame = CGRectMake(dx, dy, w, h);
  154. }
  155. #pragma mark - Start/stop loading animation
  156. - (void)startActivityAnimating
  157. {
  158. [self.playButton setHidden:YES];
  159. [self.selectionButton setHidden:YES];
  160. [self.activityView startAnimating];
  161. [self postPlayerWillPlayNotification];
  162. }
  163. - (void)stopActivityAnimating
  164. {
  165. [self.playButton setHidden:NO];
  166. [self.selectionButton setHidden:NO];
  167. [self.activityView stopAnimating];
  168. [self postPlayerWillPauseNotification];
  169. }
  170. #pragma mark - Set progress
  171. - (void)setProgress:(CGFloat)progress
  172. {
  173. #if !defined(CT_APP_EXTENSIONS)
  174. [UIApplication sharedApplication].networkActivityIndicatorVisible = progress < 1;
  175. #endif
  176. [self.progressView setProgress:progress animated:(progress < 1)];
  177. self.progressView.hidden = progress == 1;
  178. }
  179. // To mimic image downloading progress
  180. // as PHImageRequestOptions does not work as expected
  181. - (void)mimicProgress
  182. {
  183. CGFloat progress = self.progressView.progress;
  184. if (progress < 0.95)
  185. {
  186. int lowerbound = progress * 100 + 1;
  187. int upperbound = 95;
  188. int random = lowerbound + arc4random() % (upperbound - lowerbound);
  189. CGFloat randomProgress = random / 100.0f;
  190. [self setProgress:randomProgress];
  191. NSInteger randomDelay = 1 + arc4random() % (3 - 1);
  192. [self performSelector:@selector(mimicProgress) withObject:nil afterDelay:randomDelay];
  193. }
  194. }
  195. #pragma mark - asset size
  196. - (CGSize)assetSize
  197. {
  198. return CGSizeMake(self.asset.pixelWidth, self.asset.pixelHeight);
  199. }
  200. #pragma mark - Bind asset image
  201. - (void)bind:(PHAsset *)asset image:(UIImage *)image requestInfo:(NSDictionary *)info
  202. {
  203. self.asset = asset;
  204. self.imageView.accessibilityLabel = asset.accessibilityLabel;
  205. self.playButton.hidden = [asset ctassetsPickerIsPhoto];
  206. BOOL isDegraded = [info[PHImageResultIsDegradedKey] boolValue];
  207. if (self.image == nil || !isDegraded)
  208. {
  209. BOOL zoom = (!self.image);
  210. self.image = image;
  211. self.imageView.image = image;
  212. if (isDegraded)
  213. [self mimicProgress];
  214. else
  215. [self setProgress:1];
  216. [self setNeedsUpdateConstraints];
  217. [self updateConstraintsIfNeeded];
  218. [self updateZoomScalesAndZoom:zoom];
  219. }
  220. }
  221. #pragma mark - Bind player item
  222. - (void)bind:(AVPlayerItem *)playerItem requestInfo:(NSDictionary *)info
  223. {
  224. [self unbindPlayerItem];
  225. AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
  226. AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
  227. playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
  228. CALayer *layer = self.imageView.layer;
  229. [layer addSublayer:playerLayer];
  230. playerLayer.frame = layer.bounds;
  231. self.player = player;
  232. [self addPlayerNotificationObserver];
  233. [self addPlayerLoadedTimeRangesObserver];
  234. }
  235. - (void)unbindPlayerItem
  236. {
  237. [self removePlayerNotificationObserver];
  238. [self removePlayerLoadedTimeRangesObserver];
  239. for (CALayer *layer in self.imageView.layer.sublayers)
  240. [layer removeFromSuperlayer];
  241. self.player = nil;
  242. }
  243. #pragma mark - Upate zoom scales
  244. - (void)updateZoomScalesAndZoom:(BOOL)zoom
  245. {
  246. if (!self.asset)
  247. return;
  248. CGSize assetSize = [self assetSize];
  249. CGSize boundsSize = self.bounds.size;
  250. CGFloat xScale = boundsSize.width / assetSize.width; //scale needed to perfectly fit the image width-wise
  251. CGFloat yScale = boundsSize.height / assetSize.height; //scale needed to perfectly fit the image height-wise
  252. CGFloat minScale = MIN(xScale, yScale);
  253. CGFloat maxScale = 3.0 * minScale;
  254. if ([self.asset ctassetsPickerIsVideo])
  255. {
  256. self.minimumZoomScale = minScale;
  257. self.maximumZoomScale = minScale;
  258. }
  259. else
  260. {
  261. self.minimumZoomScale = minScale;
  262. self.maximumZoomScale = maxScale;
  263. }
  264. // update perspective zoom scale
  265. self.perspectiveZoomScale = (boundsSize.width > boundsSize.height) ? xScale : yScale;
  266. if (zoom)
  267. [self zoomToInitialScale];
  268. }
  269. #pragma mark - Zoom
  270. - (void)zoomToInitialScale
  271. {
  272. if ([self canPerspectiveZoom])
  273. [self zoomToPerspectiveZoomScaleAnimated:NO];
  274. else
  275. [self zoomToMinimumZoomScaleAnimated:NO];
  276. }
  277. - (void)zoomToMinimumZoomScaleAnimated:(BOOL)animated
  278. {
  279. [self setZoomScale:self.minimumZoomScale animated:animated];
  280. }
  281. - (void)zoomToMaximumZoomScaleWithGestureRecognizer:(UITapGestureRecognizer *)recognizer
  282. {
  283. CGRect zoomRect = [self zoomRectWithScale:self.maximumZoomScale withCenter:[recognizer locationInView:recognizer.view]];
  284. self.shouldUpdateConstraints = NO;
  285. [UIView animateWithDuration:0.3 animations:^{
  286. [self zoomToRect:zoomRect animated:NO];
  287. CGRect frame = self.imageView.frame;
  288. frame.origin.x = 0;
  289. frame.origin.y = 0;
  290. self.imageView.frame = frame;
  291. }];
  292. }
  293. #pragma mark - Perspective zoom
  294. - (BOOL)canPerspectiveZoom
  295. {
  296. CGSize assetSize = [self assetSize];
  297. CGSize boundsSize = self.bounds.size;
  298. CGFloat assetRatio = assetSize.width / assetSize.height;
  299. CGFloat boundsRatio = boundsSize.width / boundsSize.height;
  300. // can perform perspective zoom when the difference of aspect ratios is smaller than 20%
  301. return (fabs( (assetRatio - boundsRatio) / boundsRatio ) < 0.2f);
  302. }
  303. - (void)zoomToPerspectiveZoomScaleAnimated:(BOOL)animated;
  304. {
  305. CGRect zoomRect = [self zoomRectWithScale:self.perspectiveZoomScale];
  306. [self zoomToRect:zoomRect animated:animated];
  307. }
  308. - (CGRect)zoomRectWithScale:(CGFloat)scale
  309. {
  310. CGSize targetSize;
  311. targetSize.width = self.bounds.size.width / scale;
  312. targetSize.height = self.bounds.size.height / scale;
  313. CGPoint targetOrigin;
  314. targetOrigin.x = (self.asset.pixelWidth - targetSize.width) / 2.0;
  315. targetOrigin.y = (self.asset.pixelHeight - targetSize.height) / 2.0;
  316. CGRect zoomRect;
  317. zoomRect.origin = targetOrigin;
  318. zoomRect.size = targetSize;
  319. return zoomRect;
  320. }
  321. #pragma mark - Zoom with gesture recognizer
  322. - (void)zoomWithGestureRecognizer:(UITapGestureRecognizer *)recognizer
  323. {
  324. if (self.minimumZoomScale == self.maximumZoomScale)
  325. return;
  326. if ([self canPerspectiveZoom])
  327. {
  328. if ((self.zoomScale >= self.minimumZoomScale && self.zoomScale < self.perspectiveZoomScale) ||
  329. (self.zoomScale <= self.maximumZoomScale && self.zoomScale > self.perspectiveZoomScale))
  330. [self zoomToPerspectiveZoomScaleAnimated:YES];
  331. else
  332. [self zoomToMaximumZoomScaleWithGestureRecognizer:recognizer];
  333. return;
  334. }
  335. if (self.zoomScale < self.maximumZoomScale)
  336. [self zoomToMaximumZoomScaleWithGestureRecognizer:recognizer];
  337. else
  338. [self zoomToMinimumZoomScaleAnimated:YES];
  339. }
  340. - (CGRect)zoomRectWithScale:(CGFloat)scale withCenter:(CGPoint)center
  341. {
  342. center = [self.imageView convertPoint:center fromView:self];
  343. CGRect zoomRect;
  344. zoomRect.size.height = self.imageView.frame.size.height / scale;
  345. zoomRect.size.width = self.imageView.frame.size.width / scale;
  346. zoomRect.origin.x = center.x - ((zoomRect.size.width / 2.0));
  347. zoomRect.origin.y = center.y - ((zoomRect.size.height / 2.0));
  348. return zoomRect;
  349. }
  350. #pragma mark - Gesture recognizers
  351. - (void)addGestureRecognizers
  352. {
  353. UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapping:)];
  354. UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapping:)];
  355. doubleTap.numberOfTapsRequired = 2.0;
  356. [singleTap requireGestureRecognizerToFail:doubleTap];
  357. singleTap.delegate = self;
  358. doubleTap.delegate = self;
  359. [self addGestureRecognizer:singleTap];
  360. [self addGestureRecognizer:doubleTap];
  361. }
  362. #pragma mark - Handle tappings
  363. - (void)handleTapping:(UITapGestureRecognizer *)recognizer
  364. {
  365. [[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewDidTapNotification object:recognizer];
  366. if (recognizer.numberOfTapsRequired == 2)
  367. [self zoomWithGestureRecognizer:recognizer];
  368. }
  369. #pragma mark - Scroll view delegate
  370. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
  371. {
  372. return self.imageView;
  373. }
  374. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
  375. {
  376. self.shouldUpdateConstraints = YES;
  377. }
  378. - (void)scrollViewDidZoom:(UIScrollView *)scrollView
  379. {
  380. self.scrollEnabled = self.zoomScale != self.perspectiveZoomScale;
  381. if (self.shouldUpdateConstraints)
  382. {
  383. [self setNeedsUpdateConstraints];
  384. [self updateConstraintsIfNeeded];
  385. }
  386. }
  387. #pragma mark - Gesture recognizer delegate
  388. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
  389. {
  390. return !([touch.view isDescendantOfView:self.playButton] || [touch.view isDescendantOfView:self.selectionButton]);
  391. }
  392. #pragma mark - Notification observer
  393. - (void)addPlayerNotificationObserver
  394. {
  395. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  396. [center addObserver:self
  397. selector:@selector(applicationWillResignActive:)
  398. name:UIApplicationWillResignActiveNotification
  399. object:nil];
  400. }
  401. - (void)removePlayerNotificationObserver
  402. {
  403. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  404. [center removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
  405. }
  406. #pragma mark - Video player item key-value observer
  407. - (void)addPlayerLoadedTimeRangesObserver
  408. {
  409. [self.player addObserver:self
  410. forKeyPath:@"currentItem.loadedTimeRanges"
  411. options:NSKeyValueObservingOptionNew
  412. context:nil];
  413. }
  414. - (void)removePlayerLoadedTimeRangesObserver
  415. {
  416. @try {
  417. [self.player removeObserver:self forKeyPath:@"currentItem.loadedTimeRanges"];
  418. }
  419. @catch (NSException *exception) {
  420. // do nothing
  421. }
  422. }
  423. - (void)addPlayerRateObserver
  424. {
  425. [self.player addObserver:self
  426. forKeyPath:@"rate"
  427. options:NSKeyValueObservingOptionNew
  428. context:nil];
  429. }
  430. - (void)removePlayerRateObserver
  431. {
  432. @try {
  433. [self.player removeObserver:self forKeyPath:@"rate"];
  434. }
  435. @catch (NSException *exception) {
  436. // do nothing
  437. }
  438. }
  439. #pragma mark - Video playback Key-Value changed
  440. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  441. {
  442. if (object == self.player && [keyPath isEqual:@"currentItem.loadedTimeRanges"])
  443. {
  444. NSArray *timeRanges = change[NSKeyValueChangeNewKey];
  445. if (timeRanges && timeRanges.count)
  446. {
  447. CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
  448. if (CMTIME_COMPARE_INLINE(timeRange.duration, ==, self.player.currentItem.duration))
  449. [self performSelector:@selector(playerDidLoadItem:) withObject:object];
  450. }
  451. }
  452. if (object == self.player && [keyPath isEqual:@"rate"])
  453. {
  454. CGFloat rate = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
  455. if (rate > 0)
  456. [self performSelector:@selector(playerDidPlay:) withObject:object];
  457. if (rate == 0)
  458. [self performSelector:@selector(playerDidPause:) withObject:object];
  459. }
  460. }
  461. #pragma mark - Notifications
  462. - (void)postPlayerWillPlayNotification
  463. {
  464. [[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewPlayerWillPlayNotification object:nil];
  465. }
  466. - (void)postPlayerWillPauseNotification
  467. {
  468. [[NSNotificationCenter defaultCenter] postNotificationName:CTAssetScrollViewPlayerWillPauseNotification object:nil];
  469. }
  470. #pragma mark - Playback events
  471. - (void)applicationWillResignActive:(NSNotification *)notification
  472. {
  473. [self pauseVideo];
  474. }
  475. - (void)playerDidPlay:(id)sender
  476. {
  477. [self setProgress:1];
  478. [self.playButton setHidden:YES];
  479. [self.selectionButton setHidden:YES];
  480. [self.activityView stopAnimating];
  481. }
  482. - (void)playerDidPause:(id)sender
  483. {
  484. [self.playButton setHidden:NO];
  485. [self.selectionButton setHidden:NO];
  486. }
  487. - (void)playerDidLoadItem:(id)sender
  488. {
  489. if (!self.didLoadPlayerItem)
  490. {
  491. [self setDidLoadPlayerItem:YES];
  492. [self addPlayerRateObserver];
  493. [self.activityView stopAnimating];
  494. [self playVideo];
  495. }
  496. }
  497. #pragma mark - Playback
  498. - (void)playVideo
  499. {
  500. if (self.didLoadPlayerItem)
  501. {
  502. if (CMTIME_COMPARE_INLINE(self.player.currentTime, == , self.player.currentItem.duration))
  503. [self.player seekToTime:kCMTimeZero];
  504. [self postPlayerWillPlayNotification];
  505. [self.player play];
  506. }
  507. }
  508. - (void)pauseVideo
  509. {
  510. if (self.didLoadPlayerItem)
  511. {
  512. [self postPlayerWillPauseNotification];
  513. [self.player pause];
  514. }
  515. else
  516. {
  517. [self stopActivityAnimating];
  518. [self unbindPlayerItem];
  519. }
  520. }
  521. @end