Expo AV实现后台音频播放问题求助
后台音频播放问题:Expo AV库在应用后台时停止播放
使用Expo AV库开发应用时,前台音频播放正常,但进入后台后音频突然停止。已在音频初始化时设置staysActiveInBackground: true,并在app.json中配置了iOS后台音频项,但问题在iOS和Android设备上均存在,使用自定义AudioPlayer组件通过URI管理音频播放。
AudioPlayer组件代码
import React, { useState, useEffect } from "react"; import { StyleSheet, Text, View, TouchableOpacity } from "react-native"; import { Audio } from "expo-av"; import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons"; import Slider from "@react-native-community/slider"; export default function AudioPlayer({ uri, stopAudio }) { const [audio, setAudio] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [position, setPosition] = useState(0); const [duration, setDuration] = useState(0); useEffect(() => { const loadAudio = async () => { if (uri) { const { sound, status } = await Audio.Sound.createAsync( { uri }, { shouldPlay: true, staysActiveInBackground: true } ); setAudio(sound); setDuration(status.durationMillis); setPosition(0); setIsPlaying(true); } }; loadAudio(); return () => { if (audio) { setIsPlaying(false); } }; }, [uri]); useEffect(() => { if (audio) { const updatePosition = setInterval(async () => { const currentPosition = await audio.getStatusAsync(); setPosition(currentPosition.positionMillis); }, 1000); return () => clearInterval(updatePosition); } }, [audio]); const setAudioMode = async () => { if (audio) { await audio.setAudioModeAsync({ staysActiveInBackground: true }); } }; useEffect(() => { setAudioMode(); }, [audio]); useEffect(() => { if (audio) { if (stopAudio) { audio.stopAsync(); setIsPlaying(false); setPosition(0); } } }, [stopAudio]); const handlePlayPause = async () => { if (audio) { if (isPlaying) { await audio.pauseAsync(); setIsPlaying(false); } else { await audio.playAsync(); setIsPlaying(true); } } }; const handleSeek = async (value) => { if (audio) { await audio.setPositionAsync(value); setPosition(value); } }; const handleStop = () => { if (audio) { audio.stopAsync(); setIsPlaying(false); setPosition(0); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={handlePlayPause} style={{ padding: 10 }}> {isPlaying ? ( <Ionicons name="pause" size={24} color="black" /> ) : ( <Ionicons name="play" size={24} color="black" /> )} </TouchableOpacity> <Slider style={styles.slider} minimumValue={0} maximumValue={duration} value={position} onSlidingComplete={handleSeek} disabled={!audio} /> <Text> {Math.floor(position / 1000)}s / {Math.floor(duration / 1000)}s </Text> <TouchableOpacity onPress={handleStop} style={{ padding: 10 }}> <MaterialCommunityIcons name="stop" size={24} color="black" /> </TouchableOpacity> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", flexDirection: "row", flexWrap: "wrap", }, slider: { width: "50%", marginTop: 20, }, });
app.json配置
{ "expo": { "name": "newtourapp", "slug": "newtourapp", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "infoPlist": { "UIBackgroundModes": ["audio"] } }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" }, "package": "com.saqibansari.newtourapp" }, "web": { "favicon": "./assets/favicon.png" } } }
已尝试的解决方法
- 在
Audio.Sound.createAsync初始化时设置staysActiveInBackground: true - 在iOS的infoPlist中添加
UIBackgroundModes: ["audio"]
解决方案
1. 全局配置音频模式
单个Sound实例的配置优先级低于全局音频模式,需在应用启动时全局设置后台播放权限:
useEffect(() => { const setupGlobalAudioMode = async () => { await Audio.setAudioModeAsync({ staysActiveInBackground: true, allowsRecordingIOS: false, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, playsInSilentModeIOS: true, shouldDuckAndroid: false, interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX, playThroughEarpieceAndroid: false, }); }; setupGlobalAudioMode(); // 组件卸载时重置模式 return async () => { await Audio.setAudioModeAsync({ staysActiveInBackground: false, }); }; }, []);
将这段代码添加到AudioPlayer组件的根级useEffect中,确保全局模式先于Sound实例初始化。
2. 完善Android后台配置
Android 8.0+需要前台服务权限才能在后台播放音频,更新app.json的Android配置:
"android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" }, "package": "com.saqibansari.newtourapp", "permissions": ["android.permission.FOREGROUND_SERVICE"], "backgroundPermissions": ["audio"] }
3. 修复组件清理逻辑
原组件的清理函数未正确卸载Sound实例,可能导致后台播放异常,修改为:
return () => { if (audio) { audio.unloadAsync(); setAudio(null); setIsPlaying(false); } };
4. 优化状态更新逻辑
避免在后台时意外触发音频停止,检查stopAudio的useEffect,确保仅在明确需要时停止:
useEffect(() => { if (audio && stopAudio) { audio.stopAsync(); setIsPlaying(false); setPosition(0); } }, [stopAudio, audio]);
5. 测试注意事项
- iOS需使用真机测试,模拟器的后台播放限制较多
- Android需在系统设置中开启应用的"后台运行权限"
- 确保测试时设备未开启省电模式,省电模式会限制后台应用的音频播放
内容的提问来源于stack exchange,提问作者Saqib Ali
相关产品推荐
相关产品推荐

