Code Name: APlay 02

文件流提供器协议 StreamProvider

StreamProviderCompatible.swift

Protocol 定义

我们先来定义一个名为 StreamProvider 的协议,这个协议是我们为播放器后续操作提供原始数据流制订的规范

  • 对外部隐藏读取 网络文件流本地文件流 的具体实现
  • 外部可以重写实现,方便定制
1
2
3
4
5
6
7
8
9
10
11
12
13
public protocol StreamProviderCompatible: AnyObject {
var outputPipeline: Delegated<StreamProvider.Event, Void> { get }
var position: StreamProvider.Position { get }
var contentLength: UInt { get }
var info: StreamProvider.URLInfo { get }
var bufferingProgress: Float { get }

func open(url: URL, at position: StreamProvider.Position)
func destroy()
func pause()
func resume()
init(config: ConfigurationCompatible)
}

我们可以通过 extension 为协议 StreamProvideropen(at:) 方法加上一个带有默认值 Position() 的实现

1
2
3
4
extension StreamProvider {
@inline(__always)
public func open() throws { return try open(at: Position()) }
}

下面我们来详细分析下这个协议

OutputPipeline

字面意思,输出管道,是该协议发出的事件流,是通过 Delegated 实现的,事件流的值是 Event

这些事件最终会流入 Composer 中,由 Composer 统一调度 StreamProviderAudioDecoder,最终目的是生成解码后的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

### position
>打开流位置的记录

### contentLength
> 文件长度

### info
>有关 url 的信息,根据 url 推断出来的 `AudioFileType` 等的信息
>
>`AudioFileType` 是对 `AudioFileTypeID` 二次封装的 `RawRepresentable` 类型,避免输入是不记得类型名字的烦恼
>```swift
public enum URLInfo {
case remote(URL, AudioFileType)
case local(URL, AudioFileType)
case unknown(URL)
...
}

bufferingProgress

缓冲的进度

网络文件流 & 本地文件流

具体实现还是看代码吧,有什么疑问可以在评论区交流下 😄,Streamer.swift

下面我们来看下 网络文件流本地文件流 的实现

使用 CFReadStreamCreateWithFile 来创建 本地文件流

使用 CFReadStreamCreateForHTTPRequest 来创建 网络文件流

加入到新建的 RunloopQueue 中循环读取数据,然后派发 StreamValue

这里要加入 TagParser,分析文件流里面的 Metadata 信息,以便于计算音频长度。

有个需要注意的地方是,MP3 文件可能同时存在 ID3v1ID3v2 两个版本的信息,所以必须要检测文件最后的 128 个字节是否是 ID3v1

使用到的第三方库

RunloopQueue: A GCD-like queue powered with the magic (and ruggedness) of CFRunLoop.

在背景线程读取文件流

Delegated: 👷‍♀️ Closure-based delegation without memory leaks

消除 [weak self]

Uroboros

环形存储类,线程安全,支持同时读写。