React Native Expo Go iOS端音频音量过小问题求助
解决Expo-Go iOS真机音频音量过小问题
问题背景
在Expo-Go iOS真机上测试应用时,录音及下载音频的播放音量仅为正常水平的一半甚至更低,远低于设备上其他应用(如YouTube)的音量表现。但网页版、Android设备、iOS模拟器及模拟器中Safari打开的网页版,音频均能正常播放。测试代码包含录音、播放录音、播放示例音频三个功能按钮。
解决方案
问题根源是Expo默认将音频输出切换到了音量较小的听筒,只需调整Audio.setAudioModeAsync中的allowsRecordingIOS参数即可解决:
当你需要播放音频时,需将allowsRecordingIOS设置为false。这是Expo中覆盖音频默认从音量较小的听筒输出、改为从扬声器输出的方法。——Gordon Childs
具体修改逻辑:
- 初始配置音频时,将
allowsRecordingIOS设为false,确保默认从扬声器输出音频 - 如果需要进行录音操作,在开始录音前动态将该参数设为
true,录音结束后再改回false以恢复扬声器播放
修改后的关键代码片段
初始音频配置修改
useEffect(() => { const setupAudio = async () => { try { await Audio.requestPermissionsAsync(); await Audio.setAudioModeAsync({ allowsRecordingIOS: false, // 核心修改:切换到扬声器输出 playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); } catch (error) { Alert.alert('Error', 'Failed to initialize audio recording'); } }; setupAudio(); return () => { if (sound) sound.unloadAsync(); if (recording) recording.stopAndUnloadAsync(); }; }, []);
录音/播放模式动态切换
const startRecording = async () => { try { // 录音前切换到允许录音的模式 await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); const { recording: newRecording } = await Audio.Recording.createAsync( Audio.RecordingOptionsPresets.HIGH_QUALITY ); setRecording(newRecording); setIsRecording(true); } catch (error) { Alert.alert('Error', 'Failed to start recording'); } }; const stopRecording = async () => { if (!recording) return; try { await recording.stopAndUnloadAsync(); const uri = recording.getURI(); setRecordingUri(uri || null); setIsRecording(false); // 录音结束后切换回扬声器播放模式 await Audio.setAudioModeAsync({ allowsRecordingIOS: false, playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); if (uri && onRecordingComplete) { onRecordingComplete(uri); } } catch (error) { Alert.alert('Error', 'Failed to stop recording'); } };
完整修改后的代码
import React, { useState, useEffect } from 'react'; import { View, TouchableOpacity, StyleSheet, Alert } from 'react-native'; import { Audio, InterruptionModeIOS, InterruptionModeAndroid } from 'expo-av'; import { FontAwesome } from '@expo/vector-icons'; interface AudioRecorderProps { onRecordingComplete?: (uri: string) => void; } export const AudioRecorder: React.FC<AudioRecorderProps> = ({ onRecordingComplete }) => { const [recording, setRecording] = useState<Audio.Recording | null>(null); const [sound, setSound] = useState<Audio.Sound | null>(null); const [isRecording, setIsRecording] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [recordingUri, setRecordingUri] = useState<string | null>(null); const [isPlayingReference, setIsPlayingReference] = useState(false); useEffect(() => { const setupAudio = async () => { try { await Audio.requestPermissionsAsync(); await Audio.setAudioModeAsync({ allowsRecordingIOS: false, playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); } catch (error) { Alert.alert('Error', 'Failed to initialize audio recording'); } }; setupAudio(); return () => { if (sound) sound.unloadAsync(); if (recording) recording.stopAndUnloadAsync(); }; }, []); const startRecording = async () => { try { await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); const { recording: newRecording } = await Audio.Recording.createAsync( Audio.RecordingOptionsPresets.HIGH_QUALITY ); setRecording(newRecording); setIsRecording(true); } catch (error) { Alert.alert('Error', 'Failed to start recording'); } }; const stopRecording = async () => { if (!recording) return; try { await recording.stopAndUnloadAsync(); const uri = recording.getURI(); setRecordingUri(uri || null); setIsRecording(false); await Audio.setAudioModeAsync({ allowsRecordingIOS: false, playsInSilentModeIOS: true, staysActiveInBackground: true, interruptionModeIOS: InterruptionModeIOS.DuckOthers, interruptionModeAndroid: InterruptionModeAndroid.DuckOthers, shouldDuckAndroid: true, playThroughEarpieceAndroid: true }); if (uri && onRecordingComplete) { onRecordingComplete(uri); } } catch (error) { Alert.alert('Error', 'Failed to stop recording'); } }; const playSound = async () => { if (!recordingUri) return; try { if (sound) { await sound.unloadAsync(); } const { sound: newSound } = await Audio.Sound.createAsync( { uri: recordingUri }, { volume: 1.0, } ); setSound(newSound); setIsPlaying(true); await newSound.playAsync(); newSound.setOnPlaybackStatusUpdate((status) => { if (status && 'didJustFinish' in status && status.didJustFinish) { setIsPlaying(false); } }); } catch (error) { Alert.alert('Error', 'Failed to play recording'); setIsPlaying(false); } }; const playReferenceTone = async () => { try { const { sound: referenceSound } = await Audio.Sound.createAsync( require('../../assets/beep.mp3'), { volume: 1.0, } ); setIsPlayingReference(true); await referenceSound.playAsync(); referenceSound.setOnPlaybackStatusUpdate((status) => { if (status && 'didJustFinish' in status && status.didJustFinish) { setIsPlayingReference(false); referenceSound.unloadAsync(); } }); } catch (error) { Alert.alert('Error', 'Failed to play reference tone'); setIsPlayingReference(false); } }; return ( <View style={styles.container}> <TouchableOpacity style={[styles.button, isRecording ? styles.recording : null]} onPress={isRecording ? stopRecording : startRecording} > <FontAwesome name={isRecording ? 'stop-circle' : 'microphone'} size={24} color="white" /> </TouchableOpacity> {recordingUri && ( <TouchableOpacity style={[styles.button, isPlaying ? styles.playing : null]} onPress={playSound} disabled={isPlaying} > <FontAwesome name={isPlaying ? 'pause' : 'play'} size={24} color="white" /> </TouchableOpacity> )} <TouchableOpacity style={[styles.button, isPlayingReference ? styles.playing : styles.referenceTone]} onPress={playReferenceTone} disabled={isPlayingReference} > <FontAwesome name="volume-up" size={24} color="white" /> </TouchableOpacity> </View> ); }; const styles = StyleSheet.create({ container: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 20, }, button: { width: 60, height: 60, borderRadius: 30, backgroundColor: '#007AFF', justifyContent: 'center', alignItems: 'center', }, recording: { backgroundColor: '#FF3B30', }, playing: { backgroundColor: '#34C759', }, referenceTone: { backgroundColor: '#5856D6', }, }); export default function AudioTest2() { const handleRecordingComplete = (uri: string) => { console.log('Recording saved at:', uri); }; return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <AudioRecorder onRecordingComplete={handleRecordingComplete} /> </View> ); }
内容的提问来源于stack exchange,提问作者Sven Ostertag
相关产品推荐
相关产品推荐

