在系统的学习了AudioQueue相关姿势之后,我尝试使用AudioQueue做一个简单的音频播放器,包括播放,暂停,停止,快进快退,同时支持本地音频和网络音频等功能。
这里将整个流程分为下面几个模块:
AudioProperty – 用来保存音频的相关属性
AudioSource – 主要负责提供音频数据
- LocalAudioSource 本地数据来源
- NetAudioSource 网络数据来源
- AudioStream – 主要负责对音频流的解析
- AudioQueue – 主要负责音频播放
- AudioPlayer – 播放器实体,负责管理上面的几个模块。
下面分模块来介绍下:
AudioProperty
|
|
当每次音频的状态发生改变时,我们通过上面的代理将状态传给UI。
这里我们保存了fileSize和audioDesc,主要是用来计算音频的总时长,计算方法在之前已经介绍过了。
AudioSource
因为这个模块有两种类型,所以我们先定义一个父类,然后让子类去继承
|
|
在我们获取到原始的音频数据后,也是通过代理的方式将数据交给其它模块去处理。
具体看一下本地和网络数据获取的方式有何不同:
LLYLocalAudioSource
|
|
这里我是使用了一个计时器去读取的本地数据,用while循环应该也是ok的。然后是整个数据读取操作都需要放在子线程去操作,因为如果放在主线程的话会阻塞当前线程,造成UI卡顿。
|
|
在数据读取过程中,我们需要记录一下当前读取了多少数据,防止最后一次读取的数据不够,这里还有2个参数是seek相关的,isContine 和 newOffset ,当newOffset不为0时,我们需要将当前文件的读取偏移量seek到newOffset处,然后在继续读文件,同时,我们标记isContine为NO,通知其他模块,清空之前的音频读取相关记录,重新开始读取新的数据。
LLYNetAudioSource
网络音频和本地音频处理起来不太一样,因为它们使用的是不同的协议,本地文件可以看做file协议,网络音频则是http协议。不过原理上其实是一样的。
|
|
同样的 ,这个数据请求过程也是放在子线程中进行。
这里,使用代理的方式而不是完成快的方式去请求数据,是因为音频数据相对来说还是有点大的,如果使用完成块的方式需要等待数据全部请求完成才会返回,等待时候会比较长,而且我们在开始播放音频时也并不需要全部的数据,边播放请求数据也比较符合正常的逻辑。
在发送请求前,也有一个seek相关的操作,如果当前是seek后第一次请求数据,通过设置http header中的Range的字段,请求seek后的数据。
LLYAudioStream
|
|
收到AudioSource的数据后,使用AudioStream来解析,然后把解析到的数据通过代理给AudioQueue,这就是AudoStream需要做的事情。
这里主要看一下下面几个方法:
计算seek的偏移量
|
|
总时长
|
|
AudioQueue模块和AudioPlayer模块相对来讲就比较简单了,之前的demo里面也使用过,就不一一介绍。