React Native/Firebase/Expo Audio - 在下载开始并且用户离开页面后取消 loadAsync
React Native/Firebase/Expo Audio - Cancel loadAsync after download starts and user navigates away from page
当用户离开我的页面时,我无法取消 loadAsync。我曾尝试在 useEffect 上使用清理函数,但由于 soundObject 尚未加载,它将给我一个错误,因为 soundObject 等于 null。我还尝试使用 redux 并在其他页面成为焦点时添加 soundObject.stopAsync 但由于 soundObject 可能尚未设置但它不会取消并且我将播放音频并且无法停止。这是我调用 loadAsync 的 Pause/Play 按钮组件。任何帮助将不胜感激。谢谢
更新我的播放暂停处理程序
尽管我觉得有更好的方法,但我已经找到了解决方法。我现在打电话给 Audio.setIsEnabledAsync(false);作为清理功能。
//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;
PlayPause组件位于我的SongItem组件中,不适用的代码我不会添加。
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>
);
};
SongItem 位于我的 SongScreen 上。当我调用 dispatch(stopPlay) 时,我将 isPlaying 切换为 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>
);
}
};
看来您没有正确使用 React 生命周期。
// STOP PLAY ON PAGE EXIT
useEffect(() => {
ifPlaying();
}, [isPlaying]);
这是不正确的(至少它没有按照评论所说的去做)。相反,它说:当 isPlaying
标志改变时,然后执行 ifPlaying
.
你需要这样的东西来停止你的异步代码:
// STOP PLAY ON PAGE EXIT
useEffect(() => {
return cleanUp() {
ifPlaying();
}
});
您 return 在 useEffect
中所做的任何事情都将在您的组件被销毁之前执行。
我一直在努力应对同样的挑战,甚至直接询问了 expo 并检查了他们的源代码,发现无法取消并且已经加载了音频。
我所做的是在 setOnPlaybackStatusUpdate
回调的帮助下解决它。
解释:如果我想先取消一首歌曲,我必须等到它加载完毕,并且在 setOnPlaybackStatusUpdate
的帮助下,可以在加载完成后立即停止并卸载音频。
所以你的 stopPlay
功能对我来说应该是这样的:
try{
await audio.stopAsync();
audio.setOnPlaybackStatusUpdate(null);
}catch(e){
//Error thrown if audio is still loading
//Wait until it has finished loading and stop it
const stopListener = new StopListener();
audio.setOnPlaybackStatusUpdate(stopListener.getListener())
}
StopListener 看起来像这样
class StopListener {
getListener = () => async (status) => {
const audio = ... //Get the loading audio object here
audio.setOnPlaybackStatusUpdate(null);
await audio.stopAsync();
}
}
如果您找到任何其他解决方案,请分享。
当用户离开我的页面时,我无法取消 loadAsync。我曾尝试在 useEffect 上使用清理函数,但由于 soundObject 尚未加载,它将给我一个错误,因为 soundObject 等于 null。我还尝试使用 redux 并在其他页面成为焦点时添加 soundObject.stopAsync 但由于 soundObject 可能尚未设置但它不会取消并且我将播放音频并且无法停止。这是我调用 loadAsync 的 Pause/Play 按钮组件。任何帮助将不胜感激。谢谢
更新我的播放暂停处理程序 尽管我觉得有更好的方法,但我已经找到了解决方法。我现在打电话给 Audio.setIsEnabledAsync(false);作为清理功能。
//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;
PlayPause组件位于我的SongItem组件中,不适用的代码我不会添加。
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>
);
};
SongItem 位于我的 SongScreen 上。当我调用 dispatch(stopPlay) 时,我将 isPlaying 切换为 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>
);
}
};
看来您没有正确使用 React 生命周期。
// STOP PLAY ON PAGE EXIT
useEffect(() => {
ifPlaying();
}, [isPlaying]);
这是不正确的(至少它没有按照评论所说的去做)。相反,它说:当 isPlaying
标志改变时,然后执行 ifPlaying
.
你需要这样的东西来停止你的异步代码:
// STOP PLAY ON PAGE EXIT
useEffect(() => {
return cleanUp() {
ifPlaying();
}
});
您 return 在 useEffect
中所做的任何事情都将在您的组件被销毁之前执行。
我一直在努力应对同样的挑战,甚至直接询问了 expo 并检查了他们的源代码,发现无法取消并且已经加载了音频。
我所做的是在 setOnPlaybackStatusUpdate
回调的帮助下解决它。
解释:如果我想先取消一首歌曲,我必须等到它加载完毕,并且在 setOnPlaybackStatusUpdate
的帮助下,可以在加载完成后立即停止并卸载音频。
所以你的 stopPlay
功能对我来说应该是这样的:
try{
await audio.stopAsync();
audio.setOnPlaybackStatusUpdate(null);
}catch(e){
//Error thrown if audio is still loading
//Wait until it has finished loading and stop it
const stopListener = new StopListener();
audio.setOnPlaybackStatusUpdate(stopListener.getListener())
}
StopListener 看起来像这样
class StopListener {
getListener = () => async (status) => {
const audio = ... //Get the loading audio object here
audio.setOnPlaybackStatusUpdate(null);
await audio.stopAsync();
}
}
如果您找到任何其他解决方案,请分享。