Spotube 插件是什么、如何工作——v5 插件架构入门
一句话概念
Spotube 的插件是 Kotlin/JS 库,打包成 .smplug 文件,在 Cash App 的 Zipline runtime 沙箱里运行,通过 Spotube 宿主暴露的 Bridge 调用 UI、网络、存储等能力,实现元数据搜索、音频流获取、歌词、scrobbling 等可替换功能。
插件不是 Dart/Flutter 代码,不是传统的 .so / .dylib / .dll 动态库,而是在一个 JS 虚拟机里跑的 Kotlin 编译产物。
宿主与插件的边界
Spotube 宿主(Flutter 写的部分)负责:
- UI 渲染(搜索页、播放页、设置页)
- 本地播放内核(MPV / media_kit)
- 数据存储(SQLite / Drift)
- 插件加载器(Zipline runtime)
- Bridge(把宿主的能力暴露给插件)
插件负责:
- 与具体音乐服务通信(调 Spotify API、抓 YouTube 音频流、连 MusicBrainz)
- 把外部服务的数据转换成 Spotube 能理解的数据模型(Track / Album / Artist)
- 在需要时主动请求宿主执行操作(比如下载文件、弹出认证窗口)
四类插件接口
Spotube 预定义了四个接口(interface),每个插件可以实现其中一个或多个:
| 接口 | 用途 | 典型插件 |
|---|---|---|
MetadataSearchAPI | 搜索曲目、专辑、歌手、歌单,返回元数据 | Spotify、MusicBrainz、JioSaavn、Spotube(Spotube 官方元数据聚合插件) |
AudioAPI | 根据 Track 元数据拿到音频流 URL | YouTube、Piped、Invidious、Qobuz、v5.1 新增自定义源 |
LyricsAPI | 根据曲目信息获取时间同步歌词 | LRCLib |
ScrobbleAPI | 把播放记录上报到外部服务 | ListenBrainz、Last.fm |
v5.1.0 还新增了 Dab(广播电台)和 FLAC 无损 AudioAPI 支持。
.smplug 文件结构
一个 .smplug 本质上是 ZIP 包,内部结构:
my-plugin.smplug (ZIP)
├── plugin.json # manifest:插件元信息、声明实现哪些接口
├── index.zipline.js # 编译好的 Kotlin/JS 主文件
└── deps/ # 可能的依赖 JS 库
└── ...
plugin.json 大致长这样:
{
"id": "dev.krtirtho.spotify-metadata",
"name": "Spotify Metadata",
"version": "1.0.0",
"author": "Spotube Team",
"description": "Spotify 元数据服务",
"abilities": ["metadata"]
}
插件加载流程
- Spotube 启动时扫描插件目录(桌面端通常在
~/.local/share/spotube/plugins/或应用支持目录;Android 在应用私有目录) - 加载每个
.smplug里的plugin.json,显示给用户看 - 点击启用 → Spotube 把插件代码喂给 Zipline runtime
- Zipline 创建沙箱 → 插件
main()函数被调用 - 插件通过 Zipline.get() 拿到宿主 Bridge 实例
- 插件向宿主注册自己实现的接口
- 搜索/播放/歌词/scrobble 请求发生时,Spotube 调插件注册的方法
与 Dart 扩展的对比
传统 Flutter 扩展(比如很多桌面端应用的 .so 插件)是原生动态库,和宿主在同一个进程空间,能做任何事。Spotube 的 Kotlin/JS 插件跑在 Zipline 沙箱里,只能通过宿主暴露的 Bridge 做事——插件不能直接访问文件系统、网络、传感器,所有操作都要走宿主授权。这是安全边界。
想自己写插件?从 Kotlin/JS + Zipline 环境搭建 开始。