I am new using React-Native and I'm creating a music player for a streaming app and almost everything works fine, it plays in the background but when I want to switch to another album or playlist it does not cut the song that is playing to play the new one, it plays the previous song and the new one at the same time.
It shows me this warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
But I don't know how to cancel all subscriptions and asynchronous tasks.
Here is my code.
import {
StyleSheet,
TouchableOpacity,
View,
Image,
ImageBackground,
Slider,
} from "react-native";
import { Title, Text } from "react-native-paper";
import { LinearGradient } from "expo-linear-gradient";
import { Button } from "../components/Button";
import { Audio, Video } from "expo-av";
import firebase from "../utils/firebase";
import "firebase/firestore";
import { Ionicons } from "@expo/vector-icons";
export default function ReproductorAudio(props) {
const { route } = props;
const { canciones } = route.params;
const [duration, setDuration] = useState(0);
const [totalDuration, setTotalDuration] = useState(0);
const cancionesPlaylist = canciones;
console.log(cancionesPlaylist);
return (
<ReproductorMusica
duration={duration}
cancionesPlaylist={cancionesPlaylist}
totalDuration={totalDuration}
/>
);
}
class ReproductorMusica extends React.Component {
state = {
isPlaying: false,
playbackInstance: null,
currentIndex: 0,
duration: 0,
volume: 1.0,
isBuffering: false,
isMounted: false,
totalDuration: 0,
};
async componentDidMount() {
this.isMounted = true;
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true,
});
this.loadAudio();
} catch (e) {
console.log(e);
}
}
async componentWillUnmount() {
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true,
});
}
async loadAudio() {
const { currentIndex, isPlaying, volume } = this.state;
try {
const playbackInstance = new Audio.Sound();
const source = {
uri: this.props.cancionesPlaylist[currentIndex].song,
};
const status = {
shouldPlay: isPlaying,
volume,
};
playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
await playbackInstance.loadAsync(source, status, false);
this.setState({ playbackInstance });
} catch (e) {
console.log(e);
}
}
onPlaybackStatusUpdate = (status) => {
this.setState({
isBuffering: status.isBuffering,
});
};
handlePlayPause = async () => {
const { isPlaying, playbackInstance } = this.state;
isPlaying
? await playbackInstance.pauseAsync()
: await playbackInstance.playAsync();
this.setState({
isPlaying: !isPlaying,
});
};
handlePreviousTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < this.props.cancionesPlaylist.length - 1
? (currentIndex -= 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
handleNextTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < this.props.cancionesPlaylist.length - 1
? (currentIndex += 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
renderFileInfo() {
const {
playbackInstance,
currentIndex,
duration,
totalDuration,
} = this.state;
return playbackInstance ? (
<View style={styles.trackInfo}>
<Image
style={styles.albumCover}
source={{
uri: this.props.cancionesPlaylist[currentIndex].image,
}}
/>
<Title style={[styles.trackInfoText, styles.largeText]}>
{this.props.cancionesPlaylist[currentIndex].name}
</Title>
<Title style={[styles.trackInfoText, styles.smallText]}></Title>
<View style={styles.progressContainer}>
<Slider
totalDuration={this.props.cancionesPlaylist[currentIndex].duracion}
onValueChange={(value) =>
this.props.cancionesPlaylist[duration](value)
}
/>
<View style={styles.durationContainer}>
<Text style={styles.durationTextLeft}>{duration}</Text>
<Text style={styles.durationTextRight}>
-{(totalDuration - duration).toFixed(2)}
</Text>
</View>
</View>
{/*<Title style={[styles.trackInfoText, styles.smallText]}>
{this.props.cancionesPlaylist[currentIndex].pista}
</Title>*/}
</View>
) : null;
}
render() {
const { playbackInstance, currentIndex } = this.state;
return (
<ImageBackground
style={styles.backgroundImage}
source={{
uri: this.props.cancionesPlaylist[currentIndex].image,
}}
blurRadius={25}
>
<View style={styles.container}>
{this.renderFileInfo()}
<View style={styles.controls}>
<TouchableOpacity
style={styles.control}
onPress={this.handlePreviousTrack}
>
<Ionicons
name="arrow-back-circle-outline"
size={48}
color="#fff"
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handlePlayPause}
>
{this.state.isPlaying ? (
<Ionicons name="ios-pause" size={48} color="#fff" />
) : (
<Ionicons name="ios-play-circle" size={48} color="#fff" />
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handleNextTrack}
>
<Ionicons
name="arrow-forward-circle-outline"
size={48}
color="#fff"
/>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
backgroundImage: {
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
},
container: {
flex: 1,
backgroundColor: "rgba(189,0,0,0.3)",
alignItems: "center",
justifyContent: "center",
},
albumCover: {
width: 250,
height: 250,
borderRadius: 10,
borderWidth: 5,
borderColor: "#fff",
},
trackInfo: {
padding: 40,
paddingBottom: 0,
//backgroundColor: "#000",
},
trackInfoText: {
textAlign: "center",
flexWrap: "wrap",
color: "#fff",
},
largeText: {
fontSize: 22,
},
smallText: {
fontSize: 16,
},
control: {
margin: 20,
color: "#fff",
},
controls: {
flexDirection: "row",
color: "#fff",
},
durationContainer: {
flexDirection: "row",
},
durationTextLeft: {
flex: 0.5,
textAlign: "left",
fontSize: 16,
fontWeight: "bold",
color: "white",
},
durationTextRight: {
flex: 0.5,
textAlign: "right",
fontSize: 16,
fontWeight: "bold",
color: "white",
},
}); ```