You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 09:54:55