iOS使用AVPlayer,播放本地,在線音頻

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ù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容