diff --git a/docs/todo/dressup-dance-room-mvcp.md b/docs/todo/dressup-dance-room-mvcp.md new file mode 100644 index 0000000..e67b34a --- /dev/null +++ b/docs/todo/dressup-dance-room-mvcp.md @@ -0,0 +1,419 @@ +# 换装跳舞直播间 — 技术设计文档(Model / View / Control / Process) + +依据:产品需求 `docs/todo/dressup-dance-room.md`(下称"需求文档",本文各处 +引用其章节号);系统架构 `docs/structure/architecture.md`(下称"架构文档")。 +本文档把已确认的产品决策落到:数据库表结构(M)、前后端界面清单(V)、 +按钮/交互动作(C)、各类用户的操作流程(P),供开发排期和实现参考。 + +约定:字段名延续现有 `library.json`/`live-state.json` 的英文小驼峰风格; +表结构在第一期仍以 JSON 文件承载,第三期(用户账号)后迁移 SQLite(见 +架构文档 1.4 节)——本文按"关系表"描述,两种落地方式都能直接映射。 + +--- + +## M — Model:数据结构设计 + +### M.1 `users` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string (uuid) | 主键 | +| `email` | string | 邮箱,唯一(7.1 节,第一期登录方式) | +| `passwordHash` | string | bcrypt/argon2,邮箱登录用 | +| `oauthProviders` | array | 预留 `["google","apple","github"]`,第一期为空数组 | +| `nickname` | string | 展示昵称,迁移自现有 cookie `viewer_nickname` | +| `legacyUid` | string \| null | 绑定的旧 `viewer_uid`(cookie),用于历史投票/点播记录迁移,见 P.4 | +| `ageConfirmed` | boolean | "我已满18岁" 勾选记录(5.1 节) | +| `ageConfirmedAt` | number (ts) | | +| `creditBalance` | number | 当前积分余额(冗余字段,权威值仍是 `credit_ledger` 流水汇总,见 M.2) | +| `createdAt` | number (ts) | | + +### M.2 `credit_ledger`(积分流水,7.2 节) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `userId` | string | 外键 → `users.id` | +| `delta` | number | 正数=充值/退款,负数=消费 | +| `reason` | enum | `topup` \| `spend_garment` \| `spend_character` \| `refund_stage1_fail` \| `refund_stage2_fail` \| `refund_moderation_reject` \| `admin_adjust` | +| `relatedAssetId` | string \| null | 关联的 `garments.id` 或 `characters.id`(消费/退款时) | +| `balanceAfter` | number | 变动后余额,写入时计算好,避免每次汇总流水 | +| `paymentRef` | string \| null | 充值时的外部支付流水号(Stripe/支付宝等,7.3 节) | +| `createdAt` | number (ts) | | + +### M.3 `characters`(人物库) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `name` | string | | +| `imageUrl` | string | 人物参考图 | +| `tags` | array | | +| `source` | enum | `admin_seed` \| `user_paid`(6.1 节) | +| `ownerUserId` | string \| null | `user_paid` 来源必填 | +| `isDefault` | boolean | 是否为直播默认人物;第一期**全库最多一条为 true**(6.1 节已确认,不做多人物轮换) | +| `moderationStatus` | enum | `auto_passed` \| `blacklisted` \| `manually_removed`(5 节:自动黑名单过滤,无强制人工预审) | +| `totalSegments` | number \| null | 付费购买的 10 秒片段总配额,`admin_seed`/默认人物为 null(不限量,只受 4.6 节去重缓存影响的是复用次数,不是配额) | +| `remainingSegments` | number \| null | 剩余可播片段数,每播放一段 -1(7.2 节;架构文档 2.7 节) | +| `status` | enum | `active` \| `expired`(配额耗尽后置 `expired`,记录不删除,7.4 节/架构文档 2.7 节) | +| `createdAt` | number (ts) | | + +### M.4 `garments`(衣服库) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `name` | string | | +| `category` | string | 分类标签(古风/泳装/校服…) | +| `thumbnailUrl` | string | | +| `imageUrl` | string | 原图 | +| `source` | enum | `admin_seed` \| `user_paid`(6.2 节) | +| `ownerUserId` | string \| null | | +| `moderationStatus` | enum | `pending` \| `approved` \| `rejected`(模式二走轻量图片审核,审核通过默认沉淀进公共库,3 节模式二第 3 点) | +| `isActive` | boolean | 上下架开关 | +| `createdAt` | number (ts) | | + +### M.5 `dance_refs`(参考跳舞视频/音乐库) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `name` | string | | +| `videoUrl` | string | | +| `styleTags` | array | | +| `durationSec` | number | | +| `copyrightSource` | enum | `test_placeholder` \| `self_made` \| `licensed`(第一期全部 `test_placeholder`,6.3/8 节) | +| `isActive` | boolean | | +| `createdAt` | number (ts) | | + +### M.6 `composed_images`(Stage1 换装图产物) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `characterId` | string | 外键 | +| `garmentId` | string | 外键 | +| `imageUrl` | string | 合成结果 | +| `model` | string | 实际调用的 fal.ai 模型标识(4.1/8 节,待选型) | +| `status` | enum | `generating` \| `done` \| `failed` | +| `createdAt` | number (ts) | | + +**唯一索引 `(characterId, garmentId)`**——这是 4.6 节去重缓存的落地位置: +提交前先按这个组合查是否已有 `status: done` 的记录,命中直接复用,不 +新建。 + +### M.7 `generated_videos`(Stage2 产物,`library.json` 风格 clip 的超集) + +在现有 `library.json` clip 字段基础上(见 `docs/done` 第 3 节)新增: + +| 新增字段 | 类型 | 说明 | +|---|---|---| +| `characterId` | string | | +| `garmentId` | string | | +| `composedImageId` | string | 外键 → M.6,点开能看 Stage1 合成图(6.4 节) | +| `refDanceVideoId` | string | 外键 → M.5 | +| `channel` | enum | `live` \| `dance`(区分是 `live.html` 还是 `dance.html` 的产物,两条独立时刻表共用同一张表时靠这个字段区分,见架构文档 2.1 节) | +| `backend` | enum | `h3_console` \| `fal`(4.3 节双算力策略,记录这条具体是哪条算力路径生成的,便于追溯成本) | +| `priority` | enum | `normal` \| `paid_boost`(`paid_boost` = 付费新人物内容,排播时走 2.6 节"插队"通道,不参与投票) | +| `requestedByUserId` | string \| null | 付费请求必填,免费匿名请求为 null(用 `legacyUid` 兜底) | +| `width`/`height` | number | `dance` 频道默认 480×832,`live` 频道现状默认已同步改为 480×832(见文末"勘误"一节) | + +### M.8 直播时刻表状态(非持久化数据库表,`live-state.json` 风格的内存+落盘结构,`dance.html` 独立一份) + +```jsonc +{ + "channel": "dance", + "schedule": [{ "videoId": "...", "startAt": 0, "endAt": 0, "requestedBy": null, "priority": "normal" }], + "priorityInbox": [ /* paid_boost 产物就绪后先进这里,ensureSchedule 优先消费,见架构文档 2.6 节 */ ], + "freeQueue": [ /* 免费模式一提交,走投票排序,结构同 v1 promptQueue */ ] +} +``` + +### M.9 `moderation_log` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | string | 主键 | +| `assetType` | enum | `garment_upload` \| `character_upload` | +| `assetId` | string | | +| `verdict` | enum | `auto_pass` \| `auto_reject_blacklist` \| `admin_takedown`(5 节:自动过 + 事后人工兜底) | +| `reason` | string \| null | | +| `reviewedBy` | string | `system` 或管理员 `userId` | +| `createdAt` | number (ts) | | + +### M.10 ER 关系图 + +```mermaid +erDiagram + users ||--o{ credit_ledger : "流水" + users ||--o{ characters : "user_paid 拥有者" + users ||--o{ garments : "user_paid 拥有者" + users ||--o{ generated_videos : "requestedBy" + characters ||--o{ composed_images : "Stage1 输入" + garments ||--o{ composed_images : "Stage1 输入" + composed_images ||--o{ generated_videos : "Stage2 输入" + dance_refs ||--o{ generated_videos : "Stage2 输入" + characters ||--o{ moderation_log : "审核记录" + garments ||--o{ moderation_log : "审核记录" +``` + +--- + +## V — View:前后端界面设计 + +### V.1 观众端页面 + +| 页面 | 路径(建议) | 说明 | +|---|---|---| +| 换装跳舞直播间 | `dance.html` | 主页面,见 V.1.1 | +| 登录/注册 | 弹窗(不单独开页,跟 v1 昵称弹窗交互一致,见架构文档/`docs/done` 2.5 节) | 邮箱输入 + 验证码/密码,首次触发付费操作时弹出 | +| 我的作品 | `my-works.html` 或 `dance.html?tab=mine`(建议做成 `dance.html` 内的一个面板而不是独立页面,减少一个页面壳) | 7.4 节 | +| 充值 | 弹窗,from 积分不足拦截 或 顶部"我的积分"入口 | 7.3 节,具体支付渠道待定 | + +#### V.1.1 `dance.html` 区域划分(复用 `live.html` 视觉语言,见需求文档 2 节) + +``` +┌─────────────────────────────────────────────┐ +│ 顶部条:Logo | 在线人数 | 我的积分 | 头像/登录 │ +├───────────┬───────────────────┬─────────────┤ +│ │ │ │ +│ 左侧栏 │ 播放器(9:16) │ 右侧栏 │ +│ 衣柜/传衣 │ + 当前播放信息 │ 在线互动展示 │ +│ /传人 tab │ (复用 live.html│ (点赞/排队 │ +│ │ 的 now-playing)│ 列表,无输 │ +│ │ │ 入框) │ +│ │ │ │ +├───────────┴───────────────────┴─────────────┤ +│ 移动端:底部 tab 切换(衣柜/传衣/传人/我的) │ +└─────────────────────────────────────────────┘ +``` + +- 播放器容器按 9:16 设计(4.4 节),不是 `live.html` 现有横向容器的简单 + 拉伸。 +- 左侧栏三个 tab:**衣柜**(默认)/ **传衣服** / **传人**,对应需求文档 + 3 节三种模式。 +- 首次进入弹年龄确认(5.1 节),非模态强制,一次性记忆(cookie/账号)。 + +### V.2 后台管理页面 + +在现有 `admin.html`(`docs/done` 2.8 节)基础上新增,交互风格保持一致 +(表格 + 行内编辑 + 新增表单,见需求文档 6 节开篇): + +| 页面 | 对应资源 | 需求文档章节 | +|---|---|---| +| `admin-characters.html` | M.3 | 6.1 | +| `admin-garments.html` | M.4 | 6.2 | +| `admin-dance-refs.html` | M.5 | 6.3 | +| `admin.html`(扩展现有表格新增列) | M.7 | 6.4 | +| `admin-playback.html` | M.8(dance 频道排播干预) | 6.5 | +| `admin-users.html` | M.1 + M.2 | 用户/积分管理,7 节 | + +后台导航建议在现有 `admin.html` 顶部加一排二级导航链到这些新页面, +不新起一套独立的后台框架。 + +### V.3 后端 API 清单 + +延续 `server.js` 手写路由风格(无框架),按资源分组: + +``` +观众端: +GET /api/dance/library?sort= # dance 频道浏览三 tab,等价 v1 /api/library +GET /api/dance/live/state?cid=&uid= # dance 频道播出状态,等价 v1 /api/live/state +GET /api/dance/wardrobe # 衣柜列表(garments,isActive=true) +POST /api/dance/pick # 模式一:{characterId?, garmentId} 免费提交 +POST /api/dance/upload-garment # 模式二:multipart 图片 + 走审核+扣积分 +POST /api/dance/upload-character # 模式三:multipart 图片 + 年龄确认+黑名单审核+积分档位(决定 remainingSegments) +POST /api/dance/queue/vote/:id # 免费队列投票,等价 v1 /api/queue/vote +GET /api/dance/my-works # 我的作品(需登录) +POST /api/auth/register # 邮箱注册 +POST /api/auth/login +POST /api/credit/topup # 发起充值(对接支付网关,7.3 节) +POST /api/credit/topup/webhook # 支付回调 + +后台: +CRUD /api/admin/characters[/:id] +CRUD /api/admin/garments[/:id] +CRUD /api/admin/dance-refs[/:id] +CRUD /api/admin/generated-videos[/:id] # 扩展现有 /api/admin/library +POST /api/admin/playback/insert # 插播/置顶 +POST /api/admin/playback/skip # 跳过当前 +POST /api/admin/playback/pause-fill # 暂停自动填充 +POST /api/admin/blacklist # 拉黑用户/资产 +GET /api/admin/moderation-log +GET /api/admin/users +POST /api/admin/credit/adjust # 人工调整积分(客服场景) + +内部(生成流水线编排,不对前端暴露): +generateComposedImage(characterId, garmentId) # Stage1,查缓存(M.6唯一索引)→未命中调fal.ai +generateDanceVideo(composedImageId, refDanceVideoId, backend) # Stage2,backend按4.3节路由 +``` + +--- + +## C — Control:界面按钮/交互动作设计 + +### C.1 `dance.html` + +| 按钮/交互 | 触发条件 | 动作 | 反馈 | +|---|---|---|---| +| 衣柜卡片点击 | 始终可点(免费,无需登录) | `POST /api/dance/pick {garmentId}` | 立即在"生成中"区出现一条;命中缓存(4.6节)秒级进"接下来播放" | +| "传衣服"上传按钮 | 需登录 | 若未登录先弹登录;选图 → 客户端基础校验(大小/格式)→ 上传 → 展示预计消耗积分 → 二次确认 | 提交后进图片审核队列;通过后自动排入生成,展示状态 | +| "传人"上传按钮 | 需登录 + 已完成年龄确认(5.1节) | 选图 → 选择/展示积分档位(决定 `remainingSegments`,7.2节)→ 可选搭配衣服(选衣柜或再传一张,不选则随机)→ 二次确认扣积分 | 提交后走黑名单自动审核(5节),通过直接生成,不阻塞用户等待 | +| 年龄确认弹窗"确认"按钮 | 首次进入 `dance.html` | 写 `ageConfirmed=true`(cookie 或账号,取决于是否已登录) | 弹窗关闭,露出主界面 | +| 点赞(❤️) | 浏览三 tab 内 | `POST /api/like/:id`(复用 v1) | 数字 +1 | +| 排队投票(▲) | 免费队列条目 | `POST /api/dance/queue/vote/:id`(复用 v1 投票逻辑,仅对 `priority: normal` 条目生效——`paid_boost` 条目不展示投票按钮,见需求文档 3 节"共同后置流程") | 高亮 + 票数更新 | +| "我的作品"入口 | 需登录 | 打开 V.1 的我的作品面板 | 列出该账号名下 `generated_videos`(含 `status: expired` 的人物产出) | +| "我的积分"/充值入口 | 始终可见(未登录显示"登录后查看") | 打开充值弹窗 | 对接支付网关(7.3节,渠道待定) | +| 静音/全屏 | 复用 `live.html` 现有 `muteBtn`/播放器交互 | 同 v1 | 同 v1 | + +### C.2 后台管理页面 + +| 按钮/交互 | 所在页面 | 动作 | +|---|---|---| +| 新增/编辑/删除 人物、衣服、参考视频 | 各自 admin 页 | 对应 CRUD API(V.3),风格照抄现有 `admin.html` 行内编辑 | +| "设为直播默认人物" 开关 | `admin-characters.html` | 置该条 `isDefault=true`,**同时把其他所有条目置 false**(第一期全局唯一,6.1节) | +| 审核通过/驳回(`pending`→`approved`/`rejected`) | `admin-garments.html`(`user_paid` 来源条目) | 更新 `moderationStatus`,写 `moderation_log` | +| 人工下架人物/内容 | `admin-characters.html`/`admin-generated-videos` | 5节兜底手段,写 `moderation_log(verdict: admin_takedown)` | +| 插播/置顶下一条 | `admin-playback.html` | `POST /api/admin/playback/insert`,写入 M.8 `priorityInbox` | +| 跳过当前 | `admin-playback.html` | `POST /api/admin/playback/skip` | +| 暂停/恢复自动填充 | `admin-playback.html` | `POST /api/admin/playback/pause-fill`,暂停期间 `ensureSchedule` 只消费 `priorityInbox`/`freeQueue`,不再随机填充素材库 | +| 拉黑用户/资产 | `admin-playback.html` 或 `admin-users.html` | 写黑名单表,后续该用户/资产提交直接拒绝,不进流水线 | +| 人工调整积分 | `admin-users.html` | `POST /api/admin/credit/adjust`,写 `credit_ledger(reason: admin_adjust)` | + +--- + +## P — Process:用户流程设计 + +### P.1 匿名免费观众:选衣柜(对应需求文档 3 节模式一) + +```mermaid +sequenceDiagram + participant V as 观众(未登录) + participant S as server.js + participant Cache as composed_images 缓存(M.6) + participant Fal as fal.ai + participant H3 as H3 Console + + V->>S: 首次进入 dance.html + S-->>V: 弹年龄确认(5.1节) + V->>S: 确认已满18岁 + V->>S: POST /api/dance/pick {garmentId} + S->>Cache: 查 (defaultCharacterId, garmentId) + alt 命中缓存 + S-->>V: 直接排入队列(秒级) + else 未命中 + S->>Fal: Stage1 换装合成 + Fal-->>S: composedImageUrl + S->>H3: Stage2(冷启动算力,4.3节) + H3-->>S: 视频完成 + S->>S: 写入 generated_videos + composed_images 缓存 + S-->>V: 排入队列 + end + Note over S: ensureSchedule 按 v1 投票逻辑排序(priority: normal) +``` + +### P.2 注册观众:付费传衣服(模式二) + +1. 点"传衣服" → 未登录则先走 P.4 登录/注册。 +2. 选图 → 前端展示"这将消耗 N 积分"(数值待定,8节)。 +3. 确认 → 后端校验余额 → 不足则拦截,引导充值(P.4 充值流程)。 +4. 余额充足 → 预扣积分(`credit_ledger` 写一条负数流水)→ 图片走轻量 + 内容审核。 +5. 审核不过 → 全额退还积分(写正数流水,`reason: refund_moderation_reject`) + → 前端提示。 +6. 审核通过 → `garments` 新建一条 `moderationStatus: approved`,默认 + `isActive: true`(沉淀进公共库)→ 走 Stage1/Stage2(可能命中已有 + "默认人物+这件新衣服"缓存吗?不会命中,因为这件衣服刚创建,Stage1 + 必然是新生成)→ 完成后排入队列,`priority: normal`(模式二不享受 + 插队特权,只有模式三新人物才有,见需求文档 3 节)。 +7. 生成失败(Stage1/Stage2)→ 退还积分,`garments` 该条标记不可用/删除 + (避免衣柜库里出现一件"存在但没有可用换装产物"的死资产)。 + +### P.3 注册观众:付费传人物(模式三,风险最高的流程) + +```mermaid +sequenceDiagram + participant V as 观众(已登录,已年龄确认) + participant S as server.js + participant Blk as 人脸黑名单模型 + participant Fal as fal.ai + participant Credit as credit_ledger + + V->>S: 选积分档位(决定 remainingSegments)+ 上传人物照 + (可选)选/传衣服 + S->>Credit: 预扣积分 + S->>Blk: 自动人脸识别过滤(5节,非强制人工预审) + alt 命中黑名单 + S->>Credit: 全额退还 + S-->>V: 拒绝,提示原因 + else 未命中,自动通过 + S->>S: characters 新建一条 source:user_paid, remainingSegments=购买档位对应值 + S->>Fal: Stage1 换装合成(若未指定衣服则随机挑一件衣柜库) + S->>Fal: Stage2(付费实时算力,4.3节,约4s级) + Fal-->>S: 视频完成 + S->>S: generated_videos 新建,priority: paid_boost + S-->>V: 排入 priorityInbox,插队播放(不参与投票) + end + Note over S: 每播完一段,remainingSegments -= 1 + Note over S: 配额耗尽 → characters.status = expired,从排播/复用池移除
但记录保留,用户可在"我的作品"回看,可再充值续播 +``` + +### P.4 登录/注册 + 身份迁移 + +1. 触发点:模式二/三的任一付费操作、或主动点"登录"。 +2. 邮箱注册/登录(7.1节)成功后,检查是否存在 `viewer_uid`/ + `viewer_nickname` cookie(v1 遗留身份)——若存在,把该 `uid` 写入 + 新账号的 `legacyUid` 字段,历史投票/点播记录(挂在旧 `uid` 上的) + 通过 `legacyUid` 关联查询,不做数据搬迁,只做关联(架构文档 2.7 + 节过渡方案)。 +3. 后续 `uid` 仍然写在 cookie 里(免费投票等匿名场景继续用它),但 + 付费操作一律要求 `userId`(登录态)。 + +### P.5 充值 + +1. 触发点:付费操作余额不足自动弹出,或主动点"我的积分"。 +2. 选充值档位 → 跳转/弹出支付网关(7.3节,渠道待定,标注 Stripe 对 + 国内支付方式支持有限的风险,见架构文档待验证表)。 +3. 支付成功回调 `POST /api/credit/topup/webhook` → 写 `credit_ledger + (reason: topup, delta: +N, paymentRef)` → 更新 `users.creditBalance`。 +4. 支付失败/取消 → 不产生流水,前端提示重试。 + +### P.6 运营/管理员:素材维护 + 事后审核 + 播放干预 + +- **日常巡查**(对接 5 节"事后人工兜底"):定期查看 `moderation_log` + 里 `verdict: auto_pass` 但被举报/人工发现有问题的人物内容 → 在 + `admin-characters.html` 手工下架 → 写 `admin_takedown` 记录。 +- **播放干预**:直播过程中运营通过 `admin-playback.html` 插播/跳过/ + 暂停自动填充(6.5节),不需要走观众端的正常提交流程。 +- **素材库维护**:定期给 `garments`/`dance_refs` 上下架,给 + `admin_seed` 人物/衣服打标签分类。 + +### P.7 系统自动化流程 + +1. **冷启动批量生成**(第一期,9节):运营在后台批量选定 + 人物×衣服组合,脚本/后台按钮触发批量 `generateComposedImage` + + `generateDanceVideo`(走内部 H3 Console,4.3节),铺满初始衣柜库/ + `composed_images` 缓存,不涉及观众端。 +2. **排播 tick**:`dance.html` 独立的 `ensureSchedule` 定时任务(同 v1 + 每 2s,架构文档 1.3/2.6节),优先消费 `priorityInbox`(付费插队), + 其次按投票消费 `freeQueue`,都为空时随机填充素材库(同 v1 逻辑, + 避开冷却期)。 +3. **人物到期检测**:每次消费完 `priorityInbox` 里某人物的一段后, + `remainingSegments -= 1`,归零则置 `characters.status = expired` + 并从后续排播候选中过滤掉(架构文档 2.7节)。 + +--- + +## 勘误:`live.html` 分辨率现状更新 + +写这份需求文档时(`docs/todo/dressup-dance-room.md` 4.4节)记录的是 +"`live.html` 现状横屏 832×480 不变,只有 `dance.html` 默认竖屏"。但 +`server.js` 的 `H3_GEN_PARAMS` 在另一次改动中已经被改成 +`{ width: 480, height: 832 }`(commit `1dc7084`,`live.html` 侧的 +说明是"未来生成的新片段走竖屏,存量片段保留原比例,播放器 +`object-fit: contain` 兼容混合比例")——也就是说 **`live.html` 现在 +新生成的内容其实也已经是竖屏了**,只是存量片段还是横屏混着播。 + +`dance.html` 侧的设计不受影响(本来就是独立的 `H3_GEN_PARAMS` 配置, +见架构文档 2.4节),但需求文档 2/4.4节"live.html 现状横屏不变"这句 +描述已经不准确,建议后续同步一次勘误(不在本次技术设计文档范围内, +先在此记录,避免依赖这份文档的人被误导)。 diff --git a/docs/todo/dressup-dance-room.md b/docs/todo/dressup-dance-room.md index 980ab1c..71079a5 100644 --- a/docs/todo/dressup-dance-room.md +++ b/docs/todo/dressup-dance-room.md @@ -26,8 +26,10 @@ - 两个页面各自维护独立的播出时刻表(各自的 `schedule`/`promptQueue`), 不是同一个直播间的两个 tab——它们是两条并行的产品线,共用后端的 素材库/生成流水线/账号体系等基础设施。 -- **`dance.html` 默认竖屏、移动端优先**(`live.html` 现状横屏不变, - 已上线跑着,不在本次改造范围内)——具体规格见第 4.4 节。 +- **`dance.html` 默认竖屏、移动端优先**——具体规格见第 4.4 节。 + (勘误:`live.html` 一侧此后也已独立改成竖屏默认,见 4.4 节末尾说明, + 两个频道各自的 `H3_GEN_PARAMS` 配置互不影响,只是当时判断的"横屏不变" + 前提已经过时。) ## 3. 观众体验流程(已确认:使用已有资产库免费,新增资产付费) @@ -124,6 +126,13 @@ Stage1(换装图合成)在两条路径下大概率都是 fal.ai(H3 Console `live.html` 现有横屏播放器的宽高比,否则会出现大黑边——这是 `dance.html` 相对 `live.html` 除了"没有文字输入框"之外的第二个视觉 差异点。 +- **勘误**:`server.js` 的 `H3_GEN_PARAMS`(`live.html` 使用的那份) + 之后被单独改成了 `480×832`(commit `1dc7084`),也就是说 `live.html` + 新生成的内容现在其实也是竖屏了,存量片段仍是原比例混播(播放器靠 + `object-fit: contain` 兼容)。这是 `live.html` 那条独立产品线自己的 + 改动,跟 `dance.html` 的配置互不影响,但上面"仅 `dance.html`"的表述 + 已不完全准确,记录于此,详见 `docs/todo/dressup-dance-room-mvcp.md` + 文末"勘误"一节。 ### 4.5 失败/超时处理