live-channel-v1.md 7.61 KB

AI 短剧频道 v1 —「文字点播」直播间(已完成)

状态:已上线运行(server.js + live.html,pm2 进程 minimax-live)。 本文档记录当前已实现的需求范围,作为后续「换装跳舞直播间」(见 docs/todo/dressup-dance-room.md)改造的基线参照。

1. 产品概述

一个 7x24 播放的虚拟直播间。所有观众看到同一个「虚拟播出时刻表」 (server 端统一计算的播放进度,非各自独立播放),可以:

  • 直接观看正在播放的 AI 生成短视频(文字弹幕点播 + 随机填充)
  • 在聊天区发一句话 → 命中素材库则很快排入播放;不命中则提交给 H3 后台生成新片段,生成完成后排入播放
  • 给候选片段投票,得票最高的优先播出
  • 浏览「最新 / 最早 / 最热」三种非直播的素材库列表,任意点播

2. 已实现的核心能力

2.1 虚拟播出时刻表(直播的核心机制)

  • server.js 维护内存中的 schedule([{clip, startAt, endAt, requestedBy}]), 每 2 秒 tick 一次(ensureSchedule),始终保证时刻表填充到「未来至少 FILLER_DURATION_MS(30s)」。
  • 选片优先级:投票最高的「已生成待播」(ready)> 投票最高的「素材库命中」 (matched)> 素材库随机填充(避开上一条、避开 20 分钟内播过的,两者都 没有时退化为纯随机,保证时刻表永不断流)。
  • 所有观众通过轮询 GET /api/live/state(4s 一次)拿到同一份 {current, upNext, queue},前端用「服务端 endAt 减本地时间」精确定时 切下一条,轮询只作为纠偏/兜底,不是切片的唯一触发点。
  • 状态持久化到 live-state.json,进程重启后从中恢复,不会冷启动断播。

2.2 文字点播驱动生成

  • 观众聊天发送文本 → 先做违禁词审核(moderateText,子串黑名单,非语义 审核)→ 通过后 submitPromptToQueue:
    • 命中素材库(中/英文 prompt 精确或子串匹配)→ 直接进入待播队列 (status: matched)。
    • 未命中 → 调用 H3 Console POST /v1/videos(文生视频,480x832 竖屏/ 120帧/20步——第一期默认横屏 832x480,后改为竖屏移动端优先,见 commit 1dc7084)异步生成,status: generating,生成完成后自动 写入 library.json 并推入待播队列(status: ready)。存量片段 保留生成时的原比例,播放器 object-fit: contain 兼容混合比例。
  • 生成是完全后台化的(H3 自建集群单次约 200s),绝不阻塞直播时刻表或 HTTP 响应。
  • POST /api/prompt 是同一条提交路径的非聊天版本(供测试/直接调用)。

2.3 排队投票

  • POST /api/queue/vote/:id:按 uid(cookie)对某条排队中的 matched/generating/ready 条目投票/取消投票,票数决定谁先播(2.1)。

2.4 素材库浏览模式

  • 三个 tab:最新 / 最早 / 最热(GET /api/library?sort=),点击任意条目 立即播放,播完自动顺延下一条(ended 事件),与直播模式的时刻表机制 完全独立。

2.5 观众身份

  • 首次进聊天框(focus 触发,不需要先点发送)弹昵称输入框,生成 uid(crypto.randomUUID)+ nickname,写入 cookie(365 天)。
  • 顶部常驻「昵称 ✎」可随时改名,uid 不变(投票/点播历史不丢失)。
  • 无正式账号体系——身份完全基于浏览器 cookie,换设备/清 cookie 即视为 新观众。这是 v2 用户注册需求要解决的缺口,见 docs/todo。

2.6 在线人数

  • 每次 /api/live/state 轮询带 cid(随机 clientId,非 cookie,纯前端 内存变量)打点到 viewers: Map<cid, lastSeenAt>,12s 内有心跳视为在线, 服务端返回 watching 计数。

2.7 内容审核 / 限流

  • 审核:仅关键词黑名单(BANNED_WORDS),非语义级别;生成内容本身的 安全检查依赖 H3 Console 自身的生成时安全策略(被拒会以「生成失败」 形式体现在队列里)。
  • 限流:按 IP,5 分钟内最多 3 次聊天/点播提交(RATE_LIMIT_MAX/WINDOW_MS)。 无账号维度限流。

2.8 后台管理(admin.html)

  • 表格形式管理 library.json:搜索(名称/prompt/id)、按来源筛选 (h3console / manual)、任意列排序、行内编辑、删除。
  • 「新增」表单可手动挂载一条外部视频地址(source: manual)的素材, 不经过 H3 生成。
  • 编辑/新增会置 edited: true,让后续自动同步(2.9)不覆盖人工修改。
  • 删除一条 h3console 来源的素材,会级联调用 H3 Console 的 DELETE /v1/videos/:id(移入 H3 自己的回收站),避免下次同步又冒出来。
  • 无鉴权——admin.html 和所有 /api/admin/* 接口对能访问到该端口的 任何人开放,依赖网络边界(内网/反代)而非应用层鉴权。

2.9 与 H3 Console 的集成

  • ingest.js 的 syncLibrary():启动时 + 每 5 分钟,从 H3 Console 拉取 全量 job 列表重建 library.json(规则提取短 prompt,过滤基础设施 烟雾测试 job,保留 likes,跳过 edited: true 的条目)。这让直接在 H3 Console 网页端产出的内容,5 分钟内自动出现在直播间素材库。
  • 视频本体不落本地盘:GET /lib/video/:id 服务端代理转发 H3 Console 的 /v1/videos/:id/file(H3_CONSOLE_TOKEN 不下发到浏览器);若素材有 videoUrl(manual 来源或管理后台设置过)则 302 跳转到该地址。
  • 详细 API 契约见 H3-CONSOLE.md(H3 Console 是独立于本项目的内部服务, 4 卡 GPU 单节点,无按次计费)。

3. 数据模型(现状)

library.json(数组,逐条追加,无数据库):

{
  "id": "19c6b745a68e",          // h3console: H3 job id;manual: manual-<ts><rand>
  "source": "h3console",          // "h3console" | "manual"
  "edited": false,                // true = 后台改过/手动新增,同步时跳过重建
  "prompt": "...",                // 展示用短 prompt(中文)
  "promptEn": "...",              // 可选,英文短 prompt
  "fullPrompt": "...",            // 完整生成 prompt(H3 结构化 prompt 或用户原话)
  "videoUrl": "...",              // 可选,外部视频地址(manual 或后台改过播放源)
  "width": 480, "height": 832,     // 当前默认竖屏;早期/存量片段可能是 832x480 横屏
  "duration": 5,                  // 秒
  "partition": "text",            // text | ref2va | fl2va | manual
  "likes": 0,
  "lastPlayedAt": 1788250666831,  // 可选,用于播出冷却
  "createdAt": 1788250666831
}

live-state.json:{ schedule, promptQueue, chatMessages },结构见 server.js 顶部注释,逐 tick 全量覆写。

4. 已知限制(未做的事,供 v2 参考)

  • 无正式用户账号(仅 cookie 身份)、无积分/消费体系。
  • 素材类型单一:只有「生成好的视频片段」,没有可复用的「人物」「服装」 「参考动作/音乐视频」这类结构化中间资产——每条素材都是一次性的最终 产物,无法组合复用。
  • 生成路径只有「纯文字 → H3 文生视频」一条,没有「参考图/参考视频驱动」 的生成路径(H3 Console 本身支持 ref2va/fl2va,但本项目尚未从 /api/prompt、/api/chat 之外的路径调用过)。
  • 无人工审核队列,内容一旦生成完成即自动排入公开播放,无「先审后播」 开关。
  • 无任何鉴权,管理后台和写接口都是「网络可达即可操作」。