fix: Canvas黑屏修复+配音时长校验fallback #1438

Closed
xiaoxia wants to merge 1 commits from fix/canvas-black-screen-voice-validation into develop
4 changed files with 78 additions and 26 deletions
+8 -10
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 ── */
@@ -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>
@@ -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) {
@@ -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)
}