You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
)

问题原因

  1. blur事件触发时机滞后:调用navigation.goBack()返回A页面时,B页面会被直接卸载,blur事件可能来不及触发,导致暂停逻辑未执行。
  2. 异步操作未等待完成:组件卸载时的清理函数调用了异步的pauseSound(),但组件会直接销毁,不会等待异步操作完成,导致音频暂停失败。
  3. 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的返回按钮点击时,先触发音频暂停,再执行返回操作:

  1. 修改B.tsx,将暂停和返回逻辑封装后传递给Header:
// B.tsx返回部分
return (
<View>
<Header onBackPress={() => pauseSound().then(() => navigation.goBack())}/>
...
</View>
)
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 15:40:01