iOS AVPlayer 的使用實踐

EulahRoybal 7年前發布 | 10K 次閱讀 iOS開發 移動開發

前兩天在網上看到一篇博客,介紹AVPlayer的使用,但是只簡單介紹了一下單個的本地文件如何播放,心血來潮,就想著做一個類似于播放器的東西,能夠實現播放網絡歌曲,循環播放多首音樂,下面我們來實現一下

首先明確一下,在本文中需要講到的幾點:

  • 實現網絡歌曲的播放
  • 實現在后臺也能播放歌曲
  • 實現多首歌曲的循環播放
  • 需要有播放/暫停和下一首的功能
  • 需要在播放期間能夠得知該首歌曲的總時長和當前播放時長

本文中就暫時將這名多,后面還會豐富,例如實現緩存下載,實現歌曲緩存的進度查看,實現能夠使用耳機按鈕控制播放等等。

播放網絡歌曲

因為需要播放網絡歌曲,我就往七牛云上傳了幾首歌,就不用再自己到處去找歌曲了

首先,明確我們播放歌曲使用的是AVPlayer,至于為什么使用它不使用其他的,因為它好用啊,蘋果封裝了強大的功能,讓我們使用,干嘛不用!其實還有其他原因,這個就等著你自己去搜索了。

AVQueuePlayer

AVQueuePlayer是AVPlayer的一個子類,他可以實現多首歌曲播放,所以我們直接使用它了

//傳入多個AVPlayerItem來初始化AVQueuePlayer
+ (instancetype)queuePlayerWithItems:(NSArray<AVPlayerItem *> *)items;

AVPlayerItem

AVPlayerItem是一個資源對象,我們加載歌曲的時候都是使用它,它提供了兩種初始化方法

//初始化網絡資源
+ (instancetype)playerItemWithURL:(NSURL *)URL;

//初始化本地資源,本地的音樂或者影片資源都是通過AVAsset拿出來
+ (instancetype)playerItemWithAsset:(AVAsset *)asset;

先來試一下:

//初始化AVPlayerItem
NSMutableArray *items = [NSMutableArray array];
NSArray *urls = @[MUSIC_URL1,MUSIC_URL2,MUSIC_URL3,MUSIC_URL4,MUSIC_URL5];
for (NSString *url in urls) {
        AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:url]];
        [items addObject:item];
    }
//初始化AVQueuePlayer
AVQueuePlayer *player = [AVQueuePlayer queuePlayerWithItems: items];
//測試播放
if(player.status == AVPlayerStatusReadyToPlay){
    [player play];
}

上面的代碼看起來沒有錯,但是我在做的時候,卻遇到一個問題,第一次點擊的時候,并不會播放,第二次第三次就會開始播放了。

其實這里是有一個緩沖的原因,因為是網絡資源,涉及到一個緩沖,后面我們會對這個做處理,歌曲確實是能夠播放的

就這樣,簡單實現了多首歌曲的播放,但是我們還需要實現循環播放,這個就相對麻煩一點了。

要實現循環播放,我們就需要知道AVQueuePlayer的播放機制,對于AVQueuePlayer播放,是有一個隊列,每次播放完成一首歌曲過后,這首歌曲就會從隊列中刪除,即這個item會從隊列中刪除,并且如果我們想直接再將這個item再次加入隊列,是不能夠加入的,我們必須要在new 一個item,再次加載到這個隊列當中,才能夠實現再次播放。這個也是挺蛋疼的。

知道了這個,我們就有想法了,我們能夠在player最后一首歌曲即將播放完成后,再來新建一個隊列啊。思路是正確的,但是我們不能夠直接得到player正在播放最后一首歌曲,這時候我想到的是一個timer檢測,通過timer去檢測player的播放隊列是否還剩下一首歌曲,如果是的話,我們就新建隊列,加入到player的播放序列中

首先,我們在開始播放歌曲的時候,就需要將timer啟動,監測player

self.checkMusicTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(checkMusic) userInfo:nil repeats:YES];

在checkMusic我們判斷當前是否隊列中只有一首歌曲

- (void)checkMusic
{

    if (self.player.items.count == 1){
        [self prepareItems];//這個方法即是再次創建隊列,加入到player播放序列中
        [self play];
    }
}
// 準備歌曲
// 因為需要歌曲循環播放,每次AVQueuePlayer播放完成一首歌曲,就會將其從隊列中移除
// 所以我們需要在歌曲最后一首播放完之前重新為AVQueuePlayer創建一個播放隊列,這樣就能夠實現循環播放
//
//
- (void)prepareItems{
    NSMutableArray *items = [NSMutableArray array];
    NSArray *urls = @[MUSIC_URL1,MUSIC_URL2,MUSIC_URL3,MUSIC_URL4,MUSIC_URL5];
    for (NSString *url in urls) {
        AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:url]];
        [items addObject:item];
//這里是添加每首歌曲的監測,我們后面會講到
        [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
    }
    self.playerItems = items;
    for (AVPlayerItem *item in items) {
        if ([self.player canInsertItem:item afterItem:self.player.items.lastObject]) {
            [self.player insertItem:item afterItem:self.player.items.lastObject];
        }
    }
}

這樣一來,我們就能夠實現循環播放了,這里的代碼和后面要講到的有關聯,所以這里看不清晰也沒關系,接著往后看

上面我們講了,有個緩沖的原因,導致首次點擊播放的時候,不能夠成功播放,在AVPlayerItem中有一個屬性loadedTimeRanges,表示的是緩存狀態,我們可以對他進行觀察,來進行播放

//上面的代碼已經寫出了對緩沖的檢測
[item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

然后我們在觀察者中

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        NSLog(@"緩沖");
        [self play];
    }
}

我們在每個item中加入了觀察者,在什么時候移除呢,當然是在每首歌曲播放完成后移除,如果不移除將會崩潰

再次對每個item進行觀測,播放結束時

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];

在播放結束,移除觀察者

- (void)playbackFinished:(NSNotification *)notice {
    NSLog(@"播放完成");
    [self.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

實現后臺播放

要實現后臺播放,很簡單只需要加入幾行代碼

//設置可后臺播放
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:nil];

然后我們還需要在項目里設置

播放暫停

這個就很簡單了

直接調方法就行

上一首下一首也是直接調用方法就行

/*!
 @method        play
 @abstract      Signals the desire to begin playback at the current item's natural rate.
 @discussion    Equivalent to setting the value of rate to 1.0.
 */

- (void)play;
- /*!
 @method        pause
 @abstract      Pauses playback.
 @discussion    Equivalent to setting the value of rate to 0.0.
 */
- (void)pause;

/*!
    @method     advanceToNextItem
    @abstract   Ends playback of the current item and initiates playback of the next item in the player's queue.
    @discussion Removes the current item from the play queue.
*/
- (void)advanceToNextItem;

時長計算

為player加一個觀察者就行

-(void)playerDidPlay{
    // //添加播放進度觀察者
        __weak typeof(self) weakSelf = self;
        self.timeObserver = [self.manager.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0,1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            float current = CMTimeGetSeconds(time);
            float total = CMTimeGetSeconds(weakSelf.manager.currentItem.duration);
            weakSelf.total = [NSString stringWithFormat:@"%.2f",total];
            weakSelf.current = [NSString stringWithFormat:@"%.f",current];
            weakSelf.label.text = [NSString stringWithFormat:@"%@/%@",weakSelf.current,weakSelf.total];
        }];
    _isPlaying = YES;
    [self.play setTitle:@"暫停" forState:UIControlStateNormal];
}

其中的CMTime指的是幀數

 本文由用戶 EulahRoybal 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!