如果我只是簡單的播放一個視頻贼急,而不需要考慮播放器的界面匈子。iOS9.0 之前使用 MPMoviePlayerController, 或者內(nèi)部自帶一個 view 的 MPMoviePlayerViewController. iOS9.0 之后杂数,可以使用AVPictureInPictureController, AVPlayerViewController, 或者 WKWebView。
以上系統(tǒng)提供的播放器由于高度的封裝性彻磁, 使得自定義播放器變的很難留凭。 所以,如果我需要自定義播放器樣式的時候升略,可以使用 AVPlayer微王。
AVPlayer 存在于 AVFoundtion 中,更接近于底層品嚣,也更加靈活炕倘。
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)前播放時間)蛇更。