因為產(chǎn)品需求要修改視頻播放的展示策略趣避,就簡單的梳理了一下目前端上的視頻播放功能啸箫,簡單整理了一下基本實現(xiàn)拧廊,以便于之后查閱虹茶。
播放器實現(xiàn)思路
由于視頻的展示在不同的產(chǎn)品上會有不同的樣式盏混,比如列表頁的視頻樣式诗轻,具體詳情頁視頻的樣式以及點擊視頻放大播放的樣式都是不同的苞笨,因此最好把具體的視頻播放邏輯與展示 UI 分開寫稠腊,便于以后的功能添加及修改东跪。
AVPlayer 實現(xiàn)視頻播放
- AVPlayer : AVPlayer 提供了單個視頻播放功能畸陡,可以播放本地視頻和網(wǎng)絡資源,提供播放虽填,暫停等功能丁恭。
- AVPlayerItem : 一個媒體資源管理對象,管理者視頻的一些基本信息和狀態(tài)
- AVPlayerLayer : 是 CALayer 的子類卤唉,AVPlayer 實例化的對象(視頻內(nèi)容)需要需要放到 AVPlayerLayer 上才能播放涩惑。
視頻播放功能的實現(xiàn)大致需要以下幾個步驟:
1、創(chuàng)建 AVPlayer 實例桑驱,并將其添加到 AVPlayerLayer 實例上竭恬;
2跛蛋、監(jiān)聽 AVPlayerItem 的 status 屬性狀態(tài)變化,判斷視頻是否可以正常播放痊硕;
3赊级、監(jiān)聽 AVPlayerItem 的 loadedTimeRanges 屬性狀態(tài)變化,處理下載進度條顯示岔绸;
4理逊、調(diào)用 addPeriodicTimeObserverForInterval:queue:usingBlock: 方法來處理視頻播放進度條的變化;
5盒揉、調(diào)用- (void)seekToTime: toleranceBefore: toleranceAfter: 方法晋被,實現(xiàn)視頻跳轉(zhuǎn)到某一時刻播放
6、 視頻播放結(jié)束處理
以下是具體實現(xiàn)過程:
創(chuàng)建 AVPlayerItem 對象刚盈,并通過該對象實例化 AVPlayer 對象羡洛,將 AVPlayer 添加到 AVPlayerLayer 對象上
self.playerItem = [AVPlayerItem playerItemWithURL:videoUrl];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
[self.playerLayer setPlayer:self.player];
以上對象都創(chuàng)建完了并不能順利播放視頻,需要知道視頻是否下載成功藕漱,能不能順利播放欲侮,網(wǎng)絡不好或者鏈接無效的情況下我們都應該對其作出不同處理,這里就需要添加 KVO 監(jiān)聽視頻的緩存進度和播放狀態(tài)肋联,并且注意要在適當?shù)臅r候移除威蕉。
// 監(jiān)聽播放狀態(tài)
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 監(jiān)聽緩沖進度
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[self.playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty" context:nil];
Apple 為我們提供了三種播放狀態(tài):
- AVPlayerStatusReadyToPlay : 說明已經(jīng)準備ok,可以播放了橄仍;
- AVPlayerStatusFailed: 可以通過 playerItem.error 信息來獲取失敗原因韧涨,例如比較常見的錯誤碼 playerItem.error.code == NSURLErrorNotConnectedToInternet || playerItem.error.code == NSURLErrorCannotFindHost ,可以給出找不到網(wǎng)絡的錯誤提示沙兰;
- ** AVPlayerStatusUnknown**: 嘗試下載資源但發(fā)生未知錯誤
另外在項目中還會出現(xiàn)緩存不足無法播放的情況氓奈,這種情況我們需要添加對 playbackBufferEmpty 的KVO觀察, 如果self.playbackBufferEmpty == YES鼎天,則視頻無法正常播放。
實際情況下可能還有其他狀態(tài)暑竟,比如網(wǎng)絡狀態(tài)的好壞轉(zhuǎn)換會影響視頻的播放斋射,對于不同的狀態(tài)我們都應該有相應的處理,以保證視頻的正常播放但荤。
一般的視頻播放器都有下載進度條的顯示罗岖,通過 KVO 方法監(jiān)聽 loadedTimeRanges 屬性我們可以得到視頻緩沖的進度,具體實現(xiàn)可以查看下面代碼腹躁。
typedef NS_ENUM(NSInteger, AVPlayerStatus) {
AVPlayerStatusUnknown,
AVPlayerStatusReadyToPlay,
AVPlayerStatusFailed
};
// KVO 方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
AVPlayerItem *playerItem = (AVPlayerItem *)object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status = [[change objectForKey:@"new"] integerValue];
if (status == AVPlayerStatusReadyToPlay) {
// 停止緩存動畫桑包,開始播放
// 設置視頻的總時長
if ([self.delegate respondsToSelector:@selector(videoTotalTime:)]) {
[self.delegate videoTotalTime:CMTimeGetSeconds(self.player.currentItem.duration)];
}
} else if (status == AVPlayerStatusFailed) {
NSLog(@"AVPlayerStatusFailed == %@", playerItem.error);
return;
} else if (status == AVPlayerStatusUnknown) {
return;
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
// 處理緩沖進度條
NSTimeInterval bufferTime = [self currentVideoLoadedTime];
NSTimeInterval totalTime = CMTimeGetSeconds(self.player.currentItem.duration);
CGFloat progress = bufferTime/totalTime;
if ([self.delegate respondsToSelector:@selector(videoPlayer: loadProgress:)]) {
[self.delegate videoPlayer:self loadProgress:progress];
}
} else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
}
}
關于播放的進度需要處理,重點是addPeriodicTimeObserverForInterval:queue:usingBlock: 方法纺非,該方法返回當前播放的 timeline哑了,當視頻暫停赘方、播放或者跳到某一時間進行播放的時候都會調(diào)用該方法。需要注意的是暫停和重新播放并不需要我們做額外的操作弱左,只需要調(diào)用 pause 或者 play 方法即可窄陡,時間的問題該方法已經(jīng)幫我們處理好了。每次調(diào)用該方法對應的要調(diào)用 -removeTimeObserver: 對其進行移除拆火,避免發(fā)生未知的錯誤跳夭。
__weak typeof(self) weakSelf = self;
self.playbackTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30) queue:NULL usingBlock:^(CMTime time) {
// 播放進度條以及時間的顯示
if ([weakSelf.delegate respondsToSelector:@selector(videoCurrentTime:)]) {
Float64 durationTime = CMTimeGetSeconds(weakSelf.playerItem.currentTime);
[weakSelf.delegate videoCurrentTime:durationTime];
}
}];
// 移除操作
[self.player removeTimeObserver:_playbackTimeObserver];
關于跳轉(zhuǎn)到某一時刻進行播放,只需要執(zhí)行下面的方法即可们镜,為了跳轉(zhuǎn)到正確的位置币叹,需要將 toleranceBefore 和 toleranceAfter 都設置為 kCMTimeZero。
- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter;
到此位置播放邏輯已經(jīng)基本實現(xiàn)模狭,調(diào)用 [self.player play]; 即可實現(xiàn)視頻的播放颈抚。
在具體實現(xiàn)過程中我們需要添加視頻播放結(jié)束通知,以便做下一步處理
// 添加視頻播放結(jié)束通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playDidEndNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// 這里設置視頻播放結(jié)束就自動重播胞皱,也可以停止播放顯示重播按鈕邪意,具體處理看需求
- (void)playDidEndNotification:(NSNotification *)notification {
self.playEnd = YES;
[self.delegate isVideoEnd:self.playEnd];
// 自動重播
[self.player seekToTime:CMTimeMakeWithSeconds(0, 600) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player play];
}
播放器樣式實現(xiàn)
對于進度條,播放暫停按鈕等 UI 的實現(xiàn)在另一個類中進行處理反砌,兩者可以通過 protocol 來進行數(shù)據(jù)交互處理雾鬼,具體的 UI 代碼這里就不一一列出了,可以到 github 上下載簡單的demo 來看一下 宴树。
播放邏輯與 UI 的展示通過 HJPlayView 進行組合策菜,可以通過 HJPlayViewType 來選擇不同的 UI 樣式,demo 中只給出了全屏播放的 UI 樣子酒贬,列表頁的可以通過繼承 HJMaskView 來自己實現(xiàn)又憨,只需要在以下方法進行添加就好。
- (void)setType:(HJPlayViewType)type {
- (void)setType:(HJPlayViewType)type {
if (type == HJPlayViewTypeForPlay) {
_maskView = [self maskView];
}
if (type == HJPlayViewTypeForScan) {
//添加不同的樣式即可
}
}
}
這里只是實現(xiàn)了最簡單的播放效果锭吨,繼續(xù)學習~~~