React Native + Expo AV实现视频滚动时居中/聚焦自动播放需求
实现Expo AV视频组件的屏幕中心自动播放/暂停方案
核心思路
通过检测视频组件是否处于屏幕中心可视区域,结合Expo AV的播放API自动控制播放状态;若为TV端场景,可直接通过焦点事件处理。
具体实现步骤
1. 基础依赖与状态准备
导入React Native核心API,新增状态记录视频是否处于中心区域:
import { ResizeMode, Video } from 'expo-av'; import { ControlsOverLay } from './controlsOverLay'; import { Dimensions, useEffect, useState } from 'react'; // 获取屏幕尺寸 const { height: screenHeight } = Dimensions.get('window'); // 定义中心触发区域(可按需调整,这里取屏幕中间40%高度范围) const CENTER_TRIGGER_RANGE = screenHeight * 0.4; export const AutoPlayVideo = ({ item, props, ref, playback, playbackStatus }) => { const [isInCenter, setIsInCenter] = useState(false); const videoRef = ref; // 检测视频是否处于屏幕中心区域 const checkPosition = () => { videoRef.current?.measure((_x, _y, _width, height, _pageX, pageY) => { const videoCenterY = pageY + height / 2; const screenCenterY = screenHeight / 2; // 判断视频中心是否在触发范围内 const inRange = Math.abs(videoCenterY - screenCenterY) <= CENTER_TRIGGER_RANGE / 2; setIsInCenter(inRange); }); };
2. 监听位置变化事件
根据视频所在容器类型添加对应监听:
- 滚动列表场景(FlatList/ScrollView):在父组件的
onScroll事件中调用检测方法,建议加节流优化性能:
// 父组件FlatList示例 import { throttle } from 'lodash'; <FlatList data={videoList} renderItem={({ item, index }) => <AutoPlayVideo item={item} ref={videoRefs[index]} checkPosition={checkPosition} />} onScroll={throttle(() => { videoList.forEach((_, index) => videoRefs[index].current?.checkPosition()); }, 100)} />
- 单个视频组件场景:监听组件挂载与屏幕尺寸变化:
useEffect(() => { // 组件挂载时检测一次 checkPosition(); // 监听屏幕尺寸变化 const subscription = Dimensions.addEventListener('change', checkPosition); return () => subscription?.remove(); }, []);
3. 根据位置状态控制播放
监听isInCenter状态变化,调用Expo AV的播放/暂停API:
useEffect(() => { if (!videoRef.current) return; const updatePlayback = async () => { try { if (isInCenter) { await videoRef.current.playAsync(); } else { await videoRef.current.pauseAsync(); } } catch (err) { console.error('播放状态更新失败:', err); } }; updatePlayback(); }, [isInCenter]);
4. 修改原组件属性
将原固定的shouldPlay改为由isInCenter控制,确保状态同步:
return ( <> <Video ref={videoRef} source={{ uri: item?.videoLink }} style={{ width: props.width, alignSelf: 'center', aspectRatio: 1 }} posterStyle={{ width: props.width, aspectRatio: 1, alignSelf: 'center', resizeMode: ResizeMode.COVER }} posterSource={{ uri: item.thumbLink }} usePoster={true} shouldPlay={isInCenter} // 由位置状态控制播放 resizeMode={ResizeMode.COVER} onPlaybackStatusUpdate={playbackStatus} /> {/* 原有ControlsOverLay组件保持不变 */} <ControlsOverLay isBuffering={playback.isBuffering} playerIndex={props.playerIndex} visible> {playback.isBuffering && <ActivityIndicator size={'large'} />} {playback.isLoaded && playback.isPlaying && ( <PauseIconPressable onPress={() => videoRef.current.pauseAsync()} /> )} {!playback.isBuffering && playback.isLoaded && !playback.isPlaying && !playback.didJustFinish && ( <PlayIconPressable onPress={() => videoRef.current.playAsync()} /> )} {playback.didJustFinish && ( <ReplayIconPressable onPress={() => { videoRef.current.replayAsync(); }} /> )} </ControlsOverLay> </> ); };
5. TV端焦点场景适配
如果是TV端应用,直接通过焦点事件控制更精准:
<Video onFocus={() => videoRef.current?.playAsync()} onBlur={() => videoRef.current?.pauseAsync()} // 其他属性不变 />
注意事项
- 性能优化:滚动时的位置检测建议添加节流,避免频繁触发
measure导致性能下降。 - 权限处理:确保已申请
Audio权限(Expo AV播放需要),否则可能播放失败。 - 异常捕获:播放/暂停操作需包裹
try-catch,处理加载失败等异常情况。
内容的提问来源于stack exchange,提问作者David Pham
相关产品推荐
相关产品推荐

