AVPlayer詳解系列(一)參數設置

最近工做內容基本都是圍繞視頻播放展開的,從AVPlayer到IJKPlayer,期間遇到挺多問題,趟了不少bug,也總結了一些心得。對AVPlayer瞭解的更多一些,由於涉及點比較多,因此打算作一個系列詳盡的寫一下這部份內容。但願你們多多支持,有問題的地方歡迎指正。git

思惟導圖

先來一張思惟導圖,做爲這篇文章的目錄索引: github

AVPlayer.png

爲何使用AVPlayer:

首先在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:控制播放器的播放,暫停,播放速度
  • AVURLAsset : AVAsset 的一個子類,使用 URL 進行實例化,實例化對象包換 URL 對應視頻資源的全部信息。
  • AVPlayerItem:管理資源對象,提供播放數據源
  • AVPlayerLayer:負責顯示視頻,若是沒有添加該類,只有聲音沒有畫面

咱們這片文章就圍繞這幾個參數展開,光說這些你可能還有點不明白,那咱們就圍繞一個最簡單的播放器作起,一點點擴展功能,在具體講解這幾個參數的做用。框架

最簡單的播放器

根據上面描述,咱們知道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錯誤是否是還要有播放失敗的提示,還有播放完成的相關提示。 爲完成這些,咱們須要對AVPlayerItemAVPlayerLayer進一步瞭解一下。 ###1、AVPlayer的控制 前面講過該類是控制視頻播放行爲的,他的使用比較簡單。 播放視頻:ui

[self.player play];
複製代碼

暫停視頻:

[self.player pause];
複製代碼

更改速度:

self.player.rate = 1.5;//注意更改播放速度要在視頻開始播放以後纔會生效
複製代碼

還有一下其餘的控制,咱們能夠調轉到系統API進行查看

2、AVPlayerItem的控制

AVPlayerItem做爲資源管理對象,它控制着視頻從建立到銷燬的諸多狀態。

一、播放狀態 status

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);
}];
複製代碼

三、loadedTimeRange 緩存時間

獲取視頻的緩存狀況咱們須要監聽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

監聽playbackBufferEmpty咱們能夠獲取當緩存不夠,視頻加載不出來的狀況:

[self.playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 
複製代碼

在KVO回調裏:

if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
   //some code show loading 
}
複製代碼

五、playbackLikelyToKeepUp

playbackLikelyToKeepUpplaybackBufferEmpty是一對,用於監聽緩存足夠播放的狀態

[self.playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
 /* ... */
if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {    
    //因爲 AVPlayer 緩存不足就會自動暫停,因此緩存充足了須要手動播放,才能繼續播放 
    [_player play];      
}
複製代碼

AVURLAsset

播放視頻只需一個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- ( ゜- ゜)つロ乾杯~

相關文章
相關標籤/搜索