I am having trouble cancelling loadAsync when a user navigates away from my page. I have tried to use a cleanup function on useEffect but since the soundObject hasn't loaded yet it will give me an error since soundObject equals null. I have also tried to use redux and add soundObject.stopAsync when other pages come into focus but since the soundObject may not be set yet it will not cancel and I will have audio playing and can't be stopped. Here is my Pause/Play button component where I call loadAsync. Any help would be greatly appreciated. Thanks
UPDATE TO MY PLAY PAUSE HANDLER I have found a workaround even though I feel there is a better way. I am now calling Audio.setIsEnabledAsync(false); as a cleanup function.
//CLEANUP FUNCTION
useEffect(() => {
Audio.setIsEnabledAsync(true);
return function cleanUp() {
reference.putFile(props.audioFile).cancel();
Audio.setIsEnabledAsync(false);
};
}, []);
import React, { useState, useEffect } from "react";
import { TouchableOpacity } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import storage from "@react-native-firebase/storage";
import { playPause, stopPlay } from "../../../store/actions/playerActions";
import { Audio } from "expo-av";
import SmallIndicator from "../Indicators/SmallIndicator";
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome";
import { faPlay, faPause } from "@fortawesome/pro-light-svg-icons";
import Colors from "../../../constants/Colors";
const PlayPause = (props) => {
const dispatch = useDispatch();
// LOAD FROM FIREBASE VARIABLES
let audioFile = props.audioFile;
const reference = storage().ref(audioFile);
let task = reference.getDownloadURL();
//HOOKS
const isPlaying = useSelector((state) => state.player.isPlaying);
const [iconSwitch, setIconSwitch] = useState(faPlay);
const [soundObject, setSoundObject] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// LOAD AUDIO SETTINGS
useEffect(() => {
const audioSettings = async () => {
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: false,
playThroughEarpieceAndroid: true,
});
} catch (e) {
console.log(e);
}
audioSettings();
};
}, []);
//CLEANUP FUNCTION
useEffect(() => {
Audio.setIsEnabledAsync(true);
return function cleanUp() {
reference.putFile(props.audioFile).cancel();
Audio.setIsEnabledAsync(false);
};
}, []);
// STOP PLAY ON PAGE EXIT
useEffect(() => {
ifPlaying();
}, [isPlaying]);
const ifPlaying = async () => {
if (isPlaying === false && soundObject != null) {
await soundObject.stopAsync();
await soundObject.unloadAsync();
setSoundObject(null);
setIconSwitch(faPlay);
}
};
// PLAY PAUSE TOGGLE
const handlePlayPause = async () => {
setIsLoading(true);
let uri = await task;
//PLAY
if (isPlaying === false && soundObject === null) {
const soundObject = new Audio.Sound();
await soundObject.loadAsync({ uri }, isPlaying, true);
setSoundObject(soundObject);
soundObject.playAsync();
dispatch(playPause(true));
setIconSwitch(faPause);
// PAUSE
} else if (isPlaying === true && soundObject != null) {
dispatch(playPause(false));
setIconSwitch(faPlay);
// STOP AND PLAY
} else if (isPlaying === true && soundObject === null) {
dispatch(stopPlay(true));
dispatch(playPause(true));
const soundObject = new Audio.Sound();
const status = { shouldPlay: true };
await soundObject.loadAsync({ uri }, status, true);
setSoundObject(soundObject);
soundObject.playAsync();
setIconSwitch(faPause);
// RESUME PLAY
} else if (isPlaying === false && soundObject != null) {
dispatch(playPause(true));
soundObject.playAsync();
setIconSwitch(faPause);
}
setIsLoading(false);
};
console.log(isPlaying);
if (isLoading) {
return <SmallIndicator />;
}
return (
<TouchableOpacity onPress={handlePlayPause}>
<FontAwesomeIcon icon={iconSwitch} size={35} color={Colors.primary} />
</TouchableOpacity>
);
};
export default PlayPause;
The PlayPause Component is located in my SongItem Component, I wont add the code that isn't applicable.
const SongItem = (props) => {
return (
<View>
<PurchaseModal
visible={modalToggle}
purchaseSelector={purchaseSelector}
radio_props={LicenseData}
onPress={modalToggleHandler}
/>
<View>
<Card>
<BodyText>{props.items.name}</BodyText>
<View style={styles.innerContainer}>
<PlayPause audioFile={props.items.audio} />
<TouchableOpacity onPress={cartPress}>
<FontAwesomeIcon
icon={iconSwitch}
size={35}
color={Colors.primary}
/>
</TouchableOpacity>
</View>
<TouchableOpacity onPress={modalToggleHandler} style={toggleStyle}>
<FontAwesomeIcon
icon={faFileInvoice}
size={35}
color={Colors.primary}
/>
</TouchableOpacity>
</Card>
</View>
</View>
);
};
The SongItem is located on my SongScreen. when I call dispatch(stopPlay) I am switching isPlaying to false;
const SongScreen = (props) => {
const filteredSongs = useSelector((state) => state.filter.filteredSongs);
const { goBack } = props.navigation;
const dispatch = useDispatch();
const backPress = () => {
dispatch(stopPlay());
goBack();
};
useEffect(() => {
props.navigation.addListener("didBlur", () => {
dispatch(stopPlay());
});
});
return (
<Gradient>
<FlatList
removeClippedSubviews={false}
windowSize={2}
maxToRenderPerBatch={6}
data={filteredSongs}
keyExtractor={(item) => item.id.toString()}
renderItem={(itemData) => <SongItem items={itemData.item} />}
/>
<MainButton name={"Back"} onPress={backPress} />
</Gradient>
);
}
};