Compare commits

..

28 Commits

Author SHA1 Message Date
xiaoxia 8b53c4a130 fix: 修复并发解码竞态——先标记再解码,失败时移除标记
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 31s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m55s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m10s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m43s
AI Code Review / AI Code Review (pull_request) Successful in 2m49s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m36s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m40s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 3m45s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m47s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 11m36s
CI/CD Pipeline / CI Gate (pull_request) Successful in 14s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 20s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 40s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 652h8m38s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 652h8m41s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 652h8m41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 652h18m7s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 652h19m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 652h20m15s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 652h8m38s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 652h8m39s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 652h20m17s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 652h20m17s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 652h19m46s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 652h20m11s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 652h20m13s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 652h42m26s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 652h42m28s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 652h53m59s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 652h54m3s
decodeAroundPosition 和初始化循环中,将 decodedSegmentsRef.add() 移到
await decodeSegment() 之前(先标记防重入),catch 中 delete() 移除标记
(允许失败后重试)。解决渲染循环中解码未完成时下一帧重复发起解码的竞态。

Closes: #1437
2026-08-20 11:05:53 +08:00
xiaoxia 632c3043bc fix: 修复 AI Code Review 阻塞级问题 v2(解码策略+资源泄漏+轮询超时)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 39s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 52s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m34s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m53s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m5s
AI Code Review / AI Code Review (pull_request) Successful in 2m19s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m23s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m45s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m31s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 7m39s
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 663h57m27s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 663h57m28s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 663h57m29s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 663h57m31s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 663h57m29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 664h4m27s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 664h4m30s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 664h5m4s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 664h5m5s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 664h5m9s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 664h5m6s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 664h5m9s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 664h5m10s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 664h31m12s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 664h31m15s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 664h38m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 664h38m52s
useCanvasPlayer:
- 初始化解码改为按需模式:仅解码前3片段 + decodeAroundPosition 动态加载 ±1 片段
- 新增 decodedSegmentsRef 追踪已解码片段,seek 时清空并重新解码
- 新增 lastDrawnFrameRef 追踪上一帧,绘制新帧前 close() 释放防止内存泄漏
- 解码标记移至 await decodeSegment() 之后,失败时不标记以便重试
- destroy 增加帧清理和状态重置

useStep6Cover:
- 轮询增加 pollCount(max 60) + setTimeout 120s 双重超时保护
- 400错误精确匹配预览缺失模式(errCode/message正则),避免误触发自动修复

Closes: #1437, #1438, #1439
2026-08-19 23:20:53 +08:00
张宏杰 472f371d2f fix: VisualSampleEntry 添加 fallback 搜索,兼容不同 MP4 结构
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 56s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m36s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m49s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 42s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 46s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m17s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m30s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m1s
CI/CD Pipeline / Unit Tests (push) Successful in 11m42s
CI/CD Pipeline / Integration Tests (push) Successful in 4m12s
CI/CD Pipeline / Production Browser E2E (push) Failing after 664h23m8s
CI/CD Pipeline / Deploy Production (push) Failing after 664h23m9s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 664h23m10s
CI/CD Pipeline / CI Gate (push) Failing after 664h23m10s
CI/CD Pipeline / Build Production API Image (push) Failing after 664h23m10s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 664h34m51s
CI/CD Pipeline / PR Build API Image (push) Failing after 664h34m53s
CI/CD Pipeline / Frontend Lint (push) Failing after 664h34m54s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 664h34m54s
CI/CD Pipeline / Canary Release to Production (push) Failing after 664h56m53s
CI/CD Pipeline / Build Production Web Image (push) Failing after 664h56m55s
CI/CD Pipeline / PR Build Web Image (push) Failing after 665h8m37s
- 先尝试 offset+8+78(标准 VisualSampleEntry 结构)
- 未找到则 fallback 到 offset+8(兼容非标准 MP4)
- 添加调试日志追踪搜索过程
2026-08-19 22:51:25 +08:00
张宏杰 709d65ce52 fix: VisualSampleEntry 跳过78字节固定字段,修复 hvcC 提取位置
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m21s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m6s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m25s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 45s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 44s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m26s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m16s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m0s
CI/CD Pipeline / Unit Tests (push) Successful in 11m9s
CI/CD Pipeline / Integration Tests (push) Successful in 4m16s
CI/CD Pipeline / CI Gate (push) Failing after 664h36m32s
CI/CD Pipeline / Deploy Production (push) Failing after 664h37m3s
CI/CD Pipeline / Production Browser E2E (push) Failing after 664h37m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 664h37m4s
CI/CD Pipeline / PR Build Web Image (push) Failing after 664h46m50s
CI/CD Pipeline / Build Production Web Image (push) Failing after 664h37m4s
CI/CD Pipeline / PR Build API Image (push) Failing after 664h46m52s
CI/CD Pipeline / Build Production API Image (push) Failing after 664h37m5s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 664h48m16s
CI/CD Pipeline / Canary Release to Production (push) Failing after 665h10m48s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 665h20m33s
CI/CD Pipeline / Frontend Lint (push) Failing after 665h21m59s
- findCodecConfigRecursive 区分 VisualSampleEntry 和普通容器 box
- avc1/avc3/hvc1/hev1 前 78 字节是固定字段(ISO 14496-12),子 box 从 offset+8+78 开始
- 普通容器 box(moov/trak/mdia/minf/stbl)仍从 offset+8 开始递归
- 修复 HEVC description 提取失败导致的黑屏问题
2026-08-19 22:38:01 +08:00
张宏杰 ae1b9f8ddb fix: findCodecConfig 改为纯递归搜索,修复 HEVC description 提取失败
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 47s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m22s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m40s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m13s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m55s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m57s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 44s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m20s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m53s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m4s
CI/CD Pipeline / Integration Tests (push) Successful in 3m17s
CI/CD Pipeline / Unit Tests (push) Successful in 12m10s
CI/CD Pipeline / Production Browser E2E (push) Failing after 664h55m34s
CI/CD Pipeline / PR Build Web Image (push) Failing after 665h6m58s
CI/CD Pipeline / Canary Release to Production (push) Failing after 664h55m35s
CI/CD Pipeline / PR Build API Image (push) Failing after 665h7m0s
CI/CD Pipeline / Deploy Production (push) Failing after 664h55m36s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 664h55m36s
CI/CD Pipeline / Build Production Web Image (push) Failing after 664h55m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 664h55m36s
CI/CD Pipeline / CI Gate (push) Failing after 664h55m36s
CI/CD Pipeline / Frontend Lint (push) Failing after 665h7m46s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 665h7m49s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 665h40m41s
根因:旧 findCodecConfig 对 container box 的 fullbox 检测逻辑错误,
导致递归搜索时跳过了子 box 的起始位置,永远找不到 hvcC/avcC。

修复:
- 替换为 findCodecConfigRecursive,纯递归搜索所有嵌套 box
- 不区分 fullbox/非 fullbox,统一从 offset+8 开始搜索子 box
- 返回 hvcC/avcC 的内容(不含 8 字节 box header),符合 WebCodecs 要求
- 添加日志追踪找到的 box 类型和位置
2026-08-19 22:18:26 +08:00
张宏杰 1c9903574a fix: 修复预览播放器初始化流程断裂 + 确认生成改为直接创建任务
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 59s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m16s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m33s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m5s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m27s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m24s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m35s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m5s
CI/CD Pipeline / Unit Tests (push) Successful in 11m28s
CI/CD Pipeline / Integration Tests (push) Successful in 4m11s
CI/CD Pipeline / CI Gate (push) Failing after 665h20m44s
CI/CD Pipeline / Deploy Production (push) Failing after 665h22m0s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 665h22m1s
CI/CD Pipeline / Build Production API Image (push) Failing after 665h22m1s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 665h32m28s
CI/CD Pipeline / PR Build API Image (push) Failing after 665h32m32s
CI/CD Pipeline / Frontend Lint (push) Failing after 665h33m30s
CI/CD Pipeline / Production Browser E2E (push) Failing after 665h21m59s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 665h33m33s
CI/CD Pipeline / Canary Release to Production (push) Failing after 665h55m45s
CI/CD Pipeline / Build Production Web Image (push) Failing after 665h55m46s
CI/CD Pipeline / PR Build Web Image (push) Failing after 666h6m15s
修复一:useCanvasPlayer 初始化 useEffect 中,loadSegment 内的 setState
触发重渲染 → cleanup 调用 destroy() → isDestroyedRef=true → demuxSegment
永远不执行。改用局部 cancelled 变量替代 isDestroyedRef 做异步流程控制,
添加 6 处调试日志追踪完整初始化流程。

修复二:确认生成流程从 TODO 占位改为直接调用 createGenerationTask,
传入 template_id/asset_ids/output_width/output_height/cover_url/
custom_title/title_config/video_ratio 等完整参数。

修复三:扩展 CreateGenerationTaskRequest 类型,新增 output_width/
output_height/cover_url/custom_title/duration/video_ratio/title_config
可选字段,title_ids/voice_ids 改为可选。
2026-08-19 21:52:44 +08:00
张宏杰 cfedc06df0 refactor: 清理预览旧架构 — 删除 confirmGeneration 调用 + previewTaskId 链路
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m5s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m17s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m54s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m58s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m14s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 6m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m24s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m11s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 9m14s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Unit Tests (push) Successful in 13m45s
CI/CD Pipeline / Integration Tests (push) Successful in 5m1s
CI/CD Pipeline / CI Gate (push) Failing after 666h26m43s
CI/CD Pipeline / Canary Release to Production (push) Failing after 666h27m9s
CI/CD Pipeline / Deploy Production (push) Failing after 666h27m9s
CI/CD Pipeline / Build Production Web Image (push) Failing after 666h27m10s
CI/CD Pipeline / Build Production API Image (push) Failing after 666h27m10s
CI/CD Pipeline / PR Build Web Image (push) Failing after 666h40m54s
CI/CD Pipeline / PR Build API Image (push) Failing after 666h40m56s
CI/CD Pipeline / Frontend Lint (push) Failing after 666h40m58s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 666h41m0s
CI/CD Pipeline / Production Browser E2E (push) Failing after 667h0m53s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 667h0m54s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 667h14m38s
- useGenerateVideo: 删除 confirmGeneration 导入和调用,改为 TODO 占位
- GeneratePage: 删除 previewTaskId 硬编码空字符串
- types: 删除 previewTaskId 字段定义
- api/generation: 删除 confirmGeneration 导出和 ConfirmGeneration 类型导出
- createPreview/getPreviewStatus 保留(useStep6Cover 封面流程仍在使用)
2026-08-19 20:45:18 +08:00
张宏杰 4429784a79 refactor: 删除 createPreview 调用,确认生成不再触发后端预览渲染
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m18s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m22s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 55s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m39s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 47s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m26s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m52s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m23s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 666h51m7s
CI/CD Pipeline / PR Build Web Image (push) Failing after 666h51m7s
CI/CD Pipeline / Frontend Lint (push) Failing after 666h51m11s
CI/CD Pipeline / PR Build API Image (push) Failing after 666h51m9s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 666h51m13s
- 删除 createPreview API 导入和调用
- 预览已改为前端实时播放(Canvas + WebCodecs)
- taskId 改为直接使用 props.previewTaskId
- 移除 useCallback 依赖中的 selectedTemplate
2026-08-19 20:35:07 +08:00
张宏杰 aa36e63591 refactor: 清理 descriptionCache 兜底代码
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Failing after 666h53m3s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 666h53m4s
删除 4 处 descriptionCache 相关代码:
- 删除 useRef 声明
- 删除缓存兜底块
- 删除缓存写入
- 删除缓存清理

descriptionCache 不再需要,description 直接由 extractCodecDescription 提供
2026-08-19 20:33:16 +08:00
张宏杰 c3373de4f3 fix: findCodecConfig 动态检测 fullbox,修复 container box 偏移计算错误
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m45s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m21s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m25s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m8s
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 666h56m22s
CI/CD Pipeline / PR Build API Image (push) Failing after 666h56m26s
CI/CD Pipeline / Frontend Lint (push) Failing after 666h56m27s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 666h56m30s
CI/CD Pipeline / PR Build Web Image (push) Failing after 667h30m10s
问题根因:
- 代码假设所有 container box (trak/mdia/minf/stbl) 都是 fullbox
- 但实际 MP4 文件中这些 box 可能不是 fullbox,content 从 offset+8 开始而非 offset+12
- 导致递归查找时偏移错误,永远找不到 stsd → hvcC/avcC

修复方案:
- 动态检测 fullbox:检查 offset+8 处的 version/flags 字节是否为 0
- 如果是 fullbox,content 从 offset+12 开始;否则从 offset+8 开始
- 在 containerBoxes 列表中添加 'moov'
2026-08-19 20:29:46 +08:00
张宏杰 60d95e64ab fix: HEVC description偏移修正+调试日志 (#1450)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m0s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m57s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 3m41s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 45s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 47s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 10m51s
CI/CD Pipeline / Unit Tests (push) Successful in 14m48s
CI/CD Pipeline / Integration Tests (push) Successful in 5m41s
CI/CD Pipeline / CI Gate (push) Failing after 667h3m1s
CI/CD Pipeline / Canary Release to Production (push) Failing after 667h4m43s
CI/CD Pipeline / Deploy Production (push) Failing after 667h4m43s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 667h4m44s
CI/CD Pipeline / Build Production Web Image (push) Failing after 667h4m45s
CI/CD Pipeline / Build Production API Image (push) Failing after 667h4m45s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 667h19m30s
CI/CD Pipeline / Frontend Lint (push) Failing after 667h19m34s
CI/CD Pipeline / PR Build Web Image (push) Failing after 667h19m32s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 667h19m36s
CI/CD Pipeline / Production Browser E2E (push) Failing after 667h38m28s
CI/CD Pipeline / PR Build API Image (push) Failing after 667h53m20s
2026-08-19 20:06:44 +08:00
张宏杰 7775d5d118 fix: setExtractionOptions 改为 nbSamples: Infinity (#1449)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 58s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m12s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m43s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m52s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 43s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 48s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m7s
CI/CD Pipeline / Unit Tests (push) Successful in 11m36s
CI/CD Pipeline / Integration Tests (push) Successful in 5m49s
CI/CD Pipeline / CI Gate (push) Failing after 667h19m42s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 667h21m8s
CI/CD Pipeline / Production Browser E2E (push) Failing after 667h21m6s
CI/CD Pipeline / Build Production Web Image (push) Failing after 667h21m8s
CI/CD Pipeline / Canary Release to Production (push) Failing after 667h21m6s
CI/CD Pipeline / PR Build Web Image (push) Failing after 667h32m41s
CI/CD Pipeline / PR Build API Image (push) Failing after 667h32m43s
CI/CD Pipeline / Build Production API Image (push) Failing after 667h21m9s
CI/CD Pipeline / Frontend Lint (push) Failing after 667h32m45s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 667h32m45s
CI/CD Pipeline / Deploy Production (push) Failing after 667h54m52s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 668h6m24s
2026-08-19 19:53:35 +08:00
张宏杰 f025514d16 fix: Canvas播放器全量修复 - nbSamples/description/调试日志 (#1448)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m1s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m20s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m42s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m33s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 49s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 43s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 57s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m40s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m14s
CI/CD Pipeline / Integration Tests (push) Successful in 4m15s
CI/CD Pipeline / Unit Tests (push) Successful in 11m59s
CI/CD Pipeline / Production Browser E2E (push) Failing after 667h47m8s
CI/CD Pipeline / Deploy Production (push) Failing after 667h47m9s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 667h47m9s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 667h58m5s
CI/CD Pipeline / PR Build Web Image (push) Failing after 667h58m7s
CI/CD Pipeline / CI Gate (push) Failing after 667h47m10s
CI/CD Pipeline / Frontend Lint (push) Failing after 667h59m9s
CI/CD Pipeline / Build Production Web Image (push) Failing after 667h47m10s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 667h59m12s
CI/CD Pipeline / Canary Release to Production (push) Failing after 668h20m54s
CI/CD Pipeline / Build Production API Image (push) Failing after 668h20m55s
CI/CD Pipeline / PR Build API Image (push) Failing after 668h31m54s
2026-08-19 19:27:09 +08:00
张宏杰 e043b0aa6a fix: demuxSegment添加5秒超时保护+防重复resolve (#1447)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 53s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m8s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m38s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m24s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 51s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m37s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 45s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 51s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 668h6m39s
CI/CD Pipeline / PR Build API Image (push) Failing after 668h6m43s
CI/CD Pipeline / Frontend Lint (push) Failing after 668h6m45s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 668h6m46s
CI/CD Pipeline / PR Build Web Image (push) Failing after 668h40m26s
2026-08-19 19:19:35 +08:00
张宏杰 fdce08356b fix: demuxSegment改为异步,等待onSamples完成后返回,修复黑屏根因 (#1446)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m56s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m15s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m49s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 42s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 49s
CI/CD Pipeline / Integration Tests (push) Successful in 2m46s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m5s
CI/CD Pipeline / Unit Tests (push) Successful in 11m27s
CI/CD Pipeline / Production Browser E2E (push) Failing after 668h11m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 668h11m41s
CI/CD Pipeline / Canary Release to Production (push) Failing after 668h11m40s
CI/CD Pipeline / Build Production Web Image (push) Failing after 668h11m41s
CI/CD Pipeline / Build Production API Image (push) Failing after 668h11m41s
CI/CD Pipeline / CI Gate (push) Failing after 668h11m41s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 668h22m23s
CI/CD Pipeline / PR Build API Image (push) Failing after 668h22m27s
CI/CD Pipeline / Frontend Lint (push) Failing after 668h23m6s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 668h24m55s
CI/CD Pipeline / Deploy Production (push) Failing after 668h45m25s
CI/CD Pipeline / PR Build Web Image (push) Failing after 668h56m10s
- demuxSegment 改为 async 函数,返回 Promise
- onSamples 完成后 resolve(meta),确保 samples 数据就绪
- init 中 await demuxSegment 避免空 samples 导致黑屏
2026-08-19 19:01:17 +08:00
张宏杰 89cccf294c fix: 全量可选链+空值兜底,杜绝 TypeError (#1445)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m11s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m44s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m49s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 47s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 50s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 57s
CI/CD Pipeline / Integration Tests (push) Successful in 2m47s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m10s
CI/CD Pipeline / Unit Tests (push) Successful in 11m51s
CI/CD Pipeline / Production Browser E2E (push) Failing after 668h30m41s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 668h30m43s
CI/CD Pipeline / Canary Release to Production (push) Failing after 668h30m41s
CI/CD Pipeline / Build Production Web Image (push) Failing after 668h30m43s
CI/CD Pipeline / CI Gate (push) Failing after 668h30m44s
CI/CD Pipeline / Build Production API Image (push) Failing after 668h30m44s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 668h41m34s
CI/CD Pipeline / Frontend Lint (push) Failing after 668h42m5s
CI/CD Pipeline / PR Build Web Image (push) Failing after 668h41m36s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 668h43m59s
CI/CD Pipeline / Deploy Production (push) Failing after 669h4m27s
CI/CD Pipeline / PR Build API Image (push) Failing after 669h15m23s
2026-08-19 18:42:13 +08:00
张宏杰 5f3ff6bb8c feat: 前端自主视频切片,不再依赖后端切片 (#1444)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m59s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m49s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m51s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m48s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 41s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 52s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 53s
CI/CD Pipeline / Integration Tests (push) Successful in 2m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m6s
CI/CD Pipeline / Unit Tests (push) Successful in 11m36s
CI/CD Pipeline / Production Browser E2E (push) Failing after 668h45m57s
CI/CD Pipeline / Canary Release to Production (push) Failing after 668h45m58s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 668h45m59s
CI/CD Pipeline / Build Production Web Image (push) Failing after 668h45m59s
CI/CD Pipeline / Build Production API Image (push) Failing after 668h46m0s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 668h57m11s
CI/CD Pipeline / Frontend Lint (push) Failing after 668h57m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 668h59m15s
CI/CD Pipeline / PR Build Web Image (push) Failing after 668h57m13s
CI/CD Pipeline / Deploy Production (push) Failing after 669h19m43s
CI/CD Pipeline / CI Gate (push) Failing after 669h19m45s
CI/CD Pipeline / PR Build API Image (push) Failing after 669h31m0s
2026-08-19 18:26:55 +08:00
张宏杰 ca78b182af fix: Canvas description缓存+关键帧容错+data字段修正 (#1443)
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m16s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m37s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 40s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m47s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m1s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 45s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 50s
CI/CD Pipeline / Integration Tests (push) Successful in 3m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m6s
CI/CD Pipeline / Unit Tests (push) Successful in 11m55s
CI/CD Pipeline / Production Browser E2E (push) Failing after 669h5m32s
CI/CD Pipeline / Deploy Production (push) Failing after 669h5m34s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 669h5m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 669h5m38s
CI/CD Pipeline / CI Gate (push) Failing after 669h5m38s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 669h16m36s
CI/CD Pipeline / PR Build API Image (push) Failing after 669h16m38s
CI/CD Pipeline / Frontend Lint (push) Failing after 669h17m34s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 669h19m17s
CI/CD Pipeline / Canary Release to Production (push) Failing after 669h39m19s
CI/CD Pipeline / Build Production Web Image (push) Failing after 669h39m21s
CI/CD Pipeline / PR Build Web Image (push) Failing after 669h50m23s
2026-08-19 18:06:53 +08:00
张宏杰 ca8f1079b1 chore: retrigger CI (#1442)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m20s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m50s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 54s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 46s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m40s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m4s
CI/CD Pipeline / Unit Tests (push) Successful in 11m20s
CI/CD Pipeline / Integration Tests (push) Successful in 4m7s
CI/CD Pipeline / CI Gate (push) Failing after 669h28m7s
CI/CD Pipeline / Production Browser E2E (push) Failing after 669h28m31s
CI/CD Pipeline / Deploy Production (push) Failing after 669h28m33s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 669h28m35s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 669h39m14s
CI/CD Pipeline / Build Production Web Image (push) Failing after 669h28m37s
CI/CD Pipeline / PR Build Web Image (push) Failing after 669h39m16s
CI/CD Pipeline / Frontend Lint (push) Failing after 669h39m57s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 669h39m58s
CI/CD Pipeline / Canary Release to Production (push) Failing after 670h2m18s
CI/CD Pipeline / Build Production API Image (push) Failing after 670h2m22s
CI/CD Pipeline / PR Build API Image (push) Failing after 670h13m3s
2026-08-19 17:46:08 +08:00
xiaoxia ee92f72252 fix: HEVC description返回完整box+标题分割调试日志 (#1442) (#1442)
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Failing after 669h40m19s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 669h40m22s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-08-19 17:46:00 +08:00
张宏杰 7b63219d9e fix: HEVC description返回完整box+标题分割调试日志 (#1442)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 54s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m6s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m11s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m11s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m23s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m32s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m23s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 669h49m11s
CI/CD Pipeline / PR Build API Image (push) Failing after 669h49m15s
CI/CD Pipeline / Frontend Lint (push) Failing after 669h49m17s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 669h49m18s
CI/CD Pipeline / PR Build Web Image (push) Failing after 670h22m57s
2026-08-19 17:37:04 +08:00
张宏杰 dfd821f5ba chore: retrigger CI
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m36s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 3m25s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m44s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 57s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m28s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m43s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m42s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m51s
CI/CD Pipeline / Unit Tests (push) Successful in 13m18s
CI/CD Pipeline / Integration Tests (push) Successful in 5m44s
CI/CD Pipeline / CI Gate (push) Failing after 670h4m43s
CI/CD Pipeline / Deploy Production (push) Failing after 670h5m59s
CI/CD Pipeline / Production Browser E2E (push) Failing after 670h5m58s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 670h6m0s
CI/CD Pipeline / Build Production API Image (push) Failing after 670h6m0s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 670h17m42s
CI/CD Pipeline / PR Build API Image (push) Failing after 670h17m44s
CI/CD Pipeline / Frontend Lint (push) Failing after 670h19m20s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 670h19m21s
CI/CD Pipeline / Canary Release to Production (push) Failing after 670h39m42s
CI/CD Pipeline / Build Production Web Image (push) Failing after 670h39m44s
CI/CD Pipeline / PR Build Web Image (push) Failing after 670h51m26s
2026-08-19 17:07:01 +08:00
xiaoxia bfc9e07170 fix: subBoxStart偏移修正+标题分割扩展Unicode斜杠字符 (#1441) (#1441)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 50s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m18s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m35s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m8s
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 670h24m32s
CI/CD Pipeline / PR Build API Image (push) Failing after 670h24m36s
CI/CD Pipeline / Frontend Lint (push) Failing after 670h25m13s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 670h25m17s
CI/CD Pipeline / PR Build Web Image (push) Failing after 670h58m18s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-08-19 17:01:06 +08:00
张宏杰 2c2fcabd6c fix: subBoxStart偏移修正+标题分割扩展Unicode斜杠字符 (#1441)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m2s
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 670h27m22s
CI/CD Pipeline / PR Build API Image (push) Failing after 670h27m26s
CI/CD Pipeline / Frontend Lint (push) Failing after 670h27m27s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 670h27m30s
CI/CD Pipeline / PR Build Web Image (push) Failing after 671h1m8s
2026-08-19 16:58:52 +08:00
张宏杰 4e19d08a98 fix: MP4 fullbox偏移修正+标题全角/半角斜杠换行 (#1440)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 59s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m47s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m29s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m13s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m51s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 42s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m19s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m11s
CI/CD Pipeline / Integration Tests (push) Successful in 3m19s
CI/CD Pipeline / Unit Tests (push) Successful in 11m45s
CI/CD Pipeline / Production Browser E2E (push) Failing after 672h29m15s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 672h29m18s
CI/CD Pipeline / Build Production Web Image (push) Failing after 672h29m18s
CI/CD Pipeline / Canary Release to Production (push) Failing after 672h29m16s
CI/CD Pipeline / Build Production API Image (push) Failing after 672h29m18s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 672h40m57s
CI/CD Pipeline / PR Build API Image (push) Failing after 672h41m1s
CI/CD Pipeline / Frontend Lint (push) Failing after 672h41m3s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 672h41m5s
CI/CD Pipeline / Deploy Production (push) Failing after 673h3m0s
CI/CD Pipeline / CI Gate (push) Failing after 673h3m2s
CI/CD Pipeline / PR Build Web Image (push) Failing after 673h14m43s
2026-08-19 14:45:17 +08:00
张宏杰 d9b4041215 fix: 预览播放器4个线上问题修复 (#1439)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m56s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m2s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m52s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m52s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 59s
CI/CD Pipeline / Integration Tests (push) Successful in 3m1s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m26s
CI/CD Pipeline / Unit Tests (push) Successful in 11m50s
CI/CD Pipeline / Production Browser E2E (push) Failing after 673h3m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 673h3m40s
CI/CD Pipeline / Build Production Web Image (push) Failing after 673h3m40s
CI/CD Pipeline / Canary Release to Production (push) Failing after 673h3m39s
CI/CD Pipeline / Build Production API Image (push) Failing after 673h3m40s
CI/CD Pipeline / CI Gate (push) Failing after 673h3m40s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 673h14m47s
CI/CD Pipeline / PR Build API Image (push) Failing after 673h14m51s
CI/CD Pipeline / Frontend Lint (push) Failing after 673h15m31s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 673h15m34s
CI/CD Pipeline / Deploy Production (push) Failing after 673h37m23s
CI/CD Pipeline / PR Build Web Image (push) Failing after 673h48m33s
- CSS弹窗裁剪: .ant-modal规则限定到.xx-preview-modal
- HEVC黑屏: 从MP4 buffer提取avcC/hvcC作为VideoDecoder description
- 标题换行: drawTitle按/分割多行绘制
- 封面400: useStep6Cover自动创建后端预览后重试
2026-08-19 14:10:47 +08:00
张宏杰 48d1b4dd33 fix: Canvas黑屏修复+配音时长校验fallback (#1438)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m4s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m46s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m53s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m55s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m16s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 5m53s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 40s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 43s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 48s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m6s
CI/CD Pipeline / Unit Tests (push) Successful in 10m49s
CI/CD Pipeline / Production Browser E2E (push) Failing after 673h58m42s
CI/CD Pipeline / CI Gate (push) Failing after 673h58m44s
CI/CD Pipeline / Canary Release to Production (push) Failing after 673h58m42s
CI/CD Pipeline / Deploy Production (push) Failing after 673h58m43s
CI/CD Pipeline / Build Production Web Image (push) Failing after 673h58m44s
CI/CD Pipeline / Build Production API Image (push) Failing after 673h58m44s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 674h9m24s
CI/CD Pipeline / PR Build Web Image (push) Failing after 674h9m26s
CI/CD Pipeline / Frontend Lint (push) Failing after 674h9m35s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 674h9m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 674h32m28s
CI/CD Pipeline / PR Build API Image (push) Failing after 674h43m12s
- useCanvasPlayer renderFrame 删除 frame.close(),帧释放由 FrameQueue 统一管理
- 新增 calculateTotalVideoDuration 共享工具函数
- GeneratePage: 素材为空时用模板 duration_max 之和估算总时长
- Step5VoiceSelect: Number() 类型转换 + debug log
2026-08-19 13:16:44 +08:00
张宏杰 25ce6d6286 fix: Canvas播放器修复 - 片段自动切换+宽高比+移除死代码+进度节流 (#1437)
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 57s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m32s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m35s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 6m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m30s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 43s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 58s
CI/CD Pipeline / Integration Tests (push) Successful in 2m50s
CI/CD Pipeline / Unit Tests (push) Successful in 10m41s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
CI/CD Pipeline / Canary Release to Production (push) Failing after 674h22m46s
CI/CD Pipeline / Deploy Production (push) Failing after 674h23m6s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 674h23m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 674h23m6s
CI/CD Pipeline / CI Gate (push) Failing after 674h23m7s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 674h32m47s
CI/CD Pipeline / PR Build Web Image (push) Failing after 674h32m49s
CI/CD Pipeline / PR Build API Image (push) Failing after 674h32m51s
CI/CD Pipeline / Frontend Lint (push) Failing after 674h33m47s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 674h33m50s
CI/CD Pipeline / Production Browser E2E (push) Failing after 674h56m48s
CI/CD Pipeline / Build Production Web Image (push) Failing after 674h56m50s
- 初始化时加载所有片段数据并逐段解码,帧时间戳按全局偏移对齐
- 新增computeDrawRect保持视频宽高比(letterbox/pillarbox)
- 移除未使用的audioCtxRef/audioSourceRef
- 进度更新节流到200ms(5fps)
- FrameQueue容量提升到10
- fallback路径优化预加载策略
2026-08-19 12:52:31 +08:00
10 changed files with 633 additions and 163 deletions
-4
View File
@@ -3,13 +3,9 @@ export type {
CreatePreviewRequest,
CreatePreviewResponse,
PreviewTaskResponse,
ConfirmGenerationRequest,
ConfirmGenerationResponse,
ConfirmGenerationTaskItem,
} from "./types"
export { createPreview, getPreviewStatus } from "./preview"
export { confirmGeneration } from "./confirm"
export { generateCover } from "./cover"
export type { GenerateCoverRequest, GenerateCoverResponse } from "./cover"
+25 -2
View File
@@ -57,8 +57,31 @@ export interface TaskListResponse {
export interface CreateGenerationTaskRequest {
template_id: string
asset_ids: string[]
title_ids: string[]
voice_ids: string[]
title_ids?: string[]
voice_ids?: string[]
/** 输出视频宽度 */
output_width?: number
/** 输出视频高度 */
output_height?: number
/** 自定义封面图片 URL */
cover_url?: string
/** 自定义视频标题 */
custom_title?: string
/** 视频时长(秒) */
duration?: number
/** 视频宽高比,如 "9:16" */
video_ratio?: string
/** 标题烧录配置 */
title_config?: {
text?: string
font?: string
font_size?: number
font_color?: string
position?: string
bold?: boolean
stroke?: boolean
shadow?: boolean
}
}
/** 创建生成任务响应(对齐后端 GenerationTaskResponse) */
+9 -11
View File
@@ -18,6 +18,10 @@ import { useCloneProgress } from "@/hooks/useCloneProgress"
import { getAssetsByKind } from "@/api/assets"
import CloneModal from "@/components/voice/CloneModal"
import GenerateHeader from "./components/GenerateHeader"
import {
calculateTotalVideoDuration,
estimateTotalVideoDuration,
} from "./utils/calculateTotalVideoDuration"
import GenerateStepsBar from "./components/GenerateStepsBar"
import GenerateResultPanel from "./components/GenerateResultPanel"
import PreviewVideoPanel from "./components/PreviewVideoPanel"
@@ -109,16 +113,10 @@ const GeneratePage: React.FC = () => {
/* ── 视频总时长计算(用于配音时长校验) ── */
const totalVideoDuration = useMemo(() => {
if (!previewAssets.length || !currentTemplate) return 0
const templateSegments = currentTemplate.segments || []
return previewAssets.reduce((sum, asset, i) => {
const assetDuration = asset.duration || asset.metadata?.duration || 30
const tplSeg = templateSegments[i] || templateSegments[templateSegments.length - 1]
const segDuration = tplSeg
? Math.min(tplSeg.duration_max, Math.max(tplSeg.duration_min, assetDuration))
: Math.min(assetDuration, 10)
return sum + segDuration
}, 0)
// 优先用素材精确时长;素材未加载时用模板 segments 的 duration_max 之和估算
const exact = calculateTotalVideoDuration(previewAssets, currentTemplate ?? undefined)
if (exact > 0) return exact
return estimateTotalVideoDuration(currentTemplate ?? undefined)
}, [previewAssets, currentTemplate])
/* ── 配音音频 URL ── */
@@ -173,7 +171,6 @@ const GeneratePage: React.FC = () => {
autoSubtitles,
bgm,
generateCount,
previewTaskId: "",
})
/* ================================================================
@@ -289,6 +286,7 @@ const GeneratePage: React.FC = () => {
{/* ── 视频预览弹窗 ── */}
<Modal
className="xx-preview-modal"
open={previewModalOpen}
onCancel={() => setPreviewModalOpen(false)}
footer={null}
@@ -272,21 +272,22 @@ const Step5VoiceSelect: React.FC<Step5VoiceSelectProps> = ({
>
<span style={{ display: "flex", alignItems: "center", gap: 4 }}>
{formatDuration(item.duration)}
{totalVideoDuration > 0 && (item.duration || 0) < totalVideoDuration && (
<span
style={{
color: "#ff4d4f",
fontSize: 11,
fontWeight: 500,
display: "inline-flex",
alignItems: "center",
gap: 2,
}}
>
<WarningOutlined />
时长不足
</span>
)}
{totalVideoDuration > 0 &&
(Number(item.duration) || 0) < Number(totalVideoDuration) && (
<span
style={{
color: "#ff4d4f",
fontSize: 11,
fontWeight: 500,
display: "inline-flex",
alignItems: "center",
gap: 2,
}}
>
<WarningOutlined />
时长不足
</span>
)}
</span>
<span>{formatFileSize(item.file_size)}</span>
</div>
+2 -2
View File
@@ -2881,11 +2881,11 @@
overflow: hidden;
}
.ant-modal-content {
.xx-preview-modal .ant-modal-content {
padding: 0 !important;
}
.ant-modal-close {
.xx-preview-modal .ant-modal-close {
color: #fff !important;
}
@@ -19,8 +19,6 @@ export interface UseGenerateVideoProps {
autoSubtitles: boolean
bgm: boolean
generateCount: number
/** 预览任务的 task_id(用于新确认生成 API) */
previewTaskId: string
}
/** 生成阶段 */
@@ -8,6 +8,78 @@ import { useRef, useCallback, useEffect, useState } from "react"
import { createFile } from "mp4box"
import type { Movie, Sample } from "mp4box"
// ── MP4 Box 解析辅助函数 ──
/**
* 递归搜索 box 树,找到 hvcC 或 avcC box 并返回其配置数据(不含 8 字节 box header)
*
* MP4 box 嵌套结构:moov → trak → mdia → minf → stbl → stsd → hev1 → hvcC
* 普通容器 box 从 offset+8 开始递归;VisualSampleEntry (avc1/avc3/hvc1/hev1) 前 78 字节
* 是固定字段(ISO 14496-12),子 box 从 offset+8+78 开始
*/
function findCodecConfigRecursive(
buffer: ArrayBuffer,
start: number,
end: number,
): ArrayBuffer | undefined {
const view = new DataView(buffer)
let offset = start
while (offset < end - 8) {
const size = view.getUint32(offset)
if (size < 8 || offset + size > end) break
const type = String.fromCharCode(
view.getUint8(offset + 4),
view.getUint8(offset + 5),
view.getUint8(offset + 6),
view.getUint8(offset + 7),
)
// 找到目标 codec 配置 box,返回内容(不含 8 字节 header)
if (type === "avcC" || type === "hvcC") {
console.log("[findCodecConfig] Found", type, "at offset", offset, "size", size)
return buffer.slice(offset + 8, offset + size)
}
const visualSampleEntryTypes = ["avc1", "avc3", "hvc1", "hev1"]
const isVisualSampleEntry = visualSampleEntryTypes.includes(type)
if (isVisualSampleEntry) {
// VisualSampleEntry: 前 78 字节是固定字段,子 box 在 78 字节之后
// 先尝试 offset+8+78,如果没找到再尝试 offset+8(兼容不同 MP4 结构)
console.log(
"[findCodecConfig] VisualSampleEntry:",
type,
"at",
offset,
"size",
size,
"trying offset+8+78",
)
const childResult1 = findCodecConfigRecursive(buffer, offset + 8 + 78, offset + size)
if (childResult1) return childResult1
console.log(
"[findCodecConfig] VisualSampleEntry:",
type,
"at",
offset,
"trying offset+8 (fallback)",
)
const childResult2 = findCodecConfigRecursive(buffer, offset + 8, offset + size)
if (childResult2) return childResult2
} else {
// 普通容器 box,从 offset+8 开始递归
const childResult = findCodecConfigRecursive(buffer, offset + 8, offset + size)
if (childResult) return childResult
}
offset += size
}
return undefined
}
// ── 帧队列(环形缓冲区) ──
interface FrameEntry {
frame: VideoFrame
@@ -81,6 +153,10 @@ interface SegmentMeta {
videoWidth: number
/** 视频高度(像素) */
videoHeight: number
/** 解码器配置数据(HEVC hvcC / H.264 avcC),WebCodecs 必需 */
description?: ArrayBuffer
/** 前端提取的样本数据(已按时间范围过滤,从关键帧开始) */
samples: Sample[]
}
// ── 播放器状态 ──
@@ -141,6 +217,10 @@ export function useCanvasPlayer(
// ── 内部引用 ──
const decoderRef = useRef<VideoDecoder | null>(null)
const frameQueueRef = useRef(new FrameQueue(10))
/** 已解码的片段索引集合,用于按需解码 */
const decodedSegmentsRef = useRef(new Set<number>())
/** 上一帧引用,用于在绘制新帧前释放上一帧防止内存泄漏 */
const lastDrawnFrameRef = useRef<VideoFrame | null>(null)
const rafRef = useRef<number>(0)
const playStartRef = useRef<number>(0)
const playStartOffsetRef = useRef<number>(0)
@@ -171,11 +251,51 @@ export function useCanvasPlayer(
}
}, [])
// ── 解封装单个片段,提取轨道元数据 ──
// ── 从 MP4 buffer 提取编解码器配置数据(avcC / hvcC) ──
// WebCodecs VideoDecoder 对 HEVC/H.265 必须提供 description 字段
const extractCodecDescription = useCallback((buffer: ArrayBuffer): ArrayBuffer | undefined => {
try {
const view = new DataView(buffer)
let offset = 0
// 查找 moov box
while (offset < buffer.byteLength - 8) {
const size = view.getUint32(offset)
const type = String.fromCharCode(
view.getUint8(offset + 4),
view.getUint8(offset + 5),
view.getUint8(offset + 6),
view.getUint8(offset + 7),
)
if (type === "moov") {
const result = findCodecConfigRecursive(buffer, offset + 8, offset + size)
console.log("[useCanvasPlayer] extractCodecDescription:", {
moovOffset: offset,
moovSize: size,
searchRange: [offset + 8, offset + size],
found: !!result,
resultByteLength: result?.byteLength,
})
return result
}
if (size === 0) break
offset += size
}
} catch (e) {
console.warn("[useCanvasPlayer] extractCodecDescription failed:", e)
}
return undefined
}, [])
// ── 解封装单个片段,提取轨道元数据 + 按时间范围过滤样本 ──
// ✅ 关键修复:改为异步函数,等待 MP4Box.js 的 onSamples 回调完成后再返回
const demuxSegment = useCallback(
(buffer: ArrayBuffer, segIndex: number): SegmentMeta | null => {
const segment = segments[segIndex]
if (!segment) return null
async (buffer: ArrayBuffer, segIndex: number): Promise<SegmentMeta | null> => {
const segment = segments?.[segIndex]
if (!segment) {
console.warn("[useCanvasPlayer] No segment at index", segIndex)
return null
}
// 计算全局偏移
let globalStart = 0
@@ -184,55 +304,153 @@ export function useCanvasPlayer(
}
const mp4File = createFile()
let meta: SegmentMeta | null = null
mp4File.onReady = (info: Movie) => {
const videoTrack = info.videoTracks[0]
if (!videoTrack) {
console.error("[useCanvasPlayer] No video track found for segment", segIndex)
return
return new Promise<SegmentMeta | null>((resolve) => {
let meta: SegmentMeta | null = null
let resolved = false
// ✅ 超时保护:5秒后如果 onSamples 没有触发,返回 null
const timeout = setTimeout(() => {
if (!resolved) {
console.error(
`[useCanvasPlayer] Timeout: onSamples not triggered for segment ${segIndex}`,
)
resolved = true
resolve(null)
}
}, 5000)
mp4File.onReady = (info: Movie) => {
const videoTrack = info?.videoTracks?.[0]
console.log("[useCanvasPlayer] demuxSegment:", {
segIndex,
startTime: segment.startTime,
endTime: segment.endTime,
nbSamples: videoTrack?.nb_samples,
codec: videoTrack?.codec,
videoWidth: videoTrack?.track_width,
videoHeight: videoTrack?.track_height,
})
if (!videoTrack) {
console.warn("[useCanvasPlayer] No video track found for segment", segIndex)
clearTimeout(timeout)
resolved = true
resolve(null)
return
}
// 提取编解码器配置数据(HEVC 必需,H.264 也需要)
const description = extractCodecDescription(buffer)
// ✅ 如果 description 缺失,无法解码 HEVC
if (!description) {
console.error(
`[useCanvasPlayer] No description found for segment ${segIndex}, cannot decode HEVC`,
)
clearTimeout(timeout)
resolved = true
resolve(null)
return
}
meta = {
assetId: segment.assetId,
videoUrl: segment.videoUrl,
globalStartTime: globalStart,
globalEndTime: globalStart + (segment.endTime - segment.startTime),
trackId: videoTrack.id ?? 1,
timescale: videoTrack.timescale ?? 90000,
codec: videoTrack.codec ?? "avc1.42E01E",
videoWidth: videoTrack.track_width || 1280,
videoHeight: videoTrack.track_height || 720,
description,
samples: [],
}
// 提取所有 samples
mp4File.setExtractionOptions(videoTrack.id ?? 1, null, {
nbSamples: Infinity, // 提取所有 sample
})
mp4File.start()
}
meta = {
assetId: segment.assetId,
videoUrl: segment.videoUrl,
globalStartTime: globalStart,
globalEndTime: globalStart + (segment.endTime - segment.startTime),
trackId: videoTrack.id,
timescale: videoTrack.timescale,
codec: videoTrack.codec,
videoWidth: videoTrack.track_width || 1280,
videoHeight: videoTrack.track_height || 720,
mp4File.onSamples = (_trackId: number, _user: unknown, samples: Sample[]) => {
if (resolved) return // ✅ 防止重复 resolve
if (!meta) {
clearTimeout(timeout)
resolved = true
resolve(null)
return
}
// 前端切片:按 [startTime, endTime] 时间范围过滤样本
const timescale = meta.timescale
const startCts = segment.startTime * timescale
const endCts = segment.endTime * timescale
// 过滤出时间范围内的样本
let filtered = samples.filter((s) => (s?.cts ?? 0) >= startCts && (s?.cts ?? 0) < endCts)
// 确保从关键帧开始(跳过第一个 sync 之前的非关键帧)
let foundSync = false
filtered = filtered.filter((s) => {
if (s.is_sync) {
foundSync = true
return true
}
return foundSync
})
// Fallback:如果时间范围内没有样本,使用全部样本从第一个关键帧开始
if (filtered.length === 0) {
console.warn(
`[useCanvasPlayer] No samples in range [${segment.startTime}s, ${segment.endTime}s] for segment ${segIndex}, fallback to all from keyframe`,
)
let sync = false
filtered = samples.filter((s) => {
if (s.is_sync) {
sync = true
return true
}
return sync
})
}
meta.samples = filtered
console.log(
`[useCanvasPlayer] Segment ${segIndex}: ${filtered.length}/${samples.length} samples (range ${segment.startTime}s-${segment.endTime}s)`,
)
// ✅ 关键修复:等待 onSamples 完成后再返回
clearTimeout(timeout)
resolved = true
resolve(meta)
}
// 提取所有 samples(start() 会同步触发 onSamples)
mp4File.setExtractionOptions(videoTrack.id, null, {
nbSamples: videoTrack.nb_samples,
})
mp4File.start()
}
mp4File.onError = (_module: string, message: string) => {
console.error(`[useCanvasPlayer] MP4Box error: ${message}`)
clearTimeout(timeout)
resolved = true
resolve(null)
}
mp4File.onError = (_module: string, message: string) => {
console.error(`[useCanvasPlayer] MP4Box error: ${message}`)
}
// mp4box 需要在 buffer 上设置 fileStart 属性
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(buffer as any).fileStart = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mp4File.appendBuffer(buffer as any)
return meta
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(buffer as any).fileStart = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mp4File.appendBuffer(buffer as any)
})
},
[segments],
[segments, extractCodecDescription],
)
// ── 初始化 VideoDecoder 并解码指定片段 ──
const decodeSegment = useCallback(
async (buffer: ArrayBuffer, meta: SegmentMeta): Promise<void> => {
async (_buffer: ArrayBuffer, meta: SegmentMeta): Promise<void> => {
if (isDestroyedRef.current) return
const mp4File = createFile()
let decoderReady = false
// 配置解码器(每个片段可能需要不同的 codec/分辨率)
@@ -251,11 +469,20 @@ export function useCanvasPlayer(
},
})
console.log("[useCanvasPlayer] configure:", {
codec: meta.codec,
description: meta.description,
descriptionByteLength: meta.description?.byteLength,
videoWidth: meta.videoWidth,
videoHeight: meta.videoHeight,
})
try {
await decoder.configure({
codec: meta.codec,
codedWidth: meta.videoWidth,
codedHeight: meta.videoHeight,
...(meta.description ? { description: meta.description } : {}),
})
decoderRef.current = decoder
decoderReady = true
@@ -271,45 +498,24 @@ export function useCanvasPlayer(
if (!decoderReady) return
// 收集 samples
const samplesCollected: Sample[] = []
mp4File.onReady = (info: Movie) => {
const videoTrack = info.videoTracks[0]
if (!videoTrack) return
mp4File.setExtractionOptions(videoTrack.id, null, {
nbSamples: videoTrack.nb_samples,
})
mp4File.start()
// 使用 demuxSegment 中已提取并过滤的 samples(前端切片)
const samplesCollected = meta.samples
if (samplesCollected.length === 0) {
console.warn("[useCanvasPlayer] No samples to decode for segment", meta.assetId)
return
}
mp4File.onSamples = (_trackId: number, _user: unknown, samples: Sample[]) => {
samplesCollected.push(...samples)
}
mp4File.onError = (_module: string, message: string) => {
console.error(`[useCanvasPlayer] MP4Box decode error: ${message}`)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(buffer as any).fileStart = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mp4File.appendBuffer(buffer as any)
// 等待 samples 收集完成(mp4box start() 是同步的)
await new Promise((resolve) => setTimeout(resolve, 10))
// 送入解码器
for (const sample of samplesCollected) {
if (!sample.data || isDestroyedRef.current) continue
if (decoder.state === "closed") break
if (!sample.data) continue
const chunk = new EncodedVideoChunk({
type: sample.is_sync ? "key" : "delta",
timestamp: (sample.cts / meta.timescale) * 1_000_000,
duration: (sample.duration / meta.timescale) * 1_000_000,
data: sample.data.buffer as ArrayBuffer,
timestamp: ((sample.cts ?? 0) / (meta.timescale || 90000)) * 1_000_000,
duration: ((sample.duration ?? 0) / (meta.timescale || 90000)) * 1_000_000,
data: sample.data,
})
try {
@@ -329,6 +535,52 @@ export function useCanvasPlayer(
[],
)
/**
* 按需解码当前播放位置 ±1 个片段。
* 在渲染循环中定期调用,避免一次性解码所有片段导致环形缓冲区溢出丢帧。
*/
const decodeAroundPosition = useCallback(
async (currentTime: number) => {
const metas = segmentMetaRef.current
if (!metas || metas.length === 0) return
// 找到当前时间对应的片段索引
let targetIdx = -1
let acc = 0
for (let i = 0; i < metas.length; i++) {
const dur = metas[i].globalEndTime - metas[i].globalStartTime
if (currentTime < acc + dur) {
targetIdx = i
break
}
acc += dur
}
if (targetIdx === -1) targetIdx = metas.length - 1
// 解码当前 ±1 片段
for (
let i = Math.max(0, targetIdx - 1);
i <= Math.min(metas.length - 1, targetIdx + 1);
i++
) {
if (decodedSegmentsRef.current.has(i)) continue
const meta = metas[i]
const buffer = segmentDataRef.current.get(meta.assetId)
if (!buffer) continue
// 先标记为解码中,防止下一帧重复发起解码
decodedSegmentsRef.current.add(i)
try {
await decodeSegment(buffer, meta)
} catch (e) {
// 解码失败则移除标记,允许后续重试
decodedSegmentsRef.current.delete(i)
console.warn(`[useCanvasPlayer] 按需解码片段 ${i} 失败:`, e)
}
}
},
[decodeSegment],
)
// ── 标题绘制 ──
const drawTitle = useCallback(
(
@@ -341,17 +593,24 @@ export function useCanvasPlayer(
ctx.fillStyle = title.color
ctx.textAlign = "center"
let y: number
// 按 "/" 分割为多行("/" 作为手动换行符)
const lines = title.text.split(/[//⁄∕]/)
console.log("[drawTitle] 原始标题:", JSON.stringify(title.text), "分割后:", lines)
const lineHeight = fontSize * 1.3
const totalHeight = lines.length * lineHeight
// 根据 position 计算第一行的 Y 坐标
let startY: number
switch (title.position) {
case "top":
y = fontSize + canvas.height * 0.08
startY = fontSize + canvas.height * 0.08
break
case "bottom":
y = canvas.height - canvas.height * 0.08
startY = canvas.height - canvas.height * 0.08 - totalHeight + lineHeight
break
case "center":
default:
y = canvas.height / 2
startY = (canvas.height - totalHeight) / 2 + lineHeight
break
}
@@ -362,13 +621,15 @@ export function useCanvasPlayer(
ctx.shadowOffsetY = 2
}
if (title.stroke) {
ctx.strokeStyle = "#000000"
ctx.lineWidth = 1
ctx.strokeText(title.text, canvas.width / 2, y)
}
ctx.fillText(title.text, canvas.width / 2, y)
lines.forEach((line, idx) => {
const y = startY + idx * lineHeight
if (title.stroke) {
ctx.strokeStyle = "#000000"
ctx.lineWidth = 1
ctx.strokeText(line, canvas.width / 2, y)
}
ctx.fillText(line, canvas.width / 2, y)
})
ctx.shadowColor = "transparent"
ctx.shadowBlur = 0
@@ -423,12 +684,19 @@ export function useCanvasPlayer(
const frame = frameQueueRef.current.getCurrentFrame(currentTime)
// 释放上一帧,防止 VideoFrame 内存泄漏
if (lastDrawnFrameRef.current) {
lastDrawnFrameRef.current.close()
lastDrawnFrameRef.current = null
}
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (frame) {
const rect = computeDrawRect(canvas.width, canvas.height)
ctx.drawImage(frame, rect.dx, rect.dy, rect.dw, rect.dh)
frame.close()
// 保持引用,下一帧绘制时再释放
lastDrawnFrameRef.current = frame
}
if (titleSettings?.text) {
@@ -445,6 +713,8 @@ export function useCanvasPlayer(
}
return s
})
// 按需解码当前 ±1 片段
decodeAroundPosition(currentTime)
}
if (currentTime >= totalDuration) {
@@ -453,7 +723,7 @@ export function useCanvasPlayer(
}
rafRef.current = requestAnimationFrame(renderFrame)
}, [canvasRef, totalDuration, titleSettings, drawTitle, computeDrawRect])
}, [canvasRef, totalDuration, titleSettings, drawTitle, computeDrawRect, decodeAroundPosition])
// ── 播放控制 ──
const play = useCallback(async () => {
@@ -472,15 +742,22 @@ export function useCanvasPlayer(
}, [])
const seek = useCallback(
(time: number) => {
async (time: number) => {
const clampedTime = Math.max(0, Math.min(time, totalDuration))
setState((s) => ({ ...s, currentTime: clampedTime }))
playStartOffsetRef.current = clampedTime
playStartRef.current = performance.now()
// seek 后清空帧队列,等待新帧解码
// seek 后清空帧队列 + 释放上一帧
frameQueueRef.current.clear()
if (lastDrawnFrameRef.current) {
lastDrawnFrameRef.current.close()
lastDrawnFrameRef.current = null
}
// 清空已解码标记,重新解码 seek 目标区域
decodedSegmentsRef.current.clear()
await decodeAroundPosition(clampedTime)
},
[totalDuration],
[totalDuration, decodeAroundPosition],
)
const destroy = useCallback(() => {
@@ -491,9 +768,16 @@ export function useCanvasPlayer(
decoderRef.current.close()
}
// 释放上一帧引用
if (lastDrawnFrameRef.current) {
lastDrawnFrameRef.current.close()
lastDrawnFrameRef.current = null
}
frameQueueRef.current.clear()
segmentDataRef.current.clear()
segmentMetaRef.current = []
decodedSegmentsRef.current.clear()
}, [])
// ── 预加载下一个片段的数据 ──
@@ -510,7 +794,16 @@ export function useCanvasPlayer(
// ── 初始化:加载并解码所有片段 ──
useEffect(() => {
if (!state.hasSupport || segments.length === 0) return
if (!state.hasSupport || segments.length === 0) {
console.log("[useCanvasPlayer] Skip init:", {
hasSupport: state.hasSupport,
segmentCount: segments.length,
})
return
}
let cancelled = false
console.log("[useCanvasPlayer] Init start, segments:", segments.length)
const init = async () => {
setState((s) => ({ ...s, isBuffering: true }))
@@ -518,20 +811,37 @@ export function useCanvasPlayer(
// 1. 加载所有片段数据
for (const seg of segments) {
await loadSegment(seg)
if (cancelled) {
console.log("[useCanvasPlayer] Cancelled during loadSegment")
return
}
}
if (isDestroyedRef.current) return
// 验证 buffer 是否都已存入
const bufferCheck = segments.map((s) => ({
assetId: s.assetId,
hasBuffer: segmentDataRef.current.has(s.assetId),
}))
console.log("[useCanvasPlayer] Buffers loaded:", bufferCheck)
// 2. 解析每个片段的轨道元数据
// 2. 解析每个片段的轨道元数据(await 等待 onSamples 回调完成)
const metas: SegmentMeta[] = []
for (let i = 0; i < segments.length; i++) {
const buffer = segmentDataRef.current.get(segments[i].assetId)
if (!buffer) continue
const meta = demuxSegment(buffer, i)
if (!buffer) {
console.warn("[useCanvasPlayer] No buffer for segment", i, segments[i].assetId)
continue
}
const meta = await demuxSegment(buffer, i)
if (cancelled) {
console.log("[useCanvasPlayer] Cancelled during demuxSegment")
return
}
if (meta) metas.push(meta)
}
if (isDestroyedRef.current || metas.length === 0) {
if (cancelled || metas.length === 0) {
console.warn("[useCanvasPlayer] Init failed:", { cancelled, metasCount: metas.length })
setState((s) => ({ ...s, isBuffering: false }))
return
}
@@ -543,20 +853,36 @@ export function useCanvasPlayer(
videoDimRef.current = { width: metas[0].videoWidth, height: metas[0].videoHeight }
}
// 4. 依次解码每个片段
for (const meta of metas) {
// 4. 按需解码:初始只解码前 3 个片段,后续通过 decodeAroundPosition 动态加载
// 避免一次性全量解码导致 frameQueue 环形缓冲区旧帧被丢弃引发黑屏
decodedSegmentsRef.current.clear()
const initialDecodeCount = Math.min(metas.length, 3)
for (let i = 0; i < initialDecodeCount; i++) {
if (cancelled) break
const meta = metas[i]
const buffer = segmentDataRef.current.get(meta.assetId)
if (!buffer) continue
await decodeSegment(buffer, meta)
if (isDestroyedRef.current) break
// 先标记为解码中,防止重复解码
decodedSegmentsRef.current.add(i)
try {
await decodeSegment(buffer, meta)
} catch (e) {
// 解码失败则移除标记,允许后续重试
decodedSegmentsRef.current.delete(i)
console.warn(`[useCanvasPlayer] 初始化解码片段 ${i} 失败:`, e)
}
}
setState((s) => ({ ...s, duration: totalDuration, isReady: true, isBuffering: false }))
if (!cancelled) {
console.log("[useCanvasPlayer] Init complete, isReady = true")
setState((s) => ({ ...s, duration: totalDuration, isReady: true, isBuffering: false }))
}
}
init()
return () => {
cancelled = true
destroy()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -5,7 +5,7 @@
import { useState, useCallback } from "react"
import { message } from "antd"
import type { GeneratedVideo } from "@/api/template-editor"
import { confirmGeneration, createPreview } from "@/api/generation"
import { createGenerationTask } from "@/api/tasks/tasks"
import type { UseGenerateVideoProps } from "./generate-video/types"
import { getGenerationPhase } from "./generate-video/phase"
import { useGenerationPolling } from "./generate-video/useGenerationPolling"
@@ -55,7 +55,7 @@ export function useGenerateVideo(props: UseGenerateVideoProps) {
clearTimer()
try {
// 解析分辨率:videoRatio 可能是 "9:16"(宽高比)或 "1080x1920"(分辨率)
// 解析分辨率
const ratio = props.videoRatio || "9:16"
let outputWidth: number
let outputHeight: number
@@ -87,20 +87,22 @@ export function useGenerateVideo(props: UseGenerateVideoProps) {
outputHeight = 1920
}
// 获取或创建后端任务 ID
// 预览改为前端播放后,不再有预览任务,需要在此处创建
let taskId = props.previewTaskId
if (!taskId) {
const assetIds =
props.materialMode === "auto" ? props.smartSelectedIds : props.selectedMaterials
const previewResp = await createPreview({
template_id: selectedTemplate,
asset_ids: assetIds,
duration: props.duration || undefined,
video_ratio: props.videoRatio,
voice_ids: undefined,
title_config: props.titleSettings?.title
? {
const assetIds =
props.materialMode === "auto" ? props.smartSelectedIds : props.selectedMaterials
// 直接创建正式生成任务
await createGenerationTask({
template_id: selectedTemplate,
asset_ids: assetIds,
output_width: outputWidth,
output_height: outputHeight,
cover_url: props.coverSettings?.upload_url || "",
custom_title: props.titleSettings?.title || "",
duration: props.duration || undefined,
video_ratio: props.videoRatio,
...(props.titleSettings?.title
? {
title_config: {
text: props.titleSettings.title,
font: props.titleSettings.font,
font_size: props.titleSettings.size,
@@ -109,17 +111,9 @@ export function useGenerateVideo(props: UseGenerateVideoProps) {
bold: props.titleSettings.bold,
stroke: props.titleSettings.stroke,
shadow: props.titleSettings.shadow,
}
: undefined,
})
taskId = previewResp.task_id
}
await confirmGeneration(taskId, {
output_width: outputWidth,
output_height: outputHeight,
cover_url: props.coverSettings.upload_url || "",
custom_title: props.titleSettings.title || "",
},
}
: {}),
})
startPolling()
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useState } from "react"
import { message } from "antd"
import type { CoverConfig, CoverTemplate } from "../types/cover"
import { generateCover } from "@/api/generation"
import { createPreview, getPreviewStatus } from "@/api/generation/preview"
import {
fetchCoverTemplates,
createCoverTemplate,
@@ -111,8 +112,87 @@ export function useStep6Cover({
// 提取详细错误信息
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anyErr = err as any
// 如果 API 拦截器已经弹出了后端返回的具体错误信息,这里跳过重复 toast
if (anyErr?.__msgShown) {
const statusCode = anyErr?.response?.status
// 400 错误:精确判断是否为"预览缺失",避免误判其他 400 错误
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const errCode = anyErr?.response?.data?.code as string | undefined
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const errMsg = (anyErr?.response?.data?.message ||
anyErr?.response?.data?.detail ||
"") as string
const isPreviewMissing =
statusCode === 400 &&
(errCode?.includes("PREVIEW") ||
/预览.*(?:缺失|不存在|未找到)|(?:missing|not found|does not exist).*preview/i.test(
errMsg,
))
if (isPreviewMissing) {
console.log("[Step6] 检测到预览缺失,尝试自动创建预览渲染任务...")
message.info("正在准备预览视频,请稍候...")
try {
const previewResp = await createPreview({
template_id: selectedTemplate,
asset_ids: assetIds,
duration: duration || 30,
})
// 轮询等待预览渲染完成,双重超时保护
const maxPolls = 60 // 最多轮询 60 次(每 2 秒,共 120 秒)
let pollCount = 0
await new Promise<void>((resolve, reject) => {
// 总超时保护:120 秒后强制 reject
const timeoutId = setTimeout(() => {
clearInterval(poll)
reject(new Error("预览生成超时,请稍后重试"))
}, 120_000)
const poll = setInterval(async () => {
pollCount++
try {
const status = await getPreviewStatus(previewResp.task_id)
if (status.status === "completed") {
clearTimeout(timeoutId)
clearInterval(poll)
resolve()
} else if (status.status === "failed") {
clearTimeout(timeoutId)
clearInterval(poll)
reject(new Error(status.error_message || "预览渲染失败"))
}
if (pollCount >= maxPolls) {
clearTimeout(timeoutId)
clearInterval(poll)
reject(new Error("预览生成超时,请稍后重试"))
}
} catch (e) {
clearTimeout(timeoutId)
clearInterval(poll)
reject(e)
}
}, 2000)
})
message.success("预览视频就绪,重新生成封面...")
// 重试封面生成
const retryResp = await generateCover(selectedTemplate, {
asset_ids: assetIds,
cover_type: "ai_frame",
})
const retryUrl = retryResp.cover?.image_url || ""
if (retryUrl) {
onCoverSettingsChange({
...coverSettings,
thumbnail_url: retryUrl,
ai_suggested_time: retryResp.cover?.frame_time ?? null,
})
message.success("封面生成成功")
} else {
message.warning("封面生成未返回图片,请重试")
}
} catch (retryErr) {
console.error("[Step6] 自动创建预览后重试失败:", retryErr)
message.error("预览视频创建失败,请稍后重试")
}
} else if (anyErr?.__msgShown) {
// 拦截器已处理,不再重复弹出
} else {
let errorMsg = "封面生成失败"
@@ -137,7 +217,7 @@ export function useStep6Cover({
clearTimeout(timeoutId)
setGenerating(false)
}
}, [selectedTemplate, assetIds, coverSettings, onCoverSettingsChange, generating])
}, [selectedTemplate, assetIds, coverSettings, onCoverSettingsChange, generating, duration])
// ── 模板操作方法 ──
const handleSelectTemplate = useCallback((id: string) => {
@@ -0,0 +1,54 @@
/**
* 共享:根据素材列表和模板片段计算总视频时长
* GeneratePage(配音校验)和 FrontendPreviewPlayer(播放控制)共用
*/
export interface DurationAsset {
id?: string
duration?: number
metadata?: { duration?: number }
}
export interface DurationTemplateSegment {
duration_min?: number
duration_max?: number
}
/**
* 计算总视频时长
* @param assets 素材列表
* @param template 模板(含 segments)
* @returns 总时长(秒),无有效数据时返回 0
*/
export function calculateTotalVideoDuration(
assets: DurationAsset[] | undefined,
template: { segments?: DurationTemplateSegment[] } | undefined,
): number {
if (!assets || assets.length === 0 || !template) return 0
const templateSegments = template.segments || []
return assets.reduce((sum, asset, i) => {
const assetDuration = asset.duration || asset.metadata?.duration || 30
const tplSeg = templateSegments[i] || templateSegments[templateSegments.length - 1]
const segDuration = tplSeg
? Math.min(
tplSeg.duration_max ?? assetDuration,
Math.max(tplSeg.duration_min ?? 0, assetDuration),
)
: Math.min(assetDuration, 10)
return sum + segDuration
}, 0)
}
/**
* 估算总视频时长(仅依赖模板 segments)
* 当素材未加载或加载失败时,用各片段 duration_max 之和作为估算值
* 确保配音时长校验不会因素材未就绪而跳过
*/
export function estimateTotalVideoDuration(
template: { segments?: DurationTemplateSegment[] } | undefined,
): number {
if (!template?.segments || template.segments.length === 0) return 0
return template.segments.reduce((sum, seg) => sum + (seg.duration_max || 0), 0)
}