Anni 音频仓库协议
版本信息
⚠️️ 在协议版本迈入
1.0
之前,任何改动都有可能发生。
最后修改:2022 年 05 月 10 日 协议版本:0.5.0
定义
Anni
音频仓库(Anni Audio Library
,以下简称音频仓库)是指实现了 Anni
音频仓库协议中定义 API
的服务端应用程序。其核心功能有三:
- 对用户身份的鉴定
- 对分享的权限认证及限制
- 向用户分发资源(包括音频和封面)
跨域约定
Anni
音频仓库协议约定所有请求的返回中都包含头部:
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Authorization
并建议带有:
Access-Control-Allow-Origin: *
各实现可以允许用户选择能够进行跨域请求的域名。
返回状态码
Anni
音频仓库协议使用了以下 HTTP 状态码。
状态码 | 含义 |
---|---|
400 | 请求非法 |
200 | 请求成功 |
403 | Token 校验失败或权限不足 |
404 | 资源不存在 |
鉴权
对请求者的身份鉴定通过 JWT
(JSON Web Token
) 的形式进行(下文称作 Token
)。本协议不限制签名的方式,主要针对 Claim
部分作出规定。各实现需要对请求者的身份进行鉴权,以判断是否有对应资源的权限。
协议规定了两种 Token
:用户 Token
和分享 Token
。用户 Token
的使用者为音频仓库的正式用户,部分用户拥有创建分享 Token
的权限;分享 Token
的使用者为任意用户,可以在分享 Token
的限定范围内获取 Anni
音频仓库中的资源。下文中除特殊说明外,限定的访问范围均为有用户 Token
和有对应资源访问权限的分享 Token
。
Anni
音频仓库协议允许以下两种鉴权方式中的任意一种。客户端可以根据需要选用其中任意一种调用。
Authorization 头部
当使用 Authorization
头部时,客户端需要将合法的 JWT
携带于 Authorization
头部中,不需要增加 Bearer
前缀。
Anni
音频仓库能否正确处理带 Bearer
的请求属于未定义行为。
该方式对所有请求均有效。
?auth=
和 Authorization
类似,客户端也可以选择将 JWT
夹带在 URL
的 query
中。仅对 GET 请求可用。
用户身份鉴定
以下为用户 Token
(User Token
) 的 Claim
部分样例:
{
// Token 签发的时间
"iat": 1615788479,
// JWT 类型,必须为 user
"type": "user",
// 用户标识,注意不要包含用户信息,只要能标记用户即可
"user_id": "rua",
// 当 Token 支持分享时,会携带以下信息
"share": {
// 可选,用于标识 key
"key_id": "409e2d92-d2c5-4dff-81e6-fe832e1b06d0",
// secret 字段用于对分享 Token 签名
"secret": "c398313c-b086-47ce-9a91-edfa5c93bd73",
// 可选,表示允许签署的分享范围,省略则表示无限制
// 结构与 /albums 的返回相同
// 该字段若存在则必须为数组,其他情况均未定义。
"scope": [],
// @deprecated `scope` 的别名,不建议使用。
// 协议进入 1.0 后会将该字段删除。
"allowed": []
}
}
音频仓库信息
获取音频仓库信息的 API
定义为 GET /info
,所有用户都可以请求。
返回结果如下例:
{
// 音频仓库的版本描述
// 用于客户端展示
"version": "Annil v0.1.0",
// 当前运行的 Annil 音频仓库协议版本
// 用于客户端比对其支持的能力
"protocol_version": "0.2.1",
// 音频仓库最近一次数据更新时间
// 用于客户端缓存 `/albums` 请求结果
"last_update": 1639631487
}
获取可用专辑
由于音频仓库整合了多个音频后端,因此继承了所有音频后端的可用音频数据。
获得可用专辑的 API
定义为 GET /albums
,仅合法的用户 Token
可以正常请求。
响应内容包含当前仓库所有可用的专辑 album_id
列表,如下例:
// 以下 UUID 不代表实际内容
[
// KSLA-0155~9/2018-12-29
"4992994f-9ede-56c2-b614-83bf89840e10",
// TS-0003/2015-05-20
"c12705be-6f2d-5195-aa53-5325ce9b780a",
// KSLA-0178/2020-12-16
"896a00d4-f93d-57b2-aa0a-52b1cf521c63"
]
Anni
音频仓库协议建议音频仓库在响应中携带 ETag
头部,以便客户端缓存。
客户端实现指导
Anni
音频仓库建议,客户端每次启动时携带 If-None-Match: <previous-etag-value>
头部请求 GET /albums
,并判断:
- 若响应状态码为
304
,则无需更新; - 若响应状态码为
200
,则根据返回结果自动更新索引,并向用户提示。
特别地,第一次启动时客户端无需携带 If-None-Match: *
头部,直接请求 GET /albums
并自动更新索引,并向用户提示。
资源分享
Project Anni
的设计目的是 Self Host
使用,因此存在验证用户身份的环节,非认证用户无法访问仓库中的资源。但是对部分资源的分享需求是真实存在的。因此 Anni
音频仓库草案提出了细粒度的资源共享方案。
资源共享通过 JWT
的方式进行鉴权。
签署 Token
用户 Token
的 Claim
部分定义了 share
字段,用于存储资源分享所需的相关凭据。
客户端需以 share.secret
字段作为 key
,对分享 Token
进行 HS256
签名。
分享 Token(Share Token)
Header
部分如下例:
{
"typ": "JWT",
"alg": "HS256",
// 分享 Key 的 ID,当用户 Token 中携带时须包括在分享 Token 内
"kid": "409e2d92-d2c5-4dff-81e6-fe832e1b06d0"
}
Claim
部分如下例:
{
// 该 Token 的签发时间,即分享的开始时间
"iat": 1615788479,
// 该 Token 的失效时间,即分享的结束时间
// 服务端有权拒绝向不存在该字段的 Token 提供资源,播放器实现需考虑到这一点并相应地对用户作出提示
"exp": 1615874879,
// JWT 类型,必须为 share
"type": "share",
// 分享的音频,以 JSON Object 的形式表示
// 作为 key 的字符串表示该分享允许访问者访问的专辑 album_id
// value 表示该分享允许访问者访问的唱片号(disc_id)和音轨号(track_id)
"audios": {
// KSLA-0155~9/2018-12-29
"4992994f-9ede-56c2-b614-83bf89840e10": {
// Disc 2(KSLA-0156), Track 2
"2": [2],
// Disc 3(KSLA-0157), Track 3
"3": [3]
},
// KSLA-0178/2020-12-16
"896a00d4-f93d-57b2-aa0a-52b1cf521c63": {
"1": [1, 2, 3]
}
}
}
资源分发
音频仓库接收用户的请求,从可用的音频后端获取数据,并将获得的数据转发给用户。
由于 Anni
约定中的各专辑均带有封面信息,因此资源分发同时支持音频和封面。
音频分发
音频分发基于音频所在 Disc
的 album_id
、 disc_id
和 track_id
。用户获取音频的前提是其提供的 Token
允许其访问对应的资源。
获得音频的 API
定义为 GET /{album_id}/{disc_id}/{track_id}
。通过 HEAD /{album_id}/{disc_id}/{track_id}
可以只获取 Header
信息。
音频偏好
考虑到客户端的不同网络场景和带宽因素,以下的参数可以向音频仓库表明客户端的格式偏好。
参数名 | 说明 | 可选项 |
---|---|---|
quality | 客户端期望的音频质量。 | low 、medium 、high 、lossless |
需要注意的是,客户端传递的参数仅为客户端偏好,对音频仓库仅有指导意义,不代表实际结果。
音频仓库在对参数进行处理时,应以枚举而非字符串拼接的形式进行,以防止命令注入。
请求范围
为了达到更好的播放效果,Annil
实现可以选择实现 HTTP
请求范围。
当音频仓库支持某一音频资源的单一范围请求时,HEAD
和 GET
请求的返回中必须带有 Accept-Ranges: bytes
。当该头部不存在,或为 none
时,表示音频仓库不支持该音频文件的单一范围请求。
Anni
音频仓库协议暂不支持多重范围请求。
当客户端向音频仓库通过 GET
请求某一范围时,下文所述的返回头部不保证可用。
返回头部
当成功获取音频时,返回的头部会携带如下额外信息:
Key | 含义 |
---|---|
Content-Type | 音频的 MIME Type ,根据情况可能为 audio/flac 、audio/mp3 等 |
X-Origin-Type | 源文件的 MIME Type ,当与 Content-Type 不同时表明音频经过了转码 |
X-Origin-Size | 源文件的大小,单位为字节(Byte ) |
X-Duration-Seconds | 音频的长度,单位为秒 |
X-Audio-Quality | 音频的实际音质,可选值为 low 、medium 、high 、lossless |
封面分发
封面分发基于音频所在专辑的 album_id
和可选的 disc_id
。获取封面无需 Token
。
获得封面的 API
定义为 GET /{album_id}/{disc_id}/cover
或 GET /{album_id}/cover
,默认 disc_id
为 1
。
返回头部
当成功获取封面时,返回的头部会携带如下额外信息:
Key | 含义 |
---|---|
Content-Type | 图片的 MIME Type ,当前定义中只能是 image/jpeg |
管理接口
Annil 定义了如下的管理接口,供音频仓库的管理员使用。
以下所有 API
均为可选,对接时须确认各接口是否可用,以及公网可访问性等细节。接口仅包含最小定义,在满足下文定义行为的前提下,各实现可以自行添加部分参数以实现功能扩展。
访问管理接口时需要包含 Authorization
头部,该头部的获取方式不在本文规定范围之内。
管理接口不应允许跨域访问。
签署用户 Token
签署用户 Token
的 API
定义为 POST /admin/sign
。
{
"user_id": "rua",
"share": true
}
返回结果为签名后的用户 Token
。
资源热更新
热更新 Annil
中各个 Provider
中资源的 API
定义为 POST /admin/reload
。
热更新的过程是同步的,更新结束后请求才会返回。