iOS開發(fā)--AVPlayer教程(常用功能都在這里了)

如果我只是簡單的播放一個視頻贼急,而不需要考慮播放器的界面匈子。iOS9.0 之前使用 MPMoviePlayerController, 或者內(nèi)部自帶一個 view 的 MPMoviePlayerViewController. iOS9.0 之后杂数,可以使用AVPictureInPictureController, AVPlayerViewController, 或者 WKWebView。

以上系統(tǒng)提供的播放器由于高度的封裝性彻磁, 使得自定義播放器變的很難留凭。 所以,如果我需要自定義播放器樣式的時候升略,可以使用 AVPlayer微王。

AVPlayer 存在于 AVFoundtion 中,更接近于底層品嚣,也更加靈活炕倘。


1474352198387328.jpg

Representing and Using Media with AVFoundation
AVFoundtion 框架中主要使用 AVAsset
類來展示媒體信息,比如: title翰撑, duration罩旋, size 等等。
AVAsset : 存儲媒體信息的一個抽象類眶诈,不能直接使用涨醋。

AVURLAsset : AVAsset 的一個子類,使用 URL 進(jìn)行實例化逝撬,實例化對象包換 URL 對應(yīng)視頻資源的所有信息浴骂。

AVPlayerItem : 有一個屬性為 asset。起到觀察和管理視頻信息的作用宪潮。 比如靠闭,asset, tracks , status坎炼, duration 愧膀,loadedTimeRange 等。

我的理解是谣光, AVPlayItem 相當(dāng)于 Model 層檩淋,包含 Media 的信息和播放狀態(tài),并提供這些數(shù)據(jù)給視頻觀察者 比如:屬性 asset萄金,URL視頻的信息. loadedTimeRanges,已緩沖進(jìn)度蟀悦。

AVPlayerItem 使用

初始化

playerItemWithURL或者 initWithURL:
在使用 AVPlayer 播放視頻時,提供視頻信息的是 AVPlayerItem氧敢,一個 AVPlayerItem 對應(yīng)著一個URL視頻資源日戈。
初始化一個 AVPlayItem 對象后,其屬性并不是馬上就可以使用孙乖。我們必須確保 AVPlayerItem 已經(jīng)被加載好了浙炼,可以播放了份氧,才能使用。 畢竟凡是和網(wǎng)絡(luò)扯上關(guān)系的都需要時間去加載弯屈。 那么蜗帜,什么時候?qū)傩圆拍苷J褂媚亍?官方文檔給出了解決方案:
直到 AVPlayerItem 的 status
屬性為 AVPlayerItemStatusReadyToPlay.

使用 KVO 鍵值觀察者,其屬性资厉。

因此我們在使用的時候厅缺,使用 URL 初始化 AVPlayerItem 后,還要給它添加觀察者宴偿。

添加觀察者

AVPlayreItem 的屬性需要當(dāng) status 為 ReadyToPlay 的時候才可以正常使用湘捎。
觀察status屬性
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 觀察status屬性,
觀察loadedTimeRanges
如果想做緩沖進(jìn)度條窄刘,顯示當(dāng)前視頻的緩存進(jìn)度消痛,則需要觀察 loadedTimeRanges.
[_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; // 觀察緩沖進(jìn)度

AVPlayer & AVPlayerLayer

AVPlayer創(chuàng)建方式
AVPlayer 有三種創(chuàng)建方式:
init,initWithURL:,initWithPlayerItem:(URL,Item遍歷構(gòu)造器方法)
使用 AVPlayer 時需要注意都哭,AVPlayer 本身并不能顯示視頻秩伞, 顯示視頻的是 AVPlayerLayer。 AVPlayerLayer 繼承自 CALayer欺矫,添加到 view.layer 上就可以使用了纱新。
AVPlayerLayer創(chuàng)建方式
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];[superlayer addSublayer:playerLayer];

AVPlayerLayer 顯示視頻,AVPlayerItem 提供視頻信息穆趴, AVPlayer 管理和調(diào)控脸爱。 這是不是非常熟悉。 我覺得這里也體現(xiàn)了 MVC 的思想(雖然AVPlayer繼承自NSObject)未妹, 把響應(yīng)層簿废, 顯示層, 信息層络它, 三層分離了族檬。 明確了每層做的任務(wù),使用起來就會更加得心應(yīng)手化戳。
使用 AVPlayer 的核心单料,在于 AVPlayer 和 AVPlayerItem, AVPlayerLayer 添加到視圖的layer 上后点楼,就沒有什么事兒了扫尖。 思考一下,整個播放視頻的步驟掠廓。

首先换怖,得到視頻的URL

根據(jù)URL創(chuàng)建AVPlayerItem

把AVPlayerItem 提供給 AVPlayer

AVPlayerLayer 顯示視頻。

AVPlayer 控制視頻蟀瞧, 播放沉颂, 暫停条摸, 跳轉(zhuǎn) 等等。

播放過程中獲取緩沖進(jìn)度兆览,獲取播放進(jìn)度屈溉。

視頻播放完成后做些什么塞关,是暫停還是循環(huán)播放抬探,還是獲取最后一幀圖像。

播放步驟

1.布局頁面帆赢,初始化 AVPlayer 和 AVPlayerLayer

// setAVPlayer

   self.player = [[AVPlayer alloc] init]; 
   _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; 
   [self.playerView.layer addSublayer:_playerLayer];

2.根據(jù) URL 獲取 AVPayerItem小压,并替換 AVPlayer 的 AVPlayerItem

在第一步,布局初始化時椰于,AVPlayer 并沒有 AVPlayerItem怠益,
AVPlayer 提供了
- (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
方法,用于切換視頻瘾婿。

 - (void)updatePlayerWithURL:(NSURL *)url {    
 // create item 
_playerItem = [AVPlayerItem playerItemWithURL:url]; 
// replaceCurrentItem  
[_player  replaceCurrentItemWithPlayerItem:_playerItem];
// 注冊觀察者蜻牢,通知
[self addObserverAndNotification]; 
}

3.KVO 獲取視頻信息, 觀察緩沖進(jìn)度

觀察 AVPlayerItem 的 status屬性偏陪,當(dāng)狀態(tài)變?yōu)?AVPlayerStatusReadyToPlay時才可以使用抢呆。也可以觀察 loadedTimeRanges獲取緩沖進(jìn)度
注冊觀察者:

// 觀察status屬性
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; 

執(zhí)行觀察者方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    AVPlayerItem *item = (AVPlayerItem *)object;
    if ([keyPath isEqualToString:@"status"]) {
         //獲取更改后的狀態(tài)
        AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
        if (status == AVPlayerStatusReadyToPlay) {
            CMTime duration = item.duration; // 獲取視頻長度
            // 設(shè)置視頻時間
            [self setMaxDuration:CMTimeGetSeconds(duration)];
             // 播放
            [self play];
        } else if (status == AVPlayerStatusFailed) {
            NSLog(@"AVPlayerStatusFailed");
        } else {
            NSLog(@"AVPlayerStatusUnknown");
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
         NSTimeInterval timeInterval = [self availableDurationRanges]; // 緩沖時間
         CGFloat totalDuration = CMTimeGetSeconds(_playerItem.duration); // 總時間
        [self.loadedProgress setProgress:timeInterval / totalDuration animated:YES]; // 更新緩沖條
    }
}

4. 播放過程中響應(yīng):播放、 暫停笛谦、 跳轉(zhuǎn)

AVPlayer 提供了 play , pause, 和 - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler
方法抱虐。
在看 AVPlayer 的 seekToTime 之前,先來認(rèn)識一個結(jié)構(gòu)體饥脑。

CMTime 是專門用于標(biāo)識電影時間的結(jié)構(gòu)體.

typedef struct{    
    CMTimeValue    value;     // 幀數(shù)    
    CMTimeScale    timescale;  // 幀率(影片每秒有幾幀)    
    CMTimeFlags    flags;            
    CMTimeEpoch    epoch;
} CMTime;

AVPlayerItem 的 duration 屬性就是一個 CMTime 類型的數(shù)據(jù)恳邀。 如果我們想要獲取影片的總秒數(shù)那么就可以用 duration.value / duration.timeScale 計算出來。
也可以使用 CMTimeGetSeconds 函數(shù)
CMTimeGetSeconds(CMtime time)

double seconds = CMTimeGetSeconds(item.duration);  // 相當(dāng)于 duration.value / duration.timeScale

如果一個影片為60frame(幀)每秒灶轰, 當(dāng)前想要跳轉(zhuǎn)到 120幀的位置谣沸,也就是兩秒的位置,那么就可以創(chuàng)建一個 CMTime 類型數(shù)據(jù)笋颤。
CMTime,通常用如下兩個函數(shù)來創(chuàng)建.

CMTimeMake(int64_t value, int32_t scale)

    CMTime time1 = CMTimeMake(120, 60);

CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)

   CMTime time2 = CMTimeWithSeconds(120, 60);

CMTimeMakeWithSeconds 和CMTimeMake 區(qū)別在于鳄抒,第一個函數(shù)的第一個參數(shù)可以是float,其他一樣椰弊。

拖拽方法如下:
- (IBAction)playerSliderValueChanged:(id)sender {
_isSliding = YES;
[self pause];
// 跳轉(zhuǎn)到拖拽秒處
// self.playProgress.maxValue = value / timeScale
// value = progress.value * timeScale
// CMTimemake(value, timeScale) = (progress.value, 1.0)
CMTime changedTime = CMTimeMakeWithSeconds(self.playProgress.value, 1.0);
[_playerItem seekToTime:changedTime completionHandler:^(BOOL finished) {
// 跳轉(zhuǎn)完成后
}];
}

5.觀察 AVPlayer 播放進(jìn)度

AVPlayerItem 是使用 KVO 模式觀察狀態(tài)许溅,和 緩沖進(jìn)度。而 AVPlayer 給我們直接提供了 觀察播放進(jìn)度更為方便的方法秉版。

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

方法名如其意贤重, “添加周期時間觀察者” ,
參數(shù)1 interal 為CMTime 類型的清焕,
參數(shù)2 為一個 返回值為空并蝗,參數(shù)為 CMTime 的block類型祭犯。
簡而言之就是,每隔一段時間后執(zhí)行 block滚停。
比如: 我把時間間隔設(shè)置為沃粗, 1/ 30 秒,然后 block 里面更新 UI键畴。就是一秒鐘更新 30次UI最盅。

播放進(jìn)度代碼如下:
// 觀察播放進(jìn)度
- (void)monitoringPlayback:(AVPlayerItem *)item {
__weak typeof(self)WeakSelf = self;
// 觀察間隔, CMTime 為30分之一秒
_playTimeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
if (_touchMode != TouchPlayerViewModeHorizontal) {
// 獲取 item 當(dāng)前播放秒
float currentPlayTime = (double)item.currentTime.value/ item.currentTime.timescale;
// 更新slider, 如果正在滑動則不更新
if (_isSliding == NO) {
[WeakSelf updateVideoSlider:currentPlayTime];
}
} else {
return;
}
}];
}
注意: 給 palyer 添加了 timeObserver 后,不使用的時候記得移除 removeTimeObserver, 否則會占用大量內(nèi)存起惕。比如涡贱,我在dealloc里面做了移除:

- (void)dealloc {   
[self removeObserveAndNOtification];    
[_player removeTimeObserver:_playTimeObserver]; // 移除playTimeObserver
}

6.AVPlayerItem 通知

AVPlaerItem 播放完成后,系統(tǒng)會自動發(fā)送通知惹想,通知的定義詳情請見 AVPlayerItem.h.

/* Note that NSNotifications posted by AVPlayerItem may be posted on a 
different thread from the one on which the observer was registered. */
// notifications description
AVF_EXPORT NSString *const AVPlayerItemTimeJumpedNotification            NS_AVAILABLE(10_7, 5_0);   // the item's current time has changed discontinuously
AVF_EXPORT NSString *const AVPlayerItemDidPlayToEndTimeNotification      NS_AVAILABLE(10_7, 4_0);   // item has played to its end time
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_3);   // item has failed to play to its end time
AVF_EXPORT NSString *const AVPlayerItemPlaybackStalledNotification       NS_AVAILABLE(10_9, 6_0);    // media did not arrive in time to continue playback
AVF_EXPORT NSString *const AVPlayerItemNewAccessLogEntryNotification     NS_AVAILABLE(10_9, 6_0);   // a new access log entry has been added
AVF_EXPORT NSString *const AVPlayerItemNewErrorLogEntryNotification      NS_AVAILABLE(10_9, 6_0);   // a new error log entry has been added

// notification userInfo key                                                                    type
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeErrorKey     NS_AVAILABLE(10_7, 4_3);   // NSError

因此问词,如果我們想要在某個狀態(tài)下,執(zhí)行某些操作嘀粱。監(jiān)聽 AVPlayerItem 的相關(guān)通知就行了激挪。 比如,我想要播放完成后锋叨,暫停播放垄分。 給AVPlayerItemDidPlayToEndTimeNotification
添加觀察者。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector
(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; 

// 播放完成后
- (void)playbackFinished:(NSNotification *)notification {
NSLog(@"視頻播放完成通知");
_playerItem = [notification object];
[_playerItem seekToTime:kCMTimeZero]; // item 跳轉(zhuǎn)到初始
//[_player play]; // 循環(huán)播放
}

最后

使用 AVPlayer 的時候悲柱,一定要注意 AVPlayer 锋喜、 AVPlayerLayer 和 AVPlayerItem 三者之間的關(guān)系。 AVPlayer 負(fù)責(zé)控制播放豌鸡, layer 顯示播放嘿般, item 提供數(shù)據(jù),當(dāng)前播放時間涯冠, 已加載情況炉奴。 Item 中一些基本的屬性, status, duration, loadedTimeRanges, currentTime(當(dāng)前播放時間)蛇更。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞻赶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子派任,更是在濱河造成了極大的恐慌砸逊,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掌逛,死亡現(xiàn)場離奇詭異师逸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)豆混,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門篓像,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动知,“玉大人,你說我怎么就攤上這事员辩『辛福” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵奠滑,是天一觀的道長丹皱。 經(jīng)常有香客問我,道長养叛,這世上最難降的妖魔是什么种呐? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任宰翅,我火速辦了婚禮弃甥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汁讼。我一直安慰自己淆攻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布嘿架。 她就那樣靜靜地躺著瓶珊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耸彪。 梳的紋絲不亂的頭發(fā)上伞芹,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音蝉娜,去河邊找鬼唱较。 笑死,一個胖子當(dāng)著我的面吹牛召川,可吹牛的內(nèi)容都是我干的南缓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼荧呐,長吁一口氣:“原來是場噩夢啊……” “哼汉形!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起倍阐,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤概疆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后峰搪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岔冀,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年罢艾,在試婚紗的時候發(fā)現(xiàn)自己被綠了楣颠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尽纽。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖童漩,靈堂內(nèi)的尸體忽然破棺而出弄贿,到底是詐尸還是另有隱情,我是刑警寧澤矫膨,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布差凹,位于F島的核電站,受9級特大地震影響侧馅,放射性物質(zhì)發(fā)生泄漏危尿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一馁痴、第九天 我趴在偏房一處隱蔽的房頂上張望谊娇。 院中可真熱鬧,春花似錦罗晕、人聲如沸济欢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽法褥。三九已至,卻和暖如春酬屉,著一層夾襖步出監(jiān)牢的瞬間半等,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工呐萨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留杀饵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓垛吗,卻偏偏與公主長得像凹髓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怯屉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容