0

I'm trying to create a feature where a group of videos gets played automatically without user action using React Native's Expo AV package, specifically the Video element. Right now, the code is wired up so that when the video finishes, it changes the video. However, with the way it's wired up, some videos either skip entirely or play small snippets of the video before skipping.

Here are the parts of the code I want to fix:

const EpisodePlayerScreen = ({ route }) => {
  const { episode }: { episode: Episode } = route.params;
  const videos: VideoType[] = episode.videos;
  const [currVideoIdx, setCurrVideoIdx] = useState(null);
  const [uri, setUri] = useState(null);
  const [videoStatus, setVideoStatus] = useState(null)

  useEffect(() => {
    if (typeof currVideoIdx === 'number' && currVideoIdx < videos.length) {
      setUri(videos[currVideoIdx].uri);
    } else if (currVideoIdx === videos.length) {
      setUri(null);
    }
  }, [currVideoIdx]);

  useEffect(() => {
    if (videoStatus && videoStatus.didJustFinish) {
      setCurrVideoIdx(idx => idx + 1)
    }
  }, [videoStatus])


  return (
    <SafeAreaView style={styles.container}>
        {currVideoIdx === null 
          ? <Text style={styles.text}>{episode.title}</Text>
          : (currVideoIdx === videos.length ? <Text
              style={styles.text}
              onPress={resetEpisode}
            >replay episode</Text>
            : <Video
                source={{ uri }}
                shouldPlay
                onPlaybackStatusUpdate={status => {
                  if (status.didJustFinish) setVideoStatus(() => status);
                }}
              />)
        }
    </SafeAreaView>
  );
};

Basically, the idea is that if the video finishes, we change the video and the uri. I've tried using other properties on the playback status (positionMillis and durationMillis) to no avail. I've had some luck checking if isPlaying is false but when I do that, it plays the first video but the second one doesn't play.

  • I Think Expo-av has many bugs.You can try my solution. [https://stackoverflow.com/questions/75142990/autoplay-of-an-array-videos-in-expo-av-causes-app-freezes](https://stackoverflow.com/questions/75142990/autoplay-of-an-array-videos-in-expo-av-causes-app-freezes) – Rishabh Garg Jan 22 '23 at 13:09

1 Answers1

0

It might happen that onPlaybackStatusUpdate is called twice with didJustFinish: true for the same video. (I see this when testing snack.expo with web target, but not when testing on an iPhone). If the index is bumped when the callback runs with status.didJustFinish: true (or, in OPs implementation, when the status object updates and status.didJustFinish is true), we might end up with a video being skipped.

To avoid that, we can track just the change to didJustFinish to ensure we only update index once even callback is called multiple times with didJustFinish: true for a single video.

  const [status, setStatus] = React.useState<AVPlaybackStatusSuccess>()
  const [index, setIndex] = React.useState(0)

  React.useEffect(() => {
    if (status?.didJustFinish) {
      setIndex(idx => idx + 1)
    }
  }, [status?.didJustFinish])

  return (
    <View style={{ flex: 1 }}>
      <Video
        key={index}
        source={{ uri: videos[index % videos.length]?.uri }}
        shouldPlay
        resizeMode={ResizeMode.CONTAIN}
        onPlaybackStatusUpdate={status => {
          if (status.isLoaded) {
            setStatus(status)
          }
        }}
        style={{ width: '100%', height: 300 }}
      />
    </View>
  );

This seems to work just fine as seen in the following slack: https://snack.expo.dev/iLIghiqEN?platform=web

(I omitted title and playback reset for brevity).

Marek Lisik
  • 2,003
  • 1
  • 8
  • 17
  • It's definitely cleaner but the issue remains – Adam Thometz Oct 25 '22 at 17:36
  • I see - I've noticed `onPlaybackStatusUpdate` seems to be sometimes called with `status.didJustFinish: true` twice for the same file. In the snack, especially on web, it seems to be causing the second video to be skipped often. So relying on the callback to bump index is unreliable. What is more reliable is running useEffect on changes to `status.didJustFinish` alone -- I'll update the snack and the answer to reflect that. (it does seem to produce the desired effect in the snack). – Marek Lisik Oct 25 '22 at 19:13
  • I feel like we're getting close. I implemented your suggestions and now the first video plays and goes to the next successfully but they stop playing after that. Even when I tap to go to the next or previous video (a feature unrelated to the bug at hand), the video still doesn't play. – Adam Thometz Oct 25 '22 at 22:51
  • Do you see a way of isolating this behaviour in a snack? Or modifying the snack I linked to make it reproducible? That would help debug it. – Marek Lisik Oct 26 '22 at 14:19
  • I figured it out! Along with your suggestions, after you change the uri (in the first useEffect of my code), you have to call playAsync on the video element, which you can do by setting a ref on the video using useRef. Just type 'video.current.playAsync()' Thank you for your help, Marek. – Adam Thometz Oct 28 '22 at 14:37