服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS实现播放远程网络音乐的核心技术点总结

iOS实现播放远程网络音乐的核心技术点总结

2021-02-07 18:52夜_阑珊 IOS

本篇文章主要介绍了iOS播放远程网络音乐的核心技术,采用ios系统自带的AVFoundation框架来实现,有需要的朋友可以了解一下。

一、前言

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

  • 如何播放远程网络音乐
  • 如何切换当前正在播放中的音乐资源
  • 如何监听音乐播放的各种状态(播放器状态、播放的进度、缓冲的进度,播放完成)
  • 如何手动操控播放进度
  • 如何在后台模式或者锁屏情况下正常播放音乐
  • 如何在锁屏模式下显示音乐播放信息和远程操控音乐

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

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

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

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

那么avplayer是什么?

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

1、导入avfoundation框架,创建avplayer播放器

?
1
2
3
4
5
6
7
8
9
10
11
-(avplayer *)player
{
  if (_player == nil) {
     // avplayeritem是一个包装音乐资源的类,初始化时可以传入一个音乐的url
    avplayeritem *item = [[avplayeritem alloc] initwithurl:[nsurl urlwithstring:@"http://xxxxxxxx"]];
    //通过avplayeritem初始化player
    _player = [[avplayer alloc] initwithplayeritem:item];
  }
 
  return _player;
}

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

2、播放或停止音乐

?
1
2
3
4
//开始播放
[self.player play];
 //停止播放
[self.player pause];

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

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

?
1
2
3
4
//创建需要播放的avplayeritem
 avplayeritem *item = [[avplayeritem alloc] initwithurl:[nsurl urlwithstring:model.url]];
 //替换当前音乐资源
 [self.player replacecurrentitemwithplayeritem:item];

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

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

?
1
[self.player.currentitem addobserver:self forkeypath:@"status" options:nskeyvalueobservingoptionnew context:nil];

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

?
1
2
3
4
5
typedef ns_enum(nsinteger, avplayeritemstatus) {
  avplayeritemstatusunknown,//未知状态
  avplayeritemstatusreadytoplay,//准备播放
  avplayeritemstatusfailed//加载失败
};

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//观察者回调
-(void)observevalueforkeypath:(nsstring *)keypath ofobject:(id)object change:(nsdictionary<nskeyvaluechangekey,id> *)change context:(void *)context
{
  //注意这里查看的是self.player.status属性
  if ([keypath isequaltostring:@"status"]) {
    switch (self.player.status) {
      case avplayerstatusunknown:
      {
        nslog(@"未知转态");
      }
        break;
      case avplayerstatusreadytoplay:
      {
        nslog(@"准备播放");
      }
        break;
      case avplayerstatusfailed:
      {
        nslog(@"加载失败");
      }
        break;
         default:
        break
    }
  }
}

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
2
//kvo监听音乐缓冲状态
[self.player.currentitem addobserver:self forkeypath:@"loadedtimeranges" options:nskeyvalueobservingoptionnew context:nil];

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)observevalueforkeypath:(nsstring *)keypath ofobject:(id)object change:(nsdictionary<nskeyvaluechangekey,id> *)change context:(void *)context
{
  if ([keypath isequaltostring:@"loadedtimeranges"]) {
 
    nsarray * timeranges = self.player.currentitem.loadedtimeranges;
    //本次缓冲的时间范围
    cmtimerange timerange = [timeranges.firstobject cmtimerangevalue];
    //缓冲总长度
    nstimeinterval totalloadtime = cmtimegetseconds(timerange.start) + cmtimegetseconds(timerange.duration);
    //音乐的总时间
    nstimeinterval duration = cmtimegetseconds(self.player.currentitem.duration);
    //计算缓冲百分比例
    nstimeinterval scale = totalloadtime/duration;
    //更新缓冲进度条
    self.loadtimeprogress.progress = scale;
  }
 
}

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

我们来看下这个结构体:

?
1
2
3
4
5
typedef struct
{
  cmtime      start;   
  cmtime      duration; 
} cmtimerange;

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

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

?
1
[self.player.currentitem addobserver:self forkeypath:@"loadedtimeranges" options:nskeyvalueobservingoptionnew context:nil];

6、监听音乐播放的进度

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

?
1
2
3
4
5
6
7
8
9
10
/**
 监听音乐播放进度
 
 @param interval 监听的时间间隔,用来设置多长时间回调一次
 @param queue  队列,一般传主队列
 @param block  回调的block,会把当前的播放时间传递过来
 
 @return 监听的对象
 */
- (id)addperiodictimeobserverforinterval:(cmtime)interval queue:(nullable dispatch_queue_t)queue usingblock:(void (^)(cmtime time))block;

操作如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
__weak typeof(self) weakself = self;
 self.timeobserver = [self.player addperiodictimeobserverforinterval:cmtimemake(1.0, 1.0) queue:dispatch_get_main_queue() usingblock:^(cmtime time) {
   //当前播放的时间
   float current = cmtimegetseconds(time);
   //总时间
   float total = cmtimegetseconds(item.duration);
   if (current) {
     float progress = current / total;
     //更新播放进度条
     weakself.playslider.value = progress;
     weakself.currenttime.text = [weakself timeformatted:current];
   }
 }];

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

?
1
2
3
4
5
6
typedef struct
 cmtimevalue  value;   
 cmtimescale  timescale; 
 cmtimeflags  flags;   
 cmtimeepoch  epoch;   
} cmtime;

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

?
1
float64 cmtimegetseconds(cmtime time)

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

?
1
2
3
4
if (self.timeobserver) {
    [self.player removetimeobserver:self.timeobserver];
    self.timeobserver = nil;
  }

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 定位播放时间
 
 @param time 指定的播放时间
 */
- (void)seektotime:(cmtime)time;
具体使用如下:
 
//移动滑块调整播放进度
- (ibaction)playslidervaluechange:(uislider *)sender
{
  //根据值计算时间
  float time = sender.value * cmtimegetseconds(self.player.currentitem.duration);
  //跳转到当前指定时间
  [self.player seektotime:cmtimemake(time, 1)];
}

8、监听音乐播放完成

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

?
1
2
//给avplayeritem添加播放完成通知
  [[nsnotificationcenter defaultcenter] addobserver:self selector:@selector(playfinished:) name:avplayeritemdidplaytoendtimenotification object:_player.currentitem];

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

9、设置音乐后台播放

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

(1)开启后台模式

target ->capabilities-> background modes ->打开开关 ->勾选第一个选项iOS实现播放远程网络音乐的核心技术点总结

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

?
1
2
3
4
5
6
7
//一般在方法:application: didfinishlaunchingwithoptions:设置
//获取音频会话
avaudiosession *session = [avaudiosession sharedinstance];
//设置类型是播放。
[session setcategory:avaudiosessioncategoryplayback error:nil];
//激活音频会话。
[session setactive:yes error:nil];

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//音乐锁屏信息展示
- (void)setuplockscreeninfo
{
  // 1.获取锁屏中心
  mpnowplayinginfocenter *playinginfocenter = [mpnowplayinginfocenter defaultcenter];
 
  //初始化一个存放音乐信息的字典
  nsmutabledictionary *playinginfodict = [nsmutabledictionary dictionary];
  // 2、设置歌曲名
  if (self.currentmodel.name) {
    [playinginfodict setobject:self.currentmodel.name forkey:mpmediaitempropertyalbumtitle];
  }
  // 设置歌手名
  if (self.currentmodel.artist) {
    [playinginfodict setobject:self.currentmodel.artist forkey:mpmediaitempropertyartist];
  }
  // 3设置封面的图片
  uiimage *image = [self getmusicimagewithmusicid:self.currentmodel];
  if (image) {
    mpmediaitemartwork *artwork = [[mpmediaitemartwork alloc] initwithimage:image];
    [playinginfodict setobject:artwork forkey:mpmediaitempropertyartwork];
  }
 
  // 4设置歌曲的总时长
  [playinginfodict setobject:self.currentmodel.detailduration forkey:mpmediaitempropertyplaybackduration];
 
  //音乐信息赋值给获取锁屏中心的nowplayinginfo属性
  playinginfocenter.nowplayinginfo = playinginfodict;
 
  // 5.开启远程交互,只有开启这个才能进行远程操控
  [[uiapplication sharedapplication] beginreceivingremotecontrolevents];
}

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

远程超控的回调如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//监听远程交互方法
- (void)remotecontrolreceivedwithevent:(uievent *)event
{
 
  switch (event.subtype) {
    //播放
    case uieventsubtyperemotecontrolplay:{
      [self.player play];
          }
      break;
    //停止
    case uieventsubtyperemotecontrolpause:{
      [self.player pause];
          }
      break;
    //下一首
    case uieventsubtyperemotecontrolnexttrack:
      [self nextbtnaction:nil];
      break;
    //上一首
    case uieventsubtyperemotecontrolprevioustrack:
      [self lastbtnaction:nil];
      break;
 
    default:
      break;
  }
}

三、总结

最后,画了一张图总结下播放远程网络音乐的流程:iOS实现播放远程网络音乐的核心技术点总结

根据qq音乐的界面做了个小demo,下面是demo的真机前台和后台播放的运行效果:iOS实现播放远程网络音乐的核心技术点总结

四、结束语

播放远程网络音乐的核心技术点基本上已经写完,当然avplayer还有很多强大的功能没有写出来,有兴趣的可以进一步挖掘。写到这里已经疲倦至极,后续会持续更新一些精彩的技术点,也希望大家多多支持服务器之家。

延伸 · 阅读

精彩推荐
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03