Skip to content

Commit baeb1e4

Browse files
committed
feat: video
1 parent 3a58d69 commit baeb1e4

File tree

3 files changed

+1085
-182
lines changed

3 files changed

+1085
-182
lines changed

src/components/hooks/useVideo/index.tsx

Lines changed: 95 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,61 @@ import { useCallback, useEffect, useState, useRef, type RefObject } from 'react'
22
import { useActivate, useUnactivate } from '@src/components/KeepAlive'
33

44
const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
5-
const video = ref.current
6-
75
const [videoState, setVideoState] = useState({
8-
isPaused: video ? video?.paused : true,
9-
isMuted: video ? video?.muted : false,
10-
currentVolume: video ? video?.volume : 100,
11-
currentTime: video ? video?.currentTime : 0,
6+
isPaused: true,
7+
isMuted: false,
8+
currentVolume: 100,
9+
currentTime: 0,
1210
})
1311

12+
// 首次挂载后,同步一次真实 video 初始值(ref.current 在 effect 阶段已可用)
13+
useEffect(() => {
14+
const video = ref.current
15+
if (!video) return
16+
setVideoState((prev) => ({
17+
...prev,
18+
isPaused: video.paused,
19+
isMuted: video.muted,
20+
currentVolume: video.volume * 100,
21+
currentTime: video.currentTime,
22+
}))
23+
}, [ref])
24+
1425
const play = useCallback(() => {
15-
video?.play()
16-
setVideoState((prev) => {
17-
return {
18-
...prev,
19-
isPaused: false,
20-
isMuted: video ? video.muted : prev.isMuted,
21-
}
26+
const video = ref.current
27+
if (!video) return
28+
const p = video.play()
29+
Promise.resolve(p).catch(() => {
30+
// ignore autoplay/policy errors
2231
})
23-
}, [video])
32+
}, [ref])
2433

2534
const pause = useCallback(() => {
35+
const video = ref.current
2636
video?.pause()
27-
setVideoState((prev) => {
28-
return {
29-
...prev,
30-
isPaused: true,
31-
}
32-
})
33-
}, [video])
34-
35-
const handlePlayPauseControl = (e: Event) => {
36-
setVideoState((prev) => {
37-
return {
38-
...prev,
39-
isPaused: (e.target as HTMLVideoElement).paused,
40-
}
41-
})
42-
}
37+
}, [ref])
38+
39+
const handlePlayPauseControl = useCallback((e: Event) => {
40+
const target = e.target as HTMLVideoElement
41+
setVideoState((prev) => ({
42+
...prev,
43+
isPaused: target.paused,
44+
}))
45+
}, [])
4346

44-
const togglePause = useCallback(() => (video?.paused ? play() : pause()), [video, play, pause])
47+
const togglePause = useCallback(() => {
48+
const video = ref.current
49+
if (!video) return
50+
return video.paused ? play() : pause()
51+
}, [pause, play, ref])
4552

4653
const handleVolume = useCallback(
4754
(delta: number) => {
4855
const deltaDecimal = delta / 100
4956

57+
const video = ref.current
5058
if (video) {
51-
let newVolume = video?.volume + deltaDecimal
59+
let newVolume = video.volume + deltaDecimal
5260

5361
if (newVolume >= 1) {
5462
newVolume = 1
@@ -57,53 +65,36 @@ const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
5765
}
5866

5967
video.volume = newVolume
60-
setVideoState((prev) => {
61-
return {
62-
...prev,
63-
currentVolume: newVolume * 100,
64-
}
65-
})
6668
}
6769
},
68-
[video]
70+
[ref]
6971
)
7072

71-
const handleVolumeControl = useCallback(
72-
(e: Event) => {
73-
if (e.target && video) {
74-
const newVolume = (e.target as HTMLVideoElement).volume * 100
75-
76-
if (newVolume === videoState.currentVolume) {
77-
handleMute(video.muted)
78-
return
79-
}
80-
81-
setVideoState((prev) => ({
82-
...prev,
83-
currentVolume: (e.target as HTMLVideoElement).volume * 100,
84-
}))
85-
}
86-
},
87-
[video, videoState]
88-
)
73+
const handleVolumeControl = useCallback((e: Event) => {
74+
const target = e.target as HTMLVideoElement
75+
setVideoState((prev) => ({
76+
...prev,
77+
currentVolume: target.volume * 100,
78+
isMuted: target.muted,
79+
}))
80+
}, [])
8981

9082
const handleMute = useCallback(
9183
(mute: boolean) => {
92-
if (video) {
93-
video.muted = mute
94-
setVideoState((prev) => {
95-
return {
96-
...prev,
97-
isMuted: mute,
98-
}
99-
})
100-
}
84+
const video = ref.current
85+
if (!video) return
86+
video.muted = mute
87+
setVideoState((prev) => ({
88+
...prev,
89+
isMuted: video.muted,
90+
}))
10191
},
102-
[video]
92+
[ref]
10393
)
10494

10595
const handleTime = useCallback(
10696
(delta = 5) => {
97+
const video = ref.current
10798
if (video) {
10899
let newTime = video.currentTime + delta
109100

@@ -114,15 +105,9 @@ const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
114105
}
115106

116107
video.currentTime = newTime
117-
setVideoState((prev) => {
118-
return {
119-
...prev,
120-
currentTime: newTime,
121-
}
122-
})
123108
}
124109
},
125-
[video]
110+
[ref]
126111
)
127112

128113
const handleTimeControl = useCallback((e: Event) => {
@@ -138,15 +123,17 @@ const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
138123
if (document.fullscreenElement) {
139124
document.exitFullscreen()
140125
} else {
126+
const video = ref.current
141127
video?.requestFullscreen().catch((err) => {
142128
console.log(err)
143129
})
144130
}
145-
}, [video])
131+
}, [ref])
146132

147133
const pausedByDeactivate = useRef(false)
148134

149135
useUnactivate(() => {
136+
const video = ref.current
150137
if (video && !video.paused) {
151138
pause()
152139
pausedByDeactivate.current = true
@@ -167,19 +154,41 @@ const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
167154
}, [pause])
168155

169156
useEffect(() => {
170-
if (!video) return
171-
video.addEventListener('volumechange', handleVolumeControl)
172-
video.addEventListener('play', handlePlayPauseControl)
173-
video.addEventListener('pause', handlePlayPauseControl)
174-
video.addEventListener('timeupdate', handleTimeControl)
157+
let disposed = false
158+
let attachedTo: HTMLVideoElement | null = null
159+
160+
const schedule = (cb: FrameRequestCallback) => {
161+
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(cb)
162+
return setTimeout(() => cb(performance.now()), 0) as unknown as number
163+
}
164+
165+
const attach = () => {
166+
if (disposed) return
167+
const video = ref.current
168+
if (!video) {
169+
// ref.current 理论上在 effect 时已就绪,但 KeepAlive/条件渲染下可能会延后,做一次轻量重试
170+
schedule(attach)
171+
return
172+
}
173+
174+
attachedTo = video
175+
video.addEventListener('volumechange', handleVolumeControl)
176+
video.addEventListener('play', handlePlayPauseControl)
177+
video.addEventListener('pause', handlePlayPauseControl)
178+
video.addEventListener('timeupdate', handleTimeControl)
179+
}
180+
181+
attach()
175182

176183
return () => {
177-
video.removeEventListener('volumechange', handleVolumeControl)
178-
video.removeEventListener('play', handlePlayPauseControl)
179-
video.removeEventListener('pause', handlePlayPauseControl)
180-
video.removeEventListener('timeupdate', handleTimeControl)
184+
disposed = true
185+
if (!attachedTo) return
186+
attachedTo.removeEventListener('volumechange', handleVolumeControl)
187+
attachedTo.removeEventListener('play', handlePlayPauseControl)
188+
attachedTo.removeEventListener('pause', handlePlayPauseControl)
189+
attachedTo.removeEventListener('timeupdate', handleTimeControl)
181190
}
182-
}, [video])
191+
}, [handlePlayPauseControl, handleTimeControl, handleVolumeControl, ref])
183192

184193
return {
185194
...videoState,
@@ -190,7 +199,7 @@ const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
190199
decreaseVolume: (decrease = 5) => handleVolume(decrease * -1),
191200
mute: () => handleMute(true),
192201
unmute: () => handleMute(false),
193-
toggleMute: () => handleMute(!video?.muted),
202+
toggleMute: () => handleMute(!ref.current?.muted),
194203
forward: (increase = 5) => handleTime(increase),
195204
back: (decrease = 5) => handleTime(decrease * -1),
196205
toggleFullscreen,

0 commit comments

Comments
 (0)