Spotube 插件 API 接口——MetadataSearchAPI / AudioAPI / ScrobbleAPI / LyricsAPI
四个接口的作用
Spotube 插件可以实现四个接口中的一个或多个。接口定义在 plugin_interfaces 库(dev.krtirtho.spotube group),全部是 Kotlin interface。
MetadataSearchAPI——元数据搜索
interface MetadataSearchAPI : ZiplineService {
suspend fun search(query: String, types: List<SearchType>): List<SearchResult>
suspend fun getTrack(id: String): Track?
suspend fun getAlbum(id: String): Album?
suspend fun getArtist(id: String): Artist?
suspend fun getPlaylist(id: String): Playlist?
suspend fun getArtistTracks(artistId: String, limit: Int): List<Track>
suspend fun getAlbumTracks(albumId: String): List<Track>
suspend fun getPlaylistsByCategory(categoryId: String): List<Playlist>
}
插件需要实现:
- 搜索(
search):用户输入搜索词时被调用,返回匹配的曲目/专辑/艺人/播放列表 - 详情获取:Spotube 需要某首歌的完整信息时调
getTrack/getAlbum等 - 列表获取:艺人的所有曲目 / 专辑的所有曲目 / 分类歌单
SearchResult、Track、Album、Artist、Playlist 这些数据模型也是 plugin_interfaces 提供的。
AudioAPI——音频流
interface AudioAPI : ZiplineService {
suspend fun getAudioStream(track: Track): AudioStream?
suspend fun isAvailable(track: Track): Boolean
suspend fun supportedCodecs(): List<String> // "mp3", "aac", "flac", "opus", "wav"
}
- getAudioStream:根据 Track 对象返回音频流(URL + codec + bitrate + sampleRate + bitDepth)
- isAvailable:判断某首 Track 能否在这个音频源播放(Spotube 用来在多个 AudioAPI 之间切换)
- supportedCodecs:告诉宿主自己能提供哪些格式(v5.1 FLAC 无损支持依赖这个)
ScrobbleAPI——播放记录上报
interface ScrobbleAPI : ZiplineService {
suspend fun scrobble(track: Track, progressMs: Long, durationMs: Long)
suspend fun updateNowPlaying(track: Track?, durationMs: Long)
}
- scrobble:播放到 50% 或曲目结束时被调用(Spotube 内部判断时机)
- updateNowPlaying:播放中定时通知 "正在播放的是哪首"(Last.fm / ListenBrainz 用)
LyricsAPI——歌词获取
interface LyricsAPI : ZiplineService {
suspend fun getLyrics(track: Track): Lyrics?
}
返回的 Lyrics 对象包含 LRC 格式字符串。
宿主 Bridge——插件能做什么
插件运行在 Zipline 沙箱里,不能直接访问文件系统、网络、传感器、剪贴板。所有操作都要通过宿主暴露的 Bridge。Spotube 提供的 Bridge 能力(节选):
| Bridge | 能做什么 |
|---|---|
| HttpClient | 发起 HTTP / HTTPS 请求(GET / POST / PUT / DELETE),支持 headers 和 body |
| FileStorage | 读写插件自己的私有目录(不影响其他插件,也不影响 Spotube 数据目录) |
| AuthTokenStore | 安全存储 OAuth token(Spotube 用 flutter_secure_storage 包做底层) |
| Logger | 打印 debug / info / warning / error 日志(出现在 Spotube 的 debug log 导出里) |
| UI(可选) | 弹出 Alert / Dialog / Snackbar 提示 |
| Player(只读) | 读当前播放状态(Track / 进度 / 播放中) |
Bridge 能力有限制——插件不能请求 Spotube 没暴露的能力。这是设计:沙箱保证安全,Bridge 列表可审计。
插件入口 main()
每个插件都需要一个顶层 main() 函数:
fun main() {
val host = Zipline.get()
host.register<MetadataSearchAPI>(MyMetadataPlugin())
host.register<AudioAPI>(MyAudioPlugin())
// 可以注册多个接口实现
}
这是 Spotube 加载插件时调的第一个函数。Zipline.get() 拿到宿主 Bridge,host.register() 告诉 Spotube "我能做 X"。
数据模型(节选)
Spotube 定义的核心数据模型(都在 plugin_interfaces 里):
data class Track(
val id: String, // 插件内部唯一 ID(比如 Spotify track ID 或 YouTube video ID)
val title: String,
val artists: List<Artist>,
val album: Album?,
val durationMs: Long,
val coverImageUrl: String?,
val explicit: Boolean,
val musicBrainzId: String?, // 可选,MBID 让跨插件匹配更准
val pluginId: String // 哪个插件返回的
)
data class AudioStream(
val url: String, // 音频流 URL
val codec: String, // "mp3" / "aac" / "flac" / "opus"
val bitrateKbps: Int?,
val sampleRate: Int?, // 44100 / 48000 / 96000 / 192000
val bitDepth: Int?, // 16 / 24(FLAC 无损需要)
val headers: Map<String, String>? // 某些源需要额外 HTTP headers
)
接口可选性
插件不需要实现所有四个接口。最常见的选择:
- 只想做元数据 → 只实现 MetadataSearchAPI(Spotube 会让你搭配其他音频源)
- 想做"全合一" → 同时实现 MetadataSearchAPI + AudioAPI(类似 JioSaavn 官方插件)
- 只做歌词 → 只实现 LyricsAPI(社区 LRCLib 之外的替代)
- 只做 scrobble → 只实现 ScrobbleAPI(ListenBrainz 之外的 Last.fm 替代)