app-template/docs/superpowers/specs/2026-09-25-dandanplay-web-d...

220 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Web 版弹弹play(媒体中心)— 设计规格
- **日期**:2026-09-25
- **状态**:已获设计批准,待实现计划(writing-plans)
- **仓库**:`app-template`(按**产品仓**维护,业务进入现有分层)
## 1. 背景与调研结论
弹弹play 官方定位是**本地视频 + 在线弹幕**的全功能播放器(非音乐播放器),能力包括智能识别、媒体库、AI 字幕、远程访问(浏览器可连 PC 内嵌 API)、媒体库可挂 SMB/WebDAV/Emby 等。
本项目要做的是:**独立 Web 应用**(方案 A),提供类似的「挂载源 → 浏览 → 播放 + 弹幕 → 媒体库/进度」体验,**不依赖**本机运行的 PC 弹弹play。
| 能力 | 决策 |
|------|------|
| 范围 | A — 独立完整 Web 应用 |
| 播放器 | ArtPlayer |
| 弹幕 | 对接弹弹play **开放弹幕网络**(服务端中继)+ 本地 XML 导入兜底 |
| 存储源 | **仅通用 WebDAV**(OpenList 使用其 WebDAV 端点;不做 OpenList 原生 API) |
| 后端 | 挂载代理 + 弹幕中继 + 媒体库/进度 **全做** |
| 刮削 | **Bangumi**:扫描自动匹配 + 手动搜索改绑(C) |
| UI | 全页面:播放 / 文件浏览 / 媒体库 / 挂载管理 / 弹幕设置 |
| 账号 | **多用户**:注册登录;每人独立库与进度 |
| 架构方案 | **A — 紧凑垂直切片 `media`**(复制 `user` 切片模式) |
| 视觉 | **shadcn/ui Dashboard** 风格(侧栏 + 卡片 + 中性灰阶),不以模板深空工业 HUD 为产品主视觉 |
| 落点 | **B — 产品化本仓**:业务进入 `app/web`、`packages/*` |
### 调研来源(Browser Use 实读)
- https://www.dandanplay.com/ — 产品能力与远程访问/WebDAV 叙述
- https://doc.dandanplay.com/open/library-api.html — PC 远程访问 REST(本项目**不**对接;仅作产品对照)
- https://doc.dandanplay.com/function/media-library.html — 媒体库信息架构参考
- https://github.com/OpenListTeam/OpenList — AList fork;本项目只用 **WebDAV** 接入方式
> 说明:会话内 IAB 不可用,调研经 `cua_repl` + `managed-chromium` 完成 SERP 与正文阅读。
## 2. 系统架构(方案 A)
```
浏览器 @app/web(React islands,shadcn dashboard)
├─ 登录 / 媒体库 / 文件浏览 / 播放(ArtPlayer+弹幕) / 挂载 / 弹幕设置
└─ 仅经 tRPC 与 /api/stream(Range)访问
│
▼
服务层 trpc + service + dao(@app/trpc · dao)
├─ mount.* WebDAV 列目录、凭据加密、测试连接
├─ stream Range 拉流代理
├─ danmaku.* 开放弹幕网络中继 + 缓存
├─ scrape.* Bangumi 搜索/绑定;扫描内自动刮削
├─ library.* 扫描入库、列表、海报
└─ playback.* 进度节流写入
│
┌─────┴──────┐
▼ ▼
Drizzle/Turso 外部:WebDAV · 弹弹play 开放弹幕网络 · Bangumi API
```
**分层约束(沿用仓库 AGENTS)**
- Schema 源:`@app/models`;查询:`@app/dao`;连接:`@app/db`
- 业务逻辑:`@app/trpc` service(调 dao);入参 Zod:`@app/types`
- UI:`@app/ui` + design tokens;页面在 `@app/web`
- **产品化本仓**:新增业务实体照 `user` 垂直切片扩展(本产品期间业务代码进入现有分层,视为对「模板不掺业务」的产品化豁免)
### 播放主路径
1. 登录 → 2. 配置 WebDAV 挂载 → 3. 浏览目录或扫描入库
4. 打开视频 → stream 代理 → ArtPlayer
5. 识别/拉弹幕(中继 + `danmaku_cache`)→ 弹幕层
6. 节流上报 `playback_progress`
### 首版不做
OpenList 原生 API · 本机弹弹play 远程 API 对接 · BT/RSS · 多刮削源 · NFO 优先 · 公网多租户硬化(SSO/套餐限流)
## 3. 数据模型
### users(已有 auth)
沿用模板用户表与会话,不重复造账号。
### mounts
| 字段 | 说明 |
|------|------|
| id | PK |
| userId | → users,级联隔离 |
| name | 展示名 |
| type | 固定 `webdav`(预留枚举) |
| baseUrl | WebDAV 根 URL(含 OpenList WebDAV 地址) |
| username | 可选 |
| secretEnc | **服务端加密**密文;API 永不回传明文 |
| rootPath | 根路径前缀 |
| enabled | 启用 |
### media_items
| 字段 | 说明 |
|------|------|
| id | PK |
| userId | 所属用户 |
| mountId | 可空(扫描所得) |
| path | 挂载内路径;`userId+path` 唯一 |
| rawName | 原始文件名 |
| title | 展示标题(可被刮削覆盖) |
| size / mime | 文件元数据 |
| bangumiId? | 刮削绑定 |
| epNumber? | 集数启发式 |
| scrapeStatus | `pending` \| `ok` \| `failed` \| `unmatched` |
| scrapedAt? | 最近刮削时间 |
| posterUrl? | 海报(Bangumi CDN 或占位) |
| matchedHash? | 弹幕匹配指纹(预留) |
| scannedAt | 入库时间 |
### playback_progress
`id · userId · mediaItemId · positionMs · durationMs · updatedAt`
唯一:`(userId, mediaItemId)`。
### danmaku_cache(全局,不绑 user)
`id · matchKey · payload/xml · source · expiresAt`
### bangumi_cache(可选,限流)
`bangumiId · payload JSON · expiresAt`
### Zod(@app/types)
- `mountCreateSchema` / `mountUpdateSchema`
- `scanMountSchema` · `playbackReportSchema`
- `danmakuQuerySchema` · `danmakuImportMetaSchema`
- `scrapeSearchSchema` · `scrapeBindSchema`
## 4. 页面与交互
### 路由(shadcn dashboard 侧栏)
| 路径 | 页面 |
|------|------|
| `/login` `/register` | 认证(登录前仅此可见) |
| `/library` | 媒体库(默认登录后):海报卡、筛选全部/未匹配、继续播放、扫描入口 |
| `/browse` | 文件浏览:选挂载 → 面包屑目录 → 行内播放/加入库 |
| `/watch` | 播放:ArtPlayer + 弹幕层 + 同目录剧集(可选)+ 弹幕开关/导入 XML |
| `/mounts` | 挂载管理:CRUD、测试连接、按挂载扫描 |
| `/danmaku` | 弹幕设置:默认开关/透明度/密度;导入说明 |
### 主流程
1. 注册/登录
2. 挂载管理添加 WebDAV → 测试 → 保存
3. 文件浏览点选播放,或「扫描」→ 刮削管线 → 媒体库
4. `/watch`:stream → 弹幕中继 → 进度保存
5. 媒体库「继续播放」带 progress 打开 `/watch`
6. 未匹配项手动 Bangumi 搜索绑定;详情可重新刮削
### 刮削管线(扫描)
1. 列目录,视频入 `media_items`
2. 文件名启发式 → Bangumi 搜索
3. 命中 → 写元数据/海报/`bangumiId`
4. 低置信 → `unmatched`,UI 手动搜绑
5. 重复扫描按指纹/缓存,避免打爆 Bangumi;**刮削失败不阻断入库**
### 视觉
- shadcn 风格组件语义:侧栏 nav、卡片、primary 按钮、Sheet 挂载表单、Dialog 确认、Toast
- 可基于 `@app/ui` 对齐 shadcn API,产品主题为中性暗色 dashboard(非工业信号黄主视觉)
- 动效克制,尊重 `prefers-reduced-motion`
## 5. 错误处理
| 场景 | 策略 |
|------|------|
| WebDAV 401/超时 | 明确文案;浏览 skeleton + 重试;引导改凭据 |
| stream 失败 | 播放器错误 + Toast;不暴露栈 |
| 弹幕网络失败 | 降级无弹幕,可播;可仅本地 XML |
| Bangumi 失败 | `scrapeStatus=failed`,可重试;不回滚文件 |
| 进度失败 | 静默重试,不打断播放 |
| 权限 | 所有 library/mount/progress 查询强制 `userId` |
## 6. 测试与验证
- types:Zod 单测
- dao/service:进度 upsert、刮削绑定、路径规范化(mock HTTP)
- tRPC:跨用户越权
- web:主路径冒烟(登录→列表→播放页);无 CI 浏览器则手动清单
- 命令:`yarn typecheck` · `yarn lint` · `yarn build:web`
- 不做重型视觉回归
## 7. 实现范围(一个计划可覆盖)
**In scope**
- media 垂直切片全栈(mount/stream/danmaku/scrape/library/playback)
- 六个 UI 面(含登录沿用)
- ArtPlayer 集成与弹幕层(开放网络 + XML 导入)
- Bangumi 自动/手动刮削
- shadcn dashboard 信息架构与主题
**Out of scope(本规格)**
见 §2「首版不做」及 §1 调研中未选路径。
## 8. 风险与开放问题(实现前可再核)
1. **弹弹play 开放弹幕网络** 端点、鉴权与条款:实现时以官方文档/实测为准,service 单点适配
2. **Bangumi API** 限流与字段:实现时核对官方 API 文档
3. **WebDAV 流**:Range、HTTPS 混合内容、大文件内存——stream 用流式管道,禁止整文件缓冲
4. 艺术墙纸/版权:用户自备片源与挂载凭据
## 9. 批准记录
| 节 | 结果 |
|----|------|
| 架构 + shadcn dashboard | OK |
| 数据模型 | OK |
| 页面与交互 + Bangumi 刮削 C | OK |
| 错误/测试/边界 | OK |
| 方案选型 | A |