若是我只是簡單的播放一個視頻,而不須要考慮播放器的界面。iOS9.0 以前使用 MPMoviePlayerController
, 或者內部自帶一個 view 的 MPMoviePlayerViewController
. iOS9.0 以後,可使用 AVPictureInPictureController
, AVPlayerViewController
, 或者 WKWebView
。緩存
以上系統提供的播放器因爲高度的封裝性, 使得自定義播放器變的很難。 因此,若是我須要自定義播放器樣式的時候,可使用 AVPlayer
。 AVPlayer 存在於 AVFoundtion 中,更接近於底層,也更加靈活。網絡
AVFoundtion 框架中主要使用 AVAsset
類來展現媒體信息,好比: title, duration, size 等等。框架
AVAsset : 存儲媒體信息的一個抽象類,不能直接使用。ide
AVURLAsset : AVAsset 的一個子類,使用 URL 進行實例化,實例化對象包換 URL 對應視頻資源的全部信息。函數
AVPlayerItem : 有一個屬性爲 asset。起到觀察和管理視頻信息的做用。 好比,asset, tracks , status, duration ,loadedTimeRange 等。佈局
個人理解是, AVPlayItem 至關於 Model 層,包含 Media 的信息和播放狀態,並提供這些數據給視頻觀察者 好比:屬性 asset
,URL視頻的信息. loadedTimeRanges
,已緩衝進度。post
playerItemWithURL
或者 initWithURL:
url
在使用 AVPlayer 播放視頻時,提供視頻信息的是 AVPlayerItem,一個 AVPlayerItem 對應着一個URL視頻資源。spa
初始化一個 AVPlayItem 對象後,其屬性並非立刻就可使用。咱們必須確保 AVPlayerItem 已經被加載好了,能夠播放了,才能使用。 畢竟凡是和網絡扯上關係的都須要時間去加載。 那麼,何時屬性才能正常使用呢。 官方文檔給出瞭解決方案:code
直到 AVPlayerItem 的 status
屬性爲 AVPlayerItemStatusReadyToPlay
.
使用 KVO 鍵值觀察者,其屬性。
所以咱們在使用的時候,使用 URL 初始化 AVPlayerItem 後,還要給它添加觀察者。
AVPlayreItem 的屬性須要當 status 爲 ReadyToPlay 的時候才能夠正常使用。
觀察status屬性
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 觀察status屬性,
觀察loadedTimeRanges
若是想作緩衝進度條,顯示當前視頻的緩存進度,則須要觀察 loadedTimeRanges
.
[_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; // 觀察緩衝進度
AVPlayer建立方式
AVPlayer 有三種建立方式:
init
,initWithURL:
,initWithPlayerItem:
(URL,Item遍歷構造器方法)
使用 AVPlayer 時須要注意,AVPlayer 自己並不能顯示視頻, 顯示視頻的是 AVPlayerLayer。 AVPlayerLayer 繼承自 CALayer,添加到 view.layer 上就可使用了。
AVPlayerLayer建立方式
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player]; [superlayer addSublayer:playerLayer];
AVPlayerLayer 顯示視頻,AVPlayerItem 提供視頻信息, AVPlayer 管理和調控。 這是否是很是熟悉。 我以爲這裏也體現了 MVC 的思想(雖然AVPlayer繼承自NSObject), 把響應層, 顯示層, 信息層, 三層分離了。 明確了每層作的任務,使用起來就會更加駕輕就熟。
使用 AVPlayer 的核心,在於 AVPlayer 和 AVPlayerItem, AVPlayerLayer 添加到視圖的layer 上後,就沒有什麼事兒了。 思考一下,整個播放視頻的步驟。
首先,獲得視頻的URL
根據URL建立AVPlayerItem
把AVPlayerItem 提供給 AVPlayer
AVPlayerLayer 顯示視頻。
AVPlayer 控制視頻, 播放, 暫停, 跳轉 等等。
播放過程當中獲取緩衝進度,獲取播放進度。
視頻播放完成後作些什麼,是暫停仍是循環播放,仍是獲取最後一幀圖像。
// setAVPlayer self.player = [[AVPlayer alloc] init]; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; [self.playerView.layer addSublayer:_playerLayer];
在第一步,佈局初始化時,AVPlayer 並無 AVPlayerItem,AVPlayer 提供了 - (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
方法,用於切換視頻。
- (void)updatePlayerWithURL:(NSURL *)url { _playerItem = [AVPlayerItem playerItemWithURL:url]; // create item [_player replaceCurrentItemWithPlayerItem:_playerItem]; // replaceCurrentItem [self addObserverAndNotification]; // 註冊觀察者,通知 }
觀察 AVPlayerItem 的 status
屬性,當狀態變爲 AVPlayerStatusReadyToPlay
時纔可使用。
也能夠觀察 loadedTimeRanges
獲取緩衝進度
註冊觀察者:
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 觀察status屬性
執行觀察者方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { AVPlayerItem *item = (AVPlayerItem *)object; if ([keyPath isEqualToString:@"status"]) { AVPlayerStatus status = [[change objectForKey:@"new"] intValue]; // 獲取更改後的狀態 if (status == AVPlayerStatusReadyToPlay) { CMTime duration = item.duration; // 獲取視頻長度 // 設置視頻時間 [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]; // 更新緩衝條 } }
AVPlayer 提供了 play
, pause
, 和 - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler
方法。
在看 AVPlayer 的 seekToTime 以前,先來認識一個結構體。
CMTime 是專門用於標識電影時間的結構體.
typedef struct{ CMTimeValue value; // 幀數 CMTimeScale timescale; // 幀率(影片每秒有幾幀) CMTimeFlags flags; CMTimeEpoch epoch; } CMTime;
AVPlayerItem 的 duration 屬性就是一個 CMTime 類型的數據。 若是咱們想要獲取影片的總秒數那麼就能夠用 duration.value / duration.timeScale 計算出來。也可使用 CMTimeGetSeconds 函數
CMTimeGetSeconds(CMtime time)
double seconds = CMTimeGetSeconds(item.duration); // 至關於 duration.value / duration.timeScale
若是一個影片爲60frame(幀)每秒, 當前想要跳轉到 120幀的位置,也就是兩秒的位置,那麼就能夠建立一個 CMTime 類型數據。
CMTime,一般用以下兩個函數來建立.
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 區別在於,第一個函數的第一個參數能夠是float,其餘同樣。
拖拽方法以下:
- (IBAction)playerSliderValueChanged:(id)sender { _isSliding = YES; [self pause]; // 跳轉到拖拽秒處 // 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) { // 跳轉完成後 }]; }
AVPlayerItem 是使用 KVO 模式觀察狀態,和 緩衝進度。而 AVPlayer 給咱們直接提供了 觀察播放進度更爲方便的方法。
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
方法名如其意, 「添加週期時間觀察者」 ,參數1 interal
爲CMTime 類型的,參數2 爲一個 返回值爲空,參數爲 CMTime 的block類型。
簡而言之就是,每隔一段時間後執行 block。
好比: 我把時間間隔設置爲, 1/ 30 秒,而後 block 裏面更新 UI。就是一秒鐘更新 30次UI。
播放進度代碼以下:
// 觀察播放進度 - (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 當前播放秒 float currentPlayTime = (double)item.currentTime.value/ item.currentTime.timescale; // 更新slider, 若是正在滑動則不更新 if (_isSliding == NO) { [WeakSelf updateVideoSlider:currentPlayTime]; } } else { return; } }]; }
注意: 給 palyer 添加了 timeObserver 後,不使用的時候記得移除 removeTimeObserver
不然會佔用大量內存。
好比,我在dealloc裏面作了移除:
- (void)dealloc { [self removeObserveAndNOtification]; [_player removeTimeObserver:_playTimeObserver]; // 移除playTimeObserver}
AVPlaerItem 播放完成後,系統會自動發送通知,通知的定義詳情請見 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
所以,若是咱們想要在某個狀態下,執行某些操做。監聽 AVPlayerItem 的相關通知就好了。 好比,我想要播放完成後,暫停播放。 給AVPlayerItemDidPlayToEndTimeNotification
添加觀察者。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; // 播放完成後 - (void)playbackFinished:(NSNotification *)notification { NSLog(@"視頻播放完成通知"); _playerItem = [notification object]; [_playerItem seekToTime:kCMTimeZero]; // item 跳轉到初始 //[_player play]; // 循環播放 }
使用 AVPlayer 的時候,必定要注意 AVPlayer 、 AVPlayerLayer 和 AVPlayerItem 三者之間的關係。 AVPlayer 負責控制播放, layer 顯示播放, item 提供數據,當前播放時間, 已加載狀況。 Item 中一些基本的屬性, status, duration, loadedTimeRanges, currentTime(當前播放時間)。