对于移动开发来说,省流量是必须的。前三篇讲了用AudioQueue来编写流媒体播放,但是缓存的重要不言而喻。有网络的时候可以使用网络播放,没网络的时候就可以利用缓存播放。实现离线播放。本次的实现是当有缓存的时候使用缓存播放,没有缓存的时候才联网去请求数据。
缓存系统的设计
当时在考虑这个的时候,我一直想以一种简单的方式来实现,从当初的构思到实现,总共经过三次变化。
第一次设计
在我看来,音频缓存的难点应该是在于Seek操作的时候,因为中间都是空的,在Linux中,称做“空洞文件”。怎么样才能对应起来呢?我的想法是从http的响应头中,拿到整个文件的长度,然后在硬盘中创建一个同等大小的文件,文件内容设置为空,然后再配置一个描述文件,用于描述该文件已经缓存好的字节段,如果以后再有缓存好的字节段,再进行合并。最终描述文件中只剩一个段,即0到fileLen[文件长度]。
第二次设计
第一次设计想法我觉得是可以的,但是实现有点难,于是我在想,能不能把这个描述文件去掉,利用文件本身来描述自己哪些部分已经下载了,哪些部分未下载呢?我试过使用一个0或者EOF(-1)来描述,基本功能都已经实现了,但是我却忽略了一个问题,我是以二进制的方法读入文件的,文件本身也包含EOF(-1),看来这种想法也行不通。看来还是需要一个描述文件。
第三次设计
既然以我目前的设计,依旧无法省略描述文件,也不能利用文件结束符,那么我可以简化描述文件的结构,并由此定义描述文件(desc),一个和音频文件等长的描述文件,用于记录音频缓存中对应字节的数据是否已经下载成功,如果下载成功,则将该们置置为1,初始为0,这样做的好处是编码简单,但是缺点就是音频文件有多大,就增加了一个与音频等长的描述文件。可以说是以空间换复杂度吧。根据该设计,最终完成了一个基本的缓存系统。
缓存系统的实现
缓存文件的命名
首先,缓存文件当然是存放在硬盘上的,在这里,我使用一般的常规方法,即利用url的md5字符串来做文件名,最终在缓存目录中会生成两个文件,即音频文件和描述文件,如图所示:
相关代码如下所示:
这段代码利用了iOS自带的CommonCrypto中的CC_MD5方法进行加密。。如果url中带有文件的后缀名,那么就从url上截取,如果没有,那么默认为mp3。如下所示:
但是有个问题,即使url中没有后缀,但是仍可以以.*结尾,所以我这里就自定义了几个常见的后缀。
init方法里只是计算了文件名,并没有创建文件,应该什么时候创建音频文件和描述文件呢?可以试想想,如果没有网络请求的时候,缓存文件也根本用不着,而且没有网络请求,也根本无法知道文件到底有多长。所以缓存文件的建立或打开,应该放到第一次网络请求之后。建立的过程如下所示: