接入文档
按本文接入,审核通过后即可调用
读完本文,你的 Android 应用可以通过 suchmusic:// 拉起 SuchMusic,
在用户授权后获取曲风画像、酷狗账号令牌、歌曲直链,或者让 SuchMusic 代播。
适用版本:SuchMusic 1.5.0 及以上。
能力说明
共四类能力,按需申请,用户逐项授权,随时可以撤销。
| scope | 能拿到什么 | 是否需要每次确认 |
|---|---|---|
genre.read |
用户曲风画像:topGenres[{tag, weight}]、sampleSize、updatedAt |
否,已授权可静默 |
url.resolve |
指定歌曲的可播放直链 | 否,已授权可静默 |
playback.control |
让 SuchMusic 播放、暂停或切歌 | 否,已授权可静默 |
account.token |
酷狗账号 token、userId、nickname |
是,每次都弹确认页 |
account.token 等价账号控制权,
因此不进入静默白名单,每次唤起都会要求用户在确认页上明确同意。
这是刻意的设计,不要指望它被静默续期。
接入前准备
接入采用「包名 + 签名指纹」白名单,每个应用都要经过审核。按下面四步拿到 client_id。
- 注册开放平台账户。用邮箱接收验证码完成验证后即可进入控制台。
- 在控制台点「新建应用」,填写应用名、包名、签名指纹、用途说明,并勾选需要的能力。 包名必须与安装包一致;签名指纹是发布签名的 SHA-256,64 位小写十六进制。
-
导出签名指纹(建议直接用发布包):
apksigner verify --print-certs app-release.apk # 取 Signer #1 certificate SHA-256 digest,去掉冒号并转成小写 -
提交后平台分配
client_id(形如sm_1a2b3c4d5e6f7a8b)。 审核通过后应用进入公开清单,此时才能真正被唤起。
client_id 一律返回 client_mismatch。
唤醒协议
用自定义 scheme 唤起 SuchMusic 的授权页。
suchmusic://oauth/authorize?client_id={client_id}&package={packageName}&scopes={scopes}&state={state}&silent=1
| 参数 | 必填 | 说明 |
|---|---|---|
client_id |
是 | 平台在审核通过后分配的标识 |
package |
是 | 你的应用包名,必须与登记信息一致 |
scopes |
是 | 逗号分隔的能力列表,重复项会自动去重 |
state |
是 | 1 到 128 个字符,会原样回传,用于防止跨请求混淆 |
silent |
否 | 传 1 表示请求静默续期,是否真的静默由 SuchMusic 判定 |
val uri = Uri.Builder()
.scheme("suchmusic")
.authority("oauth")
.path("/authorize")
.appendQueryParameter("client_id", "sm_1a2b3c4d5e6f7a8b")
.appendQueryParameter("package", packageName)
.appendQueryParameter("scopes", "genre.read,url.resolve")
.appendQueryParameter("state", UUID.randomUUID().toString())
.appendQueryParameter("silent", "1") // 可选,请求静默续期
.build()
startActivity(Intent(Intent.ACTION_VIEW, uri))
授权流程
- 你的应用用上面的 deep link 唤起 SuchMusic。
- SuchMusic 校验
client_id、包名与签名指纹。任一项不匹配就返回client_mismatch,并且不写入任何授权记录。 - 用户看到确认页,逐项勾选要授予的能力。只突出新增或敏感的 scope。
-
用户点「允许」后,SuchMusic 用 explicit intent
(
com.lightningteam.suchmusic.CLIENT_CALLBACK) 把code、port、state、scopes投递给你的回调 Activity。 用户点「拒绝」则返回access_denied,你的回调页不会被拉起。 - 你的应用拿
code向本机回环端口换取数据。 code60 秒过期,只能使用一次;换取成功后服务立即关闭。
静默续期:再次唤起时带 silent=1,同时满足四条才会跳过确认页。
- 该
client_id已有授权记录。 - 请求的 scopes 是已授权 scopes 的子集。
- 请求不含
account.token。 - 距上次静默调用不短于 5 秒。
静默完成时 SuchMusic 会发一条系统通知告知用户,例如「某某应用读取了你的曲风画像」。 这条通知是用户知情权的一部分,不要试图绕过。
数据交换
数据走本机回环 HTTP,不经过网络。端口由系统随机分配,在回调 intent 的 port 里给出。
POST http://127.0.0.1:{port}/v1/exchange
Authorization: Bearer {code}
Content-Type: application/json
请求体可以为空,此时返回本枚 code 覆盖的全部 scope;也可以按需指定:
{
"request": {
"url.resolve": {
"song": {
"id": "123456",
"source": "netease",
"name": "东风破",
"artist": "周杰伦"
}
},
"playback.control": {
"action": "play",
"song": { "id": "123456", "source": "netease", "name": "东风破" }
},
"account.token": { "platform": "lite" }
}
}
成功响应:
{
"client_id": "sm_1a2b3c4d5e6f7a8b",
"scopes": ["genre.read", "url.resolve"],
"issued_at": 1731600000,
"data": {
"genre.read": {
"topGenres": [{ "tag": "流行", "weight": 0.42 }],
"sampleSize": 128,
"updatedAt": 1731599000
},
"url.resolve": {
"url": "https://...",
"source": "netease",
"songId": "123456",
"resolvedAt": 1731600000
}
}
}
- 服务只在
127.0.0.1监听,端口随机,不要写死。 - 一次
code只能交换一次,成功即关服;60 秒不用自动关服。 - 请求体上限 32KB。
- 未经过前台确认的
account.token会返回 403scope_not_granted。
回调声明
SuchMusic 不用 scheme 回跳,避免被抢注劫持,而是用 explicit intent 把结果投递给你的包。
因此你必须在 AndroidManifest.xml 里声明回调 Activity:
<activity
android:name=".SuchMusicCallbackActivity"
android:exported="true">
<intent-filter>
<action android:name="com.lightningteam.suchmusic.CLIENT_CALLBACK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在回调 Activity 里取参数并立刻换数据:
class SuchMusicCallbackActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val code = intent.getStringExtra("code") ?: return finish()
val port = intent.getIntExtra("port", 0)
val state = intent.getStringExtra("state")
val scopes = intent.getStringArrayExtra("scopes") ?: emptyArray()
// 务必校验 state 与发起时一致
// 拿到 code 后立刻换数据(code 60 秒过期,且只能使用一次)
exchange(code, port, state)
finish()
}
}
no_callback_activity,授权不会落库。
这样做是为了避免「用户点了同意,数据却无处可去」。
错误码
| error | HTTP | 含义 | 你该怎么做 |
|---|---|---|---|
invalid_request |
400 | 深链参数缺失或格式错误 | 修正参数 |
client_mismatch |
— | 未登记,或包名不符,或签名不符,或未安装 SuchMusic | 检查登记信息 |
invalid_scope |
400 | scope 不合法 | 检查拼写 |
invalid_state |
400 | state 为空或超过 128 个字符 | 重新生成 |
rate_limited |
— | 同一 client 每分钟超过 10 次 | 退避重试 |
access_denied |
— | 用户拒绝授权 | 不要反复骚扰 |
no_callback_activity |
— | 未声明回调 Activity | 按「回调声明」一节声明 |
invalid_code / code_expired |
401 | code 无效、过期或已使用 | 重新走授权 |
scope_not_granted |
403 | 请求了未授权的 scope,或令牌未经前台确认 | 重新申请该 scope |
not_logged_in |
400 | 用户未登录酷狗 | 提示用户先在 SuchMusic 里登录 |
resolve_failed |
400 | 直链解析失败,通常是版权或参数问题 | 检查 song 字段 |
bad_request |
400 | 请求体不是合法 JSON 对象 | 修正请求体 |
撤销授权
用户可以在 SuchMusic 的「设置 → 第三方授权」里撤销单个应用,或一次性全部撤销。撤销后:
- 已授权的 scope 立即失效。
- 该 client 未使用的 code 全部作废。
- 进行中的回环服务立即关闭。
- 你再调用必须重新走确认页。
不要假设授权是永久的,每次静默请求都可能收到需要前台确认的结果。
安全要求
- 不要把
code或token写进日志、崩溃上报、埋点。 - 不要把换到的 token 上传到自己的服务器,除非你明确告知用户并取得同意。
- 不要伪造
package参数,SuchMusic 会校验本机安装包与签名,伪造只会得到client_mismatch。 - 不要轮询。需要最新数据时再唤起,频率受每分钟 10 次限制。
- 回调 Activity 保持
android:exported="true",其余敏感组件不要导出;回传的state必须校验。
调试建议
不需要写代码就能验证唤起是否成功,先用 adb 打一发:
adb shell am start -a android.intent.action.VIEW \
-d "suchmusic://oauth/authorize?client_id={你的 client_id}&package={你的包名}&scopes=genre.read&state=test1"
联调清单:
- 确认页出现,四项 scope 的文案与申请一致。
- 点「拒绝」,你的回调页不应被拉起。
- 点「允许」,回调页收到
code与port。 curl -X POST http://127.0.0.1:{port}/v1/exchange -H "Authorization: Bearer {code}"返回 200。- 同一
code再请求一次,应当返回 401。 - 带
silent=1再唤起,无确认页,收到系统通知。 - 在设置里撤销授权,再带
silent=1唤起,应重新弹确认页。
常见问题
| 现象 | 原因 |
|---|---|
唤起后无任何反应,或返回 client_mismatch |
SuchMusic 未安装;或 client_id/包名/签名指纹与登记信息不一致;或应用还没通过审核 |
| 用户点了允许,但应用没收到任何数据 | 回调 Activity 未声明 CLIENT_CALLBACK 的 intent-filter,或 android:exported 不为 true |
| 换数据返回 401 | code 超过 60 秒,或已经被使用过一次 |
换数据返回 403 scope_not_granted |
请求了未授权的 scope,或 account.token 未经过前台确认 |
日志里频繁 rate_limited |
同一 client 每分钟超过 10 次,改成按需唤起 |