AVPlayer講解

思維導圖

為什么使用AVPlayer:

首先在iOS平臺使用播放視頻,可用的選項一般有這四個,他們各自的作用和功能如下:

由此可以看出,如果我們不做直播功能AVPlayer就是一個最優(yōu)的選擇。

另外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>

幾個播放相關的參數

在創(chuàng)建一個播放器之前我們需要先了解一些播放器相關的類

AVPlayer:控制播放器的播放,暫停,播放速度

AVURLAsset : AVAsset 的一個子類,使用 URL 進行實例化,實例化對象包換 URL 對應視頻資源的所有信息。

AVPlayerItem:管理資源對象,提供播放數據源

AVPlayerLayer:負責顯示視頻,如果沒有添加該類,只有聲音沒有畫面

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

最簡單的播放器

根據上面描述,我們知道AVPlayer是播放的必要條件,所以我們可以構建的極簡播放器就是:

NSURL*playUrl = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14573563182394.mp4"];

self.player = [[AVPlayer alloc] initWithURL:playUrl];

[self.player play];

是不是很簡單,只有三行代碼!

但是它太簡單了,僅可以完成音頻的播放,連畫面都沒有?;乜瓷厦娌シ畔嚓P類的介紹,是因為缺少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];

現在的播放器稍微完整了一些,我們在自己創(chuàng)建的容器里可以看到畫面了!

更多功能

但是它作為一個視頻播放器,還是有很多不能讓人滿意的地方。例如:沒有暫停、快進快退、倍速播放等,另外如果遇到url錯誤是不是還要有播放失敗的提示,還有播放完成的相關提示。

為完成這些,我們需要對AVPlayerItem和AVPlayerLayer進一步了解一下。

一、AVPlayer的控制

前面講過該類是控制視頻播放行為的,他的使用比較簡單。

播放視頻:

[self.player play];

暫停視頻:

[self.player pause];

更改速度:

self.player.rate =1.5;//注意更改播放速度要在視頻開始播放之后才會生效

還有一下其他的控制,我們可以調轉到系統(tǒng)API進行查看

二、AVPlayerItem的控制

AVPlayerItem作為資源管理對象,它控制著視頻從創(chuàng)建到銷毀的諸多狀態(tài)。

1、播放狀態(tài) status

typedef NS_ENUM(NSInteger,AVPlayerItemStatus) {

????????????????????AVPlayerItemStatusUnknown,//未知

????????????????????AVPlayerItemStatusReadyToPlay,//準備播放

????????????????????AVPlayerItemStatusFailed//播放失敗

};

我們使用KVO監(jiān)測playItem.status,可以獲取播放狀態(tài)的變化

[self.playerItem addObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];

在監(jiān)聽回調中:

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{

????????if([object isKindOfClass:[AVPlayerItemclass]]) {

????????????????if([keyPath isEqualToString:@"status"]) {

????????????????????????switch(_playerItem.status) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? caseAVPlayerItemStatusReadyToPlay://推薦將視頻播放放這里? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[self play];

????????????????????????????????????????????????????????break;

????????????????????????????????????caseAVPlayerItemStatusUnknown:

????????????????????????????????????????????????????????NSLog(@"AVPlayerItemStatusUnknown");

????????????????????????????????????????????????????????break;

????????????????????????????????????caseAVPlayerItemStatusFailed:

????????????????????????????????????????????????????????NSLog(@"AVPlayerItemStatusFailed");

????????????????????????????????????????????????????????break;

????????????????????????????????????????default:break;? ? ? ?

?????????????}? ? ? ? ? ?

?}}

雖然設置完播放配置我們可以直接調用[self.player play];進行播放,但是更穩(wěn)妥的方法是在回調收到AVPlayerItemStatusReadyToPlay時進行播放

2、視頻的時間信息

在AVPlayer中時間的表示有一個專門的結構體CMTime

typedef struct{

????????????CMTimeValuevalue;// 幀數

????????????CMTimeScaletimescale;// 幀率(影片每秒有幾幀)

????????????CMTimeFlagsflags;

????????????CMTimeEpochepoch;? ??

}CMTime;

CMTime是以分數的形式表示時間,value表示分子,timescale表示分母,flags是位掩碼,表示時間的指定狀態(tài)。

獲取當前播放時間,可以用value/timescale的方式:

floatcurrentTime =self.playItem.currentTime.value/item.currentTime.timescale;

還有一種利用系統(tǒng)提供的方法,我們用它獲取視頻總時間:

floattotalTime? =CMTimeGetSeconds(item.duration);

如果我們想要添加一個計時的標簽不斷更新當前的播放進度,有一個系統(tǒng)的方法:

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullabledispatch_queue_t)queue usingBlock:(void(^)(CMTimetime))block;

方法名如其意, “添加周期時間觀察者” ,參數1 interal 為CMTime 類型的,參數2 queue為串行隊列,如果傳入NULL就是默認主線程,參數3 為CMTime 的block類型。

簡而言之就是,每隔一段時間后執(zhí)行 block。

比如:我們把interval設置成CMTimeMake(1, 10),在block里面刷新label,就是一秒鐘刷新10次。

正常觀察播放進度一秒鐘一次就行了,所以可以這么寫:

[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1,1) queue:nilusingBlock:^(CMTimetime) {

AVPlayerItem*item = WeakSelf.playerItem;

NSIntegercurrentTime = item.currentTime.value/item.currentTime.timescale;

NSLog(@"當前播放時間:%ld",currentTime);

}];

3、loadedTimeRange? 緩存時間

獲取視頻的緩存情況我們需要監(jiān)聽playerItem的loadedTimeRanges屬性

[self.playerItem addObserver:selfforKeyPath:@"loadedTimeRanges"options:NSKeyValueObservingOptionNewcontext:nil];

在KVO的回調里:

if([keyPath isEqualToString:@"loadedTimeRanges"]){

????????NSArray*array = _playerItem.loadedTimeRanges;

? ? ? ? //本次緩沖時間范圍

????????CMTimeRangetimeRange = [array.firstObject CMTimeRangeValue];

? ? ? ? ?floatstartSeconds =CMTimeGetSeconds(timeRange.start);

????????floatdurationSeconds =CMTimeGetSeconds(timeRange.duration);

????????NSTimeIntervaltotalBuffer = startSeconds + durationSeconds;//緩沖總長度

????????NSLog(@"當前緩沖時間:%f",totalBuffer);

}

4、playbackBufferEmpty

監(jiān)聽playbackBufferEmpty我們可以獲取當緩存不夠,視頻加載不出來的情況:

[self.playerItem addObserver:selfforKeyPath:@"playbackBufferEmpty"options:NSKeyValueObservingOptionNewcontext:nil];

在KVO回調里:

if([keyPath isEqualToString:@"playbackBufferEmpty"]) {

//some code show loading?

?}

5、playbackLikelyToKeepUp

playbackLikelyToKeepUp和playbackBufferEmpty是一對,用于監(jiān)聽緩存足夠播放的狀態(tài)

[self.playerItem addObserver:selfforKeyPath:@"playbackLikelyToKeepUp"options:NSKeyValueObservingOptionNewcontext:nil];

/* ... */

if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {

//由于 AVPlayer 緩存不足就會自動暫停,所以緩存充足了需要手動播放,才能繼續(xù)播放? [_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:(nullableNSDictionary *)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,但是經多人測試項目上線不受影響。

播放相關通知

1、聲音類:

//聲音被打斷的通知(電話打來)

AVAudioSessionInterruptionNotification

//耳機插入和拔出的通知

AVAudioSessionRouteChangeNotification

根據userInfo判斷具體狀態(tài)

2、播放類

//播放完成

AVPlayerItemDidPlayToEndTimeNotification

//播放失敗

AVPlayerItemFailedToPlayToEndTimeNotification

//異常中斷

AVPlayerItemPlaybackStalledNotification

對于播放完成的通知我們可以這么寫:

[[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotificationobject:[self.player currentItem]];

3、系統(tǒng)狀態(tài)

//進入后臺

UIApplicationWillResignActiveNotification

//返回前臺

UIApplicationDidBecomeActiveNotification

提示:所有通知和KVO的使用我們都要記得在不用時remove掉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容