列表插件参考
音乐列表插件是一个导出 listPlugin 对象的 CommonJS 脚本。本页是它的完整契约:对象长什么样、宿主如何调用、返回值要满足什么。
脚本结构
module.exports = {
listPlugin: {
// 元数据
name, version, author, description, icon,
// 配置表单与默认开关
config: [ /* … */ ],
defaults: { play: true, download: true },
// 来源及其能力
sources: { myid: { name, icon, capabilities: [ /* … */ ], qualities: [ /* … */ ] } },
// 方法:名字与能力一一对应
async search(source, { keyword, page, limit }) { /* … */ },
// …
},
};
宿主按下面的顺序对待脚本:
- 安装时执行一次脚本,只读取元数据、
sources、config与defaults,检查合法性。 - 之后每次调用某个方法,都重新执行整个脚本,再调用对应方法(见 每次调用都是全新的上下文)。所以不要在模块顶层做网络请求或其它耗时的事。
- 如果模块导出了
ready(一个 Promise),宿主会先await它再继续。
脚本需要是合法的 UTF-8 文本,不能为空,体积不得超过 1 MiB。
元数据
| 字段 | 类型 | 说明 |
|---|---|---|
name | string,必填 | 插件名,最多 256 个字符。缺失则安装失败。 |
version | string | 版本号,最多 64 个字符。更新时用它判断新旧。 |
author | string | 作者,最多 256 个字符。 |
description | string | 简介,最多 1000 个字符。 |
icon | string | 可选图标(emoji 或 data URI),最多 65536 个字符。来源没有自己的 icon 时使用它。 |
config | array | 配置表单,见 配置表单。 |
defaults | object | { play, download },两个布尔值,见 默认开关。 |
sources | object,必填 | 来源声明,见 来源与能力。 |
除了对象字段,脚本顶部的文档注释也建议带上 @name、@version、@author、@description:内置插件按 @name 识别、按 @version 决定启动时是否更新。
/**
* @name Navidrome(列表)
* @version 1.0.0
* @author Mio Music
* @description Navidrome 音乐服务器:搜索、专辑与歌单、歌手、歌词,并提供播放与下载。
*/
来源与能力
sources 是一个对象,键是来源 ID,值描述这个来源。一个插件可以声明多个来源,宿主为每个来源分别注册一个 provider。
来源 ID
来源 ID 同时是歌曲的 source 字段,界面、音质名称与缓存都用它区分歌曲,所以一经发布不要再改。请使用自定义 ID:2–16 个小写字母、数字或下划线,且以小写字母开头,如 navidrome、my_server。宿主另外保留了少量兼容旧版音源格式的 ID,供音源插件使用。
不合法的 ID 会被静默忽略。同一个 ID 被后注册的插件覆盖,所以请选一个能代表你的服务、足够具体的 ID,避免与其他插件或保留 ID 冲突。
来源字段
| 字段 | 说明 |
|---|---|
name | 显示名,最多 128 个字符;缺失或过长时退回使用 ID。 |
icon | 可选图标,最多 65536 个字符。 |
capabilities | 能力数组。未知的能力名会被丢弃;如果一个来源一个有效能力都没有,这个来源整体被丢弃;如果所有来源都被丢弃,安装失败。 |
qualities | 该来源可以播放的音质,由差到好排列。声明了 play 时需要。 |
能力一览
| 能力 | 对应方法 | 界面里启用的功能 |
|---|---|---|
search | search | 歌曲搜索 |
searchPlaylist | searchPlaylist | 歌单搜索 |
playlistDetail | playlistDetail | 歌单详情 |
albumDetail | albumDetail | 专辑详情 |
discovery | discoveryCategories、discoveryPlaylists、discoveryCharts、chartDetail | 发现页的分类、歌单与榜单 |
singer | singerInfo、singerSongs、singerAlbums | 歌手页 |
lyrics | lyrics | 歌词 |
comments | comments | 播放器评论 |
cover | cover | 补查缺失的封面 |
play | musicUrl | 由本插件解析播放地址 |
download | downloadUrl(缺省用 musicUrl) | 由本插件解析下载地址 |
testConnection | testConnection | 配置页的「测试连接」 |
userPlaylists | getPlaylists | 「导入歌单」:列出服务器上用户自己的歌单 |
resolvePlaylistId | resolvePlaylistId | 「从网络导入歌单」:从用户粘贴的链接里取出歌单 ID。需要同时声明 playlistDetail,应用据此决定列出哪些平台 |
recognize | recognize | 听歌识曲:根据一小段音频认出歌曲。应用只在有已启用的插件声明了该能力时才显示识曲入口 |
宿主只会调用已声明能力对应的方法。声明了能力却没有实现对应方法,调用时会得到「插件缺少 xxx」的错误;调用了未声明的能力,则得到「插件未声明该能力」。play 与 download 还需要用户在插件卡片上打开「由本插件提供播放 / 下载」才会生效。
音质
qualities 与歌曲的 types 只能使用下列名称,其它值会被丢弃:
128k、320k、flac、flac24bit、hires、atmos、atmos_plus、master。名称由低到高大致从有损过渡到无损与更高规格。
配置表单
需要服务器地址、账号这类用户输入时,在 config 里声明表单,应用会在插件卡片的「配置」里渲染它。脚本里通过 cerumusic.config.<key> 读取(见 运行时 API)。
config: [
{ key: 'baseUrl', label: '服务器地址', type: 'text', required: true,
placeholder: 'https://music.example.com', description: '服务器根地址,不含 /rest' },
{ key: 'password', label: '密码', type: 'password', required: true },
{ key: 'quality', label: '默认音质', type: 'select', default: '320k',
options: [{ label: '标准', value: '128k' }, { label: '高品质', value: '320k' }] },
{ key: 'allowHttp', label: '允许 HTTP', type: 'switch', default: false },
],
| 字段 | 说明 |
|---|---|
key | 必填。最多 64 个字符,只能包含字母、数字、_、-、.,且不能重复;不合法或重复的字段会被丢弃。 |
label | 显示名,最多 64 个字符,缺省用 key。 |
type | text、password、number、switch、select、textarea。未知类型按 text 处理。 |
default | 默认值。 |
required | 是否必填。 |
placeholder | 占位提示,最多 128 个字符。 |
description | 说明文字,最多 256 个字符。 |
options | select 的选项 [{ label, value }],最多 32 项,value 为字符串或数字。 |
一个插件最多 32 个表单字段,超出的会被忽略。
password 类型字段)保存在应用数据目录下的 list-plugins.json 中,不使用系统钥匙串。password 类型只是在界面上遮盖输入。请尽量让协议本身不暴露明文(例如 Subsonic 的令牌认证),并把登录得到的会话令牌放进 cerumusic.storage,而不是反复发送密码。
默认开关
defaults: { play: true, download: true } 决定安装之后「由本插件提供播放」「由本插件提供下载」两个开关的初始状态。用户随时可以在插件卡片上改变它们。当开关关闭时,即便你声明了 play / download,该来源的歌曲仍交给当前的音源插件解析。
方法参考
所有方法都是 async(返回 Promise 亦可),签名统一为 method(source, params):source 是被调用的来源 ID(字符串),params 是一个对象。this 指向 listPlugin 对象本身。宿主在调用前会校验参数,下面列出的范围都已由宿主保证。
| 方法 | params | 返回 |
|---|---|---|
search | { keyword, page, limit } | SearchResult |
searchPlaylist | { keyword, page, limit } | PlaylistResult |
playlistDetail | { id, page, limit } | PlaylistDetail |
albumDetail | { id, page, limit } | PlaylistDetail |
discoveryCategories | {} | DiscoveryCategories |
discoveryPlaylists | { category, page } | DiscoveryPlaylistPage |
discoveryCharts | {} | DiscoveryChart[] |
chartDetail | { id, page } | PlaylistDetail |
singerInfo | { id } | SingerInfo 或 null |
singerSongs | { id, page, limit } | SearchResult |
singerAlbums | { id, page, limit } | SingerAlbumPage |
lyrics | { song } | Lyric |
comments | { song, page, limit, hot, cursor } | Comments |
cover | { song } | 封面 URL 字符串 |
musicUrl | { song, quality } | URL 字符串,或 { url } |
downloadUrl | { song, quality } | URL 字符串,或 { url } |
testConnection | {} | true 或 {success: true} |
getPlaylists | { page, limit } | PlaylistResult |
resolvePlaylistId | { input } | 歌单 ID 字符串,没有就 null |
recognize | { pcm, sampleRate } | { matches: [{ item, startTimeMs }] } |
分页约定
page从 1 开始,宿主保证它不会为 0。search、searchPlaylist、singerSongs、singerAlbums的limit在 1–30 之间;playlistDetail、albumDetail的limit固定为 30;getPlaylists的limit固定为 100。你可以返回少于limit的条数,但每页最多取前 500 条,多出的被截断。- 返回值里的
allPage是总页数,total是总条数,limit是本页大小。服务器给不出总数时,请按已知信息给出合理的值,并让最后一页的page与allPage对得上。
search / searchPlaylist
keyword 非空且不超过 1024 字节。搜索结果里的每首歌都要带足够的信息:之后它会作为 song 传回 musicUrl、lyrics 等方法。注意它会经过 MusicItem 结构往返,结构里没有的字段不会保留,详见数据结构一页。
playlistDetail / albumDetail
id 就是你在 searchPlaylist 或 discoveryPlaylists 里返回的 PlaylistItem.id(或歌手页里 SingerAlbum.id)。专辑与歌单用不同的方法,如果你的服务器里两者 ID 会冲突,可以像内置插件那样给 ID 加前缀(pl-123、al-123),再在方法里去掉。
discovery 系列
discoveryCategories返回分类,由「热门」与若干分组组成。discoveryPlaylists的category是分类 ID(最多 128 字符),空字符串表示全部 / 默认。page范围 1–100000。discoveryCharts返回榜单列表,chartDetail返回某个榜单的歌曲。榜单 ID 不能超过 40 个字符。- 这四个方法共用
discovery这一个能力。
singer 系列
歌手 id 是字符串,非空、不超过 128 个字符,且不为 "0"。服务器没有「歌手资料」接口时,singerInfo 可以返回 null,歌手页会退回使用打开它的那首歌的歌手名。
lyrics / comments / cover
params.song 是 MusicItem 的 JSON 对象。
- lyrics 返回 Lyric。若要提供逐字歌词,可额外返回
ttml字符串;宿主会优先解析它。 - comments 返回 Comments。
hot为true表示取热门,否则取最新;cursor是上一页返回的游标(首页为空串)。一页最多 300 条,整体不超过 4 MB。 - cover 返回一个
http(s)图片 URL,可以是空串。
musicUrl / downloadUrl
quality 是用户当前选择的音质,来自你为该来源声明的 qualities。返回一个可直接请求的 http / https 地址(字符串,或 { url }):
- 地址不能含用户名密码(
https://user:pass@host会被拒绝),长度不超过 8192 字符。 - 下载时调用
downloadUrl,没有实现则使用musicUrl。 - 地址里可以携带令牌或签名,但请注意它会被播放器与下载器直接使用,且可能出现在日志里。
testConnection / getPlaylists
testConnection要求至少有一个来源声明了testConnection能力。返回true或{success: true}视为成功,其余视为失败;失败时请throw带原因的Error,比如「用户名或密码错误」。应用的「测试连接」使用当前表单里的值(保存之前),并且每次都应实际请求一次服务器,所以请从cerumusic.config读取参数,不要只依赖cerumusic.storage里缓存的会话。getPlaylists返回用户在服务器上的歌单(区别于公开歌单的搜索与发现)。声明userPlaylists与playlistDetail后,应用就可以「导入歌单」:先列出歌单,再分页读取完整的歌曲,最后事务性地创建本地歌单。
resolvePlaylistId
- 用户在「从网络导入歌单」里选一个已安装、声明了
playlistDetail的来源,再粘贴链接或 ID。应用先调用resolvePlaylistId(source, { input }),返回的字符串就作为playlistDetail的id;返回null或空串时,只有纯数字的输入会被原样当作 ID,其余提示「无法识别」。 - 来源用什么写法分享歌单由插件自己决定(链接里的参数名、路径、短码都可以),应用不内置任何平台的链接规则。
recognize
- 入参
pcm是 base64 编码的音频:单声道、sampleRate(固定为 8000)Hz、32 位浮点、小端,最长 15 秒。应用负责录音或解码文件、重采样与静音检测;只有指纹或特征,不含原始音频文件本身,是否上传由你的实现决定,请在插件说明里写清楚。 - 返回
{ matches: [{ item, startTimeMs }] }:item是 MusicItem,startTimeMs是这段音频在歌曲中的起点(毫秒),最多保留 20 条。和其他调用一样,宿主会把每首歌的source改成这个来源,识别出的歌曲之后通过这个来源播放。 - 没有匹配时请
throw带原因的Error(例如「未识别到歌曲」),消息会显示给用户。 - 计算量大的部分(音频指纹算法)可以用
cerumusic.utils.wasm运行随脚本附带的 WebAssembly 模块。
宿主替你做的事
理解宿主的保证,可以省掉很多防御性代码,也能解释一些「为什么我的数据被改了」。
- 盖章
source:返回的每首歌、每个结果、每个歌单、每个榜单的source都会被改写为本次调用的来源 ID。 - 截断:歌曲、歌单、榜单列表每页最多保留 500 项。
- 反序列化校验:返回值必须能解析为对应的数据结构(字段名是 camelCase,见 数据结构)。解析失败时用户只会看到「插件返回的数据格式无效」,具体字段错误写在插件日志里。
- 地址校验:播放、下载与封面地址必须是合法的
http(s)URL。
错误处理
在方法里 throw new Error('…')(或让 Promise reject)就会让这次调用失败,错误信息前 300 个字符会展示给用户并写入日志。因此:
- 写面向用户的、可操作的信息,例如「请先在插件“配置”中填写服务器地址」「用户名或密码错误」。
- 不要把密码、令牌或带签名的 URL 拼进错误信息。
- 网络层的失败(超时、拒绝连接)会让
cerumusic.request的 Promise reject,直接让它抛出去通常就够了,也可以捕获后换成更友好的提示。 - HTTP 状态码不是 2xx 时
cerumusic.request不会抛错,请自己检查statusCode。
旧版 Mio 服务脚本
没有导出 listPlugin、但导出了 configSchema,或同时有 getPlaylists() 与 getPlaylistSongs(id) 的 CommonJS 脚本,会被适配层包装成一个列表插件:它只具备 userPlaylists、playlistDetail、testConnection(有 testConnection 或 ping 时)以及 play / download(有 musicUrl 时)能力,来源 ID 取 sources 的键,缺省则由插件名派生。新插件请直接使用 listPlugin 契约。