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

81 Views Asked by At

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%',
  }
})
1

There are 1 best solutions below

0
Iman Maleki On BEST ANSWER

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:

  • One way is to add a key to the Video that changes with every new link.
  • Another way is to restart the video with a callback or an onPress event. It's achievable via React.useRef, setStatusAsync, and adding ref={videoRef} to Video component

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>
  );
}