React Native自定义视频控件问题:快进/快退及进度滑块功能失效
嘿,我仔细扒了你的代码,发现了几个导致快进/快退和滑块功能罢工的关键点,咱们一个个来修复:
一、进度滑块失效的核心问题
1. 视频时长获取逻辑踩坑
你用useEffect监听videoRef.current来拿时长,但useRef的current是引用类型,React根本检测不到它的变化,所以这个useEffect大概率只在组件挂载时跑一次,要是视频加载慢,拿到的时长就是0,滑块自然动不了。
修复方案:改用视频自带的onPlaybackStatusUpdate事件获取时长,这个事件会在视频状态变化时触发(包括加载完成),靠谱多了:
<Video ref={videoRef} style={styles.media} source={{uri:media}} useNativeControls={false} resizeMode={ResizeMode.COVER} onPlaybackStatusUpdate={(status) => { setIsPlaying(status.isPlaying); // 只有时长有效时才更新 if (status.durationMillis && status.durationMillis > 0) { setVideoDuration(status.durationMillis / 1000); } }} isLooping={true} />
同时删掉原来那个获取时长的useEffect哈。
2. 滑块更新和拖动打架
你用setInterval每秒更新滑块位置,但用户拖动滑块时,这个定时器会强行把滑块值拉回当前播放进度,导致滑块“跳回去”,看起来就像没反应。
修复方案:更新滑块时判断sliderBeingDragged状态,用户拖动时就暂停更新:
useEffect(() => { const updateSliderPosition = async () => { // 拖动时不更新滑块,避免冲突 if (sliderBeingDragged || !videoRef.current) return; const status = await videoRef.current.getStatusAsync(); if (status.durationMillis > 0) { const progress = status.positionMillis / status.durationMillis; setSliderValue(progress); } }; const intervalId = setInterval(updateSliderPosition, 1000); return () => clearInterval(intervalId); }, [sliderBeingDragged, videoRef.current]);
3. 滑块跳转单位搞错了
setPositionAsync接收的是毫秒参数,但你现在传的是秒(videoDuration是秒,newPosition = value * videoDuration),相当于只跳了预期位置的1/1000,肉眼根本看不出变化。
修复方案:把计算后的位置转成毫秒:
const onSlidingComplete = async (value) => { setSliderBeingDragged(false); setSliderValue(value); if (videoRef.current && videoDuration > 0) { const newPositionMillis = value * videoDuration * 1000; // 转成毫秒 await videoRef.current.setPositionAsync(newPositionMillis); } };
二、双击快进/快退失效的问题
1. 异步逻辑写错了
你在Math.min和Math.max里直接嵌套await ...then(),这种写法会导致await没正确等待Promise完成,拿到的是Promise对象而非实际位置值,计算出来的新位置根本不对。
修复方案:先拿到播放状态,再计算新位置:
useEffect(() => { const handleDoubleTap = async () => { if (!videoRef.current || !videoDuration) return; const status = await videoRef.current.getStatusAsync(); const currentPositionSec = status.positionMillis / 1000; let newPositionSec = currentPositionSec; if (doubleTapRight) { newPositionSec = Math.min(currentPositionSec + 5, videoDuration); } else if (doubleTapLeft) { newPositionSec = Math.max(currentPositionSec - 5, 0); } if (newPositionSec !== currentPositionSec) { await videoRef.current.setPositionAsync(newPositionSec * 1000); // 转毫秒 await videoRef.current.playAsync(); } }; handleDoubleTap(); }, [doubleTapRight, doubleTapLeft, videoDuration]);
2. 双击状态逻辑可以简化
你用setTimeout重置doubleTapRight/Left,其实可以直接在手势回调里处理,省掉两个状态变量,逻辑更清爽:
<TapGestureHandler onHandlerStateChange={async ({ nativeEvent }) => { if (nativeEvent.state === State.ACTIVE && videoRef.current && videoDuration) { const status = await videoRef.current.getStatusAsync(); const currentPositionSec = status.positionMillis / 1000; const newPositionSec = Math.min(currentPositionSec + 5, videoDuration); await videoRef.current.setPositionAsync(newPositionSec * 1000); await videoRef.current.playAsync(); } }} numberOfTaps={2} maxDelayMs={300} > <View style={styles.rightDoubleTapArea}></View> </TapGestureHandler>
左边的双击手势也照这个逻辑改就行。
最后总结
把上面这些修改点套进你的代码里,滑块和双击快进/快退应该就能正常工作了。记得测试下视频加载慢的情况,确保时长能正确获取到哦~
备注:内容来源于stack exchange,提问作者elvis obondo

