@@ -2,53 +2,61 @@ import { useCallback, useEffect, useState, useRef, type RefObject } from 'react'
22import { useActivate , useUnactivate } from '@src/components/KeepAlive'
33
44const 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