javascriptreactjsreact-nativeexpoexpo-av

How can I make an expo-av video restart onPress?


I am building a quiz that randomizes 20 items from a data array. Each quiz question plays a video with a judo technique and the player tries to identify the technique shown. Once an answer is clicked on (right or wrong), it counts the score and moves on to the next video.

PROBLEM - It doesn't play on load even though it has shouldPlay in the props. Oddly enough, the videos play automatically after the game is over and you click on Play Again. When I load the screen and I play it manually, if I pause it, I notice the next item on the quiz loads the next video but it plays from the position on which I paused the previous video <-- that lost me completely.

SOLUTION - Essentially, autoplay from the beginning on each question. I'm kinda new to React Native but I'm sure some of you may find this a rather silly issue lol

I appreciate your help

import React, {useState, useEffect} 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}) => {
  return (
    <View style={styles.videoitems}>
      <Video
        source={{ uri: `https://judopedia.wiki/assets/videos/${data}` }}
        resizeMode="contain"
        shouldPlay={shouldPlay}
        style={styles.video}
        useNativeControls
      />
    </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);
  }, [])

  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)
      if (progress < quizData.length - 1) {
        setProgress(progress + .05);
      }
    } else {
      setShowScore(true)
    }
  }

  const handleRestart = () => {
    setCurrentQuestion(0)
    setScore(0)
    setShowScore(false)
    setProgress(0);
  }

  return (
    <View style={{flex:1}}>
      {showScore 
        ? null
        : <VideoPlay data={quizData[currentQuestion]?.url} shouldPlay/>
      }
      {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}}>
                You scored {score} out of {quizData.length}
              </Text>

              <View style={styles.btnGroup}>
                <CustomButton text="Play Again" primary onPress={handleRestart}/>                
              </View>
            </View>
            
          </View>
        :           
          <View style={styles.gamecnt}>
            <View style={{display:"flex", alignItems:"center"}}>
              <Text style={styles.counter}> 
                {Math.round(progress * 20) + 1 } of {quizData.length}
              </Text>
              <Progress.Bar 
                progress={progress+0.05} 
                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%',
  }
})

Solution

  • The Video component state stays the same as long as it's being rendered, including positionMillis and isPlaying, even if you change the video link.

    To reset video:

    Regarding shouldPlay on start. I copied your code with half of gamecnt part and it worked fine. It might be some other underlaying issues with your original code, e.g., setting shoulPlay to null in a component.

    Restart Video on link change:

    <Video
      key={data} // this will reset video state, every time data changes
    
      source={{ uri: `https://judopedia.wiki/assets/videos/${data}` }}
      resizeMode="contain"
      shouldPlay={shouldPlay}
      style={styles.video}
      useNativeControls
    />
    

    Restart Video onPress:

    import React from "react";
    import { View, Button, SafeAreaView, StatusBar } from "react-native";
    import { Video } from "expo-av";
    
    export default function App() {
      const videoRef = React.useRef(null);
    
      return (
        <SafeAreaView style={{ flex: 1, paddingTop: StatusBar.currentHeight }}>
          <Button
            title={"Restart"}
            onPress={() =>
              videoRef.current.setStatusAsync({ shouldPlay: true, positionMillis: 0 })
            }
          />
    
          <View style={{ flex: 1, width: "100%" }}>
            <Video
              ref={videoRef}
              style={{ width: "100%", height: "100%" }}
              source={{ uri: "https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4"}}
              resizeMode="contain"
              useNativeControls
            />
          </View>
        </SafeAreaView>
      );
    }