声音加开关

This commit is contained in:
Xin Wang 2025-07-03 07:35:13 +08:00
parent f6eea4afe1
commit 561516bc0c

View File

@ -1,8 +1,8 @@
'use client'; 'use client';
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { Edit3, Play, Pause } from 'lucide-react'; import { Edit3, Play, Pause, Volume2, VolumeX } from 'lucide-react';
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal'; import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { GlassIconButton } from '@/components/ui/glass-icon-button'; import { GlassIconButton } from '@/components/ui/glass-icon-button';
@ -40,10 +40,45 @@ export function MediaViewer({
final final
}: MediaViewerProps) { }: MediaViewerProps) {
const mainVideoRef = useRef<HTMLVideoElement>(null); const mainVideoRef = useRef<HTMLVideoElement>(null);
const finalVideoRef = useRef<HTMLVideoElement>(null);
// 音量控制状态
const [isMuted, setIsMuted] = useState(false);
const [volume, setVolume] = useState(0.8);
// 音量控制函数
const toggleMute = () => {
setIsMuted(!isMuted);
if (mainVideoRef.current) {
mainVideoRef.current.muted = !isMuted;
}
if (finalVideoRef.current) {
finalVideoRef.current.muted = !isMuted;
}
};
const handleVolumeChange = (newVolume: number) => {
setVolume(newVolume);
if (mainVideoRef.current) {
mainVideoRef.current.volume = newVolume;
}
if (finalVideoRef.current) {
finalVideoRef.current.volume = newVolume;
}
};
// 应用音量设置到视频元素
const applyVolumeSettings = (videoElement: HTMLVideoElement) => {
if (videoElement) {
videoElement.volume = volume;
videoElement.muted = isMuted;
}
};
// 视频播放控制 // 视频播放控制
useEffect(() => { useEffect(() => {
if (mainVideoRef.current) { if (mainVideoRef.current) {
applyVolumeSettings(mainVideoRef.current);
if (isVideoPlaying) { if (isVideoPlaying) {
mainVideoRef.current.play().catch(error => { mainVideoRef.current.play().catch(error => {
console.log('视频播放失败:', error); console.log('视频播放失败:', error);
@ -57,6 +92,7 @@ export function MediaViewer({
// 当切换视频时重置视频播放 // 当切换视频时重置视频播放
useEffect(() => { useEffect(() => {
if (mainVideoRef.current) { if (mainVideoRef.current) {
applyVolumeSettings(mainVideoRef.current);
mainVideoRef.current.currentTime = 0; mainVideoRef.current.currentTime = 0;
if (isVideoPlaying) { if (isVideoPlaying) {
mainVideoRef.current.play().catch(error => { mainVideoRef.current.play().catch(error => {
@ -66,6 +102,56 @@ export function MediaViewer({
} }
}, [currentSketchIndex]); }, [currentSketchIndex]);
// 音量设置变化时应用到所有视频
useEffect(() => {
if (mainVideoRef.current) {
applyVolumeSettings(mainVideoRef.current);
}
if (finalVideoRef.current) {
applyVolumeSettings(finalVideoRef.current);
}
}, [volume, isMuted]);
// 渲染音量控制组件
const renderVolumeControls = () => (
<div className="flex items-center gap-2">
{/* 静音按钮 */}
<GlassIconButton
icon={isMuted ? VolumeX : Volume2}
tooltip={isMuted ? "取消静音" : "静音"}
onClick={toggleMute}
size="sm"
/>
{/* 音量滑块 - 一直显示 */}
<div className="flex items-center gap-2">
<div className="relative">
<input
type="range"
min="0"
max="1"
step="0.1"
value={volume}
onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
className="w-16 h-1 bg-white/20 rounded-lg appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-lg
[&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3 [&::-moz-range-thumb]:rounded-full
[&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:cursor-pointer
[&::-moz-range-thumb]:border-none [&::-moz-range-thumb]:shadow-lg"
style={{
background: `linear-gradient(to right, white 0%, white ${volume * 100}%, rgba(255,255,255,0.2) ${volume * 100}%, rgba(255,255,255,0.2) 100%)`
}}
/>
</div>
<span className="text-xs text-white/70 w-8 text-center">
{Math.round(volume * 100)}%
</span>
</div>
</div>
);
// 渲染最终成片 // 渲染最终成片
const renderFinalVideo = () => { const renderFinalVideo = () => {
// 使用真实的final数据如果没有则使用默认值 // 使用真实的final数据如果没有则使用默认值
@ -108,12 +194,14 @@ export function MediaViewer({
className="relative z-10" className="relative z-10"
> >
<video <video
ref={finalVideoRef}
className="w-full h-full object-cover rounded-lg" className="w-full h-full object-cover rounded-lg"
src={finalVideo.url} src={finalVideo.url}
poster={taskSketch[currentSketchIndex]?.url} poster={taskSketch[currentSketchIndex]?.url}
autoPlay autoPlay
loop loop
playsInline playsInline
onLoadedData={() => applyVolumeSettings(finalVideoRef.current!)}
/> />
</motion.div> </motion.div>
@ -162,6 +250,16 @@ export function MediaViewer({
</div> </div>
</motion.div> </motion.div>
{/* 底部音量控制 */}
<motion.div
className="absolute bottom-4 left-4 z-10 flex items-center gap-3"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 1, duration: 0.6 }}
>
{renderVolumeControls()}
</motion.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 className="absolute top-4 right-4 px-3 py-1.5 rounded-full bg-emerald-500/20 backdrop-blur-sm
@ -253,6 +351,7 @@ export function MediaViewer({
autoPlay={isVideoPlaying} autoPlay={isVideoPlaying}
loop={true} loop={true}
playsInline playsInline
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
onEnded={() => { onEnded={() => {
if (isVideoPlaying) { if (isVideoPlaying) {
// 自动切换到下一个视频的逻辑在父组件处理 // 自动切换到下一个视频的逻辑在父组件处理
@ -292,6 +391,7 @@ export function MediaViewer({
autoPlay={isVideoPlaying} autoPlay={isVideoPlaying}
loop={true} loop={true}
playsInline playsInline
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
onEnded={() => { onEnded={() => {
if (isVideoPlaying) { if (isVideoPlaying) {
// 自动切换到下一个视频的逻辑在父组件处理 // 自动切换到下一个视频的逻辑在父组件处理
@ -320,15 +420,16 @@ export function MediaViewer({
)} )}
</AnimatePresence> </AnimatePresence>
{/* 底部播放按钮 */} {/* 底部控制区域 */}
<AnimatePresence> <AnimatePresence>
<motion.div <motion.div
className="absolute bottom-4 left-4 z-[11]" className="absolute bottom-4 left-4 z-[11] flex items-center gap-3"
initial={{ opacity: 0, scale: 0.8 }} initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }} exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
> >
{/* 播放按钮 */}
<motion.div <motion.div
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
@ -356,6 +457,9 @@ export function MediaViewer({
size="sm" size="sm"
/> />
</motion.div> </motion.div>
{/* 音量控制 */}
{renderVolumeControls()}
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
</div> </div>