iOS AVPlayer 的使用實踐

  前兩天在網上看到一篇博客,介紹AVPlayer的使用,可是隻簡單介紹了一下單個的本地文件如何播放,心血來潮,就想着作一個相似於播放器的東西,可以實現播放網絡歌曲,循環播放多首音樂,下面咱們來實現一下緩存

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

  實現網絡歌曲的播放 實如今後臺也能播放歌曲 實現多首歌曲的循環播放 須要有播放/暫停和下一首的功能 須要在播放期間可以得知該首歌曲的總時長和當前播放時長測試

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

  播放網絡歌曲url

  由於須要播放網絡歌曲,我就往七牛雲上傳了幾首歌,就不用再本身處處去找歌曲了spa

  首先,明確咱們播放歌曲使用的是AVPlayer,至於爲何使用它不使用其餘的,由於它好用啊,蘋果封裝了強大的功能,讓咱們使用,幹嗎不用!其實還有其餘緣由,這個就等着你本身去搜索了。orm

  AVQueuePlayerserver

  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指的是幀數

相關文章
相關標籤/搜索