构建 React 原生语音转文本听写应用

React Native 语音转文本功能是当今开发人员的常见用例。无论是一般用途还是辅助功能,项目中对语音转文本的需求都很可能在某个时候出现,这是我们作为开发人员应该准备在我们的应用程序中实现的功能。

构建一个 React 本机语音到文本应用程序

本文将向您展示如何使用 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

对于导航,有三个组件:

  1. NavigationContainer

  2. Tab.Navigator

  3. Tab.Screen

把所有东西都包在里面. 趣知笔记有助于在不同组件之间导航,同时呈现组件本身。NavigationContainerTab.NavigatorTab.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: {},
});

现在,我们在应用程序底部有一个导航栏。让我们运行该应用程序,看看它是如何工作的。若要运行应用,请执行以下操作之一:

  1. 使用命令 ,它将在 iOS 和 Android 模拟器中构建并运行应用程序expo start

  2. 使用命令 ,它将为 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
  1. Voice.start("en-US");

  2. Voice.stop();

  3. Voice.cancel();

  4. 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 等移动框架访问这些资源非常重要。

我们在本文中详细研究了这些内容,我希望您现在对如何在未来的项目中实施它们有更深入的了解。您可以在此处找到完整的源代码。

猜你喜欢

转载自blog.csdn.net/weixin_47967031/article/details/132470228
今日推荐