AVPlayer屬于AVFoundation框架,不僅能夠播放音頻,還可以播放視頻,支持本地和網(wǎng)鏈,更加接近底層,定制也更加靈活。
為什么要寫這篇文章呢?其因有二:
- 1、github上有很多播放音頻的優(yōu)秀三方的框架,很方便,也很容易集成,但問題也比較多,底層都是C或者C++,要修改一個小BUG,難度系數(shù)比較高,例如:
StreamingKit
FreeStreamer
AudioStreamer
AFSoundManager
DOUAudioStreamer
以上,除了FreeStreamer,其他的幾個框架都使用過,其中StreamingKit和DOUAudioStreamer在線上版本使用過,性能都比較不錯。
DOUAudioStreamer 唯一的問題就是在使用緩存繼續(xù)播放,有問題,并且不支持seekToTime,這句話的意思呢就是,整個音頻緩沖完畢才能繼續(xù)播放,如果快進的時候整個音頻沒有緩沖完成(網(wǎng)絡(luò)較差的時候,音頻較大),這個就比較坑了。
StreamingKit 這個有個問題就是緩沖的進度無法回調(diào),無法獲取,源碼都是互斥鎖,自旋鎖,看著各種暈菜,有個問題就是,可能是我使用的姿勢不對,緩存文件比較大,但是在沙盒又找不見這個文件的存在,無法刪除,API也沒有提供對應(yīng)的接口,在手機的空間存儲中查看,占用空間很大。
- 2、這些框架很久都沒人維護,面對現(xiàn)在的產(chǎn)品需求,不能滿足。
基于以上原因,結(jié)合自己項目中出現(xiàn)的問題,決定用強悍的AV框架中AVPlayer,自己寫一個, 功能如下:
不要求實現(xiàn)流播
能支持緩存播放
有緩存進度回調(diào)
可以清除緩存文件即可
AVPlayer基礎(chǔ)用法介紹
以前做視頻開發(fā),在播放視頻時,只是簡單的播放一個視頻,而不需要考慮播放器的界面。
1、iOS9.0 之前使用 MPMoviePlayerController, 或者自帶一個 view 的 MPMoviePlayerViewController。
2、iOS9.0 之后,可以使用新的API AVPictureInPictureController, AVPlayerViewController。
3、甚至使用WKWebView。
以上播放器都是系統(tǒng)提供,優(yōu)點:封裝性很強,使用簡單方便。缺點:自由定制度太低。
所以在我們需要自己定制播放器的時候,就需要時用AVPlayer。
AVPlayer繼承NSObject,所以單獨使用AVPlayer時無法顯示視頻的,必須將視頻圖層添加到AVPlayerLayer中方能顯示視頻。使用AVPlayer首先了解一下幾個常用的類:
1、AVAsset:AVAsset類專門用于獲取多媒體的相關(guān)信息,包括獲取多媒體的畫面、聲音等信息,屬于一個抽象類,不能直接使用。
2、AVURLAsset:AVAsset的子類,可以根據(jù)一個URL路徑創(chuàng)建一個包含媒體信息的AVURLAsset對象。
3、AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態(tài),一個AVPlayerItem對應(yīng)著一個視頻資源。
4、AVPlayer:播放器。
5、CMTime:是一個結(jié)構(gòu)體,里面存儲著當(dāng)前的播放進度,總的播放時長。
一般項目中會初始化一個播放管理的工具類(單例):
1、實例化一個AVPlayer:
- (AVPlayer *)player {
if (_player == nil) {
_player = [[AVPlayer alloc] init];
_player.volume = 1.0; // 默認(rèn)最大音量
}
return _player;
}
2、播放一個音頻(本地和網(wǎng)絡(luò)都可以)
//播放音頻的方法
- (void)p_musicPlayerWithURL:(NSURL *)playerItemURL{
// 移除監(jiān)聽
[self p_currentItemRemoveObserver];
// 創(chuàng)建要播放的資源
AVPlayerItem *playerItem = [[AVPlayerItem alloc]initWithURL:playerItemURL];
// 播放當(dāng)前資源
[self.player replaceCurrentItemWithPlayerItem:playerItem];
// 添加觀察者
[self p_currentItemAddObserver];
}
3、注冊,使用KVO監(jiān)聽self.player.currentItem
1、監(jiān)聽status,AVPlayerItemStatus有三種狀態(tài):
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,
AVPlayerItemStatusReadyToPlay,
AVPlayerItemStatusFailed
};
2、監(jiān)聽loadedTimeRanges,這個就是緩沖進度,可以進行緩沖進度條的設(shè)置
3、AVPlayerItemDidPlayToEndTimeNotification,注冊這個通知,當(dāng)播放器播放完成的時候進行回調(diào)。
4、addPeriodicTimeObserverForInterval,監(jiān)聽當(dāng)前播放進度。
監(jiān)聽和移除代碼如下:
- (void)p_currentItemRemoveObserver {
[self.player.currentItem removeObserver:self forKeyPath:@"status"];
[self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
[self.player removeTimeObserver:self.timeObserver];
}
- (void)p_currentItemAddObserver {
//監(jiān)控狀態(tài)屬性,注意AVPlayer也有一個status屬性,通過監(jiān)控它的status也可以獲得播放狀態(tài)
[self.player.currentItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew) context:nil];
//監(jiān)控緩沖加載情況屬性
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
//監(jiān)控播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
//監(jiān)控時間進度
@weakify(self);
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
@strongify(self);
// 在這里將監(jiān)聽到的播放進度代理出去,對進度條進行設(shè)置
if (self.delegate && [self.delegate respondsToSelector:@selector(updateProgressWithPlayer:)]) {
[self.delegate updateProgressWithPlayer:self.player];
}
}];
}
4、KVO處理
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
AVPlayerItem *playerItem = object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = [change[@"new"] integerValue];
switch (status) {
case AVPlayerItemStatusReadyToPlay:
{
// 開始播放
[self play];
// 代理回調(diào),開始初始化狀態(tài)
if (self.delegate && [self.delegate respondsToSelector:@selector(startPlayWithplayer:)]) {
[self.delegate startPlayWithplayer:self.player];
}
}
break;
case AVPlayerItemStatusFailed:
{
NSLog(@"加載失敗");
TOAST_MSG(@"播放錯誤");
}
break;
case AVPlayerItemStatusUnknown:
{
NSLog(@"未知資源");
TOAST_MSG(@"播放錯誤");
}
break;
default:
break;
}
} else if([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray *array=playerItem.loadedTimeRanges;
//本次緩沖時間范圍
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
//緩沖總長度
NSTimeInterval totalBuffer = startSeconds + durationSeconds;
NSLog(@"共緩沖:%.2f",totalBuffer);
if (self.delegate && [self.delegate respondsToSelector:@selector(updateBufferProgress:)]) {
[self.delegate updateBufferProgress:totalBuffer];
}
} else if ([keyPath isEqualToString:@"rate"]) {
// rate=1:播放,rate!=1:非播放
float rate = self.player.rate;
if (self.delegate && [self.delegate respondsToSelector:@selector(player:changeRate:)]) {
[self.delegate player:self.player changeRate:rate];
}
} else if ([keyPath isEqualToString:@"currentItem"]) {
NSLog(@"新的currentItem");
if (self.delegate && [self.delegate respondsToSelector:@selector(changeNewPlayItem:)]) {
[self.delegate changeNewPlayItem:self.player];
}
}
}
- (void)playbackFinished:(NSNotification *)notifi {
NSLog(@"播放完成");
}
以上只是demo片段,核心代碼。
后面會介紹如何處理緩沖進度條、如何使用緩存進行播放。
未完待續(xù)...