一罗岖、 前言
- AVPlayer本身并不顯示視頻确买!需要一個AVPlayerLayer播放層來顯示視頻,然后添加到父視圖的layer中。
- AVPlayer只負責視頻管理和調控候生!而視頻資源是由AVPlayerItem提供的同眯,每個AVPlayerItem對應一個視頻地址。
- 待補充...
二唯鸭、 重要屬性和方法
1. AVplayerItem:###
+ (instancetype)playerItemWithURL:(NSURL *)URL;
初始化視頻資源方法
duration
視頻總時長 (有光CMTime結構體可以自己查下)
status
視頻資源的狀態(tài) (需要監(jiān)聽的屬性)
loadedTimeRanges
在線視頻的緩沖進度 (需要監(jiān)聽的屬性)
playbackBufferEmpty
進行跳轉后沒數(shù)據(jù) (可選監(jiān)聽)
playbackLikelyToKeepUp
進行跳轉后有數(shù)據(jù) (可選監(jiān)聽)
2. AVplayer:###
rate
播放速度 常用判斷播放狀態(tài) =1播放 =0暫停
- (void)play; - (void)pause;
播放暫停
- (void)seekToTime:(CMTime)time;
跳轉到指定時間
- (CMTime)currentTime;
獲取當前播放進度
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
監(jiān)聽當前播放進度
- (void)removeTimeObserver:(id)observer;
移除監(jiān)聽 (銷毀播放器的時候調用)
- (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
切換視頻資源
3. AVPlayerLayer###
videoGravity
視頻的填充方式
4. AVQueuePlayer:這個是用于處理播放列表的操作须蜗,待學習...###
三、 剩下的都在代碼里了
LPAVPlayer.h
typedef NS_ENUM(NSInteger, LPAVPlayerStatus) {
LPAVPlayerStatusReadyToPlay = 0, // 準備好播放
LPAVPlayerStatusLoadingVideo, // 加載視頻
LPAVPlayerStatusPlayEnd, // 播放結束
LPAVPlayerStatusCacheData, // 緩沖視頻
LPAVPlayerStatusCacheEnd, // 緩沖結束
LPAVPlayerStatusPlayStop, // 播放中斷 (多是沒網(wǎng))
LPAVPlayerStatusItemFailed, // 視頻資源問題
LPAVPlayerStatusEnterBack, // 進入后臺
LPAVPlayerStatusBecomeActive, // 從后臺返回
};
@protocol LPAVPlayerDelegate <NSObject>
@optional
// 數(shù)據(jù)刷新
- (void)refreshDataWith:(NSTimeInterval)totalTime Progress:(NSTimeInterval)currentTime LoadRange:(NSTimeInterval)loadTime;
// 狀態(tài)/錯誤 提示
- (void)promptPlayerStatusOrErrorWith:(LPAVPlayerStatus)status;
@end
@interface LPAVPlayer : UIView
@property (nonatomic, weak) id<LPAVPlayerDelegate>delegate;
// 視頻總長度
@property (nonatomic, assign) NSTimeInterval totalTime;
// 視頻總長度
//@property (nonatomic, assign) NSTimeInterval currentTime;
// 緩存數(shù)據(jù)
@property (nonatomic, assign) NSTimeInterval loadRange;
/**
準備播放器
@param videoPath 視頻地址
*/
//- (void)setupPlayerWith:(NSString *)videoPath;
- (void)setupPlayerWith:(NSURL *)videoURL;
/** 播放 */
- (void)play;
/** 暫停 */
- (void)pause;
/** 播放|暫停 */
- (void)playOrPause:(void (^)(BOOL isPlay))block;
/** 拖動視頻進度 */
- (void)seekPlayerTimeTo:(NSTimeInterval)time;
/** 跳動中不監(jiān)聽 */
- (void)startToSeek;
/**
切換視頻
@param videoPath 視頻地址
*/
//- (void)replacePalyerItem:(NSString *)videoPath;
- (void)replacePalyerItem:(NSURL *)videoURL;
@end
LPAVPlayer.m
#import <AVFoundation/AVFoundation.h>
@interface LPAVPlayer ()
/** 播放器 */
@property (nonatomic, strong) AVPlayer *player;
/** 視頻資源 */
@property (nonatomic, strong) AVPlayerItem *currentItem;
/** 播放器觀察者 */
@property (nonatomic ,strong) id timeObser;
// 拖動進度條的時候停止刷新數(shù)據(jù)
@property (nonatomic ,assign) BOOL isSeeking;
// 是否需要緩沖
@property (nonatomic, assign) BOOL isCanPlay;
// 是否需要緩沖
@property (nonatomic, assign) BOOL needBuffer;
@end
@implementation LPAVPlayer
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];
self.isCanPlay = NO;
self.needBuffer = NO;
self.isSeeking = NO;
/**
* 這里view用來做AVPlayer的容器
* 完成對AVPlayer的二次封裝
* 要求 :
* 1. 暴露視頻輸出的API 視頻時長 當前播放時間 進度
* 2. 暴露出易于控制的data入口 播放 暫停 進度拖動 音量 亮度 清晰度調節(jié)
*/
}
return self;
}
#pragma mark - 屬性和方法
- (NSTimeInterval)totalTime
{
return CMTimeGetSeconds(self.player.currentItem.duration);
}
/**
準備播放器
@param videoPath 視頻地址
*/
- (void)setupPlayerWith:(NSURL *)videoURL
{
[self creatPlayer:videoURL];
[_player play];
[self useDelegateWith:LPAVPlayerStatusLoadingVideo];
}
/**
avplayer自身有一個rate屬性
rate ==1.0目溉,表示正在播放明肮;rate == 0.0,暫停缭付;rate == -1.0柿估,播放失敗
*/
/** 播放 */
- (void)play
{
if (self.player.rate == 0) {
[self.player play];
}
}
/** 暫停 */
- (void)pause
{
if (self.player.rate == 1.0) {
[self.player pause];
}
}
/** 播放|暫停 */
- (void)playOrPause:(void (^)(BOOL isPlay))block;
{
if (self.player.rate == 0) {
[self.player play];
block(YES);
}else if (self.player.rate == 1.0) {
[self.player pause];
block(NO);
}else {
NSLog(@"播放器出錯");
}
}
/** 拖動視頻進度 */
- (void)seekPlayerTimeTo:(NSTimeInterval)time
{
[self pause];
[self startToSeek];
__weak typeof(self)weakSelf = self;
[self.player seekToTime:CMTimeMake(time, 1.0) completionHandler:^(BOOL finished) {
[weakSelf endSeek];
[weakSelf play];
}];
}
/** 跳動中不監(jiān)聽 */
- (void)startToSeek
{
self.isSeeking = YES;
}
- (void)endSeek
{
self.isSeeking = NO;
}
/**
切換視頻
@param videoURL 視頻地址
*/
- (void)replacePalyerItem:(NSURL *)videoURL
{
self.isCanPlay = NO;
[self pause];
[self removeNotification];
[self removeObserverWithPlayItem:self.currentItem];
self.currentItem = [self getPlayerItem:videoURL];
[self.player replaceCurrentItemWithPlayerItem:self.currentItem];
[self addObserverWithPlayItem:self.currentItem];
[self addNotificatonForPlayer];
[self play];
}
/**
播放狀態(tài)代理調用
@param status 播放狀態(tài)
*/
- (void)useDelegateWith:(LPAVPlayerStatus)status
{
if (self.isCanPlay == NO) {
return;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(promptPlayerStatusOrErrorWith:)]) {
[self.delegate promptPlayerStatusOrErrorWith:status];
}
}
#pragma mark - 創(chuàng)建播放器
/**
獲取播放item
@param videoURL 視頻網(wǎng)址
@return AVPlayerItem
*/
- (AVPlayerItem *)getPlayerItem:(NSURL *)videoURL
{
// 轉utf8 防止中文報錯
// videoPath = [videoPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// videoPath = [videoPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
// NSURL *url = [NSURL URLWithString:videoPath];
// 如果播放本地視頻要用 NSURL *url = [NSURL fileURLWithPath:videoURL];
// 所以替換成URL
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
return item;
}
/**
創(chuàng)建播放器
*/
- (void)creatPlayer:(NSURL *)videoURL
{
if (!_player) {
self.currentItem = [self getPlayerItem:videoURL];
_player = [AVPlayer playerWithPlayerItem:self.currentItem];
[self creatPlayerLayer];
[self addPlayerObserver];
[self addObserverWithPlayItem:self.currentItem];
[self addNotificatonForPlayer];
}
}
/**
創(chuàng)建播放器 layer 層
AVPlayerLayer的videoGravity屬性設置
AVLayerVideoGravityResize, // 非均勻模式。兩個維度完全填充至整個視圖區(qū)域
AVLayerVideoGravityResizeAspect, // 等比例填充陷猫,直到一個維度到達區(qū)域邊界
AVLayerVideoGravityResizeAspectFill, // 等比例填充秫舌,直到填充滿整個視圖區(qū)域的妖,其中一個維度的部分區(qū)域會被裁剪
*/
- (void)creatPlayerLayer
{
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.player];
layer.frame = self.bounds;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.layer addSublayer:layer];
}
#pragma mark - 添加 監(jiān)控
/** 給player 添加 time observer */
- (void)addPlayerObserver
{
__weak typeof(self)weakSelf = self;
_timeObser = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
AVPlayerItem *playerItem = weakSelf.player.currentItem;
float current = CMTimeGetSeconds(time);
float total = CMTimeGetSeconds([playerItem duration]);
if (weakSelf.isSeeking) {
return;
}
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(refreshDataWith:Progress:LoadRange:)]) {
[weakSelf.delegate refreshDataWith:total Progress:current LoadRange:weakSelf.loadRange];
}
// NSLog(@"當前播放進度 %.2f/%.2f.",current,total);
}];
}
/** 移除 time observer */
- (void)removePlayerObserver
{
[_player removeTimeObserver:_timeObser];
}
/** 給當前播放的item 添加觀察者
需要監(jiān)聽的字段和狀態(tài)
status : AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay,AVPlayerItemStatusFailed
loadedTimeRanges : 緩沖進度
playbackBufferEmpty : seekToTime后,緩沖數(shù)據(jù)為空足陨,而且有效時間內數(shù)據(jù)無法補充嫂粟,播放失敗
playbackLikelyToKeepUp : seekToTime后,可以正常播放,相當于readyToPlay墨缘,一般拖動滑竿菊花轉星虹,到了這個這個狀態(tài)菊花隱藏
*/
- (void)addObserverWithPlayItem:(AVPlayerItem *)item
{
[item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
[item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
}
/** 移除 item 的 observer */
- (void)removeObserverWithPlayItem:(AVPlayerItem *)item
{
[item removeObserver:self forKeyPath:@"status"];
[item removeObserver:self forKeyPath:@"loadedTimeRanges"];
[item removeObserver:self forKeyPath:@"playbackBufferEmpty"];
[item removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
}
/** 數(shù)據(jù)處理 獲取到觀察到的數(shù)據(jù) 并進行處理 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
AVPlayerItem *item = object;
if ([keyPath isEqualToString:@"status"]) {// 播放狀態(tài)
[self handleStatusWithPlayerItem:item];
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {// 緩沖進度
[self handleLoadedTimeRangesWithPlayerItem:item];
} else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {// 跳轉后沒數(shù)據(jù)
// 轉菊花
if (self.isCanPlay) {
NSLog(@"跳轉后沒數(shù)據(jù)");
self.needBuffer = YES;
[self useDelegateWith:LPAVPlayerStatusCacheData];
}
} else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {// 跳轉后有數(shù)據(jù)
// 隱藏菊花
if (self.isCanPlay && self.needBuffer) {
NSLog(@"跳轉后有數(shù)據(jù)");
self.needBuffer = NO;
[self useDelegateWith:LPAVPlayerStatusCacheEnd];
}
}
}
/**
處理 AVPlayerItem 播放狀態(tài)
AVPlayerItemStatusUnknown 狀態(tài)未知
AVPlayerItemStatusReadyToPlay 準備好播放
AVPlayerItemStatusFailed 播放出錯
*/
- (void)handleStatusWithPlayerItem:(AVPlayerItem *)item
{
AVPlayerItemStatus status = item.status;
switch (status) {
case AVPlayerItemStatusReadyToPlay: // 準備好播放
NSLog(@"AVPlayerItemStatusReadyToPlay");
self.isCanPlay = YES;
[self useDelegateWith:LPAVPlayerStatusReadyToPlay];
break;
case AVPlayerItemStatusFailed: // 播放出錯
NSLog(@"AVPlayerItemStatusFailed");
[self useDelegateWith:LPAVPlayerStatusItemFailed];
break;
case AVPlayerItemStatusUnknown: // 狀態(tài)未知
NSLog(@"AVPlayerItemStatusUnknown");
break;
default:
break;
}
}
/** 處理緩沖進度 */
- (void)handleLoadedTimeRangesWithPlayerItem:(AVPlayerItem *)item
{
NSArray *loadArray = item.loadedTimeRanges;
CMTimeRange range = [[loadArray firstObject] CMTimeRangeValue];
float start = CMTimeGetSeconds(range.start);
float duration = CMTimeGetSeconds(range.duration);
NSTimeInterval totalTime = start + duration;// 緩存總長度
_loadRange = totalTime;
// NSLog(@"緩沖進度 -- %.2f",totalTime);
}
/**
添加關鍵通知
AVPlayerItemDidPlayToEndTimeNotification 視頻播放結束通知
AVPlayerItemTimeJumpedNotification 視頻進行跳轉通知
AVPlayerItemPlaybackStalledNotification 視頻異常中斷通知
UIApplicationDidEnterBackgroundNotification 進入后臺
UIApplicationDidBecomeActiveNotification 返回前臺
*/
- (void)addNotificatonForPlayer
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(videoPlayEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// [center addObserver:self selector:@selector(videoPlayToJump:) name:AVPlayerItemTimeJumpedNotification object:nil];//沒意義
[center addObserver:self selector:@selector(videoPlayError:) name:AVPlayerItemPlaybackStalledNotification object:nil];
[center addObserver:self selector:@selector(videoPlayEnterBack:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[center addObserver:self selector:@selector(videoPlayBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
/** 移除 通知 */
- (void)removeNotification
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// [center removeObserver:self name:AVPlayerItemTimeJumpedNotification object:nil];
[center removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil];
[center removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[center removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
[center removeObserver:self];
}
/** 視頻播放結束 */
- (void)videoPlayEnd:(NSNotification *)notic
{
NSLog(@"視頻播放結束");
[self useDelegateWith:LPAVPlayerStatusPlayEnd];
[self.player seekToTime:kCMTimeZero];
}
///** 視頻進行跳轉 */ 沒有意義的方法 會被莫名的多次調動,不清楚機制
//- (void)videoPlayToJump:(NSNotification *)notic
//{
// NSLog(@"視頻進行跳轉");
//}
/** 視頻異常中斷 */
- (void)videoPlayError:(NSNotification *)notic
{
NSLog(@"視頻異常中斷");
[self useDelegateWith:LPAVPlayerStatusPlayStop];
}
/** 進入后臺 */
- (void)videoPlayEnterBack:(NSNotification *)notic
{
NSLog(@"進入后臺");
[self useDelegateWith:LPAVPlayerStatusEnterBack];
}
/** 返回前臺 */
- (void)videoPlayBecomeActive:(NSNotification *)notic
{
NSLog(@"返回前臺");
[self useDelegateWith:LPAVPlayerStatusBecomeActive];
}
#pragma mark - 銷毀 release
- (void)dealloc
{
NSLog(@"--- %@ --- 銷毀了",[self class]);
[self removeNotification];
[self removePlayerObserver];
[self removeObserverWithPlayItem:self.player.currentItem];
}
@end
四镊讼、 在控制器中的調用
因為控制器還有一些其他的代碼搁凸,就不整個貼出來了,截取一部分重要的貼出來狠毯。
1. 創(chuàng)建播放器View
/**
創(chuàng)建播放器視圖
*/
- (void)buildPlayerView
{
CGRect rect = CGRectMake(0, 64, self.view.bounds.size.width, 250);
_playView = [[LPAVPlayer alloc] initWithFrame:rect];
_playView.delegate = self;
[self.view addSubview:_playView];
[_playView setupPlayerWith:url];
}
2. 代理 -- 狀態(tài)提示
// LPAVPlayer delegate ----- 狀態(tài)提示
- (void)promptPlayerStatusOrErrorWith:(LPAVPlayerStatus)status
{
switch (status) {
case LPAVPlayerStatusLoadingVideo:// 開始準備
[self.activity startAnimating];
break;
case LPAVPlayerStatusReadyToPlay:// 準備完成
[self.activity stopAnimating];
[self.playOrPause setTitle:@"暫停" forState:UIControlStateNormal];
self.slider.maximumValue = _playView.totalTime;
self.slider.value = 0;
self.loadProgress.progress = 0;
break;
default:
break;
}
}
3. 代理 -- 刷新數(shù)據(jù)
// LPAVPlayer delegate ----- 刷新數(shù)據(jù)
- (void)refreshDataWith:(NSTimeInterval)totalTime Progress:(NSTimeInterval)currentTime LoadRange:(NSTimeInterval)loadTime
{
[self.loadProgress setProgress:(loadTime/totalTime) animated:YES];
[self.slider setValue:currentTime animated:YES];
self.timeLabel.text = [NSString stringWithFormat:@"%@/%@",[self otherConvertTimeFormat:currentTime],[self otherConvertTimeFormat:totalTime]];
}
- (NSString *)otherConvertTimeFormat:(NSInteger)time
{
NSString *needStr = @"";
if (time < 3600) {
needStr = [NSString stringWithFormat:@"%02li:%02li",lround(floor(time/60.f)),lround(floor(time/1.f))%60];
} else {
needStr = [NSString stringWithFormat:@"%02li:%02li:%02li",lround(floor(time/3600.f)),lround(floor(time%3600)/60.f),lround(floor(time/1.f))%60];
}
return needStr;
}
五护糖、 本文參考的文章
作者:夜千尋墨 - AVPlayer
作者:SomeBoy - iOS開發(fā)AVPlayer深入淺出
作者:KenshinCui iOS開發(fā)系列--音頻播放、錄音嚼松、視頻播放嫡良、拍照、視頻錄制