123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- //
- // ZFAVPlayerManager.m
- // ZFPlayer
- //
- // Copyright (c) 2016年 任子丰 ( http://github.com/renzifeng )
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- #import "ZFAVPlayerManager.h"
- #import <UIKit/UIKit.h>
- #if __has_include(<ZFPlayer/ZFPlayer.h>)
- #import <ZFPlayer/ZFKVOController.h>
- #import <ZFPlayer/ZFPlayerConst.h>
- #import <ZFPlayer/ZFReachabilityManager.h>
- #else
- #import "ZFKVOController.h"
- #import "ZFPlayerConst.h"
- #import "ZFReachabilityManager.h"
- #endif
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored"-Wdeprecated-declarations"
- /*!
- * Refresh interval for timed observations of AVPlayer
- */
- 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 ZFPlayerPresentView : UIView
- @property (nonatomic, strong) AVPlayer *player;
- /// default is AVLayerVideoGravityResizeAspect.
- @property (nonatomic, strong) AVLayerVideoGravity videoGravity;
- @end
- @implementation ZFPlayerPresentView
- + (Class)layerClass {
- return [AVPlayerLayer class];
- }
- - (AVPlayerLayer *)avLayer {
- return (AVPlayerLayer *)self.layer;
- }
- - (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 ZFAVPlayerManager () {
- id _timeObserver;
- id _itemEndObserver;
- ZFKVOController *_playerItemKVO;
- }
- @property (nonatomic, strong) AVPlayerLayer *playerLayer;
- @property (nonatomic, assign) BOOL isBuffering;
- @property (nonatomic, assign) BOOL isReadyToPlay;
- @property (nonatomic, strong) AVAssetImageGenerator *imageGenerator;
- @end
- @implementation ZFAVPlayerManager
- @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 shouldAutoPlay = _shouldAutoPlay;
- @synthesize scalingMode = _scalingMode;
- @synthesize playerPlayFailed = _playerPlayFailed;
- @synthesize presentationSizeChanged = _presentationSizeChanged;
- - (instancetype)init {
- self = [super init];
- if (self) {
- _scalingMode = ZFPlayerScalingModeAspectFit;
- _shouldAutoPlay = YES;
- }
- return self;
- }
- - (void)prepareToPlay {
- if (!_assetURL) return;
- _isPreparedToPlay = YES;
- [self initializePlayer];
- if (self.shouldAutoPlay) {
- [self play];
- }
- self.loadState = ZFPlayerLoadStatePrepare;
- if (self.playerPrepareToPlay) self.playerPrepareToPlay(self, self.assetURL);
- }
- - (void)reloadPlayer {
- self.seekTime = self.currentTime;
- [self prepareToPlay];
- }
- - (void)play {
- if (!_isPreparedToPlay) {
- [self prepareToPlay];
- } else {
- [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;
- self.playState = ZFPlayerPlayStatePlayStopped;
- if (self.player.rate != 0) [self.player pause];
- [_playerItem cancelPendingSeeks];
- [_asset cancelLoading];
- [self.player removeTimeObserver:_timeObserver];
- [self.player replaceCurrentItemWithPlayerItem:nil];
- self.presentationSize = CGSizeZero;
- _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;
- }
- - (void)replay {
- @zf_weakify(self)
- [self seekToTime:0 completionHandler:^(BOOL finished) {
- @zf_strongify(self)
- if (finished) {
- [self play];
- }
- }];
- }
- - (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
- if (self.totalTime > 0) {
- [_player.currentItem cancelPendingSeeks];
- int32_t timeScale = _player.currentItem.asset.duration.timescale;
- CMTime seekTime = CMTimeMakeWithSeconds(time, timeScale);
- [_player seekToTime:seekTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
- } else {
- self.seekTime = time;
- }
- }
- - (UIImage *)thumbnailImageAtCurrentTime {
- CMTime expectedTime = self.playerItem.currentTime;
- CGImageRef cgImage = NULL;
-
- self.imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
- self.imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
- cgImage = [self.imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
- if (!cgImage) {
- self.imageGenerator.requestedTimeToleranceBefore = kCMTimePositiveInfinity;
- self.imageGenerator.requestedTimeToleranceAfter = kCMTimePositiveInfinity;
- cgImage = [self.imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
- }
-
- UIImage *image = [UIImage imageWithCGImage:cgImage];
- return image;
- }
- - (void)thumbnailImageAtCurrentTime:(void(^)(UIImage *))handler {
- CMTime expectedTime = self.playerItem.currentTime;
- [self.imageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:expectedTime]] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
- if (handler) {
- UIImage *finalImage = [UIImage imageWithCGImage:image];
- handler(finalImage);
- }
- }];
- }
- #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 URLAssetWithURL:self.assetURL options:self.requestHeader];
- _playerItem = [AVPlayerItem playerItemWithAsset:_asset];
- _player = [AVPlayer playerWithPlayerItem:_playerItem];
- _imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
- [self enableAudioTracks:YES inPlayerItem:_playerItem];
-
- ZFPlayerPresentView *presentView = [[ZFPlayerPresentView alloc] init];
- presentView.player = _player;
- self.view.playerView = presentView;
- self.scalingMode = _scalingMode;
- if (@available(iOS 9.0, *)) {
- _playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = NO;
- }
- if (@available(iOS 10.0, *)) {
- _playerItem.preferredForwardBufferDuration = 5;
- /// 关闭AVPlayer默认的缓冲延迟播放策略,提高首屏播放速度
- _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;
- /// 没有网络
- if ([ZFReachabilityManager sharedManager].networkReachabilityStatus == ZFReachabilityStatusNotReachable) return;
- self.isBuffering = YES;
-
- // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
- [self pause];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // 如果此时用户已经暂停了,则不再需要开启播放了
- if (!self.isPlaying && self.loadState == ZFPlayerLoadStateStalled) {
- 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(self.timeRefreshInterval > 0 ? self.timeRefreshInterval : 0.1, NSEC_PER_SEC);
- @zf_weakify(self)
- _timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
- @zf_strongify(self)
- if (!self) return;
- NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
- if (self.isPlaying && self.loadState == ZFPlayerLoadStateStalled) self.player.rate = self.rate;
- 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) {
- @zf_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.isReadyToPlay) {
- self.isReadyToPlay = YES;
- self.loadState = ZFPlayerLoadStatePlaythroughOK;
- if (self.playerReadyToPlay) self.playerReadyToPlay(self, self.assetURL);
- }
- if (self.seekTime) {
- if (self.shouldAutoPlay) [self pause];
- @zf_weakify(self)
- [self seekToTime:self.seekTime completionHandler:^(BOOL finished) {
- @zf_strongify(self)
- if (finished) {
- if (self.shouldAutoPlay) [self play];
- }
- }];
- self.seekTime = 0;
- } else {
- if (self.shouldAutoPlay && self.isPlaying) [self play];
- }
- self.player.muted = self.muted;
- NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
- if (loadedRanges.count > 0) {
- if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
- }
- } else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
- self.playState = ZFPlayerPlayStatePlayFailed;
- self->_isPlaying = NO;
- NSError *error = self.player.currentItem.error;
- NSLog(@"%@",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.player 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;
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- });
- }
- #pragma mark - getter
- - (ZFPlayerView *)view {
- if (!_view) {
- ZFPlayerView *view = [[ZFPlayerView alloc] init];
- _view = view;
- }
- return _view;
- }
- - (AVPlayerLayer *)avPlayerLayer {
- ZFPlayerPresentView *view = (ZFPlayerPresentView *)self.view.playerView;
- return [view avLayer];
- }
- - (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;
- }
- #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];
- _assetURL = 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;
- ZFPlayerPresentView *presentView = (ZFPlayerPresentView *)self.view.playerView;
- self.view.scalingMode = scalingMode;
- 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;
- }
- }
- - (void)setVolume:(float)volume {
- _volume = MIN(MAX(0, volume), 1);
- self.player.volume = _volume;
- }
- - (void)setPresentationSize:(CGSize)presentationSize {
- _presentationSize = presentationSize;
- self.view.presentationSize = presentationSize;
- if (self.presentationSizeChanged) {
- self.presentationSizeChanged(self, self.presentationSize);
- }
- }
- @end
- #pragma clang diagnostic pop
|