# 对齐弹弹play能力(核心链路 + 协议/数据格式)— 设计规格 - **日期**:2026-09-26 - **状态**:设计已获用户批准,待规格审查 → writing-plans - **仓库**:本产品仓(业务进入现有分层 + 新增 `@app/danmaku`) - **前置**:`2026-09-25-dandanplay-web-design.md`(Web 版弹弹play 主体)已实现主链路 ## 1. 目标与范围 在已有「挂载 → 找片 → 播放 → 弹幕 → 进度」之上,把弹幕能力与弹弹play **开放弹幕网络**在体验与协议上对齐。 **In scope** 1. **协议/数据兼容** - 开放 API:`/api/v2/match`、`/api/v2/comment/{episodeId}`(含 `withRelated`)、AppId 签名 - 数据格式:文件识别 hash(前 16MB MD5)、B 站/弹弹play 兼容弹幕 XML、匹配元数据 2. **核心链路深对齐** - 候选集匹配与手选(不盲取第一条) - 弹幕持久化与同目录自动关联(修 P1-3) - 关联弹幕与渲染体验(`withRelated`、prefs 服务端同步) - 弹幕过滤与屏蔽(关键词、类型) **Out of scope(YAGNI)** - 远程访问协议(弹弹play 连帆幕) - protobuf 弹幕、多弹幕站聚合 - BT/RSS、AI 字幕、投屏、Emby/Jellyfin 直连(见产品文档「还没有」) **验收口径**:真实链路可跑通(文件 → 匹配候选 → 手选 → 拉弹幕 → 过滤 → 持久化,刷新/多设备可恢复)。无凭证时降级路径也要可用。 **实现路径(用户选定)**:**方案 B — 独立 `@app/danmaku` 协议包**(非垂直增强、非先契约后功能)。 ## 2. 架构与包边界 新增纯协议包 `@app/danmaku`:无 DB、无 tRPC;HTTP 仅用全局 `fetch`。 | 模块 | 职责 | |------|------| | `signature` | `base64(sha256(AppId + Timestamp + Path + AppSecret))` | | `hash` | 文件头 16MB MD5(流式,禁止整文件缓冲) | | `match` | `POST /api/v2/match` 入参/出参、`hashAndFileName` / `fileNameOnly`、候选排序纯函数 | | `comment` | `GET /api/v2/comment/{episodeId}`、`withRelated`、comments 结构化 | | `xml` | B 站/弹弹play 兼容 XML 解析与序列化(`p` 字段双向) | | `types` | `EpisodeId`、`MatchCandidate`、`DanmakuComment` 等纯类型 | | `filter` | `filterComments`(关键词、类型、防重叠可选) | 依赖方向(只新增向下边,禁止循环): ``` web ──► trpc ──► dao ──► db ──► models │ │ │ ├──► types │ └──► danmaku(协议包) └──► danmaku(XML 解析复用) ``` - **trpc** `danmaku.service`:鉴权、落库、缓存、绑定编排 + 调 `@app/danmaku` - **web**:`danmaku-render.ts` 格式解析改为引用 `@app/danmaku` 的 `xml`(单一解析实现) - **包位置**:`packages/danmaku`,构建方式对齐 `@app/types`(vite + tsc dts) ## 3. 数据模型 Schema 源仍为 `@app/models`;查询进 `@app/dao`。 ### `media_items` 增列 | 字段 | 说明 | |------|------| | `dandanEpisodeId` | 开放网络 episodeId;空 = 未绑定 | | `danmakuMatchSource` | `auto` \| `manual` \| `none` | | `danmakuMatchedAt` | 上次匹配时间 | ### `danmaku_prefs`(用户级,新表) `userId · enabled · opacity · density · blockKeywords(JSON) · blockTypes(JSON) · updatedAt` 唯一 `(userId)`。blockTypes:`scroll` / `top` / `bottom`。 ### `danmaku_docs`(正文持久化,新表,修 P1-3) `id · userId · mediaItemId · source: open-network|local-xml|related · xml · byteSize · createdAt` 唯一 `(userId, mediaItemId, source)`(同源仅留最新)。`userId` 级隔离。 ### `danmaku_cache`(保留,全局共享) 未绑定时 `matchKey = sha256(path:size)`;**绑定后**缓存键改为 `episodeId`(集身份比文件身份稳定)。 ### Zod(`@app/types`) - `danmakuMatchSelectSchema`(mediaItemId + episodeId) - `danmakuImportSchema`(替代仅 `byteSize` 的 `importMeta`,可含 fileName) - `danmakuPrefsSchema`(含 blockKeywords / blockTypes) - 输出:`MatchCandidate[]`、`DanmakuFetchOutput`(source 扩展 `local-xml` 等) ## 4. 协议/数据格式对齐 | 对齐点 | 规范 | |--------|------| | 文件识别 | 前 16MB MD5 + 文件名;有 hash 用 `hashAndFileName`,否则 `fileNameOnly` | | 匹配 | `POST /api/v2/match` → `matches[]`;按置信/用户选择写入 `dandanEpisodeId` | | 弹幕 | `GET /api/v2/comment/{id}?withRelated=true` | | 鉴权 | `X-AppId` / `X-Timestamp` / `X-Signature`(现有算法保留 + 单测向量) | | XML | `text`;round-trip 测试 | | 降级 | 无凭证/失败 → 本地 XML 仍可用 | 环境变量沿用:`OPEN_DANMAKU_APP_ID` / `OPEN_DANMAKU_APP_SECRET` / `OPEN_DANMAKU_API_BASE`。 ## 5. 页面与交互 路由不变;只增强 `/watch`、`/danmaku` 等组件。 ### 候选集匹配与手选 1. 打开 `/watch`:无 `dandanEpisodeId` → 自动 match → 高置信绑定(`source=auto`)→ 拉弹幕 2. 未匹配/低置信 → 「重新匹配」→ 候选列表(标题、集数、类型、置信)→ 点选绑定(`source=manual`)→ 立即拉弹幕 3. 已绑定可「更换匹配」;弹幕集绑定与 Bangumi 绑定**相互独立** ### 弹幕持久化与自动关联 - 导入 XML → 服务端 `danmaku_docs(source=local-xml)`(≤20MB)→ 刷新/换端可恢复 - 播放优先级:**本地已导入 > 开放网络** - 浏览/扫描发现同目录 `同名.xml` → 登记 `local-xml`(正文按需从 WebDAV 拉取)→ 播放页提示「发现同名弹幕」 ### 关联弹幕与渲染体验 - `withRelated` 并入;过滤面板可显示本集/关联计数 - 继续用 artplayer-plugin-danmuku;`danmaku_prefs` 服务端同步(开关/透明度/密度),localStorage 仅作无网络回退 - 密度 `thinDanmaku` 仍在客户端 ### 弹幕过滤与屏蔽 - 播放页过滤面板:关键词、类型(滚动/顶部/底部)、防重叠(插件已有) - 设置页:默认值 + 屏蔽词编辑 → `danmaku_prefs` - 过滤在 XML → comments 之后、喂播放器之前执行(`filterComments` 纯函数) ## 6. 错误处理 | 场景 | 策略 | |------|------| | 无开放网络凭证 | 明确提示「未配置弹幕网络」;本地 XML 全功能可用 | | match 401/签名失败 | Toast 鉴权失败;不重试风暴 | | match 无候选/低置信 | 进手选候选面板,不静默 | | comment 失败 | 保留绑定;可重试;本地 XML 优先 | | XML 解析失败 | 导入报错拒绝;自动关联静默跳过 | | prefs 失败 | 回退 localStorage 会话级;不阻断播放 | | 权限 | docs / prefs / 绑定均强制 `userId` 隔离 | ## 7. 测试与验证 **`@app/danmaku` 单测**:签名向量、16MB MD5、XML round-trip、`filterComments`、候选排序;match/comment 用 fixture 锁字段名。 **service 测**:绑定持久化、导入落库、prefs upsert、缓存键(path:size → episodeId)切换。 **真实链路清单(验收)** 1. 配置凭证 → 未绑定视频自动匹配 → 弹幕上屏 2. 低置信 → 手选 → 绑定刷新仍有效 3. 导入 XML → 刷新/换端登录仍在 4. 同目录同名 XML 自动关联 5. 关联弹幕计入;屏蔽词/类型即时生效且持久 6. 断网/无凭证降级:可播、本地 XML 可用 **命令**:`yarn typecheck` · `yarn lint` · `yarn test` · `yarn build:web` ## 8. 风险与开放问题 1. **凭证**:当前 `.env` 的 `OPEN_DANMAKU_APP_ID` 为空。真实链路需提供 AppId/AppSecret,或先用 `OPEN_DANMAKU_API_BASE` 指向 mock 跑通再切官方。 2. **开放 API 字段**:实现时以 `doc.dandanplay.com/open` 实测为准;fixture 与适配器单点收口在 `@app/danmaku`。 3. **同目录 XML 自动关联**:避免扫描时全量拉 WebDAV 正文;按需拉取 + 缓存。 4. **协议包勿膨胀**:若未来要多端复用再抽;本规格不做远程访问协议。 ## 9. 批准记录 | 节 | 结果 | |----|------| | §1 架构与包边界 | OK(方案 B) | | §2 数据模型 + 协议/数据格式 | OK | | §3 页面与交互(四块) | OK | | §4 错误处理 + 测试验证 | OK | | 验收口径 | 真实链路可跑通 | | 范围 | 协议/数据兼容 + 核心四块全选 |