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

React Native语音转文本报错及APP崩溃问题求助

语音转文本功能问题排查与修复

问题描述

在React Native应用中集成@react-native-community/voice实现语音转文本时,出现以下问题:

  1. 控制台输出两个警告:
    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.
    
  2. 点击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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 03:39:34