React Native语音转文本报错及APP崩溃问题求助
语音转文本功能问题排查与修复
问题描述
在React Native应用中集成@react-native-community/voice实现语音转文本时,出现以下问题:
- 控制台输出两个警告:
WARN new NativeEventEmitter() was called with a non-null argument without the required addListener method. WARN new NativeEventEmitter() was called with a non-null argument without the required removeListeners method. - 点击Stop按钮时应用闪退。
项目依赖:
"@react-native-community/voice": "^1.1.9", "@react-native-firebase/app": "^14.11.0", "@react-native-firebase/auth": "^14.11.1", "@react-navigation/drawer": "^6.4.1", "@react-navigation/native": "^6.0.10", "@react-navigation/native-stack": "^6.6.2", "react": "17.0.2", "react-native": "0.68.2", "react-native-gesture-handler": "^2.4.2", "react-native-linear-gradient": "^2.6.2", "react-native-paper": "^4.12.1", "react-native-reanimated": "^2.9.1", "react-native-safe-area-context": "^4.3.1", "react-native-screens": "^3.13.1", "react-native-share": "^7.6.6", "react-native-svg": "^12.3.0", "react-native-vector-icons": "^9.1.0"
相关组件代码:
import React, {useState, useEffect} from 'react'; import { StyleSheet, Text, View, ScrollView, TouchableHighlight, Image, SafeAreaView, ActivityIndicator, LogBox, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import Voice from '@react-native-community/voice'; const SpeechToText = () => { const [pitch, setPitch] = useState(''); const [error, setError] = useState(''); const [end, setEnd] = useState(''); const [started, setStarted] = useState(''); const [results, setResults] = useState([]); const [partialResults, setPartialResults] = useState([]); const [isLoading, setLoading] = useState(false); useEffect(() => { Voice.onSpeechStart = onSpeechStart; Voice.onSpeechEnd = onSpeechEnd; Voice.onSpeechError = onSpeechError; Voice.onSpeechResults = onSpeechResults; Voice.onSpeechPartialResults = onSpeechPartialResults; Voice.onSpeechVolumeChanged = onSpeechVolumeChanged; return () => { Voice.destroy().then(Voice.removeAllListeners); }; }, []); const onSpeechStart = e => { setStarted('True'); }; const onSpeechEnd = () => { setStarted(null); setEnd('True'); }; const onSpeechError = e => { setError(JSON.stringify(e.error)); }; const onSpeechResults = e => { setResults(e.value); }; const onSpeechPartialResults = e => { setPartialResults(e.value); }; const onSpeechVolumeChanged = e => { setPitch(e.value); }; const startSpeechRecognizing = async () => { setLoading(true); setPitch(''); setError(''); setStarted(''); setResults([]); setPartialResults([]); setEnd(''); try { await Voice.start('en-US', { EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS: 50000, }); } catch (e) { console.error(e); } }; const stopSpeechRecognizing = async () => { setLoading(false); try { await Voice.stop(); setStarted(null); } catch (e) { console.error(e); } }; const destroyRecognizer = async () => { try { await Voice.destroy(); setPitch(''); setError(''); setStarted(''); setResults([]); setPartialResults([]); setEnd(''); } catch (e) { console.error(e); } }; const cancelRecognizing = async () => { try { await Voice.cancel(); } catch (e) { console.error(e); } }; return ( <SafeAreaView style={styles.container}> <View style={styles.container}> <Text style={{ alignSelf: 'center', color: '#000', fontWeight: '900', textShadowColor: 'rgba(181, 5, 230,1)', textShadowOffset: {width: -1, height: 1}, textShadowRadius: 10, letterSpacing: 5, fontSize: 18, marginTop: 20, marginBottom: 10, borderWidth: 1, borderColor: 'red', borderRadius: 50, padding: 5, }}> Speech to Text Conversion </Text> <View style={styles.headerContainer}> <Text style={styles.textWithSpaceStyle}>{`Started: ${started}`}</Text> <Text style={styles.textWithSpaceStyle}>{`End: ${end}`}</Text> </View> <View style={styles.headerContainer}> <Text style={styles.textWithSpaceStyle}>{`Pitch: \n ${pitch}`}</Text> <Text style={styles.textWithSpaceStyle}>{`Error: \n ${error}`}</Text> </View> <Text style={styles.textStyle}>Partial Results</Text> <LinearGradient colors={[ '#00FFFF', '#17C8FF', '#329BFF', '#4C64FF', '#6536FF', '#8000FF', '#1ffa14', '#1ffa14', '#f20530', '#f20530', '#f76707', '#f76707', '#00FFFF', '#17C8FF', '#329BFF', '#1ffa14', '#1ffa14', '#1ffa14', '#f76707', '#f76707', '#f20530', '#f20530', '#f76707', '#00FFFF', '#17C8FF', '#329BFF', '#4C64FF', ]} start={{x: 0.0, y: 1.0}} end={{x: 1.0, y: 1.0}} style={{ width: 408, height: 304, borderRadius: 15, padding: 2, overflow: 'scroll', alignSelf: 'center', }}> <ScrollView style={{ borderWidth: 1, width: 405, height: 300, backgroundColor: '#fff', borderRadius: 15, }}> {partialResults.map((result, index) => { return ( <Text key={`partial-result-${index}`} style={styles.textStyle}> {result} </Text> ); })} </ScrollView> </LinearGradient> {isLoading ? ( <ActivityIndicator size={'large'} color={'purple'} style={{marginTop: 80}} /> ) : ( <TouchableHighlight onPress={startSpeechRecognizing} style={{marginTop: 80}}> <Image style={styles.imageButton} source={{ uri: 'https://raw.githubusercontent.com/AboutReact/sampleresource/master/microphone.png', }} /> </TouchableHighlight> )} <Text style={styles.textStyle}>Press mike to start Recognition</Text> <View style={styles.horizontalView}> <TouchableHighlight onPress={stopSpeechRecognizing} style={styles.buttonStyle}> <Text style={styles.buttonTextStyle}>Stop</Text> </TouchableHighlight> <TouchableHighlight onPress={cancelRecognizing} style={styles.buttonStyle}> <Text style={styles.buttonTextStyle}>Cancel</Text> </TouchableHighlight> <TouchableHighlight onPress={destroyRecognizer} style={styles.buttonStyle}> <Text style={styles.buttonTextStyle}>Destroy</Text> </TouchableHighlight> </View> </View> </SafeAreaView> ); }; const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', alignItems: 'center', padding: 5, }, headerContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10, }, titleText: { fontSize: 22, textAlign: 'center', fontWeight: 'bold', }, buttonStyle: { flex: 1, justifyContent: 'center', marginTop: 15, padding: 10, backgroundColor: '#8ad24e', marginRight: 2, marginLeft: 2, }, buttonTextStyle: { color: '#fff', textAlign: 'center', }, horizontalView: { flexDirection: 'row', position: 'absolute', bottom: 0, }, textStyle: { textAlign: 'center', padding: 12, }, imageButton: { width: 50, height: 50, }, textWithSpaceStyle: { flex: 1, textAlign: 'center', color: '#B0171F', }, }); export default SpeechToText;
解决方案
一、修复NativeEventEmitter警告
该警告源于React Native 0.68+版本对NativeEventEmitter的参数校验更严格,而@react-native-community/voice v1.1.9未实现要求的addListener和removeListeners方法。
方案1:升级库版本
直接升级到最新版本的库(v1.2.2及以上已修复该问题):
npm install @react-native-community/voice@latest --save # 或使用yarn yarn add @react-native-community/voice@latest
升级后,iOS项目需重新执行pod install,然后重启项目即可消除警告。
方案2:临时屏蔽警告(不推荐)
若暂时无法升级,可在项目入口文件或当前组件开头添加代码屏蔽特定警告:
import { LogBox } from 'react-native'; LogBox.ignoreLogs([ 'new NativeEventEmitter() was called with a non-null argument without the required addListener method.', 'new NativeEventEmitter() was called with a non-null argument without the required removeListeners method.' ]);
二、修复Stop按钮闪退问题
闪退通常是由于未启动语音识别时调用Voice.stop(),或异步操作未正确处理导致的,可从以下几点修复:
1. 增加状态判断,避免无效调用
修改stopSpeechRecognizing函数,先判断语音识别是否处于启动状态,再执行停止操作:
const stopSpeechRecognizing = async () => { if (!started) { setLoading(false); return; } try { await Voice.stop(); setStarted(null); setLoading(false); } catch (e) { console.error('停止语音识别出错:', e); setLoading(false); } };
2. 修复useEffect清理逻辑
原清理逻辑中异步操作的链式调用可能导致异常,改为异步函数确保顺序执行:
useEffect(() => { Voice.onSpeechStart = onSpeechStart; Voice.onSpeechEnd = onSpeechEnd; Voice.onSpeechError = onSpeechError; Voice.onSpeechResults = onSpeechResults; Voice.onSpeechPartialResults = onSpeechPartialResults; Voice.onSpeechVolumeChanged = onSpeechVolumeChanged; return async () => { try { await Voice.destroy(); Voice.removeAllListeners(); } catch (e) { console.error('清理语音识别资源出错:', e); } }; }, []);
3. 完善权限配置
确保Android和iOS的权限已正确配置:
- Android:在
android/app/src/main/AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.INTERNET" /> <!-- 云端识别需添加 -->
- iOS:在
ios/项目名/Info.plist中添加:
<key>NSSpeechRecognitionUsageDescription</key> <string>需要访问您的麦克风进行语音识别</string> <key>NSMicrophoneUsageDescription</key> <string>需要访问您的麦克风进行语音识别</string>
4. 优化启动识别的错误处理
原startSpeechRecognizing函数中,若启动失败未重置loading状态,导致界面一直显示加载,修改后:
const startSpeechRecognizing = async () => { setLoading(true); setPitch(''); setError(''); setStarted(''); setResults([]); setPartialResults([]); setEnd(''); try { await Voice.start('en-US', { EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS: 50000, }); } catch (e) { console.error('启动语音识别出错:', e); setError(JSON.stringify(e)); setLoading(false); } };
内容的提问来源于stack exchange,提问作者Haseeb Kamboh
相关产品推荐
相关产品推荐

