构建通用的文件流提供器(Stream Provider)
2018年8月1日Code Name: APlay 02
文件流提供器协议 StreamProvider
Protocol 定义
我们先来定义一个名为 StreamProvider
的协议,这个协议是我们为播放器后续操作提供原始数据流制订的规范
- 对外部隐藏读取 网络文件流 和 本地文件流 的具体实现
- 外部可以重写实现,方便定制
1 | public protocol StreamProviderCompatible: AnyObject { |
我们可以通过 extension
为协议 StreamProvider
的 open(at:)
方法加上一个带有默认值 Position()
的实现
1 | extension StreamProvider { |
下面我们来详细分析下这个协议
OutputPipeline
字面意思,输出管道,是该协议发出的事件流,是通过
Delegated
实现的,事件流的值是Event
这些事件最终会流入
Composer
中,由Composer
统一调度StreamProvider
和AudioDecoder
,最终目的是生成解码后的 PCM 数据,存储到环形缓冲区Uroboros
中
1
2 > public enum Event {
>
case readyForReady
case hasBytesAvailable(UnsafePointer<UInt8>, UInt32, Bool)
case endEncountered
case errorOccurred(APlay.Error)
case metadata([MetadataParser.Item])
case metadataSize(UInt32)
case flac(FlacMetadata)
case unknown(Error)
}
1 |
|
bufferingProgress
缓冲的进度
网络文件流 & 本地文件流
具体实现还是看代码吧,有什么疑问可以在评论区交流下 😄,Streamer.swift
下面我们来看下 网络文件流 和 本地文件流 的实现
使用 CFReadStreamCreateWithFile
来创建 本地文件流
使用 CFReadStreamCreateForHTTPRequest
来创建 网络文件流
加入到新建的 RunloopQueue
中循环读取数据,然后派发 StreamValue
这里要加入 TagParser
,分析文件流里面的 Metadata
信息,以便于计算音频长度。
有个需要注意的地方是,MP3
文件可能同时存在 ID3v1
和 ID3v2
两个版本的信息,所以必须要检测文件最后的 128 个字节是否是 ID3v1
。
使用到的第三方库
RunloopQueue: A GCD-like queue powered with the magic (and ruggedness) of CFRunLoop.
在背景线程读取文件流
Delegated: 👷♀️ Closure-based delegation without memory leaks
消除 [weak self]
环形存储类,线程安全,支持同时读写。