YBIBVideoView.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. //
  2. // YBIBVideoView.m
  3. // YBImageBrowserDemo
  4. //
  5. // Created by 波儿菜 on 2019/7/11.
  6. // Copyright © 2019 杨波. All rights reserved.
  7. //
  8. #import <AVFoundation/AVFoundation.h>
  9. #import "YBIBVideoView.h"
  10. #import "YBIBVideoActionBar.h"
  11. #import "YBIBVideoTopBar.h"
  12. #import "YBIBUtilities.h"
  13. #import "YBIBIconManager.h"
  14. @interface YBIBVideoView () <YBIBVideoActionBarDelegate>
  15. @property (nonatomic, strong) YBIBVideoTopBar *topBar;
  16. @property (nonatomic, strong) YBIBVideoActionBar *actionBar;
  17. @property (nonatomic, strong) UIButton *playButton;
  18. @end
  19. @implementation YBIBVideoView {
  20. AVPlayer *_player;
  21. AVPlayerItem *_playerItem;
  22. AVPlayerLayer *_playerLayer;
  23. BOOL _active;
  24. }
  25. #pragma mark - life cycle
  26. - (void)dealloc {
  27. [self removeObserverForSystem];
  28. [self reset];
  29. }
  30. - (instancetype)initWithFrame:(CGRect)frame {
  31. self = [super initWithFrame:frame];
  32. if (self) {
  33. [self initValue];
  34. self.backgroundColor = UIColor.clearColor;
  35. [self addSubview:self.thumbImageView];
  36. [self addSubview:self.topBar];
  37. [self addSubview:self.actionBar];
  38. [self addSubview:self.playButton];
  39. [self addObserverForSystem];
  40. _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapGesture:)];
  41. [self addGestureRecognizer:_tapGesture];
  42. }
  43. return self;
  44. }
  45. - (void)initValue {
  46. _playing = NO;
  47. _active = YES;
  48. _needAutoPlay = NO;
  49. _autoPlayCount = 0;
  50. _playFailed = NO;
  51. _preparingPlay = NO;
  52. }
  53. #pragma mark - public
  54. - (void)updateLayoutWithExpectOrientation:(UIDeviceOrientation)orientation containerSize:(CGSize)containerSize {
  55. UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation);
  56. CGFloat width = containerSize.width - padding.left - padding.right, height = containerSize.height;
  57. self.topBar.frame = CGRectMake(padding.left, padding.top, width, [YBIBVideoTopBar defaultHeight]);
  58. self.actionBar.frame = CGRectMake(padding.left, height - [YBIBVideoActionBar defaultHeight] - padding.bottom - 10, width, [YBIBVideoActionBar defaultHeight]);
  59. self.playButton.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
  60. _playerLayer.frame = (CGRect){CGPointZero, containerSize};
  61. }
  62. - (void)reset {
  63. [self removeObserverForPlayer];
  64. // If set '_playerLayer.player = nil' or '_player = nil', can not cancel observeing of 'addPeriodicTimeObserverForInterval'.
  65. [_player pause];
  66. _playerItem = nil;
  67. [_playerLayer removeFromSuperlayer];
  68. _playerLayer = nil;
  69. [self finishPlay];
  70. }
  71. - (void)hideToolBar:(BOOL)hide {
  72. if (hide) {
  73. self.actionBar.hidden = YES;
  74. self.topBar.hidden = YES;
  75. } else if (self.isPlaying) {
  76. self.actionBar.hidden = NO;
  77. self.topBar.hidden = NO;
  78. }
  79. }
  80. - (void)hidePlayButton {
  81. self.playButton.hidden = YES;
  82. }
  83. #pragma mark - private
  84. - (void)videoJumpWithScale:(float)scale {
  85. CMTime startTime = CMTimeMakeWithSeconds(scale, _player.currentTime.timescale);
  86. AVPlayer *tmpPlayer = _player;
  87. if (CMTIME_IS_INDEFINITE(startTime) || CMTIME_IS_INVALID(startTime)) return;
  88. [_player seekToTime:startTime toleranceBefore:CMTimeMake(1, 1000) toleranceAfter:CMTimeMake(1, 1000) completionHandler:^(BOOL finished) {
  89. if (finished && tmpPlayer == self->_player) {
  90. [self startPlay];
  91. }
  92. }];
  93. }
  94. - (void)preparPlay {
  95. _preparingPlay = YES;
  96. _playFailed = NO;
  97. self.playButton.hidden = YES;
  98. [self.delegate yb_preparePlayForVideoView:self];
  99. if (!_playerLayer) {
  100. _playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
  101. _player = [AVPlayer playerWithPlayerItem:_playerItem];
  102. _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
  103. _playerLayer.frame = (CGRect){CGPointZero, [self.delegate yb_containerSizeForVideoView:self]};
  104. [self.layer insertSublayer:_playerLayer above:self.thumbImageView.layer];
  105. [self addObserverForPlayer];
  106. } else {
  107. [self videoJumpWithScale:0];
  108. }
  109. }
  110. - (void)startPlay {
  111. if (_player) {
  112. _playing = YES;
  113. [_player play];
  114. [self.actionBar play];
  115. self.topBar.hidden = NO;
  116. self.actionBar.hidden = NO;
  117. [self.delegate yb_startPlayForVideoView:self];
  118. }
  119. }
  120. - (void)finishPlay {
  121. self.playButton.hidden = NO;
  122. [self.actionBar setCurrentValue:0];
  123. self.actionBar.hidden = YES;
  124. self.topBar.hidden = YES;
  125. _playing = NO;
  126. [self.delegate yb_finishPlayForVideoView:self];
  127. }
  128. - (void)playerPause {
  129. if (_player) {
  130. [_player pause];
  131. [self.actionBar pause];
  132. }
  133. }
  134. - (BOOL)autoPlay {
  135. if (self.autoPlayCount == NSUIntegerMax) {
  136. [self preparPlay];
  137. } else if (self.autoPlayCount > 0) {
  138. --self.autoPlayCount;
  139. [self.delegate yb_autoPlayCountChanged:self.autoPlayCount];
  140. [self preparPlay];
  141. } else {
  142. return NO;
  143. }
  144. return YES;
  145. }
  146. #pragma mark - <YBIBVideoActionBarDelegate>
  147. - (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPlayButton:(UIButton *)playButton {
  148. [self startPlay];
  149. }
  150. - (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPauseButton:(UIButton *)pauseButton {
  151. [self playerPause];
  152. }
  153. - (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar changeValue:(float)value {
  154. [self videoJumpWithScale:value];
  155. }
  156. #pragma mark - observe
  157. - (void)addObserverForPlayer {
  158. [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  159. __weak typeof(self) wSelf = self;
  160. [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  161. __strong typeof(wSelf) self = wSelf;
  162. if (!self) return;
  163. float currentTime = time.value / time.timescale;
  164. [self.actionBar setCurrentValue:currentTime];
  165. }];
  166. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
  167. }
  168. - (void)removeObserverForPlayer {
  169. [_playerItem removeObserver:self forKeyPath:@"status"];
  170. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
  171. }
  172. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  173. if (![self.delegate yb_isFreezingForVideoView:self]) {
  174. if (object == _playerItem) {
  175. if ([keyPath isEqualToString:@"status"]) {
  176. [self playerItemStatusChanged];
  177. }
  178. }
  179. }
  180. }
  181. - (void)didPlayToEndTime:(NSNotification *)noti {
  182. if (noti.object == _playerItem) {
  183. [self finishPlay];
  184. [self.delegate yb_didPlayToEndTimeForVideoView:self];
  185. }
  186. }
  187. - (void)playerItemStatusChanged {
  188. if (!_active) return;
  189. _preparingPlay = NO;
  190. switch (_playerItem.status) {
  191. case AVPlayerItemStatusReadyToPlay: {
  192. // Delay to update UI.
  193. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  194. [self startPlay];
  195. double max = CMTimeGetSeconds(self->_playerItem.duration);
  196. [self.actionBar setMaxValue:(isnan(max) || isinf(max)) ? 0 : max];
  197. });
  198. }
  199. break;
  200. case AVPlayerItemStatusUnknown: {
  201. _playFailed = YES;
  202. [self.delegate yb_playFailedForVideoView:self];
  203. [self reset];
  204. }
  205. break;
  206. case AVPlayerItemStatusFailed: {
  207. _playFailed = YES;
  208. [self.delegate yb_playFailedForVideoView:self];
  209. [self reset];
  210. }
  211. break;
  212. }
  213. }
  214. - (void)removeObserverForSystem {
  215. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
  216. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
  217. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
  218. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
  219. }
  220. - (void)addObserverForSystem {
  221. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
  222. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
  223. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
  224. [[AVAudioSession sharedInstance] setActive:YES error:nil];
  225. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
  226. }
  227. - (void)applicationWillResignActive:(NSNotification *)notification {
  228. _active = NO;
  229. [self playerPause];
  230. }
  231. - (void)applicationDidBecomeActive:(NSNotification *)notification {
  232. _active = YES;
  233. }
  234. - (void)didChangeStatusBarFrame {
  235. if ([UIApplication sharedApplication].statusBarFrame.size.height > YBIBStatusbarHeight()) {
  236. [self playerPause];
  237. }
  238. }
  239. - (void)audioRouteChangeListenerCallback:(NSNotification*)notification {
  240. YBIB_DISPATCH_ASYNC_MAIN(^{
  241. NSDictionary *interuptionDict = notification.userInfo;
  242. NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
  243. switch (routeChangeReason) {
  244. case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
  245. [self playerPause];
  246. break;
  247. }
  248. })
  249. }
  250. #pragma mark - event
  251. - (void)respondsToTapGesture:(UITapGestureRecognizer *)tap {
  252. if (self.isPlaying) {
  253. self.actionBar.hidden = !self.actionBar.isHidden;
  254. self.topBar.hidden = !self.topBar.isHidden;
  255. } else {
  256. [self.delegate yb_respondsToTapGestureForVideoView:self];
  257. }
  258. }
  259. - (void)clickCancelButton:(UIButton *)button {
  260. [self.delegate yb_cancelledForVideoView:self];
  261. }
  262. - (void)clickPlayButton:(UIButton *)button {
  263. [self preparPlay];
  264. }
  265. #pragma mark - getters & setters
  266. - (void)setNeedAutoPlay:(BOOL)needAutoPlay {
  267. if (needAutoPlay && _asset && !self.isPlaying) {
  268. [self autoPlay];
  269. } else {
  270. _needAutoPlay = needAutoPlay;
  271. }
  272. }
  273. @synthesize asset = _asset;
  274. - (void)setAsset:(AVAsset *)asset {
  275. _asset = asset;
  276. if (!asset) return;
  277. if (self.needAutoPlay) {
  278. if (![self autoPlay]) {
  279. self.playButton.hidden = NO;
  280. }
  281. self.needAutoPlay = NO;
  282. } else {
  283. self.playButton.hidden = NO;
  284. }
  285. }
  286. - (AVAsset *)asset {
  287. if ([_asset isKindOfClass:AVURLAsset.class]) {
  288. _asset = [AVURLAsset assetWithURL:((AVURLAsset *)_asset).URL];
  289. }
  290. return _asset;
  291. }
  292. - (YBIBVideoTopBar *)topBar {
  293. if (!_topBar) {
  294. _topBar = [YBIBVideoTopBar new];
  295. [_topBar.cancelButton addTarget:self action:@selector(clickCancelButton:) forControlEvents:UIControlEventTouchUpInside];
  296. _topBar.hidden = YES;
  297. }
  298. return _topBar;
  299. }
  300. - (YBIBVideoActionBar *)actionBar {
  301. if (!_actionBar) {
  302. _actionBar = [YBIBVideoActionBar new];
  303. _actionBar.delegate = self;
  304. _actionBar.hidden = YES;
  305. }
  306. return _actionBar;
  307. }
  308. - (UIButton *)playButton {
  309. if (!_playButton) {
  310. _playButton = [UIButton buttonWithType:UIButtonTypeCustom];
  311. _playButton.bounds = CGRectMake(0, 0, 100, 100);
  312. [_playButton setImage:YBIBIconManager.sharedManager.videoBigPlayImage() forState:UIControlStateNormal];
  313. [_playButton addTarget:self action:@selector(clickPlayButton:) forControlEvents:UIControlEventTouchUpInside];
  314. _playButton.hidden = YES;
  315. _playButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
  316. _playButton.layer.shadowOffset = CGSizeMake(0, 1);
  317. _playButton.layer.shadowOpacity = 1;
  318. _playButton.layer.shadowRadius = 4;
  319. }
  320. return _playButton;
  321. }
  322. - (UIImageView *)thumbImageView {
  323. if (!_thumbImageView) {
  324. _thumbImageView = [UIImageView new];
  325. _thumbImageView.contentMode = UIViewContentModeScaleAspectFit;
  326. _thumbImageView.layer.masksToBounds = YES;
  327. }
  328. return _thumbImageView;
  329. }
  330. @end