Spotube 的播放内核——桌面端 MPV、移动端 media_kit
为什么是本地播放器
Spotube README 上写了一句关键承诺:Playback control is done locally, not on the server。这意味着 Spotube 不做任何"服务端下发播放策略"的事——缓冲、解码、音量、进度、EQ、FLAC 处理,全部在你的设备本地完成。
对应的实现:
| 平台 | 播放引擎 | 特点 |
|---|---|---|
| 桌面(Windows / macOS / Linux) | MPV(libmpv) | 高性能、支持几乎所有格式、无服务端限制 |
| 移动(Android / iOS) | media_kit(libmpv + Flutter 封装) | 跨平台 Flutter 包,底层也是 libmpv |
本质上是同一个播放器——MPV。media_kit 是 Flutter 团队和 Spotube 作者一起维护的 Flutter 层封装。
MPV 是什么
[MPV] 是一个开源命令行媒体播放器,基于 MPlayer / mplayer2 发展而来。它的核心特点:
- 支持所有常见格式:MP3 / AAC / FLAC / OGG Opus / WAV / 所有视频格式
- 高性能解码:硬件加速、多线程、零拷贝路径
- 完全本地:不联网、不遥测、不做任何上传
- 灵活的滤镜系统:均衡器、格式转换、字幕、音轨切换都可以在本地配置
Spotube 不直接调用 MPV 命令行,而是链接 libmpv 库,通过 Dart FFI 直接调用 MPV 的 C API。这带来了几个好处:
- 低延迟:Spotube UI 发出"跳到 30 秒" → libmpv 立即响应,不需要进程间通信
- 统一控制:所有播放操作(播放/暂停/进度/音量/EQ)走同一套 API,跨平台行为一致
- 状态监听:Spotube 实时拿到 libmpv 的事件(播放结束、缓冲进度、音轨变化),UI 做响应
MPV 在 Spotube 里能做什么
本地控制一切
Spotube 不会向"服务器"发送"请跳到 X 秒"这样的指令。所有播放控制都是本地调用 libmpv:
- 播放/暂停
- 进度跳转(Seek)
- 音量调节
- 播放速度调整(0.5x ~ 2x)
- EQ 滤镜(参数均衡、图形均衡)
- 格式转换(如把 48kHz 重采样到 44.1kHz)
- 音频设备切换(耳机 / 扬声器 / HDMI)
FLAC 无损直出
v5.1.0 起 Spotube 支持 FLAC 无损播放。MPV 原生支持 FLAC 解码,Spotube 不做任何重采样或降级——拿到的是什么格式就交给 MPV 解什么格式,音频数据原样送到系统音频输出。
硬件加速
桌面端 MPV 默认开启硬件视频解码(Windows 上用 DXVA2 / D3D11VA、macOS 用 VideoToolbox、Linux 用 VAAPI)。音频解码用 CPU,但对播放器来说完全够用。
media_kit 与 libmpv 的关系
[media_kit] 是一个 Dart 包,它在移动端做了什么:
- 把 libmpv 的 Android 和 iOS 编译产物(.so / .dylib)打包进 Flutter 应用
- 提供 Dart 层 API(
player.seek(seconds: 30)/player.play()) - 处理 Flutter 和原生播放器之间的缓冲区渲染
Spotube 团队是 media_kit 的主要贡献者之一——这个项目最初就是为了 Spotube 做 Flutter 移动端播放器时搭的基础设施。
本地播放 vs 服务端控制的意义
Spotify、Apple Music、YouTube Music 的播放控制都有一部分在服务端:
- 部分功能需要 Premium 才能用(比如无损、离线)
- 播放列表的某些曲目在某些地区会被替换
- 广告是服务端插进来的(YouTube)
- 播放进度可能因为版权策略被强制截断
Spotube 把这一切都排除了。只要音频源插件能拿到流,MPV 就会原样播放——服务端不会在中途动手脚。