blob: 9183c1146bd8af810c441e09dccd5310e963a37a [file] [log] [blame]
trma102d252025-06-21 01:47:09 +00001import React, { useState, useRef } from 'react'
2import { Play, Pause, Volume2, VolumeX, Maximize2, X } from 'lucide-react'
3
4const VideoPreview = ({ src, poster, onClose, className = '', style = {} }) => {
5 const videoRef = useRef(null)
6 const [isPlaying, setIsPlaying] = useState(false)
7 const [isMuted, setIsMuted] = useState(false)
8 const [isFullscreen, setIsFullscreen] = useState(false)
9 const [duration, setDuration] = useState(0)
10 const [currentTime, setCurrentTime] = useState(0)
11
12 const togglePlay = () => {
13 if (videoRef.current) {
14 if (isPlaying) {
15 videoRef.current.pause()
16 } else {
17 videoRef.current.play()
18 }
19 setIsPlaying(!isPlaying)
20 }
21 }
22
23 const toggleMute = () => {
24 if (videoRef.current) {
25 videoRef.current.muted = !isMuted
26 setIsMuted(!isMuted)
27 }
28 }
29
30 const toggleFullscreen = () => {
31 if (videoRef.current) {
32 if (!isFullscreen) {
33 if (videoRef.current.requestFullscreen) {
34 videoRef.current.requestFullscreen()
35 }
36 } else {
37 if (document.exitFullscreen) {
38 document.exitFullscreen()
39 }
40 }
41 setIsFullscreen(!isFullscreen)
42 }
43 }
44
45 const handleTimeUpdate = () => {
46 if (videoRef.current) {
47 setCurrentTime(videoRef.current.currentTime)
48 }
49 }
50
51 const handleLoadedMetadata = () => {
52 if (videoRef.current) {
53 setDuration(videoRef.current.duration)
54 }
55 }
56
57 const handleSeek = (e) => {
58 if (videoRef.current) {
59 const rect = e.currentTarget.getBoundingClientRect()
60 const clickX = e.clientX - rect.left
61 const newTime = (clickX / rect.width) * duration
62 videoRef.current.currentTime = newTime
63 setCurrentTime(newTime)
64 }
65 }
66
67 const formatTime = (time) => {
68 const minutes = Math.floor(time / 60)
69 const seconds = Math.floor(time % 60)
70 return `${minutes}:${seconds.toString().padStart(2, '0')}`
71 }
72
73 return (
74 <div className={`video-preview ${className}`} style={style}>
75 <div className="video-container" style={{ position: 'relative', borderRadius: 8, overflow: 'hidden' }}>
76 <video
77 ref={videoRef}
78 src={src}
79 poster={poster}
80 onTimeUpdate={handleTimeUpdate}
81 onLoadedMetadata={handleLoadedMetadata}
82 onPlay={() => setIsPlaying(true)}
83 onPause={() => setIsPlaying(false)}
84 style={{ width: '100%', height: '100%', objectFit: 'cover' }}
85 preload="metadata"
86 />
87
88 {/* 视频控制层 */}
89 <div
90 className="video-controls"
91 style={{
92 position: 'absolute',
93 bottom: 0,
94 left: 0,
95 right: 0,
96 background: 'linear-gradient(transparent, rgba(0,0,0,0.7))',
97 padding: '20px 12px 12px',
98 opacity: 1,
99 transition: 'opacity 0.3s'
100 }}
101 >
102 {/* 进度条 */}
103 <div
104 className="progress-bar"
105 style={{
106 height: 4,
107 background: 'rgba(255,255,255,0.3)',
108 borderRadius: 2,
109 marginBottom: 8,
110 cursor: 'pointer'
111 }}
112 onClick={handleSeek}
113 >
114 <div
115 style={{
116 height: '100%',
117 background: '#fff',
118 borderRadius: 2,
119 width: `${duration ? (currentTime / duration) * 100 : 0}%`,
120 transition: 'width 0.1s'
121 }}
122 />
123 </div>
124
125 {/* 控制按钮 */}
126 <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
127 <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
128 <button
129 onClick={togglePlay}
130 style={{
131 background: 'none',
132 border: 'none',
133 color: 'white',
134 cursor: 'pointer',
135 padding: 4,
136 borderRadius: 4
137 }}
138 >
139 {isPlaying ? <Pause size={20} /> : <Play size={20} />}
140 </button>
141
142 <button
143 onClick={toggleMute}
144 style={{
145 background: 'none',
146 border: 'none',
147 color: 'white',
148 cursor: 'pointer',
149 padding: 4,
150 borderRadius: 4
151 }}
152 >
153 {isMuted ? <VolumeX size={18} /> : <Volume2 size={18} />}
154 </button>
155
156 <span style={{ color: 'white', fontSize: 12 }}>
157 {formatTime(currentTime)} / {formatTime(duration)}
158 </span>
159 </div>
160
161 <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
162 <button
163 onClick={toggleFullscreen}
164 style={{
165 background: 'none',
166 border: 'none',
167 color: 'white',
168 cursor: 'pointer',
169 padding: 4,
170 borderRadius: 4
171 }}
172 >
173 <Maximize2 size={18} />
174 </button>
175
176 {onClose && (
177 <button
178 onClick={onClose}
179 style={{
180 background: 'none',
181 border: 'none',
182 color: 'white',
183 cursor: 'pointer',
184 padding: 4,
185 borderRadius: 4
186 }}
187 >
188 <X size={18} />
189 </button>
190 )}
191 </div>
192 </div>
193 </div>
194 </div>
195 </div>
196 )
197}
198
199export default VideoPreview