iOS開發(fā)常用的兩種視頻播放方式戚揭,一種是使用MPMoviePlayerController,還有就是使用AVPlayer儒搭。MPMoviePlayerController系統(tǒng)高度封裝使用起來很方便吠架,但是如果要高度自定義播放器就比較麻煩。而AVPlayer則恰好相反搂鲫,靈活性更強傍药,使用起來也麻煩一點。本文將對AVPlayer的使用做個簡單的介紹魂仍。
1拐辽、AVPlayer加載播放視頻
AVPlayer繼承NSObject,所以單獨使用AVPlayer時無法顯示視頻的擦酌,必須將視頻圖層添加到AVPlayerLayer中方能顯示視頻俱诸。使用AVPlayer首先了解一下幾個常用的類:
AVAsset:AVAsset類專門用于獲取多媒體的相關(guān)信息,包括獲取多媒體的畫面、聲音等信息赊舶。
AVURLAsset:AVAsset的子類乙埃,可以根據(jù)一個URL路徑創(chuàng)建一個包含媒體信息的AVURLAsset對象。
AVPlayerItem:一個媒體資源管理對象锯岖,管理者視頻的一些基本信息和狀態(tài),一個AVPlayerItem對應(yīng)著一個視頻資源甫何。
AVPlayer加載視頻的代碼如下:
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4"]];
//添加監(jiān)聽
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
//設(shè)置模式
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
playerLayer.contentsScale = [UIScreen mainScreen].scale;
playerLayer.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
[self.view.layer addSublayer:playerLayer];
//監(jiān)聽回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
AVPlayerItem *playerItem = (AVPlayerItem *)object;
if ([keyPath isEqualToString:@"loadedTimeRanges"]){
}else if ([keyPath isEqualToString:@"status"]){
if (playerItem.status == AVPlayerItemStatusReadyToPlay){
NSLog(@"playerItem is ready");
[self.avPlayer play];
} else{
NSLog(@"load break");
}
}
}
此處代碼中添加了對AVPlayerItem的"loadedTimeRanges"和"status"屬性監(jiān)聽出吹,status枚舉值有 AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay, AVPlayerItemStatusFailed。只有當(dāng)status為AVPlayerItemStatusReadyToPlay是調(diào)用 AVPlayer的play方法視頻才能播放辙喂。
運行效果
2捶牢、AVPlayer當(dāng)前緩沖進度以及當(dāng)前播放進度的處理
獲取視頻當(dāng)前的緩沖進度:
通過監(jiān)聽AVPlayerItem的"loadedTimeRanges",可以實時知道當(dāng)前視頻的進度緩沖巍耗,計算方法如下:
- (NSTimeInterval)availableDurationWithplayerItem:(AVPlayerItem *)playerItem
{
NSArray *loadedTimeRanges = [playerItem loadedTimeRanges];
CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩沖區(qū)域
NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);
NSTimeInterval durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;// 計算緩沖總進度
return result;
}
獲取視頻當(dāng)前的播放進度:
//視頻當(dāng)前的播放進度
NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
//視頻的總長度
NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
AVPlayer提供了一個Block回調(diào)秋麸,當(dāng)播放進度改變的時候回主動回調(diào)該Block,但是當(dāng)視頻卡頓的時候是不會回調(diào)的炬太,可以在該回調(diào)里面處理進度條以及播放時間的刷新灸蟆,詳細方法如下:
__weak __typeof(self) weakSelf = self;
[self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//當(dāng)前播放的時間
NSTimeInterval current = CMTimeGetSeconds(time);
//視頻的總時間
NSTimeInterval total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
//設(shè)置滑塊的當(dāng)前進度
weakSelf.slider.sliderPercent = current/total;
NSLog(@"%f", weakSelf.slider.sliderPercent);
//設(shè)置時間
weakSelf.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [weakSelf formatPlayTime:current], [weakSelf formatPlayTime:total]];
}];
//將時間轉(zhuǎn)換成00:00:00格式
- (NSString *)formatPlayTime:(NSTimeInterval)duration
{
int minute = 0, hour = 0, secend = duration;
minute = (secend % 3600)/60;
hour = secend / 3600;
secend = secend % 60;
return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
}
改變視頻當(dāng)前的播放進度:
當(dāng)滑塊滑動的時候需要改變當(dāng)前視頻的播放進度,代碼如下:
//處理滑塊
- (void)progressValueChange:(AC_ProgressSlider *)slider
{
//當(dāng)視頻狀態(tài)為AVPlayerStatusReadyToPlay時才處理(當(dāng)視頻沒加載的時候亲族,直接禁止掉滑塊事件)
if (self.avPlayer.status == AVPlayerStatusReadyToPlay) {
NSTimeInterval duration = self.slider.sliderPercent* CMTimeGetSeconds(self.avPlayer.currentItem.duration);
CMTime seekTime = CMTimeMake(duration, 1);
[self.avPlayer seekToTime:seekTime completionHandler:^(BOOL finished) {
}];
}
}
播放進度控件的定制:
該控件應(yīng)該包含4個部分炒考,總進度可缚、緩沖進度、當(dāng)前播放進度還有一個滑塊斋枢。效果圖如下:
最簡單的實現(xiàn)方式是UIProgressView跟UISlider兩個控件疊加起來帘靡,效果不是太好。demo是自定義的UIControl瓤帚,詳細實現(xiàn)方式請查看demo中的AC_ProgressSlider類描姚。
當(dāng)視頻卡頓的時候處理旋轉(zhuǎn)loading方法:
上面說過- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;該方法在卡頓的時候不會回調(diào),所以只用該方法處理不了這種情況戈次。我好像也沒找到相關(guān)的api轩勘,所以demo中采用的是開啟定時器,然后用一個lastTime保留當(dāng)前的播放進度朝扼,當(dāng)下次調(diào)用的時候用lastTime跟當(dāng)前的進度進行比較赃阀,如果相等說明播放卡頓了,代碼如下:
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(upadte)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//更新方法
- (void)upadte
{
NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
//如果用戶在手動滑動滑塊擎颖,則不對滑塊的進度進行設(shè)置重繪
if (!self.slider.isSliding) {
self.slider.sliderPercent = current/total;
}
if (current!=self.lastTime) {
[self.activity stopAnimating];
self.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [self formatPlayTime:current], [self formatPlayTime:total]];
}else{
[self.activity startAnimating];
}
self.lastTime = current;
}
3榛斯、AVPlayer播放暫停的處理
這個比較簡單、 分別是pause和play方法
//播放暫停按鈕
- (void)playOrPauseAction:(UIButton *)sender
{
sender.selected = !sender.selected;
if (self.avPlayer.rate == 1) {
[self.avPlayer pause];
self.link.paused = YES;
[self.activity stopAnimating];
} else {
[self.avPlayer play];
self.link.paused = NO;
}
}
4搂捧、AVPlayer播放完成的處理
添加通知即可
//播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayDidEnd)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
5驮俗、更換當(dāng)前播放的AVPlayerItem
當(dāng)視頻播放完時或者用戶切換不同的視頻時候就要更換當(dāng)前的視頻,代碼如下:
//切換當(dāng)前播放的內(nèi)容
- (void)changeCurrentplayerItemWithAC_VideoModel:(AC_VideoModel *)model
{
if (self.avPlayer) {
//由暫停狀態(tài)切換時候 開啟定時器允跑,將暫停按鈕狀態(tài)設(shè)置為播放狀態(tài)
self.link.paused = NO;
self.playButton.selected = NO;
//移除當(dāng)前AVPlayerItem對"loadedTimeRanges"和"status"的監(jiān)聽
[self removeObserveWithPlayerItem:self.avPlayer.currentItem];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:model.url];
[self addObserveWithPlayerItem:playerItem];
self.avPlayerItem = playerItem;
//更換播放的AVPlayerItem
[self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
self.playButton.enabled = NO;
self.slider.enabled = NO;
}
}
感覺寫的有點亂王凑,詳細的看demo吧,demo運行效果如下:
完整代碼7牛下載連接