欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

项目基于 RN 0.72.90 开发
测试过了可以播放
在这里插入图片描述

📋 前言

在移动应用开发中,文字转语音(Text-to-Speech,TTS)是一项非常实用的功能,广泛应用于语音播报、无障碍阅读、导航提示、语言学习等场景。react-native-tts 是一个跨平台的 TTS 库,支持将文本转换为语音输出,让应用具备"开口说话"的能力,极大地提升了用户体验和应用的可访问性。

🎯 库简介

基本信息

  • 库名称: react-native-tts
  • 版本信息:
    • 4.1.1-0.0.3 + @react-native-oh-tpl/react-native-tts: 支持 RN 0.72 版本(已废弃)
    • 4.1.3 + @react-native-ohos/react-native-tts: 支持 RN 0.72 版本
    • 4.2.0 + @react-native-ohos/react-native-tts: 支持 RN 0.77 版本
  • 官方仓库: https://github.com/ak1394/react-native-tts
  • 鸿蒙仓库: https://github.com/react-native-oh-library/react-native-tts
  • 主要功能:
    • 🔊 文字转语音
    • ⏸️ 播放控制(暂停/恢复/停止)
    • 🎛️ 语速和音调调节
    • 🗣️ 多语音选择
    • 📡 事件监听

为什么需要 TTS?

特性 应用场景 价值
语音播报 新闻、消息通知 解放双眼,提升效率
无障碍阅读 视障用户辅助 提升应用可访问性
导航提示 地图导航、驾驶辅助 安全便捷
语言学习 发音教学、听力练习 提升学习效果
智能客服 语音交互 提升用户体验
电子书阅读 有声读物 碎片化阅读

核心功能

功能 说明 HarmonyOS 支持
speak 朗读文本
stop 停止朗读
pause 暂停朗读
resume 恢复朗读
setDefaultRate 设置语速
setDefaultPitch 设置音调
voices 获取可用语音列表
getInitStatus 获取初始化状态
addEventListener 添加事件监听
removeEventListener 移除事件监听
setDucking 降低其他音频音量
setDefaultEngine 设置默认引擎
setDefaultVoice 设置默认语音
setDefaultLanguage 设置默认语言
engines 获取可用引擎列表

兼容性验证

在以下环境验证通过:

  • RNOH: 0.72.90; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.2; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

# RN 0.72 版本(本项目使用)
npm install @react-native-ohos/react-native-tts@4.1.3-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-tts@4.2.0-rc.1

# 或者使用 yarn
yarn add @react-native-ohos/react-native-tts

2. 验证安装

安装完成后,检查 package.json 文件:

{
  "dependencies": {
    "@react-native-ohos/react-native-tts": "^4.1.3-rc.1"
  }
}

3. 类型定义配置(重要)

该库默认导出的是 react-native-tts,需要创建类型定义文件以获得完整的 TypeScript 类型支持。

文件路径: src/types/react-native-tts.d.ts

declare module "react-native-tts" {
  interface Voice {
    id: string;
    name: string;
    language: string;
    quality?: number;
    latency?: number;
    networkRequired?: boolean;
    notInstalled?: boolean;
  }

  interface SpeakOptions {
    rate?: number;
    pitch?: number;
    language?: string;
    voice?: string;
    onWordBoundary?: boolean;
    skipTransform?: boolean;
  }

  interface TtsStartEvent {
    utteranceId: string;
  }

  interface TtsProgressEvent {
    utteranceId: string;
    location: number;
    length: number;
  }

  interface TtsFinishEvent {
    utteranceId: string;
  }

  interface TtsCancelEvent {
    utteranceId: string;
  }

  interface TtsErrorEvent {
    utteranceId: string;
    error: string;
  }

  type TtsEventType =
    | "tts-start"
    | "tts-progress"
    | "tts-finish"
    | "tts-cancel"
    | "tts-error";

  interface TtsStatic {
    speak(text: string, options?: SpeakOptions): Promise<string>;
    stop(): void;
    pause(): void;
    resume(): void;
    setDefaultRate(rate: number): void;
    setDefaultPitch(pitch: number): void;
    setDefaultLanguage(language: string): void;
    setDefaultVoice(voiceId: string): void;
    setDucking(enabled: boolean): void;
    setIgnoreSilentSwitch(ignore: boolean): void;
    voices(): Promise<Voice[]>;
    engines(): Promise<any[]>;
    getInitStatus(): Promise<void>;
    requestInstallEngine(): Promise<void>;
    requestInstallData(): Promise<void>;
    addEventListener(
      event: "tts-start",
      callback: (event: TtsStartEvent) => void
    ): void;
    addEventListener(
      event: "tts-progress",
      callback: (event: TtsProgressEvent) => void
    ): void;
    addEventListener(
      event: "tts-finish",
      callback: (event: TtsFinishEvent) => void
    ): void;
    addEventListener(
      event: "tts-cancel",
      callback: (event: TtsCancelEvent) => void
    ): void;
    addEventListener(
      event: "tts-error",
      callback: (event: TtsErrorEvent) => void
    ): void;
    removeEventListener(event: TtsEventType, callback: (event: any) => void): void;
  }

  const Tts: TtsStatic;
  export default Tts;
}

在tpconfig.json中添加配置:

{
  "include": [
    "**/*.ts",
    "**/*.tsx"
  ],
}

🔧 HarmonyOS 平台配置 ⭐

Link 配置

版本 是否支持 autolink RN 框架版本
~4.2.0 No 0.77
~4.1.3 Yes 0.72
<= 4.1.1-0.0.3@deprecated No 0.72

1. 在工程根目录的 oh-package.json5 添加 overrides 字段

打开 harmony/oh-package.json5,添加以下配置:

{
  // ... 其他配置
  "overrides": {
    "@rnoh/react-native-openharmony": "0.72.90"
  }
}

2. 引入原生端代码

打开 harmony/entry/oh-package.json5,添加以下依赖:

"dependencies": {
  "@react-native-ohos/react-native-tts": "file:../../node_modules/@react-native-ohos/react-native-tts/harmony/rn_tts.har"
}

点击右上角的 sync 按钮,或者在终端执行:

cd entry
ohpm install

3. 配置 CMakeLists

打开 entry/src/main/cpp/CMakeLists.txt,添加:

project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-tts/src/main/cpp" ./rn_tts)
# RNOH_END: manual_package_linking_1

add_library(rnoh_app SHARED
    "./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_tts)
# RNOH_END: manual_package_linking_2

4. 引入 RNTTSPackage

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

#include "RNOH/PackageProvider.h"
+ #include "RNTTSPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
       +  std::make_shared<RNTTSPackage>(ctx)
    };
}

5. 在 ArkTS 侧引入 RNTTSPackage

打开 entry/src/main/ets/RNPackagesFactory.ts,添加:

import { RNTTSPackage } from '@react-native-ohos/react-native-tts/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new RNTTSPackage(ctx)
  ];
}

📖 API 详解

speak - 朗读文本

将文本转换为语音并朗读出来。

类型(text: string, options?: SpeakOptions) => Promise<string>

参数说明

参数 类型 必填 说明
text string 要朗读的文本内容
options SpeakOptions 朗读选项配置

SpeakOptions 配置

参数 类型 说明
rate number 语速(0.5 - 2.0)
pitch number 音调(0.5 - 2.0)
language string 语言代码(如 ‘zh-CN’)
voice string 语音 ID
onWordBoundary boolean 是否触发单词边界事件
skipTransform boolean 是否跳过文本转换

返回值:Promise<string> - 返回朗读任务的 ID

使用场景

  • 新闻播报
  • 消息通知
  • 导航提示
  • 电子书阅读
import React, { useState, useCallback } from "react";
import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
import Tts from "react-native-tts";

const SpeakExample = () => {
  const [isSpeaking, setIsSpeaking] = useState(false);

  const speakText = useCallback(async () => {
    try {
      setIsSpeaking(true);
      const utteranceId = await Tts.speak(
        "欢迎使用文字转语音功能,这是一个简单的示例。",
        {
          rate: 1.0,
          pitch: 1.0,
        }
      );
      console.log("朗读任务 ID:", utteranceId);
    } catch (error) {
      Alert.alert("错误", "朗读失败");
      setIsSpeaking(false);
    }
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>文字转语音示例</Text>
      <TouchableOpacity
        style={[styles.button, isSpeaking && styles.buttonDisabled]}
        onPress={speakText}
        disabled={isSpeaking}
      >
        <Text style={styles.buttonText}>
          {isSpeaking ? "正在朗读..." : "开始朗读"}
        </Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
    alignItems: "center",
  },
  title: {
    fontSize: 18,
    fontWeight: "600",
    marginBottom: 20,
  },
  button: {
    backgroundColor: "#007AFF",
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  buttonDisabled: {
    backgroundColor: "#CCCCCC",
  },
  buttonText: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "500",
  },
});

export default SpeakExample;

stop - 停止朗读

停止当前的语音朗读。

类型() => void

import Tts from "react-native-tts";

const stopSpeaking = () => {
  Tts.stop();
  console.log("已停止朗读");
};

pause - 暂停朗读

暂停当前的语音朗读。

类型() => void

import Tts from "react-native-tts";

const pauseSpeaking = () => {
  Tts.pause();
  console.log("已暂停朗读");
};

resume - 恢复朗读

恢复暂停的语音朗读。

类型() => void

import Tts from "react-native-tts";

const resumeSpeaking = () => {
  Tts.resume();
  console.log("已恢复朗读");
};

setDefaultRate - 设置默认语速

设置全局默认的语音朗读速度。

类型(rate: number) => void

参数说明

参数 类型 必填 说明
rate number 语速值,范围 0.5(最慢)- 2.0(最快),默认 1.0
import React, { useState } from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import Tts from "react-native-tts";

const RateExample = () => {
  const [currentRate, setCurrentRate] = useState(1.0);

  const setRate = (rate: number) => {
    Tts.setDefaultRate(rate);
    setCurrentRate(rate);
  };

  const speakTest = () => {
    Tts.speak("这是语速测试文本");
  };

  return (
    <View style={styles.container}>
      <Text style={styles.label}>当前语速: {currentRate.toFixed(1)}</Text>
      <View style={styles.buttonGroup}>
        {[0.5, 0.75, 1.0, 1.25, 1.5, 2.0].map((rate) => (
          <TouchableOpacity
            key={rate}
            style={[
              styles.rateButton,
              currentRate === rate && styles.rateButtonActive,
            ]}
            onPress={() => setRate(rate)}
          >
            <Text style={styles.rateButtonText}>{rate}x</Text>
          </TouchableOpacity>
        ))}
      </View>
      <TouchableOpacity style={styles.testButton} onPress={speakTest}>
        <Text style={styles.testButtonText}>测试语速</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  label: {
    fontSize: 16,
    marginBottom: 12,
  },
  buttonGroup: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: 8,
    marginBottom: 20,
  },
  rateButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    backgroundColor: "#E5E5EA",
    borderRadius: 8,
  },
  rateButtonActive: {
    backgroundColor: "#007AFF",
  },
  rateButtonText: {
    color: "#333333",
    fontWeight: "500",
  },
  testButton: {
    backgroundColor: "#34C759",
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: "center",
  },
  testButtonText: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "500",
  },
});

export default RateExample;

setDefaultPitch - 设置默认音调

设置全局默认的语音音调。

类型(pitch: number) => void

参数说明

参数 类型 必填 说明
pitch number 音调值,范围 0.5(低沉)- 2.0(尖锐),默认 1.0
import React, { useState } from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import Tts from "react-native-tts";

const PitchExample = () => {
  const [currentPitch, setCurrentPitch] = useState(1.0);

  const setPitch = (pitch: number) => {
    Tts.setDefaultPitch(pitch);
    setCurrentPitch(pitch);
  };

  const speakTest = () => {
    Tts.speak("这是音调测试文本");
  };

  return (
    <View style={styles.container}>
      <Text style={styles.label}>当前音调: {currentPitch.toFixed(1)}</Text>
      <View style={styles.buttonGroup}>
        {[0.5, 0.75, 1.0, 1.25, 1.5, 2.0].map((pitch) => (
          <TouchableOpacity
            key={pitch}
            style={[
              styles.pitchButton,
              currentPitch === pitch && styles.pitchButtonActive,
            ]}
            onPress={() => setPitch(pitch)}
          >
            <Text style={styles.pitchButtonText}>{pitch}</Text>
          </TouchableOpacity>
        ))}
      </View>
      <TouchableOpacity style={styles.testButton} onPress={speakTest}>
        <Text style={styles.testButtonText}>测试音调</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  label: {
    fontSize: 16,
    marginBottom: 12,
  },
  buttonGroup: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: 8,
    marginBottom: 20,
  },
  pitchButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    backgroundColor: "#E5E5EA",
    borderRadius: 8,
  },
  pitchButtonActive: {
    backgroundColor: "#FF9500",
  },
  pitchButtonText: {
    color: "#333333",
    fontWeight: "500",
  },
  testButton: {
    backgroundColor: "#34C759",
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: "center",
  },
  testButtonText: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "500",
  },
});

export default PitchExample;

voices - 获取可用语音列表

获取设备上可用的语音列表。

类型() => Promise<Voice[]>

返回值:Promise<Voice[]> - 语音列表

Voice 对象结构

属性 类型 说明
id string 语音 ID
name string 语音名称
language string 语言代码
quality number 语音质量
latency number 延迟
networkRequired boolean 是否需要网络
notInstalled boolean 是否未安装
import React, { useState, useCallback } from "react";
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from "react-native";
import Tts from "react-native-tts";

type Voice = {
  id: string;
  name: string;
  language: string;
};

const VoicesExample = () => {
  const [voices, setVoices] = useState<Voice[]>([]);
  const [loading, setLoading] = useState(false);

  const loadVoices = useCallback(async () => {
    setLoading(true);
    try {
      const voiceList = await Tts.voices();
      setVoices(voiceList as Voice[]);
    } catch (error) {
      console.error("获取语音列表失败:", error);
    } finally {
      setLoading(false);
    }
  }, []);

  const renderVoiceItem = ({ item }: { item: Voice }) => (
    <View style={styles.voiceItem}>
      <Text style={styles.voiceName}>{item.name}</Text>
      <Text style={styles.voiceLanguage}>语言: {item.language}</Text>
      <Text style={styles.voiceId}>ID: {item.id}</Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.loadButton} onPress={loadVoices}>
        <Text style={styles.loadButtonText}>
          {loading ? "加载中..." : "获取语音列表"}
        </Text>
      </TouchableOpacity>
      <FlatList
        data={voices}
        keyExtractor={(item) => item.id}
        renderItem={renderVoiceItem}
        ListEmptyComponent={
          <Text style={styles.emptyText}>暂无语音数据</Text>
        }
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  loadButton: {
    backgroundColor: "#007AFF",
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: "center",
    marginBottom: 16,
  },
  loadButtonText: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "500",
  },
  voiceItem: {
    backgroundColor: "#F5F5F5",
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
  },
  voiceName: {
    fontSize: 16,
    fontWeight: "600",
    color: "#333333",
  },
  voiceLanguage: {
    fontSize: 14,
    color: "#666666",
    marginTop: 4,
  },
  voiceId: {
    fontSize: 12,
    color: "#999999",
    marginTop: 4,
  },
  emptyText: {
    textAlign: "center",
    color: "#999999",
    marginTop: 40,
  },
});

export default VoicesExample;

getInitStatus - 获取初始化状态

获取 TTS 引擎的初始化状态。

类型() => Promise<void>

返回值:Promise<void> - 初始化完成时 resolve

import Tts from "react-native-tts";

const checkInitStatus = async () => {
  try {
    await Tts.getInitStatus();
    console.log("TTS 引擎已初始化");
    return true;
  } catch (error) {
    console.error("TTS 引擎未初始化:", error);
    return false;
  }
};

addEventListener - 添加事件监听

添加 TTS 事件监听器。

类型(event: TTSEvent, callback: (data: any) => void) => void

事件类型

事件名称 说明 回调数据
tts-start 开始朗读 { utteranceId: string }
tts-progress 朗读进度 { utteranceId: string, location: number }
tts-finish 朗读完成 { utteranceId: string }
tts-cancel 朗读取消 { utteranceId: string }
tts-error 朗读错误 { utteranceId: string, error: string }
import React, { useEffect, useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import Tts from "react-native-tts";

const EventListenerExample = () => {
  const [status, setStatus] = useState("等待中...");

  useEffect(() => {
    Tts.addEventListener("tts-start", (event) => {
      console.log("开始朗读:", event.utteranceId);
      setStatus("正在朗读...");
    });

    Tts.addEventListener("tts-progress", (event) => {
      console.log("朗读进度:", event.location);
      setStatus(`朗读进度: ${event.location}`);
    });

    Tts.addEventListener("tts-finish", (event) => {
      console.log("朗读完成:", event.utteranceId);
      setStatus("朗读完成");
    });

    Tts.addEventListener("tts-cancel", (event) => {
      console.log("朗读取消:", event.utteranceId);
      setStatus("朗读取消");
    });

    Tts.addEventListener("tts-error", (event) => {
      console.log("朗读错误:", event.error);
      setStatus(`朗读错误: ${event.error}`);
    });

    return () => {
      Tts.removeEventListener("tts-start", () => {});
      Tts.removeEventListener("tts-progress", () => {});
      Tts.removeEventListener("tts-finish", () => {});
      Tts.removeEventListener("tts-cancel", () => {});
      Tts.removeEventListener("tts-error", () => {});
    };
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.statusText}>状态: {status}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  statusText: {
    fontSize: 16,
    color: "#333333",
  },
});

export default EventListenerExample;

removeEventListener - 移除事件监听

移除 TTS 事件监听器。

类型(event: TTSEvent, callback: (data: any) => void) => void

import Tts from "react-native-tts";

const handleTtsStart = (event: any) => {
  console.log("开始朗读:", event);
};

// 添加监听
Tts.addEventListener("tts-start", handleTtsStart);

// 移除监听
Tts.removeEventListener("tts-start", handleTtsStart);

📋 完整示例

import React, { useState, useEffect, useCallback } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  TextInput,
  SafeAreaView,
  StatusBar,
  Alert,
} from "react-native";
import Tts from "react-native-tts";

type Voice = {
  id: string;
  name: string;
  language: string;
};

type TTSStatus = "idle" | "speaking" | "paused" | "error";

const App: React.FC = () => {
  const [text, setText] = useState(
    "欢迎使用文字转语音功能。这是一个完整的示例应用,展示了 TTS 的各种功能。"
  );
  const [status, setStatus] = useState<TTSStatus>("idle");
  const [rate, setRate] = useState(1.0);
  const [pitch, setPitch] = useState(1.0);
  const [voices, setVoices] = useState<Voice[]>([]);
  const [currentUtteranceId, setCurrentUtteranceId] = useState<string>("");

  useEffect(() => {
    initTTS();
    return () => {
      Tts.stop();
    };
  }, []);

  const initTTS = async () => {
    try {
      await Tts.getInitStatus();
      console.log("TTS 初始化成功");

      Tts.addEventListener("tts-start", (event) => {
        setStatus("speaking");
        setCurrentUtteranceId(event.utteranceId || "");
      });

      Tts.addEventListener("tts-finish", () => {
        setStatus("idle");
        setCurrentUtteranceId("");
      });

      Tts.addEventListener("tts-cancel", () => {
        setStatus("idle");
        setCurrentUtteranceId("");
      });

      Tts.addEventListener("tts-error", (event) => {
        setStatus("error");
        console.error("TTS 错误:", event.error);
      });

      const voiceList = await Tts.voices();
      setVoices(voiceList as Voice[]);
    } catch (error) {
      console.error("TTS 初始化失败:", error);
      Alert.alert("错误", "TTS 初始化失败");
    }
  };

  const speak = useCallback(async () => {
    if (!text.trim()) {
      Alert.alert("提示", "请输入要朗读的文本");
      return;
    }

    try {
      await Tts.speak(text);
    } catch (error) {
      Alert.alert("错误", "朗读失败");
    }
  }, [text]);

  const stop = useCallback(() => {
    Tts.stop();
    setStatus("idle");
  }, []);

  const pause = useCallback(() => {
    Tts.pause();
    setStatus("paused");
  }, []);

  const resume = useCallback(() => {
    Tts.resume();
    setStatus("speaking");
  }, []);

  const handleRateChange = useCallback((newRate: number) => {
    setRate(newRate);
    Tts.setDefaultRate(newRate);
  }, []);

  const handlePitchChange = useCallback((newPitch: number) => {
    setPitch(newPitch);
    Tts.setDefaultPitch(newPitch);
  }, []);

  const getStatusText = () => {
    switch (status) {
      case "speaking":
        return "正在朗读...";
      case "paused":
        return "已暂停";
      case "error":
        return "发生错误";
      default:
        return "就绪";
    }
  };

  const getStatusColor = () => {
    switch (status) {
      case "speaking":
        return "#34C759";
      case "paused":
        return "#FF9500";
      case "error":
        return "#FF3B30";
      default:
        return "#007AFF";
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      <View style={styles.header}>
        <Text style={styles.headerTitle}>文字转语音</Text>
        <View style={[styles.statusBadge, { backgroundColor: getStatusColor() }]}>
          <Text style={styles.statusText}>{getStatusText()}</Text>
        </View>
      </View>

      <ScrollView style={styles.scrollView}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>输入文本</Text>
          <TextInput
            style={styles.textInput}
            value={text}
            onChangeText={setText}
            placeholder="请输入要朗读的文本"
            multiline
            numberOfLines={4}
            textAlignVertical="top"
          />
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>播放控制</Text>
          <View style={styles.controlButtons}>
            <TouchableOpacity
              style={[
                styles.controlButton,
                status === "speaking" && styles.buttonDisabled,
              ]}
              onPress={speak}
              disabled={status === "speaking"}
            >
              <Text style={styles.controlButtonText}>朗读</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[
                styles.controlButton,
                styles.pauseButton,
                status !== "speaking" && styles.buttonDisabled,
              ]}
              onPress={pause}
              disabled={status !== "speaking"}
            >
              <Text style={styles.controlButtonText}>暂停</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[
                styles.controlButton,
                styles.resumeButton,
                status !== "paused" && styles.buttonDisabled,
              ]}
              onPress={resume}
              disabled={status !== "paused"}
            >
              <Text style={styles.controlButtonText}>恢复</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[
                styles.controlButton,
                styles.stopButton,
                status === "idle" && styles.buttonDisabled,
              ]}
              onPress={stop}
              disabled={status === "idle"}
            >
              <Text style={styles.controlButtonText}>停止</Text>
            </TouchableOpacity>
          </View>
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>语速调节</Text>
          <Text style={styles.currentValue}>当前语速: {rate.toFixed(2)}x</Text>
          <View style={styles.sliderContainer}>
            {[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0].map((value) => (
              <TouchableOpacity
                key={value}
                style={[
                  styles.sliderButton,
                  rate === value && styles.sliderButtonActive,
                ]}
                onPress={() => handleRateChange(value)}
              >
                <Text
                  style={[
                    styles.sliderButtonText,
                    rate === value && styles.sliderButtonTextActive,
                  ]}
                >
                  {value}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>音调调节</Text>
          <Text style={styles.currentValue}>当前音调: {pitch.toFixed(2)}</Text>
          <View style={styles.sliderContainer}>
            {[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0].map((value) => (
              <TouchableOpacity
                key={value}
                style={[
                  styles.sliderButton,
                  pitch === value && styles.sliderButtonActive,
                ]}
                onPress={() => handlePitchChange(value)}
              >
                <Text
                  style={[
                    styles.sliderButtonText,
                    pitch === value && styles.sliderButtonTextActive,
                  ]}
                >
                  {value}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>可用语音 ({voices.length})</Text>
          {voices.length === 0 ? (
            <Text style={styles.emptyText}>暂无可用语音</Text>
          ) : (
            voices.slice(0, 5).map((voice) => (
              <View key={voice.id} style={styles.voiceItem}>
                <Text style={styles.voiceName}>{voice.name}</Text>
                <Text style={styles.voiceLanguage}>语言: {voice.language}</Text>
              </View>
            ))
          )}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#F5F5F5",
  },
  header: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    padding: 16,
    backgroundColor: "#FFFFFF",
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5EA",
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: "700",
    color: "#333333",
  },
  statusBadge: {
    paddingHorizontal: 12,
    paddingVertical: 4,
    borderRadius: 12,
  },
  statusText: {
    color: "#FFFFFF",
    fontSize: 12,
    fontWeight: "600",
  },
  scrollView: {
    flex: 1,
  },
  section: {
    backgroundColor: "#FFFFFF",
    margin: 16,
    marginBottom: 0,
    marginTop: 16,
    padding: 16,
    borderRadius: 12,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: "600",
    color: "#333333",
    marginBottom: 12,
  },
  textInput: {
    backgroundColor: "#F5F5F5",
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    minHeight: 100,
    color: "#333333",
  },
  controlButtons: {
    flexDirection: "row",
    gap: 8,
  },
  controlButton: {
    flex: 1,
    backgroundColor: "#007AFF",
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: "center",
  },
  pauseButton: {
    backgroundColor: "#FF9500",
  },
  resumeButton: {
    backgroundColor: "#34C759",
  },
  stopButton: {
    backgroundColor: "#FF3B30",
  },
  buttonDisabled: {
    opacity: 0.5,
  },
  controlButtonText: {
    color: "#FFFFFF",
    fontSize: 14,
    fontWeight: "600",
  },
  currentValue: {
    fontSize: 14,
    color: "#666666",
    marginBottom: 12,
  },
  sliderContainer: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: 8,
  },
  sliderButton: {
    paddingHorizontal: 14,
    paddingVertical: 8,
    backgroundColor: "#E5E5EA",
    borderRadius: 8,
  },
  sliderButtonActive: {
    backgroundColor: "#007AFF",
  },
  sliderButtonText: {
    fontSize: 14,
    color: "#333333",
    fontWeight: "500",
  },
  sliderButtonTextActive: {
    color: "#FFFFFF",
  },
  emptyText: {
    fontSize: 14,
    color: "#999999",
    textAlign: "center",
    paddingVertical: 20,
  },
  voiceItem: {
    backgroundColor: "#F5F5F5",
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
  },
  voiceName: {
    fontSize: 14,
    fontWeight: "600",
    color: "#333333",
  },
  voiceLanguage: {
    fontSize: 12,
    color: "#666666",
    marginTop: 4,
  },
});

export default App;

⚠️ 注意事项

遗留问题

问题 说明 Issue
三方引擎相关功能 无法实现三方引擎功能 issue#2
语言类型限制 目前只支持中文 issue#3
setDucking 无法实现降低其他音频音量 issue#4
setIgnoreSilentSwitch 无法实现忽略静音开关 issue#5
setDefaultVoice 无法设置默认语音 issue#6
边合成边播放 边合成边播放功能问题 issue#7
参数无效 skipTransform、onWordBoundary 参数无效 issue#8

使用建议

  1. 初始化检查:使用前建议调用 getInitStatus() 检查 TTS 引擎是否已初始化
  2. 事件监听:建议添加事件监听器以获取朗读状态
  3. 资源释放:组件卸载时调用 stop() 停止朗读
  4. 语速音调:根据实际场景调整合适的语速和音调

常见问题

Q: 朗读没有声音?

A: 检查设备音量设置,确保媒体音量已开启。同时确认 TTS 引擎已正确初始化。

Q: 只支持中文吗?

A: 目前 HarmonyOS 版本存在语言类型限制,主要支持中文。其他语言支持正在完善中。

Q: 如何切换不同的语音?

A: 使用 voices() 获取可用语音列表,但目前 setDefaultVoice 功能暂不支持。

📚 参考资料

  • 官方文档: https://github.com/ak1394/react-native-tts
  • 鸿蒙适配仓库: https://github.com/react-native-oh-library/react-native-tts
  • 问题反馈: https://github.com/react-native-oh-library/react-native-tts/issues
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐