MPlayerAttributeManager.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. #import "MPlayerAttributeManager.h"
  2. #import <UIKit/UIKit.h>
  3. #import <AVFoundation/AVFoundation.h>
  4. #import <ZFPlayer/ZFPlayer.h>
  5. #import <KTVHTTPCache/KTVHTTPCache.h>
  6. /*!
  7. * Refresh interval for timed observations of AVPlayer
  8. */
  9. static float const kTimeRefreshInterval = 0.1;
  10. static NSString *const kStatus = @"status";
  11. static NSString *const kLoadedTimeRanges = @"loadedTimeRanges";
  12. static NSString *const kPlaybackBufferEmpty = @"playbackBufferEmpty";
  13. static NSString *const kPlaybackLikelyToKeepUp = @"playbackLikelyToKeepUp";
  14. static NSString *const kPresentationSize = @"presentationSize";
  15. @interface ZFPlayerAttributePresentView : ZFPlayerView
  16. @property (nonatomic, strong) AVPlayer *player;
  17. /// default is AVLayerVideoGravityResizeAspect.
  18. @property (nonatomic, strong) AVLayerVideoGravity videoGravity;
  19. @end
  20. @implementation ZFPlayerAttributePresentView
  21. + (Class)layerClass {
  22. return [AVPlayerLayer class];
  23. }
  24. - (AVPlayerLayer *)avLayer {
  25. return (AVPlayerLayer *)self.layer;
  26. }
  27. - (instancetype)initWithFrame:(CGRect)frame {
  28. self = [super initWithFrame:frame];
  29. if (self) {
  30. self.backgroundColor = [UIColor blackColor];
  31. }
  32. return self;
  33. }
  34. - (void)setPlayer:(AVPlayer *)player {
  35. if (player == _player) return;
  36. self.avLayer.player = player;
  37. }
  38. - (void)setVideoGravity:(AVLayerVideoGravity)videoGravity {
  39. if (videoGravity == self.videoGravity) return;
  40. [self avLayer].videoGravity = videoGravity;
  41. }
  42. - (AVLayerVideoGravity)videoGravity {
  43. return [self avLayer].videoGravity;
  44. }
  45. @end
  46. @interface MPlayerAttributeManager () {
  47. id _timeObserver;
  48. id _itemEndObserver;
  49. ZFKVOController *_playerItemKVO;
  50. }
  51. @property (nonatomic, strong, readonly) AVURLAsset *asset;
  52. @property (nonatomic, strong, readonly) AVPlayerItem *playerItem;
  53. @property (nonatomic, strong, readonly) AVPlayer *player;
  54. @property (nonatomic, strong) AVPlayerLayer *playerLayer;
  55. @property (nonatomic, assign) BOOL isBuffering;
  56. @property (nonatomic, assign) BOOL isReadyToPlay;
  57. @property (nonatomic, copy) void(^muteStateBlock)(void);
  58. /// 记录每个url的加载失败次数的字典,最多重试3次
  59. @property (nonatomic, strong) NSMutableDictionary *retryDic;
  60. /// 记录可以播放的视频后缀
  61. @property (nonatomic, copy) NSArray *playableSuffixArray;
  62. @end
  63. @implementation MPlayerAttributeManager
  64. @synthesize view = _view;
  65. @synthesize currentTime = _currentTime;
  66. @synthesize totalTime = _totalTime;
  67. @synthesize playerPlayTimeChanged = _playerPlayTimeChanged;
  68. @synthesize playerBufferTimeChanged = _playerBufferTimeChanged;
  69. @synthesize playerDidToEnd = _playerDidToEnd;
  70. @synthesize bufferTime = _bufferTime;
  71. @synthesize playState = _playState;
  72. @synthesize loadState = _loadState;
  73. @synthesize assetURL = _assetURL;
  74. @synthesize playerPrepareToPlay = _playerPrepareToPlay;
  75. @synthesize playerReadyToPlay = _playerReadyToPlay;
  76. @synthesize playerPlayStateChanged = _playerPlayStateChanged;
  77. @synthesize playerLoadStateChanged = _playerLoadStateChanged;
  78. @synthesize seekTime = _seekTime;
  79. @synthesize muted = _muted;
  80. @synthesize volume = _volume;
  81. @synthesize presentationSize = _presentationSize;
  82. @synthesize isPlaying = _isPlaying;
  83. @synthesize rate = _rate;
  84. @synthesize isPreparedToPlay = _isPreparedToPlay;
  85. @synthesize scalingMode = _scalingMode;
  86. @synthesize playerPlayFailed = _playerPlayFailed;
  87. @synthesize presentationSizeChanged = _presentationSizeChanged;
  88. + (void)initialize
  89. {
  90. [KTVHTTPCache logSetConsoleLogEnable:NO];
  91. NSError *error = nil;
  92. [KTVHTTPCache proxyStart:&error];
  93. if (error) {
  94. NSLog(@"Proxy Start Failure, %@", error);
  95. }
  96. [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) {
  97. // NSLog(@"URL Filter reviced URL : %@", URL);
  98. return URL;
  99. }];
  100. [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) {
  101. return NO;
  102. }];
  103. // 设置缓存最大容量
  104. [KTVHTTPCache cacheSetMaxCacheLength:1024 * 1024 * 1024];
  105. }
  106. - (instancetype)init {
  107. self = [super init];
  108. if (self) {
  109. _scalingMode = ZFPlayerScalingModeAspectFit;
  110. self.retryDic = [NSMutableDictionary dictionary];
  111. }
  112. return self;
  113. }
  114. - (void)setupMuteStateBlock:(void (^)(void))muteStateBlock
  115. {
  116. self.muteStateBlock = muteStateBlock;
  117. }
  118. - (void)removeMuteStateBlock
  119. {
  120. self.muteStateBlock = nil;
  121. }
  122. - (void)prepareToPlay {
  123. if (!_assetURL) return;
  124. _isPreparedToPlay = YES;
  125. [self initializePlayer];
  126. [self play];
  127. self.loadState = ZFPlayerLoadStatePrepare;
  128. if (_playerPrepareToPlay) _playerPrepareToPlay(self, self.assetURL);
  129. }
  130. - (void)reloadPlayer {
  131. self.seekTime = self.currentTime;
  132. [self prepareToPlay];
  133. }
  134. - (void)play {
  135. if (!_isPreparedToPlay) {
  136. [self prepareToPlay];
  137. } else {
  138. if (self.muteStateBlock) {
  139. self.muteStateBlock();
  140. }
  141. [self.player play];
  142. self.player.rate = self.rate;
  143. self->_isPlaying = YES;
  144. self.playState = ZFPlayerPlayStatePlaying;
  145. }
  146. }
  147. - (void)pause {
  148. [self.player pause];
  149. self->_isPlaying = NO;
  150. self.playState = ZFPlayerPlayStatePaused;
  151. [_playerItem cancelPendingSeeks];
  152. [_asset cancelLoading];
  153. }
  154. - (void)stop {
  155. [_playerItemKVO safelyRemoveAllObservers];
  156. self.loadState = ZFPlayerLoadStateUnknown;
  157. if (self.player.rate != 0) [self.player pause];
  158. [self.player removeTimeObserver:_timeObserver];
  159. [self.player replaceCurrentItemWithPlayerItem:nil];
  160. _timeObserver = nil;
  161. [[NSNotificationCenter defaultCenter] removeObserver:_itemEndObserver name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
  162. _itemEndObserver = nil;
  163. _isPlaying = NO;
  164. _player = nil;
  165. _assetURL = nil;
  166. _playerItem = nil;
  167. _isPreparedToPlay = NO;
  168. self->_currentTime = 0;
  169. self->_totalTime = 0;
  170. self->_bufferTime = 0;
  171. self.isReadyToPlay = NO;
  172. self.playState = ZFPlayerPlayStatePlayStopped;
  173. }
  174. - (void)replay {
  175. @weakify(self)
  176. [self seekToTime:0 completionHandler:^(BOOL finished) {
  177. @strongify(self)
  178. [self play];
  179. }];
  180. }
  181. /// Replace the current playback address
  182. - (void)replaceCurrentAssetURL:(NSURL *)assetURL {
  183. self.assetURL = assetURL;
  184. }
  185. - (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
  186. CMTime seekTime = CMTimeMake(time, 1);
  187. [_player seekToTime:seekTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
  188. }
  189. - (UIImage *)thumbnailImageAtCurrentTime {
  190. AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
  191. CMTime expectedTime = _playerItem.currentTime;
  192. CGImageRef cgImage = NULL;
  193. imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
  194. imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
  195. cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
  196. if (!cgImage) {
  197. imageGenerator.requestedTimeToleranceBefore = kCMTimePositiveInfinity;
  198. imageGenerator.requestedTimeToleranceAfter = kCMTimePositiveInfinity;
  199. cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
  200. }
  201. UIImage *image = [UIImage imageWithCGImage:cgImage];
  202. return image;
  203. }
  204. - (BOOL)isPlayableSuffix: (NSString *)suffix
  205. {
  206. if (!suffix || suffix.length == 0)
  207. return NO;
  208. suffix = [suffix uppercaseString];
  209. return [self.playableSuffixArray containsObject:suffix];
  210. }
  211. #pragma mark - private method
  212. /// Calculate buffer progress
  213. - (NSTimeInterval)availableDuration {
  214. NSArray *timeRangeArray = _playerItem.loadedTimeRanges;
  215. CMTime currentTime = [_player currentTime];
  216. BOOL foundRange = NO;
  217. CMTimeRange aTimeRange = {0};
  218. if (timeRangeArray.count) {
  219. aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];
  220. if (CMTimeRangeContainsTime(aTimeRange, currentTime)) {
  221. foundRange = YES;
  222. }
  223. }
  224. if (foundRange) {
  225. CMTime maxTime = CMTimeRangeGetEnd(aTimeRange);
  226. NSTimeInterval playableDuration = CMTimeGetSeconds(maxTime);
  227. if (playableDuration > 0) {
  228. return playableDuration;
  229. }
  230. }
  231. return 0;
  232. }
  233. - (void)initializePlayer {
  234. _asset = [AVURLAsset assetWithURL:self.assetURL];
  235. _playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
  236. _player = [AVPlayer playerWithPlayerItem:_playerItem];
  237. [self enableAudioTracks:YES inPlayerItem:_playerItem];
  238. ZFPlayerAttributePresentView *presentView = (ZFPlayerAttributePresentView *)self.view;
  239. presentView.player = _player;
  240. self.scalingMode = _scalingMode;
  241. if (@available(iOS 9.0, *)) {
  242. _playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = NO;
  243. }
  244. if (@available(iOS 10.0, *)) {
  245. _playerItem.preferredForwardBufferDuration = 5;
  246. _player.automaticallyWaitsToMinimizeStalling = NO;
  247. }
  248. [self itemObserving];
  249. }
  250. /// Playback speed switching method
  251. - (void)enableAudioTracks:(BOOL)enable inPlayerItem:(AVPlayerItem*)playerItem {
  252. for (AVPlayerItemTrack *track in playerItem.tracks){
  253. if ([track.assetTrack.mediaType isEqual:AVMediaTypeVideo]) {
  254. track.enabled = enable;
  255. }
  256. }
  257. }
  258. /**
  259. * 缓冲较差时候回调这里
  260. */
  261. - (void)bufferingSomeSecond {
  262. // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
  263. if (self.isBuffering || self.playState == ZFPlayerPlayStatePlayStopped) return;
  264. self.isBuffering = YES;
  265. // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
  266. [self.player pause];
  267. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  268. // 如果此时用户已经暂停了,则不再需要开启播放了
  269. if (!self.isPlaying) {
  270. self.isBuffering = NO;
  271. return;
  272. }
  273. [self play];
  274. // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
  275. self.isBuffering = NO;
  276. if (!self.playerItem.isPlaybackLikelyToKeepUp) [self bufferingSomeSecond];
  277. });
  278. }
  279. - (void)itemObserving {
  280. [_playerItemKVO safelyRemoveAllObservers];
  281. _playerItemKVO = [[ZFKVOController alloc] initWithTarget:_playerItem];
  282. [_playerItemKVO safelyAddObserver:self
  283. forKeyPath:kStatus
  284. options:NSKeyValueObservingOptionNew
  285. context:nil];
  286. [_playerItemKVO safelyAddObserver:self
  287. forKeyPath:kPlaybackBufferEmpty
  288. options:NSKeyValueObservingOptionNew
  289. context:nil];
  290. [_playerItemKVO safelyAddObserver:self
  291. forKeyPath:kPlaybackLikelyToKeepUp
  292. options:NSKeyValueObservingOptionNew
  293. context:nil];
  294. [_playerItemKVO safelyAddObserver:self
  295. forKeyPath:kLoadedTimeRanges
  296. options:NSKeyValueObservingOptionNew
  297. context:nil];
  298. [_playerItemKVO safelyAddObserver:self
  299. forKeyPath:kPresentationSize
  300. options:NSKeyValueObservingOptionNew
  301. context:nil];
  302. CMTime interval = CMTimeMakeWithSeconds(kTimeRefreshInterval, NSEC_PER_SEC);
  303. @weakify(self)
  304. _timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  305. @strongify(self)
  306. if (!self) return;
  307. NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
  308. /// 大于0才把状态改为可以播放,解决黑屏问题
  309. if (CMTimeGetSeconds(time) > 0 && !self.isReadyToPlay) {
  310. self.isReadyToPlay = YES;
  311. self.loadState = ZFPlayerLoadStatePlaythroughOK;
  312. }
  313. if (loadedRanges.count > 0) {
  314. if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
  315. }
  316. }];
  317. _itemEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
  318. @strongify(self)
  319. if (!self) return;
  320. self.playState = ZFPlayerPlayStatePlayStopped;
  321. if (self.playerDidToEnd) self.playerDidToEnd(self);
  322. }];
  323. }
  324. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  325. dispatch_async(dispatch_get_main_queue(), ^{
  326. if ([keyPath isEqualToString:kStatus]) {
  327. if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
  328. /// 第一次初始化
  329. if (self.loadState == ZFPlayerLoadStatePrepare) {
  330. if (self.playerPrepareToPlay) self.playerReadyToPlay(self, self.assetURL);
  331. }
  332. if (self.seekTime) {
  333. [self seekToTime:self.seekTime completionHandler:nil];
  334. self.seekTime = 0;
  335. }
  336. if (self.isPlaying) [self play];
  337. self.player.muted = self.muted;
  338. NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
  339. if (loadedRanges.count > 0) {
  340. /// Fix https://github.com/renzifeng/ZFPlayer/issues/475
  341. if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
  342. }
  343. } else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
  344. NSUInteger retryCount = 1;
  345. if (!self.retryDic[self.assetURL.absoluteString]) {
  346. self.retryDic[self.assetURL.absoluteString] = @(retryCount);
  347. }else {
  348. retryCount = [self.retryDic[self.assetURL.absoluteString] intValue];
  349. if (retryCount <= 3) {
  350. retryCount += 1;
  351. self.retryDic[self.assetURL.absoluteString] = @(retryCount);
  352. }
  353. }
  354. if (retryCount <= 3) {
  355. [self reloadPlayer];
  356. }
  357. self.playState = ZFPlayerPlayStatePlayFailed;
  358. NSError *error = self.player.currentItem.error;
  359. if (self.playerPlayFailed) self.playerPlayFailed(self, error);
  360. }
  361. } else if ([keyPath isEqualToString:kPlaybackBufferEmpty]) {
  362. // When the buffer is empty
  363. if (self.playerItem.playbackBufferEmpty) {
  364. self.loadState = ZFPlayerLoadStateStalled;
  365. [self bufferingSomeSecond];
  366. }
  367. } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUp]) {
  368. // When the buffer is good
  369. if (self.playerItem.playbackLikelyToKeepUp) {
  370. self.loadState = ZFPlayerLoadStatePlayable;
  371. if (self.isPlaying) [self play];
  372. }
  373. } else if ([keyPath isEqualToString:kLoadedTimeRanges]) {
  374. NSTimeInterval bufferTime = [self availableDuration];
  375. self->_bufferTime = bufferTime;
  376. if (self.playerBufferTimeChanged) self.playerBufferTimeChanged(self, bufferTime);
  377. } else if ([keyPath isEqualToString:kPresentationSize]) {
  378. self->_presentationSize = self.playerItem.presentationSize;
  379. if (self.presentationSizeChanged) {
  380. self.presentationSizeChanged(self, self->_presentationSize);
  381. }
  382. } else {
  383. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  384. }
  385. });
  386. }
  387. #pragma mark - getter
  388. - (UIView *)view {
  389. if (!_view) {
  390. _view = [[ZFPlayerAttributePresentView alloc] init];
  391. }
  392. return _view;
  393. }
  394. - (float)rate {
  395. return _rate == 0 ?1:_rate;
  396. }
  397. - (NSTimeInterval)totalTime {
  398. NSTimeInterval sec = CMTimeGetSeconds(self.player.currentItem.duration);
  399. if (isnan(sec)) {
  400. return 0;
  401. }
  402. return sec;
  403. }
  404. - (NSTimeInterval)currentTime {
  405. NSTimeInterval sec = CMTimeGetSeconds(self.playerItem.currentTime);
  406. if (isnan(sec) || sec < 0) {
  407. return 0;
  408. }
  409. return sec;
  410. }
  411. - (NSArray *)playableSuffixArray
  412. {
  413. if (!_playableSuffixArray) {
  414. _playableSuffixArray = @[@"WMV",@"AVI",@"MKV",@"RMVB",@"RM",@"XVID",@"MP4",@"3GP",@"MPG"];
  415. }
  416. return _playableSuffixArray;
  417. }
  418. #pragma mark - setter
  419. - (void)setPlayState:(ZFPlayerPlaybackState)playState {
  420. _playState = playState;
  421. if (self.playerPlayStateChanged) self.playerPlayStateChanged(self, playState);
  422. }
  423. - (void)setLoadState:(ZFPlayerLoadState)loadState {
  424. _loadState = loadState;
  425. if (self.playerLoadStateChanged) self.playerLoadStateChanged(self, loadState);
  426. }
  427. - (void)setAssetURL:(NSURL *)assetURL {
  428. if (self.player) [self stop];
  429. // 如果有缓存,直接取本地缓存
  430. NSURL *url = [KTVHTTPCache cacheCompleteFileURLWithURL:assetURL];
  431. NSString *suffix = [url pathExtension];
  432. if (url && [self isPlayableSuffix:suffix]) {
  433. _assetURL = url;
  434. }else {
  435. // 设置代理
  436. _assetURL = [KTVHTTPCache proxyURLWithOriginalURL:assetURL];
  437. }
  438. [self prepareToPlay];
  439. }
  440. - (void)setRate:(float)rate {
  441. _rate = rate;
  442. if (self.player && fabsf(_player.rate) > 0.00001f) {
  443. self.player.rate = rate;
  444. }
  445. }
  446. - (void)setMuted:(BOOL)muted {
  447. _muted = muted;
  448. self.player.muted = muted;
  449. }
  450. - (void)setScalingMode:(ZFPlayerScalingMode)scalingMode {
  451. _scalingMode = scalingMode;
  452. ZFPlayerAttributePresentView *presentView = (ZFPlayerAttributePresentView *)self.view;
  453. [CATransaction begin];
  454. [CATransaction setDisableActions:YES];
  455. switch (scalingMode) {
  456. case ZFPlayerScalingModeNone:
  457. presentView.videoGravity = AVLayerVideoGravityResizeAspect;
  458. break;
  459. case ZFPlayerScalingModeAspectFit:
  460. presentView.videoGravity = AVLayerVideoGravityResizeAspect;
  461. break;
  462. case ZFPlayerScalingModeAspectFill:
  463. presentView.videoGravity = AVLayerVideoGravityResizeAspectFill;
  464. break;
  465. case ZFPlayerScalingModeFill:
  466. presentView.videoGravity = AVLayerVideoGravityResize;
  467. break;
  468. default:
  469. break;
  470. }
  471. [CATransaction commit];
  472. }
  473. - (void)setVolume:(float)volume {
  474. _volume = MIN(MAX(0, volume), 1);
  475. self.player.volume = volume;
  476. }
  477. @end