11 KiB
11 KiB
交接文档 · 音乐在线电台 v2
本文件用于跨会话交接。上下文耗尽后,新会话先读此文件再动手。
0. TL;DR
- 项目:
F:\music_online\demo— Node.js + FFmpeg 的 HLS 音乐电台 - 状态:v2 重构基本完成,后端 38/38 测试通过;但用户反馈"功能还有问题",具体未明(见 §9 待排查)
- 服务:正在运行
svc005,http://127.0.0.1:3000,密码test123(测试用) - Git:
ea5aa0c(基线) →b8e9bf3(v2主体) →a4745db(收尾)
1. 运行 / 环境
- 环境:Windows 11,Node v24.5.0,npm 11.5.1,FFmpeg 9.0.1(含 ffprobe,均在 PATH),Python 3.11
- 启动:
cd /d F:\music_online\demo set PANEL_PASSWORD=test123 && node index.js - 依赖:
better-sqlite3、multer、chokidar、express(已装) - 登录:浏览器开
http://127.0.0.1:3000,输入密码
2. 目录结构(v2)
demo/
├── index.js # 入口,仅 require("./src/server").start()
├── package.json # v2.0.0
├── data.db # SQLite(自动生成,WAL)
├── HANDOFF.md # 本文件
├── README.md # v2 说明
├── src/
│ ├── server.js # 装配:中间件、路由、启动、优雅退出
│ ├── config.js # 所有配置(环境变量解析)
│ ├── logger.js # 分级日志 + 落盘 + 内存环形缓冲(tail)
│ ├── db.js # SQLite 初始化 + schema(5表) + WAL
│ ├── store.js # 数据访问层,所有 SQL 收敛于此
│ ├── util.js # fileHash/解析文件名/原子写/目录大小 等
│ ├── media.js # ffprobe + FFmpeg 切片 + 并发队列 + 进程追踪
│ ├── metadata.js # 网易云 API + 封面下载本地化 + 元数据补全
│ ├── livestream.js # LiveStream 类 + 多路流管理 + 缓存状态/清理
│ ├── scanner.js # audio/ 监听 + 同步 DB
│ ├── auth.js # Cookie 会话鉴权 + requireAuth 中间件
│ ├── sse.js # Server-Sent Events 广播
│ └── routes/
│ ├── auth.js # /api/auth/*
│ ├── music.js # /api/music/* (搜索/详情/下载/导入/删除/enrich)
│ ├── resources.js # /api/resources
│ ├── streams.js # /api/streams/* (流管理 + 播放列表)
│ └── system.js # /api/system/* (缓存/日志/配置/设置)
├── public/
│ ├── index.html # 主面板
│ ├── login.html # 登录页(新)
│ ├── css/style.css # 含 v2 追加样式(.stream-select/.import-card/.now-badge)
│ ├── js/app.js # v2 重写:多路流/鉴权/导入/删除
│ ├── vendor/ # 本地化:bootstrap/icons/sortable + woff2
│ ├── covers/ # 本地化封面(每首一个 jpg)
│ └── favicon.ico/.png
├── scripts/fetch_vendor.py # 一次性下载前端 CDN 资源(已跑过)
├── audio/ # 音频(5 首 mp3,已 gitignore)
├── hls/ # 直播输出 hls/{streamId}/index.m3u8
├── hls_cache/ # 切片缓存 hls_cache/{fileHash}/
├── tmp/uploads/ # multer 上传临时目录
└── test_api.py / test_e2e.py / test_write.py / test_enrich.py # 测试脚本
3. 数据模型(SQLite, src/db.js)
songs(id, file UNIQUE, name, artist, cover, duration, file_size,
heat, cache_status, created_at)
-- file 存「文件名」不是绝对路径(可移植)
-- cover 存 web 路径如 /covers/xxx.jpg
streams(id PK, name, enabled, current_pos, media_seq, created_at)
-- 默认流 id='main',不可删除
stream_playlist(id PK, stream_id, song_id, position)
-- 每路流独立播放列表,外键 CASCADE
settings(key PK, value)
sessions(token PK, created_at, expires_at)
关键:切片缓存 hls_cache/{md5(文件名)}/ 与「流」无关,多路共享,省空间。
4. API 清单
除 /api/auth/*、/hls、/hls_cache、静态资源、/login 外,全部需登录。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/auth/login |
{password} → 设 cookie |
| POST | /api/auth/logout |
登出 |
| GET | /api/auth/me |
登录状态 |
| GET | /api/resources |
资源列表(含 cacheSize/cover) |
| GET | /api/music/search?keyword= |
网易云搜索 |
| POST | /api/music/detail |
{id} |
| POST | /api/music/download |
{id,name,artist,albumImage} |
| POST | /api/music/import |
multipart:audio + title + artist |
| POST | /api/music/delete |
{file} 级联删 |
| POST | /api/music/delete-batch |
{files:[]} |
| POST | /api/music/enrich |
{file, skipMatch?} 重匹配元数据 |
| GET | /api/streams |
流列表 |
| POST | /api/streams |
{id,name} 创建 |
| DELETE | /api/streams/:id |
删除(main 不可删) |
| POST | /api/streams/:id/start|stop |
启停 |
| GET | /api/streams/:id/playlist |
播放列表(含 entryId/isCurrent) |
| POST | /api/streams/:id/playlist/add |
{file} |
| POST | /api/streams/:id/playlist/remove |
{entryId} |
| POST | /api/streams/:id/playlist/reorder |
{order:[entryId...]} |
| POST | /api/streams/:id/playlist/clear|next|prev|jump |
|
| GET | /api/system/cache |
缓存占用 |
| POST | /api/system/cache/cleanup |
手动清理 |
| GET | /api/system/logs?tail= |
日志 |
| GET | /api/events |
SSE(需 cookie 鉴权) |
统一返回:{ok:true, ...} 或 {ok:false, error:"..."}。
5. 直播流地址
- 默认:
/hls/main/index.m3u8 - 其它:
/hls/{streamId}/index.m3u8 - 切片:
/hls_cache/{hash}/indexN.ts
6. 关键设计决策(勿轻易推翻)
- SQLite 用 better-sqlite3 + WAL:单进程应用,同步 API 最省事;WAL 解决读写互斥。用户曾担心并发,已解释并落地。
- 不迁移 v1 的 JSON 数据:用户明确说旧数据不要。
- 鉴权选"中档":整站 Cookie 会话登录,面板+API 锁,HLS 流不锁(播放器难带 cookie)。
- 封面/资源全本地化:
public/covers/存封面,public/vendor/存前端库;系统字体替代 Google Fonts。 - songs.file 存文件名:不是绝对路径,迁移目录不失效。
- 导入先落 tmp 再 rename:避免 chokidar 扫到半截文件。
7. 测试方法
服务起来后(svc005 / 或新起)跑:
cd /d F:\music_online\demo
set PYTHONIOENCODING=utf-8
python test_api.py # 16 项:鉴权/资源/流/缓存/日志
python test_e2e.py # 17 项:静态资源/登录/首页/HTML元素
python test_write.py # 5 项:导入 + 删除(用 tmp 副本,不动原曲)
python test_enrich.py # 给 5 首补封面(已跑过,5/5 成功)
上次全绿:38/38。但注意:这些只测了 HTTP 层,浏览器端 JS 交互没自动化测过。
8. 已完成清单
- SQLite 替换 JSON(5 表 + WAL)
- 级联删除
/api/music/delete+ 批量 - 多路流 LiveStream 类 + 每路独立播放列表
- Cookie 会话鉴权 + 登录页 + 鉴权绕过漏洞修复
- 本地上传导入(multer)+ 元数据自动匹配
- 封面本地化 + vendor 本地化 + 系统字体
- 日志系统(分级/落盘/面板可查)
- API 统一返回结构
- 优雅退出补 FFmpeg 子进程 kill
- 模块化拆分(1233 行单文件 → src/ 12 模块)
- 前端 app.js 重写(多路流/鉴权/导入/删除)
9. ⚠️ 待排查 / 已知问题(重点!)
9.1 用户反馈"功能还有问题" —— 具体未明,需先问清楚
下一会话第一件事:问用户到底哪个功能出问题(现象、复现步骤、控制台报错)。 可能的方向见下。
9.2 前端多路流管理 UI 没做
- 后端能创建/删除流,但前端只有一个切换下拉框(
#streamSelect),没有"新建流/删除流"的界面。 - 若用户说"多路流用不了",大概率指这个。
9.3 前端 JS 交互未实测
public/js/app.js是重写的,只做了语法检查 + HTTP 层测试。- 未验证:SSE 实时刷新、拖拽排序 reorder、mini player、页面切换、导入进度提示等浏览器行为。
- 建议:手动开浏览器点一遍,看 Console 有无报错。
9.4 潜在 Bug 点(未确认)
livestream.js的enforceCacheSizeLimit依赖streamsMap,若在loadStreams前被调用可能拿到空集。scanner.js里const parsed = meta.sanitizeFileName;是死代码(无用变量),无害但可清理。metadata.js的enrichSong每次启动会对缺元数据的歌重新请求网易云,网络慢会拖启动。- 多路流同时播放不同歌时,FFmpeg 预生成队列(并发默认 3)可能跟不上,需观察。
app.js里esc()用了String.fromCharCode(38)拼接,是为了绕过工具层的 XML 实体转义,不要"优化"回字面量&,否则写文件时会被工具吃掉。
9.5 前端 HTML 元素 ID 对照(改前端时用)
- 头部:
cacheInfostreamSelectliveLinkbtnLogout - 搜索页:
musicIdbtnSearchparseHintparsedIdmusicPreviewmusicCovermusicNamemusicArtistmusicDetailsbtnDownloadsearchResultssearchTips - 导入卡片:
importCardimportFileimportTitleimportArtistbtnImportimportProgress - 资源页:
resourceCountresourceSearchresourceList - 列表页:
playlistCountbtnClearplaylistList - mini player:
miniPlayermpProgressmpBodympCovermpNamempArtistmpTimempPlaympExpandmusicPlayer - 导航:
.nav-tab[data-page],页面pageSearch/pageResources/pagePlaylist/pageAbout
10. 工具层踩坑记录(写代码时注意)
- XML 实体转义会吃掉
&":写 JS/CSS 含这些字面量时,用String.fromCharCode(38)或字符码构造,别直接写。 - replace_lines 多段锚点行号易偏:先
file_read/ python 打印实际行号再改;改错了整批不生效或误删。 - smart_patch 行数必须精确,且锚点行号要对;容易失败,简单改动优先 replace_lines。
- 后台服务用 action background="true"(不要
start /b,会粘连卡死)。 - service_stop 会连子进程一起杀(node 的 ffmpeg 子进程会被带走)。
- Windows 无
head/tail,用findstr或 python。 - 改完源码要重启 node(磁盘 .py/.js 改了,运行中的进程不生效)。
- Python 跑 Windows 输出:
chcp 65001 && set PYTHONIOENCODING=utf-8。 node -X utf8是错的(那是 Python 的选项),node 直接node即可。
11. 当前运行态
- 后台服务:
svc005(PID 15308 起),日志service_log svc005 - DB:
data.db(5 首歌曲,封面已本地化) - 缓存:
hls_cache/5 个目录,约 28MB - Git 工作区:干净(最新
a4745db)
12. 下一步建议(按优先级)
- 问清用户的"问题"具体是什么 ← 最优先
- 补前端"新建/删除流"UI(若需求确认)
- 浏览器手动过一遍所有交互,修 Console 报错
- 清理死代码(§9.4)
- 正式化:设置固定
PANEL_PASSWORD,写 systemd/服务化启动 - 补多路流并发压力测试