最近工做內容基本都是圍繞視頻播放展開的,從AVPlayer到IJKPlayer,期間遇到挺多問題,趟了不少bug,也總結了一些心得。對AVPlayer瞭解的更多一些,由於涉及點比較多,因此打算作一個系列詳盡的寫一下這部份內容。但願你們多多支持,有問題的地方歡迎指正。git
先來一張思惟導圖,做爲這篇文章的目錄索引: github
首先在iOS平臺使用播放視頻,可用的選項通常有這四個,他們各自的做用和功能以下:緩存
使用環境 | 優勢 | 缺點 | |
---|---|---|---|
MPMoviePlayerController | MediaPlayer | 簡單易用 | 不可定製 |
AVPlayerViewController | AVKit | 簡單易用 | 不可定製 |
AVPlayer | AVFoundation | 可定製度高,功能強大 | 不支持流媒體 |
IJKPlayer | IJKMediaFramework | 定製度高,支持流媒體播放 | 使用稍複雜 |
由此能夠看出,若是咱們不作直播功能AVPlayer就是一個最優的選擇。安全
另外AVPlayer是一個能夠播聽任何格式的全功能影音播放器 支持視頻格式: WMV,AVI,MKV,RMVB,RM,XVID,MP4,3GP,MPG等。 支持音頻格式:MP3,WMA,RM,ACC,OGG,APE,FLAC,FLV等。 支持視頻格式: MP4,MOV,M4V,3GP,AVI等。 支持音頻格式:MP3,AAC,WAV,AMR,M4A等。 詳見AVPlayer支持的視頻格式 ##如何使用 AVPlayer存在於AVFoundation框架,咱們使用時須要導入: #import <AVFoundation/AVFoundation.h>
bash
在建立一個播放器以前咱們須要先了解一些播放器相關的類app
咱們這片文章就圍繞這幾個參數展開,光說這些你可能還有點不明白,那咱們就圍繞一個最簡單的播放器作起,一點點擴展功能,在具體講解這幾個參數的做用。框架
根據上面描述,咱們知道AVPlayer
是播放的必要條件,因此咱們能夠構建的極簡播放器就是:ide
NSURL *playUrl = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14573563182394.mp4"];
self.player = [[AVPlayer alloc] initWithURL:playUrl];
[self.player play];
複製代碼
是否是很簡單,只有三行代碼! 可是它太簡單了,僅能夠完成音頻的播放,連畫面都沒有。回看上面播放相關類的介紹,是由於缺乏AVPlayerLayer
;做爲一個播放器,我不能只播放一條視頻啊,我還想根據須要切換視頻,那咱們就得把AVPlayerItem
也加上。 加上這兩個屬性以後的播放器是這樣的:測試
NSURL *playUrl = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14573563182394.mp4"];
self.playerItem = [AVPlayerItem playerItemWithURL:playUrl];
//若是要切換視頻須要調AVPlayer的replaceCurrentItemWithPlayerItem:方法
self.player = [AVPlayer playerWithPlayerItem:_playerItem];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = _videoView.bounds;
//放置播放器的視圖
[self.videoView.layer addSublayer:self.playerLayer];
[_player play];
複製代碼
如今的播放器稍微完整了一些,咱們在本身建立的容器裏能夠看到畫面了! ##更多功能 可是它做爲一個視頻播放器,仍是有不少不能讓人滿意的地方。例如:沒有暫停、快進快退、倍速播放等,另外若是遇到url錯誤是否是還要有播放失敗的提示,還有播放完成的相關提示。 爲完成這些,咱們須要對AVPlayerItem
和AVPlayerLayer
進一步瞭解一下。 ###1、AVPlayer的控制 前面講過該類是控制視頻播放行爲的,他的使用比較簡單。 播放視頻:ui
[self.player play];
複製代碼
暫停視頻:
[self.player pause];
複製代碼
更改速度:
self.player.rate = 1.5;//注意更改播放速度要在視頻開始播放以後纔會生效
複製代碼
還有一下其餘的控制,咱們能夠調轉到系統API進行查看
AVPlayerItem做爲資源管理對象,它控制着視頻從建立到銷燬的諸多狀態。
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,//未知
AVPlayerItemStatusReadyToPlay,//準備播放
AVPlayerItemStatusFailed//播放失敗
};
複製代碼
咱們使用KVO監測playItem.status,能夠獲取播放狀態的變化
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
複製代碼
在監聽回調中:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([object isKindOfClass:[AVPlayerItem class]]) {
if ([keyPath isEqualToString:@"status"]) {
switch (_playerItem.status) {
case AVPlayerItemStatusReadyToPlay:
//推薦將視頻播放放在這裏
[self play];
break;
case AVPlayerItemStatusUnknown:
NSLog(@"AVPlayerItemStatusUnknown");
break;
case AVPlayerItemStatusFailed:
NSLog(@"AVPlayerItemStatusFailed")
break;
default:
break;
}
}
}
}
複製代碼
雖然設置完播放配置咱們能夠直接調用[self.player play];
進行播放,可是更穩妥的方法是在回調收到AVPlayerItemStatusReadyToPlay
時進行播放
在AVPlayer中時間的表示有一個專門的結構體CMTime
typedef struct{
CMTimeValue value; // 幀數
CMTimeScale timescale; // 幀率(影片每秒有幾幀)
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
複製代碼
CMTime是以分數的形式表示時間,value表示分子,timescale表示分母,flags是位掩碼,表示時間的指定狀態。
獲取當前播放時間,能夠用value/timescale的方式:
float currentTime = self.playItem.currentTime.value/item.currentTime.timescale;
複製代碼
還有一種利用系統提供的方法,咱們用它獲取視頻總時間:
float totalTime = CMTimeGetSeconds(item.duration);
複製代碼
若是咱們想要添加一個計時的標籤不斷更新當前的播放進度,有一個系統的方法:
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
複製代碼
方法名如其意, 「添加週期時間觀察者」 ,參數1 interal 爲CMTime
類型的,參數2 queue爲串行隊列,若是傳入NULL就是默認主線程,參數3 爲CMTime
的block類型。 簡而言之就是,每隔一段時間後執行 block。 好比:咱們把interval設置成CMTimeMake(1, 10)
,在block裏面刷新label,就是一秒鐘刷新10次。
正常觀察播放進度一秒鐘一次就好了,因此能夠這麼寫:
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:nil usingBlock:^(CMTime time) {
AVPlayerItem *item = WeakSelf.playerItem;
NSInteger currentTime = item.currentTime.value/item.currentTime.timescale;
NSLog(@"當前播放時間:%ld",currentTime);
}];
複製代碼
獲取視頻的緩存狀況咱們須要監聽playerItem的loadedTimeRanges屬性
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
複製代碼
在KVO的回調裏:
if ([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray *array = _playerItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩衝時間範圍
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration); NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩衝總長度
NSLog(@"當前緩衝時間:%f",totalBuffer);
}
複製代碼
監聽playbackBufferEmpty
咱們能夠獲取當緩存不夠,視頻加載不出來的狀況:
[self.playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
複製代碼
在KVO回調裏:
if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
//some code show loading
}
複製代碼
playbackLikelyToKeepUp
和playbackBufferEmpty
是一對,用於監聽緩存足夠播放的狀態
[self.playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
/* ... */
if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
//因爲 AVPlayer 緩存不足就會自動暫停,因此緩存充足了須要手動播放,才能繼續播放
[_player play];
}
複製代碼
播放視頻只需一個url就能進行這樣太不安全了,別人能夠輕易的抓包盜鏈,爲此咱們須要爲視頻連接作一個請求頭的認證,這個功能能夠藉助AVURLAsset完成。
AVPlayerItem
除了能夠用URL初始化,還能夠用AVAsset
初始化,而AVAsset
不能直接使用,咱們看下AVURLAsset
的一個初始化方法:
/*! @param URL An instance of NSURL that references a media resource. @param options An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above. */
+ (instancetype)URLAssetWithURL:(NSURL *)URL options:(nullable NSDictionary<NSString *, id> *)options;
複製代碼
AVURLAssetPreferPreciseDurationAndTimingKey.
這個key對應的value是一個布爾值, 用來代表資源是否須要爲時長的精確展現,以及隨機時間內容的讀取進行提早準備。
除了這個蘋果官方介紹的功能外,他還能夠設置請求頭,這個算是隱藏功能了,由於蘋果並無明說這個功能,我是費了很大勁找到的。
NSMutableDictionary * headers = [NSMutableDictionary dictionary];
[headers setObject:@"yourHeader"forKey:@"User-Agent"];
self.urlAsset = [AVURLAsset URLAssetWithURL:self.videoURL options:@{@"AVURLAssetHTTPHeaderFieldsKey" : headers}];
// 初始化playerItem
self.playerItem = [AVPlayerItem playerItemWithAsset:self.urlAsset];
複製代碼
補充:後來得知這個參數是非公開的API,可是經多人測試項目上線不受影響。
//聲音被打斷的通知(電話打來)
AVAudioSessionInterruptionNotification
//耳機插入和拔出的通知
AVAudioSessionRouteChangeNotification
複製代碼
根據userInfo判斷具體狀態
//播放完成
AVPlayerItemDidPlayToEndTimeNotification
//播放失敗
AVPlayerItemFailedToPlayToEndTimeNotification
//異常中斷
AVPlayerItemPlaybackStalledNotification
複製代碼
對於播放完成的通知咱們能夠這麼寫:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
複製代碼
//進入後臺
UIApplicationWillResignActiveNotification
//返回前臺
UIApplicationDidBecomeActiveNotification
複製代碼
提示:全部通知和KVO的使用咱們都要記得在不用時remove掉。
視頻播放相關的知識比較多,細節的方面須要一點一點去扣。暫且寫這麼多吧,之後有須要會及時補充。 參考: ZFPlayer AVPlayer那些坑 若是還有什麼不理解的能夠簡書私信問我,或者查看我寫的Demo,歡迎star- ( ゜- ゜)つロ乾杯~