React Native 语音转文本功能是当今开发人员的常见用例。无论是一般用途还是辅助功能,项目中对语音转文本的需求都很可能在某个时候出现,这是我们作为开发人员应该准备在我们的应用程序中实现的功能。
本文将向您展示如何使用 React Native 构建语音转文本听写应用程序。
-
先决条件
-
安装和设置
-
构建用户界面
-
Home/index.tsx
-
Notes/index.tsx
-
-
集成语音识别库
-
hooks/useCreateNote.ts
-
hooks/useNotes.ts
-
最终演示
先决条件
-
节点.js
-
安卓工作室
-
Xc颂歌
-
VS 代码
要在 iOS 上运行应用程序,您需要 macOS 和 Xcode 来编译和运行模拟器。
对于 Android,您可以使用 Android Studio 中的 Android Emulator,或者只需连接 Android 设备即可运行它。此外,如何下载和更新IE浏览器?只需三步轻松解决我们还将使用 VS Code 来构建应用程序。
安装和设置
在使用 React Native 构建移动应用程序时,有两种典型的方法。
一个使用Expo——一套围绕React Native构建的工具,旨在提高开发效率。另一种方法是使用 React Native CLI,这基本上就像从头开始,没有任何支持React Native 开发的工具集。
在本教程中,我们将使用 Expo,因此开发过程更易于管理。
让我们开始吧:
npm install -g expo-cli
全局安装世博会 CLI 后,可以使用以下命令初始化项目:
expo init <Name of Project>
在这里,我们将选择一个具有 TypeScript 配置的空白项目。如果您愿意,您还可以创建具有一些预定义功能的JavaScript版本。
一旦它为项目搭建了基架,您就可以运行适用于 Android 或 iOS 的应用程序。导航到该目录并运行以下 npm 命令之一:
- cd SpeechToTextAppDemo - npm run android - npm run ios - npm run web
正如您在文件夹结构中看到的,如何修复联想电源管理器不起作用?怎么打开电源管理器? 是应用程序的入口点。 是一个世博会配置,用于配置项目加载和生成 Android 和 iOS 重建的方式。App.tsx``app.json
构建用户界面
上面显示的是一个简单的线框,我们将使用它来构建应用程序的 UI。
我们让它变得简单,因为我们想专注于功能 - 一旦我们构建了应用程序,您就可以根据需要对其进行自定义并在其上练习。
让我们首先在应用程序中构建导航。
要在 React Native 应用程序中实现导航,我们需要安装以下软件包:
npm install @react-navigation/bottom-tabs @react-navigation/native @expo/vector-icons
由于我们将在底部实现导航,因此我们需要安装 和 核心包。@react-navigation/bottom-tabs``@react-navigation/native
此外,要添加对图标和文本的支持,我们需要该包。@expo/vector-icons
对于导航,有三个组件:
-
NavigationContainer
-
Tab.Navigator
-
Tab.Screen
把所有东西都包在里面. 趣知笔记有助于在不同组件之间导航,同时呈现组件本身。NavigationContainerTab.Navigator
Tab.Screen
现在,我们将更改 ,包括导航功能:App.tsx
import { StatusBar } from "expo-status-bar"; import { FontAwesome5 } from "@expo/vector-icons"; import { StyleSheet, Text, View } from "react-native"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { NavigationContainer } from "@react-navigation/native"; import Home from "./components/Home"; import Notes from "./components/Notes"; const Tab = createBottomTabNavigator(); export default function App() { return ( <NavigationContainer> <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: () => { let iconName = "record"; if (route.name === "Record") { iconName = "record-vinyl"; } else if (route.name === "History") { iconName = "history"; } return <FontAwesome5 name={iconName} size={24} color="black" />; }, })} > <Tab.Screen name="Record" component={Home} /> <Tab.Screen name="History" component={Notes} /> </Tab.Navigator> </NavigationContainer> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, });
通过添加以下代码创建 和 组件:Home``Notes
Home/index.tsx
import { useState, useEffect } from "react"; import { View, Text, TextInput, StyleSheet, Button, Pressable, } from "react-native"; export default function Home() { return ( <View style={styles.container}> <Text>Home Screen</Text> </View> ); } const styles = StyleSheet.create({ container: {}, });
Notes/index.tsx
import { useState, useEffect } from "react"; import { View, Text, TextInput, StyleSheet, Button, Pressable, } from "react-native"; export default function Notes() { return ( <View style={styles.container}> <Text>Notes Screen</Text> </View> ); } const styles = StyleSheet.create({ container: {}, });
现在,我们在应用程序底部有一个导航栏。让我们运行该应用程序,看看它是如何工作的。若要运行应用,请执行以下操作之一:
-
使用命令 ,它将在 iOS 和 Android 模拟器中构建并运行应用程序expo start
-
使用命令 ,它将为 iOS 和 Android 构建项目,在项目目录中创建本机代码,然后运行它npx expo run:<ios| Android>
在这里,我们将遵循第二种方法,网站地图因为我们有一个需要与自定义本机代码交互的库。当您没有这样的要求时,使用命令会更容易和更简单,因为我们不需要自己管理本机代码构建。voice``expo start
使用命令运行应用程序时,您将看到以下错误:npx expo run:ios
此错误是因为使用了一些我们在设置时跳过安装的核心实用程序。因此,让我们安装它们并重新运行应用程序:Navigation``Navigation
npm install react-native-safe-area-context react-native-gesture-handler react-native-screens react-native-web
现在,我们的应用程序中有屏幕导航。因此,让我们在应用程序中集成一个用于语音转文本功能的库。voice
集成语音识别库
首先,在应用程序中安装库,如下所示:react-native-voice
npm i @react-native-voice/voice --save
安装 npm 包后,将配置插件添加到 的插件数组中:app.json
{ "expo": { "plugins": ["@react-native-voice/voice"] } }
然后,在配置中添加权限:app.json
"ios": { "supportsTablet": true, "bundleIdentifier": "com.anonymous.SpeectToTextApp", "infoPlist": { "NSSpeechRecognitionUsageDescription": "This app uses speech recognition to convert your speech to text.", "NSCameraUsageDescription": "This app uses the camera to let user put a photo in his profile page." } }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#FFFFFF" }, "permissions": ["android.permission.RECORD_AUDIO"], "package": "com.anonymous.SpeectToTextApp" },
让我们构建一个与组件中的库集成的 UI。react-native-voice``Home
在我们进入之前,UI 需要一些动画来实现记录/停止功能。因此,让我们安装并实现它:react-native-reanimated``@motify/components
npm i @motify/components react-native-reanimated
在 Babel 配置中添加插件:react-native-reanimated
module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], plugins: ["react-native-reanimated/plugin"], }; };
安装 后,在 中实现语音识别功能:react-native-reanimated``Record/index.tsx
import React, { Component } from "react"; import { FontAwesome } from "@expo/vector-icons"; import { MotiView } from "@motify/components"; import { StyleSheet, Text, View, Image, TouchableHighlight, } from "react-native"; import Voice, { SpeechRecognizedEvent, SpeechResultsEvent, SpeechErrorEvent, } from "@react-native-voice/voice"; import { Easing } from "react-native-reanimated"; type Props = { onSpeechStart: () => void; onSpeechEnd: (result: any[]) => void; }; type State = { recognized: string; pitch: string; error: string; end: string; started: boolean; results: string[]; partialResults: string[]; }; class VoiceTest extends Component<Props, State> { state = { recognized: "", pitch: "", error: "", end: "", started: false, results: [], partialResults: [], }; 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; Voice.onSpeechVolumeChanged = this.onSpeechVolumeChanged; } componentWillUnmount() { Voice.destroy().then(Voice.removeAllListeners); } onSpeechStart = (e: any) => { console.log("onSpeechStart: ", e); this.setState({ started: true, }); }; onSpeechRecognized = (e: SpeechRecognizedEvent) => { console.log("onSpeechRecognized: ", e); this.setState({ recognized: "√", }); }; onSpeechEnd = (e: any) => { console.log("onSpeechEnd: ", e); this.setState({ end: "√", started: false, }); this.props.onSpeechEnd(this.state.results); }; 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!, }); }; onSpeechVolumeChanged = (e: any) => { console.log("onSpeechVolumeChanged: ", e); this.setState({ pitch: e.value, }); }; _startRecognizing = async () => { this.setState({ recognized: "", pitch: "", error: "", started: false, results: [], partialResults: [], end: "", }); try { await Voice.start("en-US"); this.props.onSpeechStart(); } catch (e) { console.error(e); } }; _stopRecognizing = async () => { try { await Voice.stop(); } catch (e) { console.error(e); } }; _cancelRecognizing = async () => { try { await Voice.cancel(); } catch (e) { console.error(e); } }; _destroyRecognizer = async () => { try { await Voice.destroy(); } catch (e) { console.error(e); } this.setState({ recognized: "", pitch: "", error: "", started: false, results: [], partialResults: [], end: "", }); }; render() { return ( <View style={styles.container}> {this.state.started ? ( <TouchableHighlight onPress={this._stopRecognizing}> <View style={ { width: 75, height: 75, borderRadius: 75, backgroundColor: "#6E01EF", alignItems: "center", justifyContent: "center", }} > {[...Array(3).keys()].map((index) => { return ( <MotiView from={ { opacity: 1, scale: 1 }} animate={ { opacity: 0, scale: 4 }} transition={ { type: "timing", duration: 2000, easing: Easing.out(Easing.ease), delay: index * 200, repeatReverse: false, loop: true, }} key={index} style={[ StyleSheet.absoluteFillObject, { backgroundColor: "#6E01EF", borderRadius: 75 }, ]} /> ); })} <FontAwesome name="microphone-slash" size={24} color="#fff" /> </View> </TouchableHighlight> ) : ( <TouchableHighlight onLongPress={this._startRecognizing}> <View style={ { width: 75, height: 75, borderRadius: 75, backgroundColor: "#6E01EF", alignItems: "center", justifyContent: "center", }} > <FontAwesome name="microphone" size={24} color="#fff" /> </View> </TouchableHighlight> )} </View> ); } } const styles = StyleSheet.create({ button: { width: 50, height: 50, }, container: {}, 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, }, }); export default Record; @react-native-voice`提供一个具有启动和停止语音录制和识别功能的类。一些重要的方法是:`Voice
-
Voice.start("en-US");
-
Voice.stop();
-
Voice.cancel();
-
Voice.destroy();
在这里,我们有两个主要功能:和 — 这些是处理语音识别功能的启动和停止。startRecognizing``stopRecognizing
另一个需要注意的重要函数是 ,它通过 props 将语音结果作为文本传递给函数。onSpeechEnd
onSpeechEnd = (e: any) => { console.log("onSpeechEnd: ", e); this.setState({ end: "√", started: false, }); this.props.onSpeechEnd(this.state.results); };
在此之后,我们将该语音组件导入:Record``Home/index.tsx
import { useState, useEffect } from "react"; import { View, Text, TextInput, StyleSheet, Button, Pressable, } from "react-native"; import Record from "../Record"; export default function Home() { const [speechText, setSpeechText] = useState(""); return ( <View style={styles.container}> <View style={styles.inputContainer}> <Text style={styles.label}>Speech Text</Text> <TextInput multiline style={styles.textInput} numberOfLines={6} value={speechText} maxLength={500} editable={true} /> <View style={ { alignItems: "flex-end", flex: 1, flexDirection: "row", justifyContent: "space-between", }} > <Button title="Save" color={"#007AFF"} onPress={async () => { console.log("save"); }} /> <Button title="Clear" color={"#007AFF"} onPress={() => { setSpeechText(""); }} /> </View> </View> <View style={styles.voiceContainer}> <Record onSpeechEnd={(value) => { setSpeechText(value[0]); }} onSpeechStart={() => { setSpeechText(""); }} /> </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", width: "100%", backgroundColor: "#F5FCFF", }, label: { fontWeight: "bold", fontSize: 15, paddingTop: 10, paddingBottom: 10, }, inputContainer: { height: "50%", width: "100%", flex: 1, padding: 10, justifyContent: "center", }, textInput: { padding: 10, borderColor: "#d1d5db", borderWidth: 1, height: 200, borderRadius: 5, }, saveButton: { right: 0, }, voiceContainer: { height: "50%", width: "100%", alignItems: "center", justifyContent: "space-around", }, });
现在,我们可以在 中访问结果。让我们实现保存功能以将其存储在数据库中。我们将使用以下命令使用伪造的 JSON 服务器进行 API 模拟:speechText``Home/index.tsx
json-service -watch db.json
为其创建并添加结构:db.json
{ "notes": [] }
我们将在我们的应用程序中用于数据获取和 API 调用。react-query
npm install react-query axios
要创建和获取笔记,我们可以创建自定义钩子来处理查询和突变:
hooks/useCreateNote.ts
import { QueryClient, useMutation } from "react-query"; import axios from "axios"; const createNote = async (note: string) => { const { data } = await axios.post("http://localhost:3000/notes", { note, }); return data; }; const useCreateNote = () => useMutation(createNote, { onSuccess: (response) => { }, }); export default useCreateNote;
hooks/useNotes.ts
import { useQuery } from "react-query"; import axios from "axios"; const fetchNotes = async () => { const { data } = await axios.get("http://localhost:3000/notes"); return data; }; const useNotes = () => useQuery("notes", fetchNotes); export default useNotes;
在里面添加钩子。useCreateNote``Home/index.tsx
import { useState, useEffect } from "react"; import { View, Text, TextInput, StyleSheet, Button, Pressable, } from "react-native"; import { useMutation, useQueryClient } from "react-query"; import useCreateNote from "../../hooks/useCreateNote"; import Record from "../Record"; export default function Home() { const [speechText, setSpeechText] = useState(""); const { mutate, isError, isLoading, isSuccess } = useCreateNote(); const queryClient = useQueryClient(); useEffect(() => { if (isSuccess) { setSpeechText(""); queryClient.invalidateQueries(["notes"]); } }, [isSuccess]); return ( <View style={styles.container}> <View style={styles.inputContainer}> <Text style={styles.label}>Speech Text</Text> <TextInput multiline style={styles.textInput} numberOfLines={6} value={speechText} maxLength={500} editable={true} /> <View style={ { alignItems: "flex-end", flex: 1, flexDirection: "row", justifyContent: "space-between", }} > <Button title="Save" color={"#007AFF"} onPress={async () => { console.log("save"); try { await mutate(speechText); } catch (e) { console.log(e); } }} /> <Button title="Clear" color={"#007AFF"} onPress={() => { setSpeechText(""); }} /> </View> </View> <View style={styles.voiceContainer}> <Record onSpeechEnd={(value) => { setSpeechText(value[0]); }} onSpeechStart={() => { setSpeechText(""); }} /> </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", width: "100%", backgroundColor: "#F5FCFF", }, label: { fontWeight: "bold", fontSize: 15, paddingTop: 10, paddingBottom: 10, }, inputContainer: { height: "50%", width: "100%", flex: 1, padding: 10, justifyContent: "center", }, textInput: { padding: 10, borderColor: "#d1d5db", borderWidth: 1, height: 200, borderRadius: 5, }, saveButton: { right: 0, }, voiceContainer: { height: "50%", width: "100%", alignItems: "center", justifyContent: "space-around", }, });
在里面添加以下代码:Note/index.tsx
import React from "react"; import { View, StyleSheet, FlatList, TouchableOpacity, Text, } from "react-native"; import useNotes from "../../hooks/useNotes"; export const Posts = ({}) => { const { data, isLoading, isSuccess } = useNotes(); console.log(data); return ( <View style={styles.container}> {isLoading && ( <React.Fragment> <Text>Loading...</Text> </React.Fragment> )} {isSuccess && ( <React.Fragment> <Text style={styles.header}>All Notes</Text> <FlatList data={data} style={styles.wrapper} keyExtractor={(item) => `${item.id}`} renderItem={({ item }) => ( <TouchableOpacity onPress={() => {}} style={styles.post}> <View style={styles.item}> <Text style={styles.postTitle}>{item.note}</Text> </View> </TouchableOpacity> )} /> </React.Fragment> )} </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, // backgroundColor: colors.white, padding: 10, }, wrapper: { flex: 1, paddingVertical: 30, }, item: { paddingVertical: 10, paddingHorizontal: 20, }, header: { textAlign: "center", textTransform: "capitalize", fontWeight: "bold", fontSize: 30, // color: colors.primary, paddingVertical: 10, }, post: { // backgroundColor: colors.primary, padding: 15, borderRadius: 10, marginBottom: 20, }, postTitle: { color: "#000", textTransform: "capitalize", }, }); export default Posts;
在这里,我们使用钩子获取数据并将它们渲染在 .useNotes``FlatList
结论
在本文中,我们介绍了如何使用 React Native 构建语音转文本听写应用程序。
当您构建需要硬件访问或访问核心库的移动应用程序时,了解如何使用 React Native 等移动框架访问这些资源非常重要。
我们在本文中详细研究了这些内容,我希望您现在对如何在未来的项目中实施它们有更深入的了解。您可以在此处找到完整的源代码。