React Native:如何让expo-av视频 onPress重启且每题自动从头播放?
问题描述
我正在开发一款测验应用,会从数据数组中随机选取20道题目。每道题目播放一段柔道技术视频,玩家需识别视频展示的技术。点击答案(无论对错)后,系统会计分并切换至下一个视频。
问题:尽管已在props中设置shouldPlay,但视频在页面加载时无法自动播放。奇怪的是,游戏结束后点击Play Again,视频却能自动播放。手动播放并暂停后,切换至下一题时,新视频会从上一题暂停的位置继续播放——这让我完全困惑。
需求:核心是实现每道题的视频都能从头自动播放,同时需实现视频 onPress 重启功能。我是React Native新手,希望得到帮助。
解决方案
核心问题分析
- 初始加载时
quizData为空,VideoPlay组件接收到的data是undefined,视频资源未正确加载,导致shouldPlay不生效。 - 切换题目时,
Video组件复用了之前的实例,没有重置播放状态,所以会从上次暂停的位置继续播放。
具体修复步骤
1. 强制组件重新挂载
通过currentQuestion作为VideoPlay的key,每次切换题目时强制卸载并重新创建Video组件,确保每次都是全新实例从头播放。
2. 控制视频播放状态
给Video添加引用,实现:
- 组件挂载/视频资源变化时自动重置到开头并播放
- 点击视频时重启播放
3. 完善初始加载逻辑
确保quizData加载完成后再渲染视频和题目,避免资源未就绪的问题。
修改后的完整代码
import React, { useState, useEffect, useRef } from 'react' import { StyleSheet, Text, View } from 'react-native' import * as Progress from 'react-native-progress'; import theme from '../assets/theme' import CustomButton from './CustomButton'; import { Video } from 'expo-av'; const VideoPlay = ({ data, shouldPlay }) => { const videoRef = useRef(null); useEffect(() => { // 组件挂载或视频资源变化时,重置到开头并播放 if (videoRef.current && data) { videoRef.current.setPositionAsync(0); videoRef.current.playAsync(); } }, [data]); const handleVideoPress = () => { // 点击视频时重启播放 if (videoRef.current) { videoRef.current.setPositionAsync(0); videoRef.current.playAsync(); } }; return ( <View style={styles.videoitems}> <Video ref={videoRef} source={{ uri: `https://judopedia.wiki/assets/videos/${data}` }} resizeMode="contain" shouldPlay={shouldPlay} style={styles.video} useNativeControls onPress={handleVideoPress} /> </View> ) } const VideoTriviaGame = ({ data }) => { const [currentQuestion, setCurrentQuestion] = useState(0) const [score, setScore] = useState(0) const [showScore, setShowScore] = useState(false) const [quizData, setQuizData] = useState([]); const [progress, setProgress] = useState(0); useEffect(() => { const random = [...data].sort(() => Math.random()).slice(0, 20); setQuizData(random); }, [data]) const handleAnswer = (selectedAnswer) => { const answer = quizData[currentQuestion]?.answer; if (answer === selectedAnswer) { setScore((prevScore) => prevScore + 1) } handleNextQuestion() } const handleNextQuestion = () => { const nextQuestion = currentQuestion + 1; if (nextQuestion < quizData.length) { setCurrentQuestion(nextQuestion) // 动态计算进度,避免硬编码 setProgress(prev => prev + 1 / quizData.length); } else { setShowScore(true) } } const handleRestart = () => { // 重启时重新生成随机题目 const random = [...data].sort(() => Math.random()).slice(0, 20); setQuizData(random); setCurrentQuestion(0) setScore(0) setShowScore(false) setProgress(0); } return ( <View style={{ flex: 1 }}> {!showScore && quizData.length > 0 && ( <VideoPlay key={currentQuestion} data={quizData[currentQuestion].url} shouldPlay={true} /> )} {showScore ? <View style={styles.gamecnt}> <View style={{ flex: 1, alignItems: "center", justifyContent: "center", width: "100%" }}> <Text style={{ color: theme.colors.white, fontSize: 24, textAlign: "center", marginBottom: 10 }}> 你答对了 {score} 题,总分 {quizData.length} 题 </Text> <View style={styles.btnGroup}> <CustomButton text="再玩一次" primary onPress={handleRestart} /> </View> </View> </View> : quizData.length > 0 && ( <View style={styles.gamecnt}> <View style={{ display: "flex", alignItems: "center" }}> <Text style={styles.counter}> {currentQuestion + 1} of {quizData.length} </Text> <Progress.Bar progress={progress} width={300} animated={true} unfilledColor={theme.colors.darkgray} color={theme.colors.primaryend} height={8} borderWidth={0} /> </View> <View style={styles.btnGroup}> {quizData[currentQuestion].options.map((item, index) => ( <CustomButton key={index} onPress={() => handleAnswer(item)} text={item} primary /> ))} </View> </View> ) } </View> ) } export default VideoTriviaGame const styles = StyleSheet.create({ counter: { color: theme.colors.white, fontSize: 18, fontWeight: "bold", }, gamecnt: { flex: 1, alignItems: "center", justifyContent: "start", }, btnGroup: { width: "100%", }, videoitems: { flex: 1, width: '100%', backgroundColor: '#000', alignItems: 'center', justifyContent: 'center', }, video: { width: '100%', height: '100%', } })
关键修改说明
- 给
VideoPlay添加key={currentQuestion},确保切换题目时组件重新挂载 - 使用
useRef获取视频实例,在资源变化时自动重置播放位置 - 新增
onPress事件实现点击视频重启播放 - 修复进度条计算逻辑,改为基于题目总数的动态值
- 重启游戏时重新生成随机题目数组,避免重复
- 增加
quizData.length > 0判断,确保数据就绪后再渲染内容
内容的提问来源于stack exchange,提问作者LOTUSMS
相关产品推荐
相关产品推荐

