// List Plugin Reference

列表插件参考

音乐列表插件是一个导出 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 }) { /* … */ },
    // …
  },
};

宿主按下面的顺序对待脚本:

  1. 安装时执行一次脚本,只读取元数据、sources、config 与 defaults,检查合法性。
  2. 之后每次调用某个方法,都重新执行整个脚本,再调用对应方法(见 每次调用都是全新的上下文)。所以不要在模块顶层做网络请求或其它耗时的事。
  3. 如果模块导出了 ready(一个 Promise),宿主会先 await 它再继续。

脚本需要是合法的 UTF-8 文本,不能为空,体积不得超过 1 MiB。

元数据

字段类型说明
namestring,必填插件名,最多 256 个字符。缺失则安装失败。
versionstring版本号,最多 64 个字符。更新时用它判断新旧。
authorstring作者,最多 256 个字符。
descriptionstring简介,最多 1000 个字符。
iconstring可选图标(emoji 或 data URI),最多 65536 个字符。来源没有自己的 icon 时使用它。
configarray配置表单,见 配置表单。
defaultsobject{ play, download },两个布尔值,见 默认开关。
sourcesobject,必填来源声明,见 来源与能力。

除了对象字段,脚本顶部的文档注释也建议带上 @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 时需要。

能力一览

能力对应方法界面里启用的功能
searchsearch歌曲搜索
searchPlaylistsearchPlaylist歌单搜索
playlistDetailplaylistDetail歌单详情
albumDetailalbumDetail专辑详情
discoverydiscoveryCategories、discoveryPlaylists、discoveryCharts、chartDetail发现页的分类、歌单与榜单
singersingerInfo、singerSongs、singerAlbums歌手页
lyricslyrics歌词
commentscomments播放器评论
covercover补查缺失的封面
playmusicUrl由本插件解析播放地址
downloaddownloadUrl(缺省用 musicUrl)由本插件解析下载地址
testConnectiontestConnection配置页的「测试连接」
userPlaylistsgetPlaylists「导入歌单」:列出服务器上用户自己的歌单
resolvePlaylistIdresolvePlaylistId「从网络导入歌单」:从用户粘贴的链接里取出歌单 ID。需要同时声明 playlistDetail,应用据此决定列出哪些平台
recognizerecognize听歌识曲:根据一小段音频认出歌曲。应用只在有已启用的插件声明了该能力时才显示识曲入口

宿主只会调用已声明能力对应的方法。声明了能力却没有实现对应方法,调用时会得到「插件缺少 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。
typetext、password、number、switch、select、textarea。未知类型按 text 处理。
default默认值。
required是否必填。
placeholder占位提示,最多 128 个字符。
description说明文字,最多 256 个字符。
optionsselect 的选项 [{ 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 }] }

分页约定

search / searchPlaylist

keyword 非空且不超过 1024 字节。搜索结果里的每首歌都要带足够的信息:之后它会作为 song 传回 musicUrl、lyrics 等方法。注意它会经过 MusicItem 结构往返,结构里没有的字段不会保留,详见数据结构一页。

playlistDetail / albumDetail

id 就是你在 searchPlaylist 或 discoveryPlaylists 里返回的 PlaylistItem.id(或歌手页里 SingerAlbum.id)。专辑与歌单用不同的方法,如果你的服务器里两者 ID 会冲突,可以像内置插件那样给 ID 加前缀(pl-123、al-123),再在方法里去掉。

discovery 系列

singer 系列

歌手 id 是字符串,非空、不超过 128 个字符,且不为 "0"。服务器没有「歌手资料」接口时,singerInfo 可以返回 null,歌手页会退回使用打开它的那首歌的歌手名。

lyrics / comments / cover

params.song 是 MusicItem 的 JSON 对象。

musicUrl / downloadUrl

quality 是用户当前选择的音质,来自你为该来源声明的 qualities。返回一个可直接请求的 http / https 地址(字符串,或 { url }):

testConnection / getPlaylists

resolvePlaylistId

recognize

宿主替你做的事

理解宿主的保证,可以省掉很多防御性代码,也能解释一些「为什么我的数据被改了」。

错误处理

在方法里 throw new Error('…')(或让 Promise reject)就会让这次调用失败,错误信息前 300 个字符会展示给用户并写入日志。因此:

旧版 Mio 服务脚本

没有导出 listPlugin、但导出了 configSchema,或同时有 getPlaylists() 与 getPlaylistSongs(id) 的 CommonJS 脚本,会被适配层包装成一个列表插件:它只具备 userPlaylists、playlistDetail、testConnection(有 testConnection 或 ping 时)以及 play / download(有 musicUrl 时)能力,来源 ID 取 sources 的键,缺省则由插件名派生。新插件请直接使用 listPlugin 契约。