What I am trying to do
I am trying to get a set of songs play one after another. My app leverages Expo-Av API for playing the songs. The app is consisted of two components. App.js and a ChildComponent.
App.js renders three songs using a flatlist. It also keeps track of which song has played using a state variable. The sequence of events as I intend them to happen are as follows:
Expected Steps
- nextSongToPlay index is set to 0 when the app loads
- Flatlist renders 3 ChildComponents using the Data array
- Each ChildComponent is passed the index of renderItem as well as the nextSongToPlay
- Within the first instance of ChildComponent, once the audio has been loaded using the loadAsync() function which returns a promise. If the promise is resolved AND the index (zero 0) and nextSongToPlay (zero 0 at first run) props are equal, the button within the ChildComponent is pressed by calling its reference (ref.current.props.onPress() )
- Once the song finished playing the function passed to onPlayBackStatusUpdate method in ChildComponent is run (looking at the playBackObjectStatus.didJustFinish) and if the didJustFinish property is true, the NextSongToPlay method in App.js is called which has been passed to ChildComponent as a prop.
- Once the NextSongToPlay method is run, the netSongToPlay state variable in App.js is incremented by 1.
- This causes the re-render of App.js along with the FlatList. In order to force the FlatList to re-render (since FlatList is a pure component), the nextSongToPlay variable is passed to the extraData prop within FlatList
- The renderItem is ran again and this time the second ChildComponent will receive index (1) and nextSongToPlay (1). This will cause the loadAsync() method in second ChildComponent to call the ref.current.props.onPress() and play the song.
- The process should continue until the last song in the Data array.
Here is what my App.js looks like:
import { View, Text, FlatList } from "react-native";
import React, { useState, useRef, useEffect } from "react";
import ChildComponent from "./ChildComponent";
const Data = [
{
key: "1",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
{
key: "2",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
{
key: "3",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
];
export default function App() {
const [nextSongToPlay, setNextSongToPlay] = useState(0);
const shouldPlayOnItsOwn = useRef(false);
useEffect(() => {
return () => (shouldPlayOnItsOwn.current = false);
});
const NextSongToPlay = () => {
setNextSongToPlay(nextSongToPlay + 1);
};
const setShouldPlayOnItsOwn = () => {
shouldPlayOnItsOwn.current = true;
};
const renderItem = ({ item, index }) => {
return (
<View style={{ marginTop: 10 }}>
<ChildComponent
path={item.path}
NextSongToPlay={() => NextSongToPlay()}
nextSongToPlay={nextSongToPlay}
index={index}
songURL={item.song}
setShouldPlayOnItsOwn={setShouldPlayOnItsOwn}
shouldPlayOnItsOwn={shouldPlayOnItsOwn.current}
/>
</View>
);
};
return (
<View
style={{ justifyContent: "center", alignItems: "center", marginTop: 200 }}
>
<FlatList
data={Data}
renderItem={renderItem}
extraData={nextSongToPlay}
/>
<Text style={{ marginTop: 30 }}>
{" "}
Number of Songs Played: {nextSongToPlay}{" "}
</Text>
</View>
);
}
And this is what my ChildComponent looks like:
import { View, Button } from "react-native";
import React, { useRef, useEffect } from "react";
import { Audio } from "expo-av";
export default function ChildComponent(props) {
const sound = useRef(new Audio.Sound());
const PlayBackStatus = useRef();
const ref = useRef();
const alreadyPlayed = useRef(false);
useEffect(() => {
LoadAudio();
return () => sound.current.unloadAsync();
}, []);
const LoadAudio = async () => {
PlayBackStatus.current = sound.current
.loadAsync({ uri: props.songURL })
.then((res) => {
console.log(`load result : ${res}`);
if (props.index === props.nextSongToPlay && props.shouldPlayOnItsOwn) {
ref.current.props.onPress();
}
})
.catch((err) => console.log(err));
};
const PlayAuido = async () => {
alreadyPlayed
? sound.current.replayAsync()
: (PlayBackStatus.current = sound.current
.playAsync()
.then(() =>
console.log(`result of playing: ${PlayBackStatus.current}`)
)
.catch((err) => console.log(`PlayAsync Failed ${err}`)));
};
sound.current.setOnPlaybackStatusUpdate((playBackObjectStatus) => {
console.log(
`Audio Finished Playing: ${playBackObjectStatus.didJustFinish}`
);
if (playBackObjectStatus.didJustFinish) {
console.log(
`Inside the If Condition, Did the Audio Finished Playing?: ${playBackObjectStatus.didJustFinish}`
);
alreadyPlayed.current = true;
props.NextSongToPlay();
}
});
const onPressHandler = () => {
PlayAuido();
props.setShouldPlayOnItsOwn();
};
return (
<View>
<Button title="Play Sound" onPress={onPressHandler} ref={ref} />
</View>
);
}
What is the Problem
Everything seems to work fine until step 7 in the expected steps section above. Even though the nextSongToPlay state variable does increment after the first song is played, the Flatlist doesnot seem to be getting rendered.
Here is the snack to reproduce this.
Any help in determining the issue is greatly appreciated!
Thanks in advance!