forked from 77media/video-flow
添加播放声音
This commit is contained in:
parent
aa5be1d5ff
commit
adda2077e1
@ -1,7 +1,7 @@
|
|||||||
'use client'; // Add this to ensure it's a client component
|
'use client'; // Add this to ensure it's a client component
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
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 { Button } from '@/components/ui/button';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ interface VideoGridLayoutProps {
|
|||||||
function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutProps) {
|
function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutProps) {
|
||||||
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
||||||
const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({});
|
const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({});
|
||||||
|
const [isMuted, setIsMuted] = useState<{ [key: string]: boolean }>({});
|
||||||
|
|
||||||
const handleMouseEnter = (id: string) => {
|
const handleMouseEnter = (id: string) => {
|
||||||
setHoveredId(id);
|
setHoveredId(id);
|
||||||
@ -26,11 +27,13 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
|||||||
|
|
||||||
const handleMouseLeave = (id: string) => {
|
const handleMouseLeave = (id: string) => {
|
||||||
setHoveredId(null);
|
setHoveredId(null);
|
||||||
// 暂停视频
|
// 暂停视频并重新静音以便下次预览
|
||||||
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
||||||
if (video) {
|
if (video) {
|
||||||
video.pause();
|
video.pause();
|
||||||
|
video.muted = true;
|
||||||
setIsPlaying(prev => ({ ...prev, [id]: false }));
|
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;
|
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
||||||
if (video) {
|
if (video) {
|
||||||
if (video.paused) {
|
if (video.paused) {
|
||||||
|
// 在用户主动播放时取消静音
|
||||||
|
video.muted = false;
|
||||||
video.play();
|
video.play();
|
||||||
setIsPlaying(prev => ({ ...prev, [id]: true }));
|
setIsPlaying(prev => ({ ...prev, [id]: true }));
|
||||||
|
setIsMuted(prev => ({ ...prev, [id]: false }));
|
||||||
} else {
|
} else {
|
||||||
video.pause();
|
video.pause();
|
||||||
setIsPlaying(prev => ({ ...prev, [id]: false }));
|
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 (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
|
<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) => (
|
{videos.map((video) => (
|
||||||
@ -64,7 +79,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
|||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
loop
|
loop
|
||||||
muted
|
muted={isMuted[video.id] !== false} // 默认静音,除非明确设置为false
|
||||||
playsInline
|
playsInline
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -86,6 +101,19 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
|||||||
${hoveredId === video.id ? 'translate-y-0 opacity-100' : 'translate-y-[-10px] opacity-0'}
|
${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
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client'; // Add this to ensure it's a client component
|
'use client'; // Add this to ensure it's a client component
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
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 { Button } from '@/components/ui/button';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
@ -16,8 +16,17 @@ interface VideoScreenLayoutProps {
|
|||||||
function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [isAnimating, setIsAnimating] = useState(false);
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
const [isMuted, setIsMuted] = useState(true); // 默认静音
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
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 getPanelStyle = (index: number) => {
|
||||||
const position = index - currentIndex;
|
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') => {
|
const handleSlide = (direction: 'prev' | 'next') => {
|
||||||
if (isAnimating) return;
|
if (isAnimating) return;
|
||||||
@ -55,8 +73,15 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
|||||||
|
|
||||||
setCurrentIndex(newIndex);
|
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 (
|
return (
|
||||||
@ -79,20 +104,41 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
|||||||
<div className="relative w-full h-full overflow-hidden rounded-lg">
|
<div className="relative w-full h-full overflow-hidden rounded-lg">
|
||||||
{/* 视频 - Add suppressHydrationWarning to prevent className mismatch warnings */}
|
{/* 视频 - Add suppressHydrationWarning to prevent className mismatch warnings */}
|
||||||
<video
|
<video
|
||||||
|
id={`video-screen-${index}`}
|
||||||
src={video.url}
|
src={video.url}
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
autoPlay
|
autoPlay
|
||||||
loop
|
loop
|
||||||
muted
|
muted={index === currentIndex ? isMuted : true} // 只有当前视频受状态控制
|
||||||
playsInline
|
playsInline
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 视频标题 - 只在中间面板显示 */}
|
{/* 视频标题和控制 - 只在中间面板显示 */}
|
||||||
{index === currentIndex && (
|
{index === currentIndex && (
|
||||||
|
<>
|
||||||
|
{/* 音量控制按钮 */}
|
||||||
|
<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">
|
<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>
|
<h3 className="text-white text-lg font-medium">{video.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 玻璃态遮罩 - 侧面板半透明效果 */}
|
{/* 玻璃态遮罩 - 侧面板半透明效果 */}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user