123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- #import "MPlayerAttributeManager.h"
- #import <UIKit/UIKit.h>
- #import <AVFoundation/AVFoundation.h>
- #import <ZFPlayer/ZFPlayer.h>
- #import <KTVHTTPCache/KTVHTTPCache.h>
- /*!
- * Refresh interval for timed observations of AVPlayer
- */
- static float const kTimeRefreshInterval = 0.1;
- static NSString *const kStatus = @"status";
- static NSString *const kLoadedTimeRanges = @"loadedTimeRanges";
- static NSString *const kPlaybackBufferEmpty = @"playbackBufferEmpty";
- static NSString *const kPlaybackLikelyToKeepUp = @"playbackLikelyToKeepUp";
- static NSString *const kPresentationSize = @"presentationSize";
- @interface ZFPlayerAttributePresentView : ZFPlayerView
- @property (nonatomic, strong) AVPlayer *player;
- /// default is AVLayerVideoGravityResizeAspect.
- @property (nonatomic, strong) AVLayerVideoGravity videoGravity;
- @end
- @implementation ZFPlayerAttributePresentView
- + (Class)layerClass {
- return [AVPlayerLayer class];
- }
- - (AVPlayerLayer *)avLayer {
- return (AVPlayerLayer *)self.layer;
- }
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- self.backgroundColor = [UIColor blackColor];
- }
- return self;
- }
- - (void)setPlayer:(AVPlayer *)player {
- if (player == _player) return;
- self.avLayer.player = player;
- }
- - (void)setVideoGravity:(AVLayerVideoGravity)videoGravity {
- if (videoGravity == self.videoGravity) return;
- [self avLayer].videoGravity = videoGravity;
- }
- - (AVLayerVideoGravity)videoGravity {
- return [self avLayer].videoGravity;
- }
- @end
- @interface MPlayerAttributeManager () {
- id _timeObserver;
- id _itemEndObserver;
- ZFKVOController *_playerItemKVO;
- }
- @property (nonatomic, strong, readonly) AVURLAsset *asset;
- @property (nonatomic, strong, readonly) AVPlayerItem *playerItem;
- @property (nonatomic, strong, readonly) AVPlayer *player;
- @property (nonatomic, strong) AVPlayerLayer *playerLayer;
- @property (nonatomic, assign) BOOL isBuffering;
- @property (nonatomic, assign) BOOL isReadyToPlay;
- @property (nonatomic, copy) void(^muteStateBlock)(void);
- /// 记录每个url的加载失败次数的字典,最多重试3次
- @property (nonatomic, strong) NSMutableDictionary *retryDic;
- /// 记录可以播放的视频后缀
- @property (nonatomic, copy) NSArray *playableSuffixArray;
- @end
- @implementation MPlayerAttributeManager
- @synthesize view = _view;
- @synthesize currentTime = _currentTime;
- @synthesize totalTime = _totalTime;
- @synthesize playerPlayTimeChanged = _playerPlayTimeChanged;
- @synthesize playerBufferTimeChanged = _playerBufferTimeChanged;
- @synthesize playerDidToEnd = _playerDidToEnd;
- @synthesize bufferTime = _bufferTime;
- @synthesize playState = _playState;
- @synthesize loadState = _loadState;
- @synthesize assetURL = _assetURL;
- @synthesize playerPrepareToPlay = _playerPrepareToPlay;
- @synthesize playerReadyToPlay = _playerReadyToPlay;
- @synthesize playerPlayStateChanged = _playerPlayStateChanged;
- @synthesize playerLoadStateChanged = _playerLoadStateChanged;
- @synthesize seekTime = _seekTime;
- @synthesize muted = _muted;
- @synthesize volume = _volume;
- @synthesize presentationSize = _presentationSize;
- @synthesize isPlaying = _isPlaying;
- @synthesize rate = _rate;
- @synthesize isPreparedToPlay = _isPreparedToPlay;
- @synthesize scalingMode = _scalingMode;
- @synthesize playerPlayFailed = _playerPlayFailed;
- @synthesize presentationSizeChanged = _presentationSizeChanged;
- + (void)initialize
- {
- [KTVHTTPCache logSetConsoleLogEnable:NO];
- NSError *error = nil;
- [KTVHTTPCache proxyStart:&error];
- if (error) {
- NSLog(@"Proxy Start Failure, %@", error);
- }
- [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) {
- // NSLog(@"URL Filter reviced URL : %@", URL);
- return URL;
- }];
- [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) {
- return NO;
- }];
- // 设置缓存最大容量
- [KTVHTTPCache cacheSetMaxCacheLength:1024 * 1024 * 1024];
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _scalingMode = ZFPlayerScalingModeAspectFit;
- self.retryDic = [NSMutableDictionary dictionary];
- }
- return self;
- }
- - (void)setupMuteStateBlock:(void (^)(void))muteStateBlock
- {
- self.muteStateBlock = muteStateBlock;
- }
- - (void)removeMuteStateBlock
- {
- self.muteStateBlock = nil;
- }
- - (void)prepareToPlay {
- if (!_assetURL) return;
- _isPreparedToPlay = YES;
- [self initializePlayer];
- [self play];
- self.loadState = ZFPlayerLoadStatePrepare;
- if (_playerPrepareToPlay) _playerPrepareToPlay(self, self.assetURL);
- }
- - (void)reloadPlayer {
- self.seekTime = self.currentTime;
- [self prepareToPlay];
- }
- - (void)play {
- if (!_isPreparedToPlay) {
- [self prepareToPlay];
- } else {
- if (self.muteStateBlock) {
- self.muteStateBlock();
- }
- [self.player play];
- self.player.rate = self.rate;
- self->_isPlaying = YES;
- self.playState = ZFPlayerPlayStatePlaying;
- }
- }
- - (void)pause {
- [self.player pause];
- self->_isPlaying = NO;
- self.playState = ZFPlayerPlayStatePaused;
- [_playerItem cancelPendingSeeks];
- [_asset cancelLoading];
- }
- - (void)stop {
- [_playerItemKVO safelyRemoveAllObservers];
- self.loadState = ZFPlayerLoadStateUnknown;
- if (self.player.rate != 0) [self.player pause];
- [self.player removeTimeObserver:_timeObserver];
- [self.player replaceCurrentItemWithPlayerItem:nil];
- _timeObserver = nil;
- [[NSNotificationCenter defaultCenter] removeObserver:_itemEndObserver name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
- _itemEndObserver = nil;
- _isPlaying = NO;
- _player = nil;
- _assetURL = nil;
- _playerItem = nil;
- _isPreparedToPlay = NO;
- self->_currentTime = 0;
- self->_totalTime = 0;
- self->_bufferTime = 0;
- self.isReadyToPlay = NO;
- self.playState = ZFPlayerPlayStatePlayStopped;
- }
- - (void)replay {
- @weakify(self)
- [self seekToTime:0 completionHandler:^(BOOL finished) {
- @strongify(self)
- [self play];
- }];
- }
- /// Replace the current playback address
- - (void)replaceCurrentAssetURL:(NSURL *)assetURL {
- self.assetURL = assetURL;
- }
- - (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
- CMTime seekTime = CMTimeMake(time, 1);
- [_player seekToTime:seekTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
- }
- - (UIImage *)thumbnailImageAtCurrentTime {
- AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
- CMTime expectedTime = _playerItem.currentTime;
- CGImageRef cgImage = NULL;
-
- imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
- imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
- cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
-
- if (!cgImage) {
- imageGenerator.requestedTimeToleranceBefore = kCMTimePositiveInfinity;
- imageGenerator.requestedTimeToleranceAfter = kCMTimePositiveInfinity;
- cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
- }
-
- UIImage *image = [UIImage imageWithCGImage:cgImage];
- return image;
- }
- - (BOOL)isPlayableSuffix: (NSString *)suffix
- {
- if (!suffix || suffix.length == 0)
- return NO;
- suffix = [suffix uppercaseString];
- return [self.playableSuffixArray containsObject:suffix];
- }
- #pragma mark - private method
- /// Calculate buffer progress
- - (NSTimeInterval)availableDuration {
- NSArray *timeRangeArray = _playerItem.loadedTimeRanges;
- CMTime currentTime = [_player currentTime];
- BOOL foundRange = NO;
- CMTimeRange aTimeRange = {0};
- if (timeRangeArray.count) {
- aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];
- if (CMTimeRangeContainsTime(aTimeRange, currentTime)) {
- foundRange = YES;
- }
- }
-
- if (foundRange) {
- CMTime maxTime = CMTimeRangeGetEnd(aTimeRange);
- NSTimeInterval playableDuration = CMTimeGetSeconds(maxTime);
- if (playableDuration > 0) {
- return playableDuration;
- }
- }
- return 0;
- }
- - (void)initializePlayer {
- _asset = [AVURLAsset assetWithURL:self.assetURL];
- _playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
- _player = [AVPlayer playerWithPlayerItem:_playerItem];
- [self enableAudioTracks:YES inPlayerItem:_playerItem];
-
- ZFPlayerAttributePresentView *presentView = (ZFPlayerAttributePresentView *)self.view;
- presentView.player = _player;
- self.scalingMode = _scalingMode;
- if (@available(iOS 9.0, *)) {
- _playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = NO;
- }
- if (@available(iOS 10.0, *)) {
- _playerItem.preferredForwardBufferDuration = 5;
- _player.automaticallyWaitsToMinimizeStalling = NO;
- }
- [self itemObserving];
- }
- /// Playback speed switching method
- - (void)enableAudioTracks:(BOOL)enable inPlayerItem:(AVPlayerItem*)playerItem {
- for (AVPlayerItemTrack *track in playerItem.tracks){
- if ([track.assetTrack.mediaType isEqual:AVMediaTypeVideo]) {
- track.enabled = enable;
- }
- }
- }
- /**
- * 缓冲较差时候回调这里
- */
- - (void)bufferingSomeSecond {
- // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
- if (self.isBuffering || self.playState == ZFPlayerPlayStatePlayStopped) return;
- self.isBuffering = YES;
-
- // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
- [self.player pause];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // 如果此时用户已经暂停了,则不再需要开启播放了
- if (!self.isPlaying) {
- self.isBuffering = NO;
- return;
- }
- [self play];
- // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
- self.isBuffering = NO;
- if (!self.playerItem.isPlaybackLikelyToKeepUp) [self bufferingSomeSecond];
- });
- }
- - (void)itemObserving {
- [_playerItemKVO safelyRemoveAllObservers];
- _playerItemKVO = [[ZFKVOController alloc] initWithTarget:_playerItem];
- [_playerItemKVO safelyAddObserver:self
- forKeyPath:kStatus
- options:NSKeyValueObservingOptionNew
- context:nil];
- [_playerItemKVO safelyAddObserver:self
- forKeyPath:kPlaybackBufferEmpty
- options:NSKeyValueObservingOptionNew
- context:nil];
- [_playerItemKVO safelyAddObserver:self
- forKeyPath:kPlaybackLikelyToKeepUp
- options:NSKeyValueObservingOptionNew
- context:nil];
- [_playerItemKVO safelyAddObserver:self
- forKeyPath:kLoadedTimeRanges
- options:NSKeyValueObservingOptionNew
- context:nil];
- [_playerItemKVO safelyAddObserver:self
- forKeyPath:kPresentationSize
- options:NSKeyValueObservingOptionNew
- context:nil];
-
- CMTime interval = CMTimeMakeWithSeconds(kTimeRefreshInterval, NSEC_PER_SEC);
- @weakify(self)
- _timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
- @strongify(self)
- if (!self) return;
- NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
- /// 大于0才把状态改为可以播放,解决黑屏问题
- if (CMTimeGetSeconds(time) > 0 && !self.isReadyToPlay) {
- self.isReadyToPlay = YES;
- self.loadState = ZFPlayerLoadStatePlaythroughOK;
- }
- if (loadedRanges.count > 0) {
- if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
- }
- }];
-
- _itemEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
- @strongify(self)
- if (!self) return;
- self.playState = ZFPlayerPlayStatePlayStopped;
- if (self.playerDidToEnd) self.playerDidToEnd(self);
- }];
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
- dispatch_async(dispatch_get_main_queue(), ^{
- if ([keyPath isEqualToString:kStatus]) {
- if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
- /// 第一次初始化
- if (self.loadState == ZFPlayerLoadStatePrepare) {
- if (self.playerPrepareToPlay) self.playerReadyToPlay(self, self.assetURL);
- }
- if (self.seekTime) {
- [self seekToTime:self.seekTime completionHandler:nil];
- self.seekTime = 0;
- }
- if (self.isPlaying) [self play];
- self.player.muted = self.muted;
- NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
- if (loadedRanges.count > 0) {
- /// Fix https://github.com/renzifeng/ZFPlayer/issues/475
- if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
- }
- } else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
- NSUInteger retryCount = 1;
- if (!self.retryDic[self.assetURL.absoluteString]) {
- self.retryDic[self.assetURL.absoluteString] = @(retryCount);
- }else {
- retryCount = [self.retryDic[self.assetURL.absoluteString] intValue];
- if (retryCount <= 3) {
- retryCount += 1;
- self.retryDic[self.assetURL.absoluteString] = @(retryCount);
- }
- }
- if (retryCount <= 3) {
- [self reloadPlayer];
- }
- self.playState = ZFPlayerPlayStatePlayFailed;
- NSError *error = self.player.currentItem.error;
- if (self.playerPlayFailed) self.playerPlayFailed(self, error);
- }
- } else if ([keyPath isEqualToString:kPlaybackBufferEmpty]) {
- // When the buffer is empty
- if (self.playerItem.playbackBufferEmpty) {
- self.loadState = ZFPlayerLoadStateStalled;
- [self bufferingSomeSecond];
- }
- } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUp]) {
- // When the buffer is good
- if (self.playerItem.playbackLikelyToKeepUp) {
- self.loadState = ZFPlayerLoadStatePlayable;
- if (self.isPlaying) [self play];
- }
- } else if ([keyPath isEqualToString:kLoadedTimeRanges]) {
- NSTimeInterval bufferTime = [self availableDuration];
- self->_bufferTime = bufferTime;
- if (self.playerBufferTimeChanged) self.playerBufferTimeChanged(self, bufferTime);
- } else if ([keyPath isEqualToString:kPresentationSize]) {
- self->_presentationSize = self.playerItem.presentationSize;
- if (self.presentationSizeChanged) {
- self.presentationSizeChanged(self, self->_presentationSize);
- }
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- });
- }
- #pragma mark - getter
- - (UIView *)view {
- if (!_view) {
- _view = [[ZFPlayerAttributePresentView alloc] init];
- }
- return _view;
- }
- - (float)rate {
- return _rate == 0 ?1:_rate;
- }
- - (NSTimeInterval)totalTime {
- NSTimeInterval sec = CMTimeGetSeconds(self.player.currentItem.duration);
- if (isnan(sec)) {
- return 0;
- }
- return sec;
- }
- - (NSTimeInterval)currentTime {
- NSTimeInterval sec = CMTimeGetSeconds(self.playerItem.currentTime);
- if (isnan(sec) || sec < 0) {
- return 0;
- }
- return sec;
- }
- - (NSArray *)playableSuffixArray
- {
- if (!_playableSuffixArray) {
- _playableSuffixArray = @[@"WMV",@"AVI",@"MKV",@"RMVB",@"RM",@"XVID",@"MP4",@"3GP",@"MPG"];
- }
- return _playableSuffixArray;
- }
- #pragma mark - setter
- - (void)setPlayState:(ZFPlayerPlaybackState)playState {
- _playState = playState;
- if (self.playerPlayStateChanged) self.playerPlayStateChanged(self, playState);
- }
- - (void)setLoadState:(ZFPlayerLoadState)loadState {
- _loadState = loadState;
- if (self.playerLoadStateChanged) self.playerLoadStateChanged(self, loadState);
- }
- - (void)setAssetURL:(NSURL *)assetURL {
- if (self.player) [self stop];
- // 如果有缓存,直接取本地缓存
- NSURL *url = [KTVHTTPCache cacheCompleteFileURLWithURL:assetURL];
- NSString *suffix = [url pathExtension];
- if (url && [self isPlayableSuffix:suffix]) {
- _assetURL = url;
- }else {
- // 设置代理
- _assetURL = [KTVHTTPCache proxyURLWithOriginalURL:assetURL];
- }
- [self prepareToPlay];
- }
- - (void)setRate:(float)rate {
- _rate = rate;
- if (self.player && fabsf(_player.rate) > 0.00001f) {
- self.player.rate = rate;
- }
- }
- - (void)setMuted:(BOOL)muted {
- _muted = muted;
- self.player.muted = muted;
- }
- - (void)setScalingMode:(ZFPlayerScalingMode)scalingMode {
- _scalingMode = scalingMode;
- ZFPlayerAttributePresentView *presentView = (ZFPlayerAttributePresentView *)self.view;
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- switch (scalingMode) {
- case ZFPlayerScalingModeNone:
- presentView.videoGravity = AVLayerVideoGravityResizeAspect;
- break;
- case ZFPlayerScalingModeAspectFit:
- presentView.videoGravity = AVLayerVideoGravityResizeAspect;
- break;
- case ZFPlayerScalingModeAspectFill:
- presentView.videoGravity = AVLayerVideoGravityResizeAspectFill;
- break;
- case ZFPlayerScalingModeFill:
- presentView.videoGravity = AVLayerVideoGravityResize;
- break;
- default:
- break;
- }
- [CATransaction commit];
- }
- - (void)setVolume:(float)volume {
- _volume = MIN(MAX(0, volume), 1);
- self.player.volume = volume;
- }
- @end
|