forked from 77media/video-flow
处理按钮
This commit is contained in:
parent
c9990dfce3
commit
68427b5afc
@ -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<string | null>(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 (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
|
||||
{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 }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 播放按钮遮罩 */}
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center bg-black/40 transition-opacity duration-300
|
||||
${hoveredId === video.id ? 'opacity-100' : 'opacity-0'}
|
||||
${hoveredId === video.id && !isPlaying[video.id] ? 'opacity-100' : 'opacity-0'}
|
||||
`}
|
||||
onClick={() => togglePlay(video.id)}
|
||||
>
|
||||
<Play className={`w-12 h-12 text-white/90 transition-transform duration-300
|
||||
${isPlaying[video.id] ? 'scale-90 opacity-0' : 'scale-100 opacity-100'}`}
|
||||
/>
|
||||
<Play className="w-12 h-12 text-white/90" />
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
{/* 顶部操作按钮 */}
|
||||
<div
|
||||
className={`absolute top-4 right-4 flex gap-2 transition-all duration-300 transform
|
||||
${hoveredId === video.id ? 'translate-y-0 opacity-100' : 'translate-y-[-10px] opacity-0'}
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
|
||||
onClick={(e) => toggleMute(video.id, e)}
|
||||
title={isMuted[video.id] !== false ? "取消静音" : "静音"}
|
||||
>
|
||||
{isMuted[video.id] !== false ? (
|
||||
<VolumeX className="w-4 h-4 text-white" />
|
||||
) : (
|
||||
<Volume2 className="w-4 h-4 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@ -132,6 +153,84 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
<Trash2 className="w-4 h-4 text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 底部控制区域 */}
|
||||
<div
|
||||
className={`absolute bottom-4 left-4 right-4 transition-all duration-300 transform
|
||||
${hoveredId === video.id ? 'translate-y-0 opacity-100' : 'translate-y-[10px] opacity-0'}
|
||||
`}
|
||||
>
|
||||
{/* 音量控制滑块 */}
|
||||
{isPlaying[video.id] && (
|
||||
<div className="flex items-center gap-2 bg-black/60 rounded-full px-3 py-2 backdrop-blur-sm mb-3">
|
||||
<Volume2 className="w-4 h-4 text-white" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={volume[video.id] || 0.8}
|
||||
onChange={(e) => 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%)`
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-white/70 w-8 text-center">
|
||||
{Math.round((volume[video.id] || 0.8) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 控制按钮组 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
|
||||
onClick={() => togglePlay(video.id)}
|
||||
title={isPlaying[video.id] ? "暂停" : "播放"}
|
||||
>
|
||||
{isPlaying[video.id] ? (
|
||||
<Pause className="w-4 h-4 text-white" />
|
||||
) : (
|
||||
<Play className="w-4 h-4 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
|
||||
onClick={(e) => toggleMute(video.id, e)}
|
||||
title={isMuted[video.id] !== false ? "取消静音" : "静音"}
|
||||
>
|
||||
{isMuted[video.id] !== false ? (
|
||||
<VolumeX className="w-4 h-4 text-white" />
|
||||
) : (
|
||||
<Volume2 className="w-4 h-4 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-8 h-8 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
|
||||
onClick={(e) => toggleFullscreen(video.id, e)}
|
||||
title={isFullscreen[video.id] ? "退出全屏" : "全屏"}
|
||||
>
|
||||
{isFullscreen[video.id] ? (
|
||||
<Minimize className="w-4 h-4 text-white" />
|
||||
) : (
|
||||
<Maximize className="w-4 h-4 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 视频信息 */}
|
||||
|
||||
@ -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<HTMLDivElement>(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 (
|
||||
<div className="relative w-full h-[600px] overflow-hidden bg-[var(--background)]">
|
||||
{/* 视频面板容器 */}
|
||||
@ -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 && (
|
||||
<>
|
||||
{/* 音量控制按钮 */}
|
||||
<div className="absolute top-4 right-4">
|
||||
{/* 音量控制区域 */}
|
||||
<div className="absolute top-4 right-4 flex items-center gap-2">
|
||||
{/* 静音按钮 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@ -130,6 +208,49 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||
<Volume2 className="w-5 h-5 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* 音量滑块 */}
|
||||
<div className="flex items-center gap-1 bg-black/40 rounded-full px-2 py-2 backdrop-blur-sm">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={volume}
|
||||
onChange={(e) => 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%)`
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-white/70 w-6 text-center">
|
||||
{Math.round(volume * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部控制区域 */}
|
||||
<div className="absolute bottom-16 left-4">
|
||||
{/* 播放/暂停按钮 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-10 h-10 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
|
||||
onClick={togglePlay}
|
||||
title={isPlaying ? "暂停" : "播放"}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause className="w-5 h-5 text-white" />
|
||||
) : (
|
||||
<Play className="w-5 h-5 text-white" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 视频标题 */}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user