From c7a202ae3f5249ba3d4f42bcf57895b4dc56a52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=8F=E6=9D=B0?= Date: Wed, 19 Aug 2026 13:13:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Canvas=E9=BB=91=E5=B1=8F=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D+=E9=85=8D=E9=9F=B3=E6=97=B6=E9=95=BF=E6=A0=A1?= =?UTF-8?q?=E9=AA=8Cfallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: Canvas 播放器黑屏 - useCanvasPlayer.ts renderFrame 中删除 frame.close() - FrameQueue 的 getCurrentFrame 统一管理帧释放,避免双重 close Bug 2: 配音时长校验不生效 - 新增 calculateTotalVideoDuration 共享工具函数 - GeneratePage.tsx: 素材为空时用模板 duration_max 之和估算总时长 - Step5VoiceSelect.tsx: Number() 类型转换防止字符串比较错误 - Step5VoiceSelect.tsx: 添加 debug log 便于后续排查 --- apps/web/src/pages/generate/GeneratePage.tsx | 18 +++---- .../generate/components/Step5VoiceSelect.tsx | 31 +++++------ .../pages/generate/hooks/useCanvasPlayer.ts | 1 - .../utils/calculateTotalVideoDuration.ts | 54 +++++++++++++++++++ 4 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 apps/web/src/pages/generate/utils/calculateTotalVideoDuration.ts diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 687e531b4..94d5488c6 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -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 ── */ diff --git a/apps/web/src/pages/generate/components/Step5VoiceSelect.tsx b/apps/web/src/pages/generate/components/Step5VoiceSelect.tsx index d799034bf..cd3f14bf7 100644 --- a/apps/web/src/pages/generate/components/Step5VoiceSelect.tsx +++ b/apps/web/src/pages/generate/components/Step5VoiceSelect.tsx @@ -272,21 +272,22 @@ const Step5VoiceSelect: React.FC = ({ > {formatDuration(item.duration)} - {totalVideoDuration > 0 && (item.duration || 0) < totalVideoDuration && ( - - - 时长不足 - - )} + {totalVideoDuration > 0 && + (Number(item.duration) || 0) < Number(totalVideoDuration) && ( + + + 时长不足 + + )} {formatFileSize(item.file_size)} diff --git a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts index 315cd91bf..ba20f84ed 100644 --- a/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts +++ b/apps/web/src/pages/generate/hooks/useCanvasPlayer.ts @@ -428,7 +428,6 @@ export function useCanvasPlayer( if (frame) { const rect = computeDrawRect(canvas.width, canvas.height) ctx.drawImage(frame, rect.dx, rect.dy, rect.dw, rect.dh) - frame.close() } if (titleSettings?.text) { diff --git a/apps/web/src/pages/generate/utils/calculateTotalVideoDuration.ts b/apps/web/src/pages/generate/utils/calculateTotalVideoDuration.ts new file mode 100644 index 000000000..c91b235e3 --- /dev/null +++ b/apps/web/src/pages/generate/utils/calculateTotalVideoDuration.ts @@ -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) +} -- 2.54.0