2025-07-03 05:51:09 +08:00

571 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Edit3, Play, Pause } from 'lucide-react';
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { GlassIconButton } from '@/components/ui/glass-icon-button';
interface MediaViewerProps {
currentStep: string;
currentSketchIndex: number;
taskSketch: any[];
taskVideos: any[];
isVideoPlaying: boolean;
isPlaying: boolean;
showControls: boolean;
isGeneratingSketch: boolean;
isGeneratingVideo: boolean;
onControlsChange: (show: boolean) => void;
onEditModalOpen: (tab: string) => void;
onToggleVideoPlay: () => void;
onTogglePlay: () => void;
final?: any;
}
export function MediaViewer({
currentStep,
currentSketchIndex,
taskSketch,
taskVideos,
isVideoPlaying,
isPlaying,
showControls,
isGeneratingSketch,
isGeneratingVideo,
onControlsChange,
onEditModalOpen,
onToggleVideoPlay,
onTogglePlay,
final
}: MediaViewerProps) {
const mainVideoRef = useRef<HTMLVideoElement>(null);
// 视频播放控制
useEffect(() => {
if (mainVideoRef.current) {
if (isVideoPlaying) {
mainVideoRef.current.play().catch(error => {
console.log('视频播放失败:', error);
});
} else {
mainVideoRef.current.pause();
}
}
}, [isVideoPlaying]);
// 当切换视频时重置视频播放
useEffect(() => {
if (mainVideoRef.current) {
mainVideoRef.current.currentTime = 0;
if (isVideoPlaying) {
mainVideoRef.current.play().catch(error => {
console.log('视频播放失败:', error);
});
}
}
}, [currentSketchIndex]);
// 渲染最终成片
const renderFinalVideo = () => {
// 使用真实的final数据如果没有则使用默认值
const finalVideo = final || {
url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4'
};
return (
<div
className="relative w-full h-full rounded-lg overflow-hidden"
onMouseEnter={() => onControlsChange(true)}
onMouseLeave={() => onControlsChange(false)}
>
<div className="relative w-full h-full">
{/* 背景模糊的视频 */}
<motion.div
className="absolute inset-0 overflow-hidden"
initial={{ filter: "blur(0px)", scale: 1, opacity: 1 }}
animate={{ filter: "blur(20px)", scale: 1.1, opacity: 0.5 }}
transition={{ duration: 0.8, ease: "easeInOut" }}
>
<video
className="w-full h-full rounded-lg object-cover object-center"
src={taskVideos[currentSketchIndex]?.url}
autoPlay
loop
playsInline
/>
</motion.div>
{/* 最终成片视频 */}
<motion.div
initial={{ clipPath: "inset(0 50% 0 50%)", filter: "blur(10px)" }}
animate={{ clipPath: "inset(0 0% 0 0%)", filter: "blur(0px)" }}
transition={{
clipPath: { duration: 1.2, ease: [0.43, 0.13, 0.23, 0.96] },
filter: { duration: 0.6, delay: 0.3 }
}}
className="relative z-10"
>
<video
className="w-full h-full object-cover rounded-lg"
src={finalVideo.url}
poster={taskSketch[currentSketchIndex]?.url}
autoPlay
loop
playsInline
/>
</motion.div>
{/* 操作按钮组 */}
<AnimatePresence>
{showControls && (
<motion.div
className="absolute top-4 right-4 z-10 flex gap-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
<GlassIconButton
icon={Edit3}
tooltip="Edit sketch"
onClick={() => onEditModalOpen('4')}
/>
</motion.div>
)}
</AnimatePresence>
{/* 视频信息浮层 */}
<motion.div
className="absolute bottom-0 left-0 right-0 z-10 p-4 bg-gradient-to-t from-black/80 via-black/40 to-transparent"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1, duration: 0.6 }}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<motion.div
className="w-2 h-2 rounded-full bg-emerald-500"
animate={{
scale: [1, 1.2, 1],
opacity: [1, 0.6, 1]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
<span className="text-sm font-medium text-white/90">Final product</span>
</div>
</div>
</motion.div>
{/* 完成标记 */}
<motion.div
className="absolute top-4 right-4 px-3 py-1.5 rounded-full bg-emerald-500/20 backdrop-blur-sm
border border-emerald-500/30 text-emerald-400 text-sm font-medium"
initial={{ opacity: 0, scale: 0.8, x: 20 }}
animate={{ opacity: 1, scale: 1, x: 0 }}
transition={{ delay: 1.2, duration: 0.6 }}
>
Task completed
</motion.div>
</div>
</div>
);
};
// 渲染视频内容
const renderVideoContent = () => {
const currentSketch = taskSketch[currentSketchIndex];
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
return (
<div
className="relative w-full h-full rounded-lg"
onMouseEnter={() => onControlsChange(true)}
onMouseLeave={() => onControlsChange(false)}
>
{/* 只在生成过程中或没有视频时使用ProgressiveReveal */}
{(isGeneratingVideo || !taskVideos[currentSketchIndex]) ? (
taskVideos[currentSketchIndex] ? (
<ProgressiveReveal
key={`generte-video-${currentSketchIndex}`}
className="w-full h-full rounded-lg"
revealDuration={0.8}
blurDuration={0.3}
initialBlur={10}
customVariants={{
hidden: {
opacity: 0,
scale: 0.9,
filter: "blur(30px)",
clipPath: "inset(0 100% 0 0)"
},
visible: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
clipPath: "inset(0 0% 0 0)",
transition: {
duration: 1.5,
ease: [0.23, 1, 0.32, 1],
opacity: { duration: 0.8, ease: "easeOut" },
scale: { duration: 1.2, ease: "easeOut" },
filter: { duration: 0.8, delay: 0.4, ease: "easeOut" },
clipPath: { duration: 1, ease: "easeInOut" }
}
}
}}
loadingBgConfig={{
fromColor: `from-[${bgColors[0]}]`,
viaColor: `via-[${bgColors[1]}]`,
toColor: `to-[${bgColors[2]}]`,
glowOpacity: 0.8,
duration: 4,
}}
>
<div className="relative w-full h-full">
{/* 背景模糊的图片 */}
<div className="absolute inset-0 overflow-hidden">
<img
className="w-full h-full object-cover filter blur-lg scale-110 opacity-50"
src={taskSketch[currentSketchIndex]?.url}
alt="background"
/>
</div>
{/* 视频 */}
<motion.div
initial={{ clipPath: "inset(0 100% 0 0)" }}
animate={{ clipPath: "inset(0 0% 0 0)" }}
transition={{ duration: 0.8, ease: [0.43, 0.13, 0.23, 0.96] }}
className="relative z-10"
>
<video
ref={mainVideoRef}
key={taskVideos[currentSketchIndex].url}
className="w-full h-full rounded-lg object-cover object-center"
src={taskVideos[currentSketchIndex].url}
autoPlay={isVideoPlaying}
loop={true}
playsInline
onEnded={() => {
if (isVideoPlaying) {
// 自动切换到下一个视频的逻辑在父组件处理
}
}}
/>
</motion.div>
</div>
</ProgressiveReveal>
) : (
<div className="w-full h-full flex items-center justify-center rounded-lg overflow-hidden relative">
<img
className="absolute inset-0 w-full h-full object-cover"
src={taskSketch[currentSketchIndex]?.url}
alt={`Sketch ${currentSketchIndex + 1}`}
/>
</div>
)
) : (
/* 生成完成后直接显示视频不使用ProgressiveReveal */
<div className="relative w-full h-full">
{/* 背景模糊的图片 */}
<div className="absolute inset-0 overflow-hidden">
<img
className="w-full h-full object-cover filter blur-lg scale-110 opacity-50"
src={taskSketch[currentSketchIndex]?.url}
alt="background"
/>
</div>
{/* 视频 修复播放没有声音 */}
<video
ref={mainVideoRef}
key={taskVideos[currentSketchIndex].url}
className="w-full h-full rounded-lg object-cover object-center relative z-10"
src={taskVideos[currentSketchIndex].url}
autoPlay={isVideoPlaying}
loop={true}
playsInline
onEnded={() => {
if (isVideoPlaying) {
// 自动切换到下一个视频的逻辑在父组件处理
}
}}
/>
</div>
)}
{/* 操作按钮组 */}
<AnimatePresence>
{showControls && (
<motion.div
className="absolute top-4 right-4 flex gap-2 z-[11]"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
<GlassIconButton
icon={Edit3}
tooltip="Edit sketch"
onClick={() => onEditModalOpen('3')}
/>
</motion.div>
)}
</AnimatePresence>
{/* 底部播放按钮 */}
<AnimatePresence>
<motion.div
className="absolute bottom-4 left-4 z-[11]"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
className="relative"
>
{/* 播放时的发光效果 */}
{isVideoPlaying && (
<motion.div
className="absolute inset-0 rounded-full blur-md"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 0.8, 0.5]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
)}
<GlassIconButton
icon={isVideoPlaying ? Pause : Play}
tooltip={isVideoPlaying ? "Pause video" : "Play video"}
onClick={onToggleVideoPlay}
size="sm"
/>
</motion.div>
</motion.div>
</AnimatePresence>
</div>
);
};
// 渲染分镜草图
const renderSketchContent = () => {
const currentSketch = taskSketch[currentSketchIndex];
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
return (
<div
className="relative w-full h-full rounded-lg"
onMouseEnter={() => onControlsChange(true)}
onMouseLeave={() => onControlsChange(false)}
>
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{(isGeneratingSketch || !currentSketch) ? (
currentSketch ? (
<ProgressiveReveal
key={`sketch-generating-${currentSketchIndex}`}
className="w-full h-full rounded-lg"
revealDuration={0.8}
blurDuration={0.3}
initialBlur={10}
customVariants={{
hidden: {
opacity: 0,
scale: 0.9,
filter: "blur(30px)"
},
visible: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
transition: {
duration: 1.5,
ease: [0.23, 1, 0.32, 1],
opacity: { duration: 0.8, ease: "easeOut" },
scale: { duration: 1.2, ease: "easeOut" },
filter: { duration: 0.8, delay: 0.4, ease: "easeOut" }
}
}
}}
loadingBgConfig={{
fromColor: `from-[${bgColors[0]}]`,
viaColor: `via-[${bgColors[1]}]`,
toColor: `to-[${bgColors[2]}]`,
glowOpacity: 0.8,
duration: 4,
}}
>
<img
key={currentSketchIndex}
src={currentSketch.url}
alt={`Sketch ${currentSketchIndex + 1}`}
className="w-full h-full rounded-lg object-cover"
/>
</ProgressiveReveal>
) : (
<div className="w-full h-full flex items-center justify-center rounded-lg overflow-hidden relative">
{/* 动态渐变背景 */}
<motion.div
className={`absolute inset-0 bg-gradient-to-r from-[${bgColors[0]}] via-[${bgColors[1]}] to-[${bgColors[2]}]`}
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 5,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "200% 200%",
}}
/>
{/* 动态光效 */}
<motion.div
className="absolute inset-0 opacity-50"
style={{
background: "radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, transparent 50%)",
}}
animate={{
scale: [1, 1.2, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
<motion.div
className="flex flex-col items-center gap-4 relative z-10"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="relative">
<motion.div
className="absolute -inset-4 bg-gradient-to-r from-white via-sky-200 to-cyan-200 rounded-full opacity-60 blur-xl"
animate={{
scale: [1, 1.2, 1],
rotate: [0, 180, 360],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "linear"
}}
/>
</div>
</motion.div>
</div>
)
) : (
/* 生成完成后直接显示图片不使用ProgressiveReveal */
<img
key={currentSketchIndex}
src={currentSketch.url}
alt={`Sketch ${currentSketchIndex + 1}`}
className="w-full h-full rounded-lg object-cover"
/>
)}
{/* 操作按钮组 */}
<AnimatePresence>
{showControls && (
<motion.div
className="absolute top-4 right-4 flex gap-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
<GlassIconButton
icon={Edit3}
tooltip="Edit sketch"
onClick={() => onEditModalOpen('1')}
/>
</motion.div>
)}
</AnimatePresence>
{/* 底部播放按钮 */}
<AnimatePresence>
<motion.div
className="absolute bottom-4 left-4"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
className="relative"
>
{/* 播放时的发光效果 */}
{isPlaying && (
<motion.div
className="absolute inset-0 rounded-full bg-blue-500/30 blur-md"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 0.8, 0.5]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
)}
<GlassIconButton
icon={isPlaying ? Pause : Play}
tooltip={isPlaying ? "Pause auto play" : "Start auto play"}
onClick={onTogglePlay}
size="sm"
className={isPlaying ? "border-blue-500/50 bg-blue-500/10" : ""}
/>
</motion.div>
</motion.div>
</AnimatePresence>
{/* 播放进度指示器 */}
<AnimatePresence>
{isPlaying && (
<motion.div
className="absolute bottom-0 left-0 right-0 h-1 bg-blue-500/20"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
exit={{ scaleX: 0 }}
transition={{ duration: 2, repeat: Infinity }}
style={{ transformOrigin: "left" }}
/>
)}
</AnimatePresence>
</div>
);
};
// 根据当前步骤渲染对应内容
if (Number(currentStep) === 6) {
return renderFinalVideo();
}
if (Number(currentStep) > 2 && Number(currentStep) < 6) {
return renderVideoContent();
}
return renderSketchContent();
}