@react-native-ohos/voice
v3.4.0
Published
React Native Native Voice library for iOS and Android
Readme
文档模板:v0.4.1
@react-native-voice/voice
本项目基于@react-native-voice/voice 开发
该第三方库的仓库已迁移至 Gitcode,且支持直接从 npm 下载,新的包名为:@react-native-ohos/voice 版本所属关系如下:
| 三方库名称 | 三方库版本 | 发布信息 | 支持RN版本 | Autolink | 编译API版本 | 社区基线版本 | npm地址 |
| - | - | - | - | - | - | - | - |
| @react-native-ohos/voice | ~ 3.4.0 | Gitcode Releases | 0.82.* | 是 | API12+ | 3.2.4 | Npm Address |
简介
react-native-voice 是一个用于 React Native 应用程序的语音识别库。它提供了语音识别的启动、停止、取消、销毁以及结果、错误、音量等事件回调。
下载安装
进入到工程目录并输入以下命令:
npm
npm install @react-native-ohos/voiceyarn
yarn add @react-native-ohos/voiceLink
| | 是否支持autolink | RN框架版本 | | - | - | - | | ~3.4.0 | 是 | 0.82 |
使用AutoLink的工程需要根据该文档配置,Autolink框架指导文档:https://gitcode.com/CPF-RN/ohos_react_native/blob/master/docs/zh-cn/Autolinking.md
如您使用的版本支持 Autolink,并且工程已接入 Autolink,可跳过ManualLink配置。
首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 harmony。
1. Overrides RN SDK
为了让工程依赖同一个版本的 RN SDK,需要在工程根目录的 oh-package.json5 添加 overrides 字段,指向工程需要使用的 RN SDK 版本。替换的版本既可以是一个具体的版本号,也可以是一个模糊版本,还可以是本地存在的 HAR 包或源码目录。
关于该字段的作用请阅读官方说明
{
"overrides": {
"@rnoh/react-native-openharmony": "^0.82.30" // ohpm 在线版本
// "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // 指向本地 har 包的路径
// "@rnoh/react-native-openharmony" : "./react_native_openharmony" // 指向源码路径
}
}2. 引入原生端代码
目前有两种方法:
- 通过 har 包引入(在 IDE 完善相关功能后该方法会被遗弃,目前首选此方法);
- 直接链接源码。
方法一:通过 har 包引入(推荐)
[!TIP] har 包位于三方库安装路径的
harmony文件夹下。
打开 entry/oh-package.json5,添加以下依赖
"dependencies": {
"@rnoh/react-native-openharmony": "file:../react_native_openharmony",
"@react-native-ohos/voice": "file:../../node_modules/@react-native-ohos/voice/harmony/voice.har"
}点击右上角的 sync 按钮
或者在终端执行:
cd entry
ohpm install方法二:直接链接源码
[!TIP] 如需使用直接链接源码,请参考直接链接源码说明
3. 配置 CMakeLists 和引入 VoicePackage
打开 entry/src/main/cpp/CMakeLists.txt,添加:
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
add_compile_definitions(WITH_HITRACE_SYSTRACE)
add_subdirectory("${RNOH_CPP_DIR}" ./rn)
# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package)
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/voice/src/main/cpp" ./voice)
# RNOH_END: manual_package_linking_1
add_library(rnoh_app SHARED
${GENERATED_CPP_FILES}
"./PackageProvider.cpp"
"${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)
# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_sample_package)
+ target_link_libraries(rnoh_app PUBLIC rnoh_voice)
# RNOH_END: manual_package_linking_2打开 entry/src/main/cpp/PackageProvider.cpp,添加:
#include "RNOH/PackageProvider.h"
+ #include "VoicePackage.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx)
{
return {
+ std::make_shared<VoicePackage>(ctx),
};
}4. 在 ArkTs 侧引入 RNVoicePackage
打开 entry/src/main/ets/RNPackagesFactory.ts,或者 entry/src/main/ets/rn/RNPackagesFactory.ts,添加:
...
+ import { RNVoicePackage } from '@react-native-ohos/voice/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
new SamplePackage(ctx),
+ new RNVoicePackage(ctx),
];
}运行
点击右上角的 sync 按钮
或者在终端执行:
cd entry
ohpm install然后编译、运行即可。
约束与限制
兼容性
要使用此库,需要使用正确的 React-Native 和 RNOH 版本。另外,还需要使用配套的 DevEco Studio 和手机 ROM。
本文档内容基于以下版本验证通过:
- RNOH: 0.82.1; SDK: HarmonyOS API Version 21 Release SDK; IDE: DevEco Studio 6.0.1.260; ROM: 6.0.0.130;
权限要求
由于此库涉及语音识别会使用到系统录音功能,使用时需要配置对应的权限,权限需配置在 entry/src/main 目录下 module.json5 和 entry/src/main/resources/base/element 目录下 string.json 文件。
打开 module.json5,添加:
...
"requestPermissions": [
...
+ {
+ "name": "ohos.permission.MICROPHONE",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ }
]打开 string.json,添加权限申请原因:
...
{
"string": [
...
+ {
+ "name": "reason",
+ "value": "Used for recording recognition"
+ },
...
]
}使用示例
下面的代码展示了这个库的基本使用场景:
[!WARNING] 使用时 import 的库名不变。
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
Image,
TouchableHighlight,
ScrollView,
Button,
} from 'react-native';
import Voice, {
SpeechRecognizedEvent,
SpeechResultsEvent,
SpeechErrorEvent,
} from '@react-native-voice/voice';
type Props = {};
type State = {
recognized: string;
error: string;
end: string;
started: string;
results: string[];
partialResults: string[];
isAvailable: number;
isRecognizing: number;
};
class VoiceTest extends Component<Props, State> {
state = {
recognized: '',
error: '',
end: '',
started: '',
results: [],
partialResults: [],
isAvailable: 0,
isRecognizing: 0,
};
constructor(props: Props) {
super(props);
Voice.onSpeechStart = this.onSpeechStart;
Voice.onSpeechRecognized = this.onSpeechRecognized;
Voice.onSpeechEnd = this.onSpeechEnd;
Voice.onSpeechError = this.onSpeechError;
Voice.onSpeechResults = this.onSpeechResults;
Voice.onSpeechPartialResults = this.onSpeechPartialResults;
}
componentWillUnmount() {
Voice.destroy().then(Voice.removeAllListeners);
}
onSpeechStart = (e: any) => {
console.log('onSpeechStart: ', e);
this.setState({
started: '√',
});
};
onSpeechRecognized = (e: SpeechRecognizedEvent) => {
console.log('onSpeechRecognized: ', e);
this.setState({
recognized: '√',
});
};
onSpeechEnd = (e: any) => {
console.log('onSpeechEnd: ', e);
this.setState({
end: '√',
});
};
onSpeechError = (e: SpeechErrorEvent) => {
console.log('onSpeechError: ', e);
this.setState({
error: JSON.stringify(e.error),
});
};
onSpeechResults = (e: SpeechResultsEvent) => {
console.log('onSpeechResults: ', e);
this.setState({
results: e.value,
});
};
onSpeechPartialResults = (e: SpeechResultsEvent) => {
console.log('onSpeechPartialResults: ', e);
this.setState({
partialResults: e.value,
});
};
_startRecognizing = async () => {
this.setState({
recognized: '',
error: '',
started: '',
results: [],
partialResults: [],
end: '',
});
try {
await Voice.start('en-US');
} catch (e) {
console.error(e);
}
};
_stopRecognizing = async () => {
try {
await Voice.stop();
} catch (e) {
console.error(e);
}
};
_cancelRecognizing = async () => {
try {
await Voice.cancel();
Voice.start;
} catch (e) {
console.error(e);
}
};
_destroyRecognizer = async () => {
try {
await Voice.destroy();
} catch (e) {
console.error(e);
}
this.setState({
recognized: '',
error: '',
started: '',
results: [],
partialResults: [],
end: '',
});
};
_isAvailable = async () => {
try {
Voice.isAvailable().then((available: 0 | 1) => {
this.setState({
isAvailable: available ? 1 : 0,
});
});
} catch (e) {
console.error(e);
this.setState({
isAvailable: 0,
});
}
};
_isRecognizing = async () => {
try {
Voice.isRecognizing().then((isRecognizing: 0 | 1) => {
this.setState({
isRecognizing: isRecognizing ? 1 : 0,
});
});
} catch (e) {
console.error(e);
}
};
render() {
return (
<ScrollView>
<View style={{height: 20, marginBottom: 20, backgroundColor: '#fff'}}>
<Text style={styles.stat}>{`${this.state.started}`}</Text>
</View>
<View style={{height: 20, marginBottom: 20, backgroundColor: '#fff'}}>
<Text style={styles.stat}>{`${this.state.recognized}`}</Text>
</View>
<View style={{height: 30, marginBottom: 20, backgroundColor: '#fff'}}>
<Text style={styles.stat}>{`${this.state.error}`}</Text>
</View>
<View
style={{minHeight: 30, marginBottom: 20, backgroundColor: '#fff'}}>
{this.state.results.map((result, index) => {
return (
<Text key={`result-${index}`} style={styles.stat}>
{result}
</Text>
);
})}
</View>
<View
style={{minHeight: 50, marginBottom: 20, backgroundColor: '#fff'}}>
{this.state.partialResults.map((result, index) => {
return (
<Text key={`partial-result-${index}`} style={styles.stat}>
{result}
</Text>
);
})}
</View>
<View style={{height: 30, marginBottom: 20, backgroundColor: '#fff'}}>
<Text style={styles.stat}>{` ${this.state.end}`}</Text>
</View>
<View style={styles.baseArea}>
<Text style={{flex: 1}}>{`${this.state.isAvailable}`}</Text>
<Button
title="isAvailable"
color="#841584"
onPress={this._isAvailable}></Button>
</View>
<View style={styles.baseArea}>
<Text style={{flex: 1}}>{`${this.state.isRecognizing}`}</Text>
<Button
title="isRecognizing"
color="#841584"
onPress={this._isRecognizing}></Button>
</View>
<View style={{marginBottom: 10}}>
<Button
title="start"
color="#841584"
onPress={this._startRecognizing}
/>
</View>
<View style={{marginBottom: 10}}>
<Button
title="stop"
color="#841584"
onPress={this._stopRecognizing}
/>
</View>
<View style={{padding: 5}}>
<Button
title="cancel"
color="#841584"
onPress={() => {
this._cancelRecognizing();
}}></Button>
</View>
<View style={{padding: 5}}>
<Button
title="destroy"
color="#841584"
onPress={() => {
this._destroyRecognizer();
}}></Button>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
button: {
width: 50,
height: 50,
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
action: {
textAlign: 'center',
color: '#0000FF',
marginVertical: 5,
fontWeight: 'bold',
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
stat: {
textAlign: 'center',
color: '#B0171F',
marginBottom: 1,
},
baseArea: {
width: '100%',
height: 30,
borderRadius: 4,
borderColor: '#000000',
marginTop: 6,
backgroundColor: '#FFFFFF',
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 8,
paddingRight: 8,
marginBottom: 20,
},
});
export default VoiceTest;使用说明
[!TIP] 语音识别依赖系统麦克风权限,请先在
module.json5中配置ohos.permission.MICROPHONE并在string.json中补充权限原因(详见「约束与限制 - 权限要求」)。HarmonyOS 侧暂仅支持离线识别、暂仅支持中文。
初始化与事件绑定
import Voice, {
SpeechRecognizedEvent,
SpeechResultsEvent,
SpeechErrorEvent,
} from '@react-native-voice/voice';
// 绑定事件回调
Voice.onSpeechStart = (e) => console.log('开始识别', e);
Voice.onSpeechRecognized = (e) => console.log('识别中', e);
Voice.onSpeechEnd = (e) => console.log('结束识别', e);
Voice.onSpeechError = (e) => console.log('识别错误', e);
Voice.onSpeechResults = (e) => console.log('最终结果', e.value);
Voice.onSpeechPartialResults = (e) => console.log('部分结果', e.value);开始 / 停止 / 取消 / 销毁
// 开始识别,传入语言 locale
await Voice.start('zh-CN');
// 停止识别(会触发 onSpeechEnd 与 onSpeechResults)
await Voice.stop();
// 取消识别(不触发结果回调)
await Voice.cancel();
// 销毁识别实例,释放资源
await Voice.destroy();可用性 / 识别状态查询
// 检查语音识别服务是否可用(返回 0 | 1)
const available = await Voice.isAvailable();
// 检查是否正在识别(返回 0 | 1)
const recognizing = await Voice.isRecognizing();[!WARNING] 组件卸载时应调用
Voice.destroy()释放资源,并配合Voice.removeAllListeners()清理回调,避免内存泄漏。注意 HarmonyOS 侧removeAllListeners当前未生效(与 iOS 效果一致,见「其他」)。
接口说明
[!TIP] "Platform"列表示该属性在原三方库上支持的平台。
[!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。
API
| Name | Description | Type | Required | Platform | HarmonyOS Support | | ---- | ----------- | ---- | -------- | -------- | ------------------ | | isAvailable() | 检查语音识别服务是否可用 | function | no | Android, iOS | yes | | start(locale: any, options = {}) | 开始语音识别 | function | no | Android, iOS | yes | | stop() | 停止语音识别 | function | no | Android, iOS | yes | | cancel() | 取消语音识别 | function | no | Android, iOS | yes | | destroy() | 销毁语音识别实例 | function | no | Android, iOS | yes | | removeAllListeners() | 清除覆盖的 Voice 静态方法 | void | no | Android, iOS | no | | isRecognizing() | 检查是否正在识别 | function | no | Android, iOS | yes | | getSpeechRecognitionServices() | 获取设备上可用的语音识别引擎列表 | function | no | Android | no |
事件回调
| Name | Description | Type | Required | Platform | HarmonyOS Support | | ---- | ----------- | ---- | -------- | -------- | ------------------ | | Voice.onSpeechStart(event) | 语音开始事件 | function | no | Android, iOS | yes | | Voice.onSpeechRecognized(event) | 语音识别事件 | function | no | Android, iOS | yes | | Voice.onSpeechEnd(event) | 语音结束事件 | function | no | Android, iOS | yes | | Voice.onSpeechError(event) | 语音错误事件 | function | no | Android, iOS | yes | | Voice.onSpeechResults(event) | 语音结果事件 | function | no | Android, iOS | yes | | Voice.onSpeechPartialResults(event) | 部分结果事件 | function | no | Android, iOS | yes | | Voice.onSpeechVolumeChanged(event) | 音量变化事件 | function | no | Android | no |
遗留问题
其他
- removeAllListeners 方法未生效,与 iOS 效果一致。问题: issue#491
目录结构
/rntpc_voice # 项目根目录
├── harmony # 鸿蒙适配代码
│ └─ voice.har # har 包
│ └─ voice # 鸿蒙适配核心代码
│ └─ index.ets # 鸿蒙适配代码入口
│ └─ ts.ets # ArkTS 侧类型导出入口
│ └─ src/main
│ └─ ets
│ └─ RNVoiceTurboModule.ts # 鸿蒙侧 TurboModule 实现
│ └─ RNVoicePackage.ets # 鸿蒙侧 Package
│ └─ Constants.ts # 常量定义
│ └─ Logger.ts # 日志工具
│ └─ cpp
│ └─ VoicePackage.h # C++ 侧 Package
│ └─ CMakeLists.txt # C++ 侧构建配置
│ └─ generated # codegen 生成代码
├── src # RN 代码
│ └─ index.ts # 入口文件
│ └─ VoiceModule.ts # Voice 模块实现
│ └─ VoiceModuleTypes.ts # 类型定义文件
├── example # 示例工程(见使用示例)
├── README.md # 中文文档
├── README_en.md # 英文文档贡献代码
使用过程中发现任何问题都可以提交 Issue,当然,也非常欢迎提交 PR 。
开源协议
本项目基于 The MIT License (MIT) ,请自由地享受和参与开源。
