feat: P7 在线播放(播放队列、媒体控制)与自建歌单

- 播放器:统一 PlayItem 队列,顺序 / 列表循环 / 单曲循环 / 随机,滚动歌词含翻译,Media Session,队列与进度存 localStorage
- 在线播放音质可在设置中选择;曲库已有的歌优先播放本地文件
- 自建歌单:混排在线曲目与本地文件,拖动排序,一键下载曲库缺失曲目

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jocay
2026-10-11 11:31:29 +08:00
parent 9f908fa73f
commit 676541457b
30 changed files with 2995 additions and 174 deletions
+7 -3
View File
@@ -22,10 +22,12 @@
- `core/providers/{netease,qq}/` 实现 `MusicProvider`(`core/types.ts`),在 `providers/index.ts` 注册;`resolveLink` 处理分享短链
- QQ:`qq/client.ts` 的 `musicu()` 在 comm 中带 `uin` + `authst` 并附 Cookie —— 缺这一步拿不到 VIP 音源
- `queue.ts` — 任务存 SQLite `tasks` 表,进度只在内存,经 `events.ts` 的 bus 推送;`routes/api.ts` 的 `/api/events` 是 SSE
- `accounts.ts`(Cookie 用 `secret.ts` AES-GCM 加密存储)、`settings.ts`(运行时设置)、`auth.ts`(可选访问密码)、`library.ts`(曲库工具)、`stream.ts`(试听代理)
- `db.ts` — `node:sqlite`;history / tasks / settings 表
- `playlists.ts` — 自建歌单(`playlists` / `playlist_items` 表);条目要么是在线曲目(source + track_id)要么是曲库文件(path),`item_key` 去重。读取时给在线曲目补 `localFile`、给文件补 `missing`
- `accounts.ts`(Cookie 用 `secret.ts` AES-GCM 加密存储)、`settings.ts`(运行时设置)、`auth.ts`(可选访问密码)、`library.ts`(曲库工具)、`stream.ts`(在线播放代理,音质取设置 `playQuality`)
- `db.ts` — `node:sqlite`;history / tasks / settings / playlists / playlist_items 表
- `web/` — Vite 8 + Vue 3.5 + Vue Router 5,无 UI 库
- `lib/store.ts` 全局状态 + SSE;`lib/player.ts` 试听;`lib/api.ts` 类型与请求
- `lib/store.ts` 全局状态 + SSE;`lib/api.ts` 类型与请求
- `lib/player.ts` 播放器:单个 `<audio>` + 队列(`shallowRef`,整体替换)。曲目统一转成 `PlayItem`(`fromTrack` / `fromLocal` / `fromPlaylistItem`),key 为 `source:id` 或 `file:path`;队列与进度存 localStorage。`lib/playlists.ts` 是歌单缓存与全局「加入歌单」弹窗(`pickPlaylist`)
- `components/TrackList.vue` 是所有曲目列表(搜索 / 专辑 / 歌单 / 歌手)的核心
- 样式只用 `styles/tokens.css` 的 CSS 变量;`base.css` 有 `.page` `.btn` `.seg` `.badge` 等通用类
@@ -38,6 +40,8 @@
- 服务端永远不把 Cookie 原文返回前端;`relFile` 等路径一律用 `/`
- 传给 TrackList 的数组必须稳定(用 computed),否则每次渲染都会清空选择
- 前端 `ref<null>` 的接口要返回对象包一层(Koa 对 `null` body 返回 204)
- 子组件根元素会继承父组件的 scoped 样式作用域:PlayerBar 根元素不能用 App.vue 里已有的类名(如 `.dock`)
- 列表行用 `content-visibility: auto` + `contain-intrinsic-size`,上千首也只渲染视口附近的行
- QQ 登录在 `qq/login.ts`:扫码 = ptlogin2 → graph.qq.com OAuth → `QQConnectLogin.QQLogin`;续期 = `music.login.LoginServer.Login`(网页 Cookie 没有 refresh_key 也能续期,旧 key 不会失效)。`accounts.ts` 的 keeper 每 3 小时检查,过半寿命(key 有效 3 天)即续期。有效性必须用 `GetLoginUserInfo` 判断 —— `vip_login_base` 对失效 key 仍返回 code 0
- NAS 部署:宿主机文件是 0700,镜像里必须 `chmod -R a+rX /app`;境外请求靠 `NODE_USE_ENV_PROXY` + `HTTP(S)_PROXY`,`NO_PROXY` 覆盖 QQ / 网易云域名
- TypeScript 固定 6.x:TS 7 没有 JS API,vue-tsc 无法使用