YBIBVideoCell.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. //
  2. // YBIBVideoCell.m
  3. // YBImageBrowserDemo
  4. //
  5. // Created by 波儿菜 on 2019/7/10.
  6. // Copyright © 2019 杨波. All rights reserved.
  7. //
  8. #import "YBIBVideoCell.h"
  9. #import "YBIBVideoData.h"
  10. #import "YBIBVideoData+Internal.h"
  11. #import "YBIBCopywriter.h"
  12. #import "YBIBIconManager.h"
  13. #import <objc/runtime.h>
  14. #import "YBIBVideoCell+Internal.h"
  15. @interface NSObject (YBIBVideoPlayingRecord)
  16. - (void)ybib_videoPlayingAdd:(NSObject *)obj;
  17. - (void)ybib_videoPlayingRemove:(NSObject *)obj;
  18. - (BOOL)ybib_noVideoPlaying;
  19. @end
  20. @implementation NSObject (YBIBVideoPlayingRecord)
  21. - (NSMutableSet *)ybib_videoPlayingSet {
  22. static void *kRecordKey = &kRecordKey;
  23. NSMutableSet *set = objc_getAssociatedObject(self, kRecordKey);
  24. if (!set) {
  25. set = [NSMutableSet set];
  26. objc_setAssociatedObject(self, kRecordKey, set, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  27. }
  28. return set;
  29. }
  30. - (void)ybib_videoPlayingAdd:(NSObject *)obj {
  31. [[self ybib_videoPlayingSet] addObject:[NSString stringWithFormat:@"%p", obj]];
  32. }
  33. - (void)ybib_videoPlayingRemove:(NSObject *)obj {
  34. [[self ybib_videoPlayingSet] removeObject:[NSString stringWithFormat:@"%p", obj]];
  35. }
  36. - (BOOL)ybib_noVideoPlaying {
  37. return [self ybib_videoPlayingSet].count == 0;
  38. }
  39. @end
  40. @interface YBIBVideoCell () <YBIBVideoDataDelegate, YBIBVideoViewDelegate, UIGestureRecognizerDelegate>
  41. @end
  42. @implementation YBIBVideoCell {
  43. CGPoint _interactStartPoint;
  44. BOOL _interacting;
  45. }
  46. #pragma mark - life cycle
  47. - (void)dealloc {
  48. [self.yb_backView ybib_videoPlayingRemove:self];
  49. }
  50. - (instancetype)initWithFrame:(CGRect)frame {
  51. self = [super initWithFrame:frame];
  52. if (self) {
  53. [self initValue];
  54. [self.contentView addSubview:self.videoView];
  55. [self addGesture];
  56. }
  57. return self;
  58. }
  59. - (void)layoutSubviews {
  60. [super layoutSubviews];
  61. self.videoView.frame = self.bounds;
  62. }
  63. - (void)initValue {
  64. _interactStartPoint = CGPointZero;
  65. _interacting = NO;
  66. }
  67. - (void)prepareForReuse {
  68. ((YBIBVideoData *)self.yb_cellData).delegate = nil;
  69. self.videoView.thumbImageView.image = nil;
  70. [self hideAuxiliaryView];
  71. [self.videoView reset];
  72. self.videoView.asset = nil;
  73. [super prepareForReuse];
  74. }
  75. #pragma mark - private
  76. - (void)hideAuxiliaryView {
  77. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  78. [self.yb_auxiliaryViewHandler() yb_hideToastWithContainer:self];
  79. }
  80. - (void)updateImageLayoutWithOrientation:(UIDeviceOrientation)orientation previousImageSize:(CGSize)previousImageSize {
  81. YBIBVideoData *data = self.yb_cellData;
  82. UIImage *image = self.videoView.thumbImageView.image;
  83. CGSize imageSize = image.size;
  84. CGRect imageViewFrame = [data yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:imageSize orientation:orientation];
  85. CGFloat scale;
  86. if (previousImageSize.width > 0 && previousImageSize.height > 0) {
  87. scale = imageSize.width / imageSize.height - previousImageSize.width / previousImageSize.height;
  88. } else {
  89. scale = 0;
  90. }
  91. // '0.001' is admissible error.
  92. if (ABS(scale) <= 0.001) {
  93. self.videoView.thumbImageView.frame = imageViewFrame;
  94. } else {
  95. [UIView animateWithDuration:0.25 animations:^{
  96. self.videoView.thumbImageView.frame = imageViewFrame;
  97. }];
  98. }
  99. }
  100. - (void)hideBrowser {
  101. ((YBIBVideoData *)self.yb_cellData).delegate = nil;
  102. self.videoView.thumbImageView.hidden = NO;
  103. self.videoView.autoPlayCount = 0;
  104. [self.videoView reset];
  105. [self.videoView hideToolBar:YES];
  106. [self.videoView hidePlayButton];
  107. self.yb_hideBrowser();
  108. _interacting = NO;
  109. }
  110. - (void)hideToolViews:(BOOL)hide {
  111. if (hide) {
  112. self.yb_hideToolViews(YES);
  113. } else {
  114. if ([self.yb_backView ybib_noVideoPlaying]) {
  115. self.yb_hideToolViews(NO);
  116. }
  117. }
  118. }
  119. #pragma mark - <YBIBCellProtocol>
  120. @synthesize yb_currentOrientation = _yb_currentOrientation;
  121. @synthesize yb_containerSize = _yb_containerSize;
  122. @synthesize yb_backView = _yb_backView;
  123. @synthesize yb_collectionView = _yb_collectionView;
  124. @synthesize yb_isTransitioning = _yb_isTransitioning;
  125. @synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
  126. @synthesize yb_hideStatusBar = _yb_hideStatusBar;
  127. @synthesize yb_hideBrowser = _yb_hideBrowser;
  128. @synthesize yb_hideToolViews = _yb_hideToolViews;
  129. @synthesize yb_cellData = _yb_cellData;
  130. @synthesize yb_currentPage = _yb_currentPage;
  131. @synthesize yb_selfPage = _yb_selfPage;
  132. @synthesize yb_cellIsInCenter = _yb_cellIsInCenter;
  133. @synthesize yb_isRotating = _yb_isRotating;
  134. - (void)setYb_cellData:(id<YBIBDataProtocol>)yb_cellData {
  135. _yb_cellData = yb_cellData;
  136. YBIBVideoData *data = (YBIBVideoData *)yb_cellData;
  137. data.delegate = self;
  138. UIDeviceOrientation orientation = self.yb_currentOrientation();
  139. CGSize containerSize = self.yb_containerSize(orientation);
  140. [self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
  141. self.videoView.autoPlayCount = data.autoPlayCount;
  142. self.videoView.topBar.cancelButton.hidden = data.shouldHideForkButton;
  143. }
  144. - (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation {
  145. if (_interacting) [self restoreGestureInteractionWithDuration:0];
  146. }
  147. - (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation {
  148. [self updateImageLayoutWithOrientation:orientation previousImageSize:self.videoView.thumbImageView.image.size];
  149. CGSize containerSize = self.yb_containerSize(orientation);
  150. [self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
  151. }
  152. - (UIView *)yb_foregroundView {
  153. return self.videoView.thumbImageView;
  154. }
  155. - (void)yb_pageChanged {
  156. if (self.yb_currentPage() != self.yb_selfPage()) {
  157. [self.videoView reset];
  158. [self hideToolViews:NO];
  159. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  160. if (_interacting) [self restoreGestureInteractionWithDuration:0];
  161. self.videoView.needAutoPlay = NO;
  162. } else {
  163. self.videoView.needAutoPlay = YES;
  164. }
  165. }
  166. #pragma mark - <YBIBVideoDataDelegate>
  167. - (void)yb_startLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
  168. - (void)yb_finishLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
  169. - (void)yb_startLoadingFirstFrameForData:(YBIBVideoData *)data {
  170. if (!self.videoView.thumbImageView.image) {
  171. [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
  172. }
  173. }
  174. - (void)yb_finishLoadingFirstFrameForData:(YBIBVideoData *)data {
  175. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  176. }
  177. - (void)yb_videoData:(YBIBVideoData *)data downloadingWithProgress:(CGFloat)progress {
  178. [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self progress:progress];
  179. }
  180. - (void)yb_finishDownloadingForData:(YBIBVideoData *)data {
  181. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  182. }
  183. - (void)yb_videoData:(YBIBVideoData *)data readyForAVAsset:(AVAsset *)asset {
  184. self.videoView.asset = asset;
  185. }
  186. - (void)yb_videoData:(YBIBVideoData *)data readyForThumbImage:(UIImage *)image {
  187. if (!self.videoView.isPlaying) {
  188. self.videoView.thumbImageView.hidden = NO;
  189. }
  190. if (!self.videoView.thumbImageView.image) {
  191. CGSize previousSize = self.videoView.thumbImageView.image.size;
  192. self.videoView.thumbImageView.image = image;
  193. [self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:previousSize];
  194. }
  195. }
  196. - (void)yb_videoIsInvalidForData:(YBIBVideoData *)data {
  197. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  198. NSString *imageIsInvalid = [YBIBCopywriter sharedCopywriter].videoIsInvalid;
  199. if (self.videoView.thumbImageView.image) {
  200. [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:imageIsInvalid];
  201. } else {
  202. [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:imageIsInvalid];
  203. }
  204. }
  205. #pragma mark - <YBIBVideoViewDelegate>
  206. - (BOOL)yb_isFreezingForVideoView:(YBIBVideoView *)view {
  207. return self.yb_isTransitioning();
  208. }
  209. - (void)yb_preparePlayForVideoView:(YBIBVideoView *)view {
  210. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  211. if (!view.isPlaying && !view.isPlayFailed && self.yb_selfPage() == self.yb_currentPage()) {
  212. [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
  213. }
  214. });
  215. }
  216. - (void)yb_startPlayForVideoView:(YBIBVideoView *)view {
  217. self.videoView.thumbImageView.hidden = YES;
  218. [self.yb_backView ybib_videoPlayingAdd:self];
  219. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  220. [self hideToolViews:YES];
  221. }
  222. - (void)yb_didPlayToEndTimeForVideoView:(YBIBVideoView *)view {
  223. YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
  224. if (data.repeatPlayCount == NSUIntegerMax) {
  225. [view preparPlay];
  226. } else if (data.repeatPlayCount > 0) {
  227. --data.repeatPlayCount;
  228. [view preparPlay];
  229. } else {
  230. [self hideToolViews:NO];
  231. }
  232. }
  233. - (void)yb_finishPlayForVideoView:(YBIBVideoView *)view {
  234. [self.yb_backView ybib_videoPlayingRemove:self];
  235. [self hideToolViews:NO];
  236. }
  237. - (void)yb_playFailedForVideoView:(YBIBVideoView *)view {
  238. [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
  239. [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:YBIBCopywriter.sharedCopywriter.videoError];
  240. }
  241. - (void)yb_respondsToTapGestureForVideoView:(YBIBVideoView *)view {
  242. if (self.yb_isRotating()) return;
  243. YBIBVideoData *data = self.yb_cellData;
  244. if (data.singleTouchBlock) {
  245. data.singleTouchBlock(data);
  246. } else {
  247. [self hideBrowser];
  248. }
  249. }
  250. - (void)yb_cancelledForVideoView:(YBIBVideoView *)view {
  251. if (self.yb_isRotating()) return;
  252. [self hideBrowser];
  253. }
  254. - (CGSize)yb_containerSizeForVideoView:(YBIBVideoView *)view {
  255. return self.yb_containerSize(self.yb_currentOrientation());
  256. }
  257. - (void)yb_autoPlayCountChanged:(NSUInteger)count {
  258. YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
  259. data.autoPlayCount = count;
  260. }
  261. #pragma mark - <UIGestureRecognizerDelegate>
  262. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  263. return YES;
  264. }
  265. #pragma mark - gesture
  266. - (void)addGesture {
  267. UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToPanGesture:)];
  268. panGesture.cancelsTouchesInView = NO;
  269. panGesture.delegate = self;
  270. [self.videoView.tapGesture requireGestureRecognizerToFail:panGesture];
  271. [self.videoView addGestureRecognizer:panGesture];
  272. }
  273. - (void)respondsToPanGesture:(UIPanGestureRecognizer *)pan {
  274. if (self.yb_isRotating()) return;
  275. if ((!self.videoView.thumbImageView.image && !self.videoView.isPlaying)) return;
  276. YBIBInteractionProfile *profile = ((YBIBVideoData *)self.yb_cellData).interactionProfile;
  277. if (profile.disable) return;
  278. CGPoint point = [pan locationInView:self];
  279. CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
  280. if (pan.state == UIGestureRecognizerStateBegan) {
  281. _interactStartPoint = point;
  282. } else if (pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateRecognized || pan.state == UIGestureRecognizerStateFailed) {
  283. // End
  284. if (_interacting) {
  285. CGPoint velocity = [pan velocityInView:self.videoView];
  286. BOOL velocityArrive = ABS(velocity.y) > profile.dismissVelocityY;
  287. BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > containerSize.height * profile.dismissScale;
  288. BOOL shouldDismiss = distanceArrive || velocityArrive;
  289. if (shouldDismiss) {
  290. [self hideBrowser];
  291. } else {
  292. [self restoreGestureInteractionWithDuration:profile.restoreDuration];
  293. }
  294. }
  295. } else if (pan.state == UIGestureRecognizerStateChanged) {
  296. if (_interacting) {
  297. // Change
  298. self.videoView.center = point;
  299. CGFloat scale = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 1.2);
  300. if (scale > 1) scale = 1;
  301. if (scale < 0.35) scale = 0.35;
  302. self.videoView.transform = CGAffineTransformMakeScale(scale, scale);
  303. CGFloat alpha = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 0.7);
  304. if (alpha > 1) alpha = 1;
  305. if (alpha < 0) alpha = 0;
  306. self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:alpha];
  307. } else {
  308. // Start
  309. if (CGPointEqualToPoint(_interactStartPoint, CGPointZero) || self.yb_currentPage() != self.yb_selfPage() || !self.yb_cellIsInCenter() || self.videoView.actionBar.isTouchInside) return;
  310. CGPoint velocityPoint = [pan velocityInView:self.videoView];
  311. CGFloat triggerDistance = profile.triggerDistance;
  312. BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > triggerDistance && (ABS(point.x - _interactStartPoint.x) < triggerDistance && ABS(velocityPoint.x) < 500);
  313. BOOL shouldStart = distanceArrive;
  314. if (!shouldStart) return;
  315. [self.videoView hideToolBar:YES];
  316. _interactStartPoint = point;
  317. CGRect startFrame = self.videoView.bounds;
  318. CGFloat anchorX = (point.x - startFrame.origin.x) / startFrame.size.width,
  319. anchorY = (point.y - startFrame.origin.y) / startFrame.size.height;
  320. self.videoView.layer.anchorPoint = CGPointMake(anchorX, anchorY);
  321. self.videoView.userInteractionEnabled = NO;
  322. self.videoView.center = point;
  323. [self hideToolViews:YES];
  324. self.yb_hideStatusBar(NO);
  325. self.yb_collectionView().scrollEnabled = NO;
  326. _interacting = YES;
  327. }
  328. }
  329. }
  330. - (void)restoreGestureInteractionWithDuration:(NSTimeInterval)duration {
  331. [self.videoView hideToolBar:NO];
  332. CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
  333. void (^animations)(void) = ^{
  334. self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:1];
  335. CGPoint anchorPoint = self.videoView.layer.anchorPoint;
  336. self.videoView.center = CGPointMake(containerSize.width * anchorPoint.x, containerSize.height * anchorPoint.y);
  337. self.videoView.transform = CGAffineTransformIdentity;
  338. };
  339. void (^completion)(BOOL finished) = ^(BOOL finished){
  340. self.videoView.layer.anchorPoint = CGPointMake(0.5, 0.5);
  341. self.videoView.center = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5);
  342. self.videoView.userInteractionEnabled = YES;
  343. self.yb_hideStatusBar(YES);
  344. self.yb_collectionView().scrollEnabled = YES;
  345. if (!self.videoView.isPlaying) [self hideToolViews:NO];;
  346. self->_interactStartPoint = CGPointZero;
  347. self->_interacting = NO;
  348. };
  349. if (duration <= 0) {
  350. animations();
  351. completion(NO);
  352. } else {
  353. [UIView animateWithDuration:duration animations:animations completion:completion];
  354. }
  355. }
  356. #pragma mark - getters & setters
  357. - (YBIBVideoView *)videoView {
  358. if (!_videoView) {
  359. _videoView = [YBIBVideoView new];
  360. _videoView.delegate = self;
  361. }
  362. return _videoView;
  363. }
  364. @end