'use client'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Carousel } from 'antd'; import type { CarouselRef } from 'antd/es/carousel'; import { Play, Scissors, MessageCircleMore, RotateCcw } from 'lucide-react'; import { TaskObject } from '@/api/DTO/movieEdit'; import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer'; import ScriptLoading from './script-loading'; import { getFirstFrame } from '@/utils/tools'; interface H5MediaViewerProps { /** 任务对象,包含各阶段数据 */ taskObject: TaskObject; /** 剧本数据(仅剧本阶段使用) */ scriptData: any; /** 当前索引(视频/分镜阶段用于定位) */ currentSketchIndex: number; /** 渲染模式(仅剧本阶段透传) */ mode: string; /** 以下为剧本阶段透传必需项(与桌面版保持一致) */ setIsPauseWorkFlow: (isPause: boolean) => void; setAnyAttribute: any; isPauseWorkFlow: boolean; applyScript: any; /** 打开智能对话 */ onOpenChat?: () => void; /** 设置聊天预览视频 */ setVideoPreview?: (url: string, id: string) => void; /** 显示跳转至剪辑平台按钮 */ showGotoCutButton?: boolean; /** 跳转至剪辑平台 */ onGotoCut?: () => void; /** 智能对话是否打开(H5可忽略布局调整,仅占位) */ isSmartChatBoxOpen?: boolean; /** 失败重试生成视频 */ onRetryVideo?: (video_id: string) => void; } /** * 面向 H5 的媒体预览组件。 * - 除剧本阶段外,统一使用 antd Carousel 展示 图片/视频。 * - 视频仅保留中间的大号播放/暂停按钮。 */ export function H5MediaViewer({ taskObject, scriptData, currentSketchIndex, mode, setIsPauseWorkFlow, setAnyAttribute, isPauseWorkFlow, applyScript, onOpenChat, setVideoPreview, showGotoCutButton, onGotoCut, isSmartChatBoxOpen, onRetryVideo }: H5MediaViewerProps) { const carouselRef = useRef(null); const videoRefs = useRef>([]); const rootRef = useRef(null); const [activeIndex, setActiveIndex] = useState(0); const [isPlaying, setIsPlaying] = useState(false); // 计算当前阶段类型 const stage = taskObject.currentStage; // 生成各阶段对应的 slides 数据 const videoUrls = useMemo(() => { if (stage === 'final_video') { return taskObject.final?.url ? [taskObject.final.url] : []; } if (stage === 'video') { // 注意:不再过滤,保持与原始数组长度一致,避免索引错位 const list = (taskObject.videos?.data ?? []) as Array; return list.map(v => (Array.isArray(v?.urls) && v.urls.length > 0 ? v.urls[0] : '')) as string[]; } return []; }, [stage, taskObject.final?.url, taskObject.videos?.data]); const imageUrls = useMemo(() => { if (stage === 'scene' || stage === 'character') { const roles = (taskObject.roles?.data ?? []) as Array; const scenes = (taskObject.scenes?.data ?? []) as Array; return [...roles, ...scenes].map(item => item?.url).filter(Boolean) as string[]; } return []; }, [stage, taskObject.roles?.data, taskObject.scenes?.data]); // 占位,避免未使用警告 useEffect(() => { void isSmartChatBoxOpen; }, [isSmartChatBoxOpen]); // 同步外部索引到 Carousel useEffect(() => { if (stage === 'video' || stage === 'scene' || stage === 'character') { const target = Math.max(0, currentSketchIndex); carouselRef.current?.goTo(target, false); setActiveIndex(target); setIsPlaying(false); // 切换时暂停全部视频 videoRefs.current.forEach(v => v?.pause()); } }, [currentSketchIndex, stage]); // 阶段变更时重置状态 useEffect(() => { setActiveIndex(0); setIsPlaying(false); videoRefs.current.forEach(v => v?.pause()); }, [stage]); const handleAfterChange = (index: number) => { setActiveIndex(index); setIsPlaying(false); videoRefs.current.forEach(v => v?.pause()); }; const togglePlay = () => { if (stage !== 'final_video' && stage !== 'video') return; const currentVideo = videoRefs.current[activeIndex] ?? null; if (!currentVideo) return; if (currentVideo.paused) { currentVideo.play().then(() => setIsPlaying(true)).catch(() => setIsPlaying(false)); } else { currentVideo.pause(); setIsPlaying(false); } }; // 渲染视频 slide const renderVideoSlides = () => (
{videoUrls.map((url, idx) => { const hasUrl = typeof url === 'string' && url.length > 0; const raw = (taskObject.videos?.data ?? [])[idx] as any; const status = raw?.video_status; const videoId = raw?.video_id as string | undefined; return (
{hasUrl ? ( <>
); })}
); // 渲染图片 slide const renderImageSlides = () => (
{imageUrls.map((url, idx) => (
scene
))}
); // 剧本阶段:不使用 Carousel,沿用 ScriptRenderer if (stage === 'script') { return (
{scriptData ? ( ) : ( )}
); } // 其他阶段:使用 Carousel return (
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()} {stage === 'video' && videoUrls.length > 0 && renderVideoSlides()} {(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()}
); } export default H5MediaViewer;