diff --git a/components/video-grid-layout.tsx b/components/video-grid-layout.tsx index 94dc84d..c8d62a2 100644 --- a/components/video-grid-layout.tsx +++ b/components/video-grid-layout.tsx @@ -1,7 +1,7 @@ 'use client'; // Add this to ensure it's a client component import React, { useState, useRef } from 'react'; -import { Edit2, Trash2, Play, Volume2, VolumeX } from 'lucide-react'; +import { Edit2, Trash2, Play, Pause, Volume2, VolumeX, Maximize, Minimize } from 'lucide-react'; import { Button } from '@/components/ui/button'; import dynamic from 'next/dynamic'; @@ -20,6 +20,8 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP const [hoveredId, setHoveredId] = useState(null); const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({}); const [isMuted, setIsMuted] = useState<{ [key: string]: boolean }>({}); + const [volume, setVolume] = useState<{ [key: string]: number }>({}); + const [isFullscreen, setIsFullscreen] = useState<{ [key: string]: boolean }>({}); const videoRefs = useRef<{ [key: string]: HTMLVideoElement | null }>({}); const handleMouseEnter = (id: string) => { @@ -43,10 +45,9 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP if (video) { if (video.paused) { // 在用户主动播放时取消静音 - video.muted = false; + video.muted = isMuted[id] !== false; video.play(); setIsPlaying(prev => ({ ...prev, [id]: true })); - setIsMuted(prev => ({ ...prev, [id]: false })); } else { video.pause(); setIsPlaying(prev => ({ ...prev, [id]: false })); @@ -63,6 +64,34 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP } }; + const handleVolumeChange = (id: string, newVolume: number) => { + const video = videoRefs.current[id]; + if (video) { + video.volume = newVolume; + setVolume(prev => ({ ...prev, [id]: newVolume })); + } + }; + + const toggleFullscreen = (id: string, event: React.MouseEvent) => { + event.stopPropagation(); + const video = videoRefs.current[id]; + if (video) { + if (!document.fullscreenElement) { + // 进入全屏 + video.requestFullscreen?.() || + (video as any).webkitRequestFullscreen?.() || + (video as any).msRequestFullscreen?.(); + setIsFullscreen(prev => ({ ...prev, [id]: true })); + } else { + // 退出全屏 + document.exitFullscreen?.() || + (document as any).webkitExitFullscreen?.() || + (document as any).msExitFullscreen?.(); + setIsFullscreen(prev => ({ ...prev, [id]: false })); + } + } + }; + return (
{videos.map((video) => ( @@ -82,39 +111,31 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP loop muted={isMuted[video.id] !== false} // 默认静音,除非明确设置为false playsInline + onLoadedData={() => { + // 设置默认音量 + if (videoRefs.current[video.id] && !volume[video.id]) { + videoRefs.current[video.id]!.volume = 0.8; + setVolume(prev => ({ ...prev, [video.id]: 0.8 })); + } + }} /> {/* 播放按钮遮罩 */}
togglePlay(video.id)} > - +
- {/* 操作按钮 */} + {/* 顶部操作按钮 */}
-
+ + {/* 底部控制区域 */} +
+ {/* 音量控制滑块 */} + {isPlaying[video.id] && ( +
+ + handleVolumeChange(video.id, parseFloat(e.target.value))} + className="flex-1 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[video.id] || 0.8) * 100}%, rgba(255,255,255,0.2) ${(volume[video.id] || 0.8) * 100}%, rgba(255,255,255,0.2) 100%)` + }} + /> + + {Math.round((volume[video.id] || 0.8) * 100)}% + +
+ )} + + {/* 控制按钮组 */} +
+ + + +
+
{/* 视频信息 */} diff --git a/components/video-screen-layout.tsx b/components/video-screen-layout.tsx index bef82e4..5c08109 100644 --- a/components/video-screen-layout.tsx +++ b/components/video-screen-layout.tsx @@ -1,7 +1,7 @@ 'use client'; // Add this to ensure it's a client component import React, { useState, useRef, useEffect } from 'react'; -import { ChevronLeft, ChevronRight, Volume2, VolumeX } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Volume2, VolumeX, Play, Pause } from 'lucide-react'; import { Button } from '@/components/ui/button'; import dynamic from 'next/dynamic'; @@ -17,6 +17,8 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) { const [currentIndex, setCurrentIndex] = useState(0); const [isAnimating, setIsAnimating] = useState(false); const [isMuted, setIsMuted] = useState(true); // 默认静音 + const [volume, setVolume] = useState(0.8); // 添加音量状态 + const [isPlaying, setIsPlaying] = useState(true); // 播放状态 const containerRef = useRef(null); const videoRefs = useRef<(HTMLVideoElement | null)[]>([]); @@ -60,6 +62,23 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) { } }; + // 音量控制函数 + const handleVolumeChange = (newVolume: number) => { + setVolume(newVolume); + const currentVideo = videoRefs.current[currentIndex]; + if (currentVideo) { + currentVideo.volume = newVolume; + } + }; + + // 应用音量设置到视频元素 + const applyVolumeSettings = (videoElement: HTMLVideoElement) => { + if (videoElement) { + videoElement.volume = volume; + videoElement.muted = isMuted; + } + }; + // 处理切换 const handleSlide = (direction: 'prev' | 'next') => { if (isAnimating) return; @@ -74,14 +93,51 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) { // 动画结束后重置状态并同步新视频的静音状态 setTimeout(() => { setIsAnimating(false); - // 同步新视频的静音状态到UI + // 同步新视频的静音状态到UI并应用音量设置 const newVideo = videoRefs.current[newIndex]; if (newVideo) { setIsMuted(newVideo.muted); + applyVolumeSettings(newVideo); + // 根据当前播放状态控制新视频 + if (isPlaying) { + newVideo.play().catch(() => { + setIsPlaying(false); + }); + } else { + newVideo.pause(); + } } }, 500); }; + // 音量设置变化时应用到当前视频 + useEffect(() => { + const currentVideo = videoRefs.current[currentIndex]; + if (currentVideo) { + applyVolumeSettings(currentVideo); + } + }, [volume, isMuted, currentIndex]); + + // 播放状态变化时应用到当前视频 + useEffect(() => { + const currentVideo = videoRefs.current[currentIndex]; + if (currentVideo) { + if (isPlaying) { + currentVideo.play().catch(() => { + // 处理自动播放策略限制 + setIsPlaying(false); + }); + } else { + currentVideo.pause(); + } + } + }, [isPlaying, currentIndex]); + + // 播放/暂停控制 + const togglePlay = () => { + setIsPlaying(!isPlaying); + }; + return (
{/* 视频面板容器 */} @@ -106,17 +162,39 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) { src={video.url} suppressHydrationWarning className="w-full h-full object-cover" - autoPlay + autoPlay={index === currentIndex ? isPlaying : false} loop muted={index === currentIndex ? isMuted : true} // 只有当前视频受状态控制 playsInline + onLoadedData={() => { + if (index === currentIndex && videoRefs.current[index]) { + applyVolumeSettings(videoRefs.current[index]!); + // 根据播放状态决定是否播放 + if (isPlaying) { + videoRefs.current[index]!.play().catch(() => { + setIsPlaying(false); + }); + } + } + }} + onPlay={() => { + if (index === currentIndex) { + setIsPlaying(true); + } + }} + onPause={() => { + if (index === currentIndex) { + setIsPlaying(false); + } + }} /> {/* 视频标题和控制 - 只在中间面板显示 */} {index === currentIndex && ( <> - {/* 音量控制按钮 */} -
+ {/* 音量控制区域 */} +
+ {/* 静音按钮 */} + + {/* 音量滑块 */} +
+ handleVolumeChange(parseFloat(e.target.value))} + className="w-12 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%)` + }} + /> + + {Math.round(volume * 100)}% + +
+
+ + {/* 底部控制区域 */} +
+ {/* 播放/暂停按钮 */} +
{/* 视频标题 */}