| import React, { useState, useRef } from 'react' |
| import { Play, Pause, Volume2, VolumeX, Maximize2, X } from 'lucide-react' |
| |
| const VideoPreview = ({ src, poster, onClose, className = '', style = {} }) => { |
| const videoRef = useRef(null) |
| const [isPlaying, setIsPlaying] = useState(false) |
| const [isMuted, setIsMuted] = useState(false) |
| const [isFullscreen, setIsFullscreen] = useState(false) |
| const [duration, setDuration] = useState(0) |
| const [currentTime, setCurrentTime] = useState(0) |
| |
| const togglePlay = () => { |
| if (videoRef.current) { |
| if (isPlaying) { |
| videoRef.current.pause() |
| } else { |
| videoRef.current.play() |
| } |
| setIsPlaying(!isPlaying) |
| } |
| } |
| |
| const toggleMute = () => { |
| if (videoRef.current) { |
| videoRef.current.muted = !isMuted |
| setIsMuted(!isMuted) |
| } |
| } |
| |
| const toggleFullscreen = () => { |
| if (videoRef.current) { |
| if (!isFullscreen) { |
| if (videoRef.current.requestFullscreen) { |
| videoRef.current.requestFullscreen() |
| } |
| } else { |
| if (document.exitFullscreen) { |
| document.exitFullscreen() |
| } |
| } |
| setIsFullscreen(!isFullscreen) |
| } |
| } |
| |
| const handleTimeUpdate = () => { |
| if (videoRef.current) { |
| setCurrentTime(videoRef.current.currentTime) |
| } |
| } |
| |
| const handleLoadedMetadata = () => { |
| if (videoRef.current) { |
| setDuration(videoRef.current.duration) |
| } |
| } |
| |
| const handleSeek = (e) => { |
| if (videoRef.current) { |
| const rect = e.currentTarget.getBoundingClientRect() |
| const clickX = e.clientX - rect.left |
| const newTime = (clickX / rect.width) * duration |
| videoRef.current.currentTime = newTime |
| setCurrentTime(newTime) |
| } |
| } |
| |
| const formatTime = (time) => { |
| const minutes = Math.floor(time / 60) |
| const seconds = Math.floor(time % 60) |
| return `${minutes}:${seconds.toString().padStart(2, '0')}` |
| } |
| |
| return ( |
| <div className={`video-preview ${className}`} style={style}> |
| <div className="video-container" style={{ position: 'relative', borderRadius: 8, overflow: 'hidden' }}> |
| <video |
| ref={videoRef} |
| src={src} |
| poster={poster} |
| onTimeUpdate={handleTimeUpdate} |
| onLoadedMetadata={handleLoadedMetadata} |
| onPlay={() => setIsPlaying(true)} |
| onPause={() => setIsPlaying(false)} |
| style={{ width: '100%', height: '100%', objectFit: 'cover' }} |
| preload="metadata" |
| /> |
| |
| {/* 视频控制层 */} |
| <div |
| className="video-controls" |
| style={{ |
| position: 'absolute', |
| bottom: 0, |
| left: 0, |
| right: 0, |
| background: 'linear-gradient(transparent, rgba(0,0,0,0.7))', |
| padding: '20px 12px 12px', |
| opacity: 1, |
| transition: 'opacity 0.3s' |
| }} |
| > |
| {/* 进度条 */} |
| <div |
| className="progress-bar" |
| style={{ |
| height: 4, |
| background: 'rgba(255,255,255,0.3)', |
| borderRadius: 2, |
| marginBottom: 8, |
| cursor: 'pointer' |
| }} |
| onClick={handleSeek} |
| > |
| <div |
| style={{ |
| height: '100%', |
| background: '#fff', |
| borderRadius: 2, |
| width: `${duration ? (currentTime / duration) * 100 : 0}%`, |
| transition: 'width 0.1s' |
| }} |
| /> |
| </div> |
| |
| {/* 控制按钮 */} |
| <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> |
| <button |
| onClick={togglePlay} |
| style={{ |
| background: 'none', |
| border: 'none', |
| color: 'white', |
| cursor: 'pointer', |
| padding: 4, |
| borderRadius: 4 |
| }} |
| > |
| {isPlaying ? <Pause size={20} /> : <Play size={20} />} |
| </button> |
| |
| <button |
| onClick={toggleMute} |
| style={{ |
| background: 'none', |
| border: 'none', |
| color: 'white', |
| cursor: 'pointer', |
| padding: 4, |
| borderRadius: 4 |
| }} |
| > |
| {isMuted ? <VolumeX size={18} /> : <Volume2 size={18} />} |
| </button> |
| |
| <span style={{ color: 'white', fontSize: 12 }}> |
| {formatTime(currentTime)} / {formatTime(duration)} |
| </span> |
| </div> |
| |
| <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> |
| <button |
| onClick={toggleFullscreen} |
| style={{ |
| background: 'none', |
| border: 'none', |
| color: 'white', |
| cursor: 'pointer', |
| padding: 4, |
| borderRadius: 4 |
| }} |
| > |
| <Maximize2 size={18} /> |
| </button> |
| |
| {onClose && ( |
| <button |
| onClick={onClose} |
| style={{ |
| background: 'none', |
| border: 'none', |
| color: 'white', |
| cursor: 'pointer', |
| padding: 4, |
| borderRadius: 4 |
| }} |
| > |
| <X size={18} /> |
| </button> |
| )} |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| ) |
| } |
| |
| export default VideoPreview |