添加播放声音

This commit is contained in:
Xin Wang 2025-07-03 06:28:57 +08:00
parent aa5be1d5ff
commit adda2077e1
2 changed files with 85 additions and 11 deletions

View File

@ -1,7 +1,7 @@
'use client'; // Add this to ensure it's a client component
import React, { useState } from 'react';
import { Edit2, Trash2, Play } from 'lucide-react';
import { Edit2, Trash2, Play, Volume2, VolumeX } from 'lucide-react';
import { Button } from '@/components/ui/button';
import dynamic from 'next/dynamic';
@ -19,6 +19,7 @@ interface VideoGridLayoutProps {
function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutProps) {
const [hoveredId, setHoveredId] = useState<string | null>(null);
const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({});
const [isMuted, setIsMuted] = useState<{ [key: string]: boolean }>({});
const handleMouseEnter = (id: string) => {
setHoveredId(id);
@ -26,11 +27,13 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
const handleMouseLeave = (id: string) => {
setHoveredId(null);
// 暂停视频
// 暂停视频并重新静音以便下次预览
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
if (video) {
video.pause();
video.muted = true;
setIsPlaying(prev => ({ ...prev, [id]: false }));
setIsMuted(prev => ({ ...prev, [id]: true }));
}
};
@ -38,8 +41,11 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
if (video) {
if (video.paused) {
// 在用户主动播放时取消静音
video.muted = false;
video.play();
setIsPlaying(prev => ({ ...prev, [id]: true }));
setIsMuted(prev => ({ ...prev, [id]: false }));
} else {
video.pause();
setIsPlaying(prev => ({ ...prev, [id]: false }));
@ -47,6 +53,15 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
}
};
const toggleMute = (id: string, event: React.MouseEvent) => {
event.stopPropagation(); // 防止触发父元素的点击事件
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
if (video) {
video.muted = !video.muted;
setIsMuted(prev => ({ ...prev, [id]: video.muted }));
}
};
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) => (
@ -64,7 +79,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
suppressHydrationWarning
className="w-full h-full object-cover"
loop
muted
muted={isMuted[video.id] !== false} // 默认静音除非明确设置为false
playsInline
/>
@ -86,6 +101,19 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
${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"

View File

@ -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 } from 'lucide-react';
import { ChevronLeft, ChevronRight, Volume2, VolumeX } from 'lucide-react';
import { Button } from '@/components/ui/button';
import dynamic from 'next/dynamic';
@ -16,8 +16,17 @@ interface VideoScreenLayoutProps {
function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const [isMuted, setIsMuted] = useState(true); // 默认静音
const containerRef = useRef<HTMLDivElement>(null);
// 初始化时同步第一个视频的状态
useEffect(() => {
const currentVideo = document.getElementById(`video-screen-${currentIndex}`) as HTMLVideoElement;
if (currentVideo) {
setIsMuted(currentVideo.muted);
}
}, [currentIndex]);
// 计算每个面板的样式
const getPanelStyle = (index: number) => {
const position = index - currentIndex;
@ -44,6 +53,15 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
};
};
// 切换静音状态
const toggleMute = () => {
const currentVideo = document.getElementById(`video-screen-${currentIndex}`) as HTMLVideoElement;
if (currentVideo) {
currentVideo.muted = !currentVideo.muted;
setIsMuted(currentVideo.muted);
}
};
// 处理切换
const handleSlide = (direction: 'prev' | 'next') => {
if (isAnimating) return;
@ -55,8 +73,15 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
setCurrentIndex(newIndex);
// 动画结束后重置状态
setTimeout(() => setIsAnimating(false), 500);
// 动画结束后重置状态并同步新视频的静音状态
setTimeout(() => {
setIsAnimating(false);
// 同步新视频的静音状态到UI
const newVideo = document.getElementById(`video-screen-${newIndex}`) as HTMLVideoElement;
if (newVideo) {
setIsMuted(newVideo.muted);
}
}, 500);
};
return (
@ -79,20 +104,41 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
<div className="relative w-full h-full overflow-hidden rounded-lg">
{/* 视频 - Add suppressHydrationWarning to prevent className mismatch warnings */}
<video
id={`video-screen-${index}`}
src={video.url}
suppressHydrationWarning
className="w-full h-full object-cover"
autoPlay
loop
muted
muted={index === currentIndex ? isMuted : true} // 只有当前视频受状态控制
playsInline
/>
{/* 视频标题 - 只在中间面板显示 */}
{/* 视频标题和控制 - 只在中间面板显示 */}
{index === currentIndex && (
<div className="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
<h3 className="text-white text-lg font-medium">{video.title}</h3>
</div>
<>
{/* 音量控制按钮 */}
<div className="absolute top-4 right-4">
<Button
variant="ghost"
size="icon"
className="w-10 h-10 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm"
onClick={toggleMute}
title={isMuted ? "取消静音" : "静音"}
>
{isMuted ? (
<VolumeX className="w-5 h-5 text-white" />
) : (
<Volume2 className="w-5 h-5 text-white" />
)}
</Button>
</div>
{/* 视频标题 */}
<div className="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
<h3 className="text-white text-lg font-medium">{video.title}</h3>
</div>
</>
)}
{/* 玻璃态遮罩 - 侧面板半透明效果 */}