feat(sdk): 新增咪咕/千千/JOOX/哔哩哔哩/5sing/Apple Music 六平台
平台注册表由 9 个扩到 15 个,同时补齐 sdk/types.ts 的类型与 OpenAPI 文档里的能力位声明。 - migu:命中 Z3D/I3D 加密候选时不返回直链,改由服务端解密代理出流, SDK 侧只负责登记票据,避免客户端拿到无法播放的密文 - apple:只提供 preview 试听片段(完整曲为 DRM),标 noAggregate 排除在跨平台精确匹配之外,防止用它顶掉其它平台的可播结果 - 其余平台按既有 PlatformModule 约定实现搜索/专辑/歌单/歌词 为什么合并成一次提交:六平台共用同一份注册表与能力位声明, 拆开会产生互相引用、单独无法通过类型检查的中间态。
This commit is contained in:
1 parent
47ce651f46
commit
33b96f92c4
54 files changed
+6161
-5
No files matched your search
@@ -0,0 +1,53 @@
|
||||
// 5sing 取流(上游 fivesing/download.go 的 GetDownloadURL + fivesing.go 的 fetchAudioLink 移植)
|
||||
//
|
||||
// 链路:GET http://mobileapi.5sing.kugou.com/song/getSongUrl?songid=<id>&songtype=<yc|fc|bz>
|
||||
// → { code: 1000, data: { squrl/squrl_backup, hqurl/hqurl_backup, lqurl/lqurl_backup, sqsize/hqsize/lqsize } }
|
||||
// 直链为 CDN 明文地址(wsaudiobssdlbig.kugou.com,https,无需 Referer/防盗链签名;实测 Range 206 可播)。
|
||||
// 说明:上游 fivesing 包内没有 base64/加密参数逻辑(不存在 _sign/song_id 签名),取流就是上述明文 GET;
|
||||
// 数字 userId 的网页链接 302 的问题只影响歌单页,与取流无关。
|
||||
// 档位:上游固定 sq(最高,常为无损/高码)→ hq → lq,返回首个非空(含 _backup);
|
||||
// 本移植默认沿用该顺序,只有显式请求 64k(或 type=lq)时才倒序优先 lq,返回实际命中档位。
|
||||
// songmid 约定 "<songId>|<songType>";Parse 出来的歌曲带 url 时直接复用(上游 GetDownloadURL: if s.URL != "" return)。
|
||||
|
||||
import { fetchAudioLink } from '../5sing/musicInfo.ts'
|
||||
import { extractSongId, isForeignSource } from '../5sing/util.ts'
|
||||
|
||||
/** 上游只有 sq/hq/lq 三档,实际命中档位以返回 type 为准(sq 常缺,多命中 hq) */
|
||||
export const supportQualitys = ['sq', 'hq', 'lq']
|
||||
|
||||
/** 上游 GetDownloadURL 的等价实现(非取消句柄版):register 用于取消当前请求 */
|
||||
export const getDownloadUrl = async(songInfo: any, type?: string, register?: (request: any) => void) => {
|
||||
if (isForeignSource(songInfo)) throw new Error('source mismatch')
|
||||
// 上游逻辑:Song.URL 已带直链(Parse 场景)直接复用
|
||||
if (songInfo?.url) {
|
||||
return { type: String(songInfo?.type || type || 'sq'), url: String(songInfo.url) }
|
||||
}
|
||||
const { songID, songType } = extractSongId(songInfo?.songmid ?? songInfo?.id ?? songInfo?.songid, songInfo?.songType)
|
||||
if (!songID || !songType) {
|
||||
throw new Error('invalid id structure: 5sing songmid 需为 "<songId>|<songType>"(songType 为 yc/fc/bz)')
|
||||
}
|
||||
const preferLow = type === '64k' || type === 'lq'
|
||||
return fetchAudioLink(songID, songType, register, preferLow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一取流接口:getMusicUrl(songInfo, type) → { promise, cancelHttp }
|
||||
* promise resolve → { type: 'sq'|'hq'|'lq', url }(type 为实际命中档位,可能低于请求档位)
|
||||
*/
|
||||
export const getMusicUrl = (songInfo: any, type?: string) => {
|
||||
const requestObj: { promise: any, cancelHttp: () => void } = { promise: null, cancelHttp() {} }
|
||||
let current: any = null
|
||||
requestObj.cancelHttp = () => {
|
||||
current?.cancelHttp?.()
|
||||
current = null
|
||||
}
|
||||
|
||||
requestObj.promise = (async() => {
|
||||
const hit = await getDownloadUrl(songInfo, type, (request) => { current = request })
|
||||
return { type: hit.type, url: hit.url }
|
||||
})()
|
||||
|
||||
return requestObj
|
||||
}
|
||||
|
||||
export default { getMusicUrl, getDownloadUrl, supportQualitys }
|
||||
Reference in new issue
Block a user