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指的是帧数

相关文章
相关标签/搜索