UDN-企业互联网技术人气社区

板块导航

浏览  : 1445
回复  : 0

[移动平台] iOS播放远程网络音乐的核心技术点

[复制链接]
哥屋恩的头像 楼主
发表于 2016-11-6 17:11:43 | 显示全部楼层 |阅读模式
  一、前言

  这两天做了个小项目涉及到了远程音乐播放,因为第一次做这种音乐项目,边查资料边做,其中涉及到主要技术点有:

  如何播放远程网络音乐

  如何切换当前正在播放中的音乐资源

  如何监听音乐播放的各种状态(播放器状态、播放的进度、缓冲的进度,播放完成)

  如何手动操控播放进度

  如何在后台模式或者锁屏情况下正常播放音乐

  如何在锁屏模式下显示音乐播放信息和远程操控音乐

  如果您对一块技术点有兴趣或者正在寻找相关资料,那么本篇或许能提供一些参考或启发。

  二、 网络音乐播放的核心技术点

  根据自己的经验和查了一些音乐播放的相关资料,最简单和最易上手的的技术方案我想应该是采用ios系统自带的AVFoundation框架。

  我们知道AVFoundation框架是苹果专门为多媒体打造的一个库,这个库非常强大,专门用来处理音视频等复杂的多媒体技术,而本篇要讲的所有技术点就是基于AVFoundation框架中的一个类——AVPlayer。

  那么AVPlayer是什么?

  你可以把他看成是一个已经封装好的播放器,它的作用是用来播放远程的或本地的视频和音频。因为本地的音视频的播放比较简单,这里就不做讲述,本编主要是讲远程音乐播放,因为都是基于AVPlayer同一套API,所以掌握远程音乐播放其实就是相当于掌握远程视频播放。好了废话就不多说了,下面开始上菜。

  1、导入AVFoundation框架,创建AVPlayer播放器

  1.   -(AVPlayer *)player

  2.   {

  3.   if (_player == nil) {

  4.   // AVPlayerItem是一个包装音乐资源的类,初始化时可以传入一个音乐的url

  5.   AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://xxxxxxxx"]];

  6.   //通过AVPlayerItem初始化player

  7.   _player = [[AVPlayer alloc] initWithPlayerItem:item];

  8.   }

  9.   return _player;

  10.   }
复制代码


  此处懒加载创建,让播放器成为控制器的全局属性,注意需要强引用,否则回收释放掉了就无法播放。

  2、播放或停止音乐

  1.   //开始播放

  2.   [self.player play];

  3.   //停止播放

  4.   [self.player pause];
复制代码


  这个没什么好讲的,只要调用AVPlayer的两个实例方法

  3、切换当前正在播放中的音乐资源

  1.   //创建需要播放的AVPlayerItem

  2.   AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:model.url]];

  3.   //替换当前音乐资源

  4.   [self.player replaceCurrentItemWithPlayerItem:item];
复制代码


  这个可以用于歌曲的切换,如上一首、下一首。

  4、通过KVO监听播放器的状态

 
  1.  [self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
复制代码


  拿到播放器的currentItem,注册当前对象为观察者,监听它的status属性。status属性是AVPlayerItemStatus类型,它是一个枚举类型,如下:

  1.   typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {

  2.   AVPlayerItemStatusUnknown,//未知状态

  3.   AVPlayerItemStatusReadyToPlay,//准备播放

  4.   AVPlayerItemStatusFailed//加载失败

  5.   };
复制代码


  当status属性值发生改变时,就会触发观察者方法的回调,如下:

  1.   //观察者回调

  2.   -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

  3.   {

  4.   //注意这里查看的是self.player.status属性

  5.   if ([keyPath isEqualToString:@"status"]) {

  6.   switch (self.player.status) {

  7.   case AVPlayerStatusUnknown:

  8.   {

  9.   NSLog(@"未知转态");

  10.   }

  11.   break;

  12.   case AVPlayerStatusReadyToPlay:

  13.   {

  14.   NSLog(@"准备播放");

  15.   }

  16.   break;

  17.   case AVPlayerStatusFailed:

  18.   {

  19.   NSLog(@"加载失败");

  20.   }

  21.   break;

  22.   default:

  23.   break;

  24.   }

  25.   }

  26.   }
复制代码


  当 self.player.status == AVPlayerStatusReadyToPlay时,音乐就会开始正常播放,另外两种状态音乐是无法播放的,可以在上面方法相应状态里给出提示。这里需要特别强调一点的是观察者监听的对象是self.player.currentItem,而不是self.player,而当监听的属性发生改变时,观察者回调的方法里需要查看的是self.player.status。当然,你也可以不这么干,但是我尝试过好几次,不这么干的后果是无法监听到self.player.status属性的改变。

  当音乐播放完成,或者切换下一首歌曲时,请务必记得移除观察者,否则会crash。操作如下:

  1.   //移除观察者

  2.   [self.player.currentItem removeObserver:self forKeyPath:@"status"];
复制代码


  5、监听音乐的缓冲进度

  这个也是通过KVO监听播放器当前播放的音乐资源AVPlayerItem的loadedTimeRanges属性。我们先看监听,如下:

  1.   //KVO监听音乐缓冲状态

  2.   [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
复制代码

  当loadedTimeRanges属性发生改变时,回调如下:

  1.   -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

  2.   {

  3.   if ([keyPath isEqualToString:@"loadedTimeRanges"]) {

  4.   NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;

  5.   //本次缓冲的时间范围

  6.   CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];

  7.   //缓冲总长度

  8.   NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);

  9.   //音乐的总时间

  10.   NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);

  11.   //计算缓冲百分比例

  12.   NSTimeInterval scale = totalLoadTime/duration;

  13.   //更新缓冲进度条

  14.   self.loadTimeProgress.progress = scale;

  15.   }

  16.   }
复制代码


  loadedTimeRanges这个属性是一个数组,里面装的是本次缓冲的时间范围,这个范围是用一个结构体CMTimeRange表示,当然在oc中结构体是不能直接存放数组的,所以它被包装成了oc对象NSValue。

  我们来看下这个结构体:

  1.   typedef struct

  2.   {

  3.   CMTime start;

  4.   CMTime duration;

  5.   } CMTimeRange;
复制代码


  start表示本次缓冲时间的起点,duratin表示本次缓冲持续的时间范围,具体详细的计算方法可以看上面方法的实现。

  当音乐播放完成,或者切换下一首歌曲时,请务必记得移除观察者,否则会crash。操作如下:

  1.   [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
复制代码

  6、监听音乐播放的进度

  这个不是通过KVO了,AVPlayer专门提供了下面这个api用来监听播放的进度:

  1.   /**

  2.   监听音乐播放进度

  3.   @param interval 监听的时间间隔,用来设置多长时间回调一次

  4.   @param queue 队列,一般传主队列

  5.   @param block 回调的block,会把当前的播放时间传递过来

  6.   @return 监听的对象

  7.   */

  8.   - (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
复制代码


  操作如下:

  1.   __weak typeof(self) weakSelf = self;

  2.   self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

  3.   //当前播放的时间

  4.   float current = CMTimeGetSeconds(time);

  5.   //总时间

  6.   float total = CMTimeGetSeconds(item.duration);

  7.   if (current) {

  8.   float progress = current / total;

  9.   //更新播放进度条

  10.   weakSelf.playSlider.value = progress;

  11.   weakSelf.currentTime.text = [weakSelf timeFormatted:current];

  12.   }

  13.   }];
复制代码


  我们可以这个block里面拿到当前播放时间,根据总时间计算出当前播放所占的时间比例,最后更新播放进度条。这里又涉及到了一个数据类类型CMTime,它也是一个结构体,用来作为时间的格式,定义如下:

  1.   typedef struct

  2.   CMTimeValue value;

  3.   CMTimeScale timescale;

  4.   CMTimeFlags flags;

  5.   CMTimeEpoch epoch;

  6.   } CMTime;
复制代码


  CMTime是以分数的形式表示时间,value表示分子,timescale表示分母,flags是位掩码,表示时间的指定状态。所以我们要获得时间的秒数需要分子除以分母。当然你还可以用下面这个函数来获取时间的秒数:

 
  1.  Float64 CMTimeGetSeconds(CMTime time)
复制代码

  最后,当音乐播放完成或者切换音乐时,依然需要移除监听:

  1.   if (self.timeObserver) {

  2.   [self.player removeTimeObserver:self.timeObserver];

  3.   self.timeObserver = nil;

  4.   }
复制代码


  7、手动超控(移动滑块)播放进度

  这是一个播放音视频很常见的功能,所以强大的AVPlayer理所当然的提供了几个api,下面只讲述其中最简单的一个:

  1.   /**

  2.   定位播放时间

  3.   @param time 指定的播放时间

  4.   */

  5.   - (void)seekToTime:(CMTime)time;
复制代码

  具体使用如下:

  1.   //移动滑块调整播放进度

  2.   - (IBAction)playSliderValueChange:(UISlider *)sender

  3.   {

  4.   //根据值计算时间

  5.   float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);

  6.   //跳转到当前指定时间

  7.   [self.player seekToTime:CMTimeMake(time, 1)];

  8.   }
复制代码


  8、监听音乐播放完成

  一般音视频播放完成时我们或多或少的都要处理一些业务,比如循环播放,播完退出界面等等。下面看下如何监听AVPlayer的播放完成。

  //给AVPlayerItem添加播放完成通知

  1.   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
复制代码


  这里是采用注册监听AVPlayerItemDidPlayToEndTimeNotification通知,当AVPlayer一播放完成时,便会发出这个通知,我们收到通知后进行处理即可

  9、设置音乐后台播放

  我们知道运行在ios系统下的程序一旦进入后台就会处于休眠状态,程序停止运行了,也就播放不了什么音乐了。但是有一些特定功能的app还是处于可以后台运行的,比如音乐类型的app正处于这个范畴。但是,并不是说你在应用中播放音乐就能后台高枕无忧的运行了,你依然需要做如下几步操作:

  (1)开启后台模式

  target ->capabilities-> Background modes ->打开开关 ->勾选第一个选项

d.jpg


  设置后台播放模式

  (2)程序启动时设置音频会话

  1.   //一般在方法:application: didFinishLaunchingWithOptions:设置

  2.   //获取音频会话

  3.   AVAudioSession *session = [AVAudioSession sharedInstance];

  4.   //设置类型是播放。

  5.   [session setCategory:AVAudioSessionCategoryPlayback error:nil];

  6.   //激活音频会话。

  7.   [session setActive:YES error:nil];
复制代码

  以上两步设置无误,程序进入后台模式,便可以进行音乐播放

  10、如何设置音乐锁频信息

  我们看百度音乐锁频时,也依然能在屏幕上展示歌曲的信息,以及切换歌曲等。下面看看这个功能是如何实现的:

  1.   //音乐锁屏信息展示

  2.   - (void)setupLockScreenInfo

  3.   {

  4.   // 1.获取锁屏中心

  5.   MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];

  6.   //初始化一个存放音乐信息的字典

  7.   NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary];

  8.   // 2、设置歌曲名

  9.   if (self.currentModel.name) {

  10.   [playingInfoDict setObject:self.currentModel.name forKey:MPMediaItemPropertyAlbumTitle];

  11.   }

  12.   // 设置歌手名

  13.   if (self.currentModel.artist) {

  14.   [playingInfoDict setObject:self.currentModel.artist forKey:MPMediaItemPropertyArtist];

  15.   }

  16.   // 3设置封面的图片

  17.   UIImage *image = [self getMusicImageWithMusicId:self.currentModel];

  18.   if (image) {

  19.   MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];

  20.   [playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork];

  21.   }

  22.   // 4设置歌曲的总时长

  23.   [playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration];

  24.   //音乐信息赋值给获取锁屏中心的nowPlayingInfo属性

  25.   playingInfoCenter.nowPlayingInfo = playingInfoDict;

  26.   // 5.开启远程交互,只有开启这个才能进行远程操控

  27.   [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

  28.   }
复制代码


  这里设置图片时需要注意下,异步加载网络图片后再设置是无效的,所以图片信息最好是先请求下来后再进行设置。

  远程超控的回调如下:

  1.   //监听远程交互方法

  2.   - (void)remoteControlReceivedWithEvent:(UIEvent *)event

  3.   {

  4.   switch (event.subtype) {

  5.   //播放

  6.   case UIEventSubtypeRemoteControlPlay:{

  7.   [self.player play];

  8.   }

  9.   break;

  10.   //停止

  11.   case UIEventSubtypeRemoteControlPause:{

  12.   [self.player pause];

  13.   }

  14.   break;

  15.   //下一首

  16.   case UIEventSubtypeRemoteControlNextTrack:

  17.   [self nextBtnAction:nil];

  18.   break;

  19.   //上一首

  20.   case UIEventSubtypeRemoteControlPreviousTrack:

  21.   [self lastBtnAction:nil];

  22.   break;

  23.   default:

  24.   break;

  25.   }

  26.   }
复制代码


  三、总结

  最后,画了一张图总结下播放远程网络音乐的流程:

c.png

  远程音乐播放流程.png

  根据QQ音乐的界面做了个小demo,下面是demo的真机前台和后台播放的运行效果:

b.png

  前台播放

a.png


  后台播放

  附上github下载地址:https://github.com/yedexiong/MsuicPlayDemo.git

  四、结束语

  播放远程网络音乐的核心技术点基本上已经写完,当然AVPlayer还有很多强大的功能没有写出来,有兴趣的可以进一步挖掘。写到这里已经疲倦至极,如果喜欢的可以点赞和关注,后续会持续更新一些精彩的技术点。

原文作者:夜_阑珊  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部