Expo AV音频调用navigation.goBack()返回时无法暂停的问题
问题描述
我有三个页面A、B、C,在B页面设置了自动播放的音频。页面头部组件支持跳转至C页面,或通过navigation.goBack()返回A页面。目前的问题是:从B跳转到C时音频能正常停止,但从B返回A时,无论用哪种方式都无法暂停音频。
相关代码
App.tsx
<Stack.Navigator screenOptions={{ headerShown: false }}> <Stack.Screen name="Debug" component={A} /> <Stack.Screen name="Detail" component={B} /> <Stack.Screen name="All" component={C} /> </Stack.Navigator>
B.tsx
useEffect(() => { if (Platform.OS === 'ios') { enableAudio(); } if (isFocused === true) { console.log('play') playSound(); } else if (isFocused === false) { console.log('stop') pauseSound(); } return () => { pauseSound() } }, [isFocused]); useEffect(() => { // ... const unsubscribe = navigation.addListener('blur', () => { console.log('blur'); pauseSound(); }); return unsubscribe; }, []); const playSound = async () => { if (sound) { await sound.playAsync(); } else { console.log('Loading Sound'); const { sound } = await Audio.Sound.createAsync(require('./../../assets/audio/001.ios.m4a'), { shouldPlay: true }); setSound(sound) console.log('Playing Sound'); await sound.playAsync(); } } const pauseSound = async () => { if (sound) { console.log('Pausing Sound'); await sound.pauseAsync(); } } return ( <View> <Header/> ... </View> )
Header.tsx
const navigation = useNavigation(); return( <View style={styles.container}> <TouchableOpacity onPress={() => { navigation.goBack() }}> <Image source={require('../../assets/icons/back_arrow.png')} style={{ height: 38, width: 19 }}/> </TouchableOpacity> ... </View> )
问题原因
blur事件触发时机滞后:调用navigation.goBack()返回A页面时,B页面会被直接卸载,blur事件可能来不及触发,导致暂停逻辑未执行。- 异步操作未等待完成:组件卸载时的清理函数调用了异步的
pauseSound(),但组件会直接销毁,不会等待异步操作完成,导致音频暂停失败。 isFocused状态更新不及时:返回A页面时,isFocused的状态切换可能滞后于组件卸载,对应的暂停逻辑无法触发。
修复方案
方案1:用beforeRemove事件确保暂停触发(优先推荐)
React Navigation 6+提供的beforeRemove事件会在页面即将被移除前触发,比blur更可靠,同时在组件卸载时销毁音频实例避免残留:
// B.tsx中替换原有的blur事件监听 useEffect(() => { const unsubscribe = navigation.addListener('beforeRemove', () => { if (sound) { sound.pauseAsync().catch(err => console.error(err)); } }); // 组件卸载时销毁音频实例 return () => { if (sound) { sound.unloadAsync().catch(err => console.error(err)); setSound(null); } unsubscribe(); }; }, [navigation, sound]); // 保留isFocused的播放逻辑,移除isFocused为false时的暂停 useEffect(() => { if (Platform.OS === 'ios') { enableAudio(); } if (isFocused) { console.log('play'); playSound(); } }, [isFocused]);
方案2:手动控制返回时的暂停逻辑
在Header的返回按钮点击时,先触发音频暂停,再执行返回操作:
- 修改B.tsx,将暂停和返回逻辑封装后传递给Header:
// B.tsx返回部分 return ( <View> <Header onBackPress={() => pauseSound().then(() => navigation.goBack())}/> ... </View> )
- 修改Header.tsx接收回调:
interface HeaderProps { onBackPress: () => void; } const Header: React.FC<HeaderProps> = ({ onBackPress }) => { return( <View style={styles.container}> <TouchableOpacity onPress={onBackPress}> <Image source={require('../../assets/icons/back_arrow.png')} style={{ height: 38, width: 19 }}/> </TouchableOpacity> ... </View> ) } export default Header;
方案3:确保异步暂停操作完成后再卸载组件
如果使用旧版React Navigation,没有beforeRemove事件,可以通过标记组件挂载状态,确保暂停操作完成:
useEffect(() => { let isMounted = true; const handlePause = async () => { if (sound && isMounted) { await sound.pauseAsync(); } }; const blurUnsubscribe = navigation.addListener('blur', handlePause); return async () => { isMounted = false; blurUnsubscribe(); await handlePause(); if (sound) { await sound.unloadAsync(); setSound(null); } }; }, [navigation, sound]);
关键注意点
- 每次使用音频后调用
unloadAsync()销毁实例,避免内存泄漏和残留播放。 - 异步操作添加错误捕获,避免控制台报错影响其他逻辑。
内容的提问来源于stack exchange,提问作者Roxa18
相关产品推荐
相关产品推荐

