更新记录

1.0.0(2026-06-22)

tts


平台兼容性

TTS 语音合成插件 — 使用手册

让你的 uni-app 项目拥有"开口说话"的能力。

支持平台: Android 5.0+ | iOS 12.0+ | 鸿蒙 NEXT 4.0+


目录


零、5 分钟快速体验

第 1 步:引入插件

.vue 文件的 <script setup> 中:

import { ttsSpeak } from '@/uni_modules/sehochen-tts'

第 2 步:一行代码让手机说话

ttsSpeak({ text: "你好,世界!" })

手机就会读出"你好,世界!"。

第 3 步:加上回调,知道播放状态

import { ttsSpeak, ttsStop } from '@/uni_modules/sehochen-tts'

// 播放
ttsSpeak({
  text: "你好,欢迎使用TTS语音合成",
  lang: "zh-CN",     // 中文
  rate: 1.0,         // 正常语速
  onStart: () => console.log("开始播放了"),
  onDone:  () => console.log("播放完成"),
  fail:    (err) => console.log("出错:", err.errMsg)
})

// 随时可以停止
ttsStop()

5 分钟,你已经掌握了核心用法。


一、完整 Demo 页面(复制即用)

以下代码复制到一个 .vue 文件中即可运行。包含:文本输入、语言切换、播放/暂停/恢复/停止、语速/音调/音量滑块、引擎状态、日志

<template>
  <view class="page">
    <view class="title">TTS 语音合成</view>

    <!-- 文本输入 -->
    <view class="box">
      <textarea v-model="text" placeholder="输入文字,点播放按钮朗读" />
    </view>

    <!-- 语言快捷选择 -->
    <view class="lang-row">
      <view v-for="item in langList" :key="item.lang"
        :class="['lang-tag', { active: lang === item.lang }]"
        @click="switchLang(item)">
        {{ item.label }}
      </view>
    </view>

    <!-- 播放控制 -->
    <view class="btn-row">
      <button class="btn-play" @click="play">播放</button>
      <button class="btn-pause" @click="pause">暂停</button>
      <button class="btn-resume" @click="resume">恢复</button>
      <button class="btn-stop" @click="stop">停止</button>
    </view>

    <!-- 状态提示 -->
    <view v-if="status" class="status">{{ status }}</view>

    <!-- 语速滑块 -->
    <view class="box">
      <view class="slider-head">
        <text>语速</text>
        <text class="slider-val">{{ rate.toFixed(1) }}x</text>
      </view>
      <slider :min="0.5" :max="2.0" :step="0.1" :value="rate"
        activeColor="#007aff" @change="rate = $event.detail.value" />
    </view>

    <!-- 音调滑块 -->
    <view class="box">
      <view class="slider-head">
        <text>音调</text>
        <text class="slider-val">{{ pitch.toFixed(1) }}</text>
      </view>
      <slider :min="0.5" :max="2.0" :step="0.1" :value="pitch"
        activeColor="#007aff" @change="pitch = $event.detail.value" />
    </view>

    <!-- 音量滑块 -->
    <view class="box">
      <view class="slider-head">
        <text>音量</text>
        <text class="slider-val">{{ (volume * 100).toFixed(0) }}%</text>
      </view>
      <slider :min="0.0" :max="1.0" :step="0.1" :value="volume"
        activeColor="#007aff" @change="volume = $event.detail.value" />
    </view>

    <!-- 引擎状态 -->
    <view class="box">
      <view class="section-title">引擎状态</view>
      <view class="info-row"><text>引擎名称:</text><text>{{ engine.engineName }}</text></view>
      <view class="info-row"><text>默认语言:</text><text>{{ engine.defaultLanguage }}</text></view>
      <view class="info-row"><text>默认人声:</text><text>{{ engine.defaultVoice }}</text></view>
      <view class="info-row"><text>初始化状态:</text><text>{{ statusText(engine.initStatus) }}</text></view>
      <view class="info-row"><text>是否播放中:</text><text>{{ engine.isSpeaking ? '是' : '否' }}</text></view>
      <view class="info-row"><text>语言可用:</text><text>{{ engine.isLanguageAvailable ? '是' : '否' }}</text></view>
      <view class="refresh-btn" @click="refreshEngine">刷新状态</view>
    </view>

    <!-- 日志 -->
    <view class="box">
      <view class="section-title">
        <text>日志</text>
        <text class="clear-btn" @click="logs = []">清空</text>
      </view>
      <view v-if="logs.length === 0" class="empty-text">暂无日志</view>
      <view v-for="(item, i) in logs" :key="i" :class="['log-item', item.type]">
        <text class="log-time">{{ item.time }}</text>
        <text>{{ item.msg }}</text>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import {
  ttsSpeak, ttsStop, ttsPause, ttsResume,
  ttsGetEngineInfo, ttsGetInstalledEngines
} from '@/uni_modules/sehochen-tts'

// 播放参数
const text = ref('你好,世界!欢迎使用TTS语音合成。')
const rate = ref(1.0)
const pitch = ref(1.0)
const volume = ref(1.0)
const lang = ref('zh-CN')
const status = ref('')

// 引擎状态
const engine = ref({
  engineName: '加载中...',
  defaultLanguage: '-',
  defaultVoice: '-',
  initStatus: 0,
  isSpeaking: false,
  isLanguageAvailable: false
})

// 日志
const logs = ref([])
function addLog(msg, type = 'info') {
  const d = new Date()
  const pad = (n) => String(n).padStart(2, '0')
  const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
  logs.value.unshift({ time, msg, type })
  if (logs.value.length > 30) logs.value.pop()
}

function statusText(s) {
  const map = { 0: '未初始化', 1: '初始化中', 2: '已就绪', '-1': '失败' }
  return map[s] || '未知'
}

function refreshEngine() {
  try {
    engine.value = ttsGetEngineInfo()
    addLog('引擎状态已刷新', 'info')
  } catch (e) {
    addLog('刷新失败: ' + e, 'error')
  }
}

// 语言列表
const langList = [
  { label: '中文',     lang: 'zh-CN', text: '你好,世界!欢迎使用TTS语音合成。' },
  { label: 'English',  lang: 'en-US', text: 'Hello, welcome to TTS speech synthesis.' },
  { label: '日本語',    lang: 'ja-JP', text: 'こんにちは、TTS音声合成へようこそ。' },
  { label: '한국어',    lang: 'ko-KR', text: '안녕하세요, TTS 음성 합성에 오신 것을 환영합니다.' },
  { label: 'Français', lang: 'fr-FR', text: 'Bonjour, bienvenue dans la synthèse vocale TTS.' },
  { label: 'Русский',  lang: 'ru-RU', text: 'Здравствуйте, добро пожаловать в синтез речи TTS.' }
]

function switchLang(item) {
  text.value = item.text
  lang.value = item.lang
  addLog(`切换到 ${item.label}`, 'info')
}

// 播放控制
function play() {
  if (!text.value.trim()) {
    uni.showToast({ title: '请先输入文字', icon: 'none' })
    return
  }

  status.value = '正在播放...'
  addLog(`播放 [${lang.value}]: ${text.value.substring(0, 20)}...`, 'info')

  ttsSpeak({
    text: text.value,
    lang: lang.value,
    rate: rate.value,
    pitch: pitch.value,
    volume: volume.value,
    onStart: () => {
      addLog('开始合成', 'success')
    },
    onDone: () => {
      status.value = '播放完成'
      addLog('播放完成', 'success')
    },
    onStop: () => {
      status.value = '已停止'
      addLog('播放被中断', 'warn')
    },
    fail: (err) => {
      status.value = ''
      addLog(`播放失败 [${err.errCode}]: ${err.errMsg}`, 'error')
      uni.showToast({ title: err.errMsg, icon: 'none', duration: 3000 })
    }
  })
}

function pause() {
  ttsPause()
  status.value = '已暂停'
  addLog('暂停', 'warn')
}

function resume() {
  ttsResume()
  status.value = '正在播放...'
  addLog('恢复播放', 'info')
}

function stop() {
  ttsStop()
  status.value = '已停止'
  addLog('手动停止', 'warn')
}

onMounted(() => {
  addLog('页面就绪', 'info')
  setTimeout(refreshEngine, 500)
})
</script>

<style>
.page { padding: 20px; background: #f5f5f5; min-height: 100vh; }
.title { font-size: 24px; font-weight: bold; text-align: center; margin-bottom: 20px; }
.box { background: #fff; padding: 15px; border-radius: 10px; margin-bottom: 12px; }
textarea { width: 100%; min-height: 60px; font-size: 16px; }
.lang-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
.lang-tag { padding: 8px 16px; border-radius: 20px; background: #eee; font-size: 14px; }
.lang-tag.active { background: #007aff; color: #fff; }
.btn-row { display: flex; gap: 8px; margin-bottom: 12px; }
.btn-row button { flex: 1; height: 42px; font-size: 15px; border-radius: 8px; color: #fff; border: none; }
.btn-play { background: #007aff; }
.btn-pause { background: #ff9500; }
.btn-resume { background: #34c759; }
.btn-stop { background: #ff3b30; }
.status { text-align: center; color: #007aff; margin-bottom: 12px; font-size: 14px; }
.slider-head { display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 15px; }
.slider-val { color: #007aff; font-weight: bold; }
.section-title { font-size: 16px; font-weight: bold; margin-bottom: 10px; display: flex; justify-content: space-between; }
.info-row { display: flex; padding: 4px 0; font-size: 14px; }
.info-row text:first-child { color: #999; width: 100px; }
.info-row text:last-child { color: #333; }
.refresh-btn { text-align: center; color: #007aff; font-size: 14px; margin-top: 10px; padding: 6px; background: #e8f0fe; border-radius: 16px; }
.clear-btn { font-size: 14px; color: #ff3b30; font-weight: normal; }
.empty-text { text-align: center; color: #ccc; font-size: 14px; padding: 20px 0; }
.log-item { display: flex; gap: 10px; padding: 6px 0; border-bottom: 1px solid #f0f0f0; font-size: 13px; }
.log-time { color: #ccc; font-family: monospace; flex-shrink: 0; }
.log-item.info { color: #666; }
.log-item.success { color: #34c759; }
.log-item.warn { color: #ff9500; }
.log-item.error { color: #ff3b30; }
</style>

二、API 详细说明

2.1 ttsSpeak — 播放文字

让手机朗读一段文字。首次调用会自动初始化 TTS 引擎(异步,约 1~3 秒)。

import { ttsSpeak } from '@/uni_modules/sehochen-tts'

ttsSpeak({
  text: "你好,欢迎使用TTS",   // 【必填】要朗读的文字
  lang: "zh-CN",               // 可选,语言代码,默认 "zh-CN"
  rate: 1.0,                   // 可选,语速 0.5~2.0,默认 1.0
  pitch: 1.0,                  // 可选,音调 0.5~2.0,默认 1.0
  volume: 1.0,                 // 可选,音量 0.0~1.0,默认 1.0
  voice: "",                   // 可选,指定人声名称

  // 回调函数(全部可选)
  onStart: () => {},           // 开始播放
  onDone:  () => {},           // 自然播放完毕
  onStop:  () => {},           // 被 ttsStop() 中断
  success: () => {},           // 播放成功(onDone 之后)
  fail:    (err) => {},        // 播放失败
  complete:() => {},           // 无论成功失败都触发(最后)
})

行为说明

  • 如果当前正在播放,新调用会自动停止旧播放,然后开始新播放(QUEUE_FLUSH 模式)
  • 如果引擎未初始化,会自动触发初始化并等待完成后播放
  • 所有参数除 text 外都是可选的,不传则使用默认值

2.2 ttsStop — 停止播放

立即停止当前正在播放的语音。

import { ttsStop } from '@/uni_modules/sehochen-tts'

ttsStop()

效果

  • 触发当前播放的 onStop 回调
  • 清除暂停状态(之后调用 ttsResume 无效)
  • 引擎保持可用,可立即再次调用 ttsSpeak

注意:如果当前没有在播放,调用也不会报错,只是什么都不做。


2.3 ttsPause — 暂停播放

暂停当前播放的语音,之后可以用 ttsResume() 从断点处继续。

import { ttsPause } from '@/uni_modules/sehochen-tts'

ttsPause()

前提条件:必须在播放中(isSpeakingFlag === true)且不在暂停状态。

平台实现差异

平台 实现方式 精度
iOS 调用原生 pauseSpeaking(at: .word) ✅ 精确到单词
Android 记录已播放时长 → stop() → 恢复时估算断点续播 ⚠️ 按时间估算(中文约 4 字/秒)
鸿蒙 同 Android,记录时长后估算断点续播 ⚠️ 按时间估算

注意事项

  • 暂停不会触发 onStop 回调
  • 暂停后如果调用了 ttsStop() 或新的 ttsSpeak(),暂停状态会被清除
  • Android/鸿蒙的暂停是模拟实现,不是音频级暂停,恢复时位置有 1~3 个字的偏差

2.4 ttsResume — 恢复播放

从暂停处继续播放。

import { ttsResume } from '@/uni_modules/sehochen-tts'

ttsResume()

前提条件

  • 必须之前调用过 ttsPause() 并且暂停状态未被清除
  • 如果期间调用了 ttsStop()ttsSpeak(),暂停状态会被清除,ttsResume() 无效果

Android/鸿蒙恢复逻辑

  1. 根据语速估算已播放的字符数(已播秒数 × 每秒字符数
  2. 从文本中截取剩余部分
  3. 用剩余文本重新开始播放
  4. 原始的所有回调(onDonefail 等)会被保留并绑定到新播放上

恢复后的回调流程:和正常播放一样,onStartonDonesuccesscomplete


2.5 ttsGetEngineInfo — 查看引擎状态

返回当前 TTS 引擎的详细信息。

import { ttsGetEngineInfo } from '@/uni_modules/sehochen-tts'

const info = ttsGetEngineInfo()

返回值(TTSEngineInfo 对象)

字段 类型 说明
engineName string 引擎名称,如 "com.google.android.tts"
defaultLanguage string 默认语言,如 "zh_CN"
defaultVoice string 默认人声名称
initStatus number 初始化状态(见下表)
isSpeaking boolean 是否正在播放
isLanguageAvailable boolean 默认语言是否可用
maxSpeechInputLength number 最大输入字符数,-1 表示未知
installedEngines string[] 已安装引擎列表

initStatus 含义

含义 此时能播放吗?
0 未初始化 调用 ttsSpeak 会自动触发初始化
1 初始化中 等 1~3 秒变成 2 就可以播
2 已就绪 ✅ 可以正常播放
-1 初始化失败 ❌ 需要安装 TTS 引擎

2.6 ttsGetInstalledEngines — 已安装引擎列表

返回设备上所有已安装的 TTS 引擎包名。

import { ttsGetInstalledEngines } from '@/uni_modules/sehochen-tts'

const engines = ttsGetInstalledEngines()
// 返回: ["com.google.android.tts", "com.iflytek.speechcloud"]

如果返回空数组 [],说明设备没有安装任何 TTS 引擎,需要安装。


2.7 ttsShutdown — 释放引擎

释放 TTS 引擎占用的系统资源。所有状态会重置。

import { ttsShutdown } from '@/uni_modules/sehochen-tts'

ttsShutdown()

使用时机

  • 页面退出时(onUnload
  • APP 不再需要 TTS 功能时
  • 想释放内存时

释放后的影响

  • initStatus 变为 0
  • isSpeaking 变为 false
  • 暂停状态被清除
  • 当前播放选项被清除
  • 下次调用 ttsSpeak 会自动重新初始化,无需手动操作

三、参数详解

3.1 text — 要朗读的文字

  • 类型string
  • 必填:✅ 是
  • 限制:取决于引擎,通常 Android 约 4000 字符,iOS 无明确限制
// 最短用法
ttsSpeak({ text: "你好" })

// 长文本也可以
ttsSpeak({ text: "这是一段很长的文字..." })

如果传入空字符串 "" 或不传,会触发 fail 回调(错误码 9020004)。


3.2 lang — 语言代码

  • 类型string
  • 默认值"zh-CN"
  • 格式语言-地区(BCP-47 标准)
ttsSpeak({ text: "Hello", lang: "en-US" })   // 英文
ttsSpeak({ text: "你好", lang: "zh-CN" })    // 中文
ttsSpeak({ text: "こんにちは", lang: "ja-JP" }) // 日文

常用语言代码

代码 语言 通常是否需要下载语言包
zh-CN 中文(简体) 一般自带
en-US 英语(美国) 可能需要下载
en-GB 英语(英国) 可能需要下载
ja-JP 日语 可能需要下载
ko-KR 韩语 可能需要下载
fr-FR 法语 可能需要下载
de-DE 德语 可能需要下载
es-ES 西班牙语 可能需要下载
pt-BR 葡萄牙语(巴西) 可能需要下载
ru-RU 俄语 可能需要下载
it-IT 意大利语 可能需要下载

切换语言后没声音? 去手机设置下载对应语言包。详见 第九节


3.3 rate — 语速

  • 类型number
  • 范围0.5 ~ 2.0
  • 默认值1.0

控制朗读的快慢。超出范围会触发错误 9020005。

效果 适合场景
0.5 ~ 0.7 很慢 学外语、听力练习
0.8 ~ 1.0 偏慢 老年人、儿童
1.0 ~ 1.2 正常 日常使用
1.3 ~ 1.6 偏快 快速浏览
1.7 ~ 2.0 很快 赶时间
ttsSpeak({ text: "我读得很慢", rate: 0.6 })
ttsSpeak({ text: "我读得很快", rate: 1.8 })

3.4 pitch — 音调

  • 类型number
  • 范围0.5 ~ 2.0
  • 默认值1.0

控制声音的高低(频率)。超出范围会触发错误 9020005。

效果
0.5 ~ 0.8 低沉(偏男声)
0.9 ~ 1.2 正常
1.3 ~ 2.0 尖细(偏童声)
ttsSpeak({ text: "低沉的声音", pitch: 0.6 })
ttsSpeak({ text: "尖细的声音", pitch: 1.8 })

3.5 volume — 音量

  • 类型number
  • 范围0.0 ~ 1.0
  • 默认值1.0

它是在系统媒体音量基础上的倍率,不是绝对音量。

效果
0.0 静音(听不到)
0.5 系统音量的一半
1.0 等于系统当前音量(最大)
ttsSpeak({ text: "轻声说", volume: 0.3 })
ttsSpeak({ text: "大声说", volume: 1.0 })

注意volume 不能超过系统音量。比如系统音量只开了 50%,插件 volume: 1.0 也只能播放 50% 的音量。


3.6 voice — 指定人声

  • 类型string
  • 默认值""(使用引擎默认人声)

指定使用哪个语音包(人声)。不同引擎支持的人声不同。

// 使用默认人声(不传或传空字符串)
ttsSpeak({ text: "你好" })

// 指定特定人声(需要引擎支持)
ttsSpeak({ text: "你好", voice: "zh-CN-Xiaoxiao" })

大多数场景不需要设置此参数,引擎会自动选择最匹配的人声。


四、回调函数详解

4.1 回调一览

回调 类型 触发时机 触发条件
onStart () => void 引擎开始合成/播放 播放成功启动
onDone () => void 整段文字自然播放完毕 没有被 stop 中断
onStop () => void ttsStop() 中断 手动停止或新播放覆盖
success () => void 播放成功 跟在 onDone 之后
fail (err: TTSFail) => void 播放出错 引擎错误、参数错误等
complete () => void 播放流程结束 成功或失败后都会触发

4.2 完整生命周期图

场景 A:正常播放完成

ttsSpeak()
  → 引擎初始化(如果需要)
  → onStart()      ← 开始播放
  → (播放中...)
  → onDone()       ← 自然播放完毕
  → success()      ← 紧随 onDone
  → complete()     ← 最后触发(无论成功失败)

场景 B:手动停止

ttsSpeak()
  → onStart()
  → (播放中...)
  → 用户调用 ttsStop()
  → onStop()       ← 被中断(不会触发 onDone/success/complete)

场景 C:播放出错

ttsSpeak()
  → (初始化或播放过程中出错)
  → fail(err)      ← 错误信息
  → complete()     ← 仍然会触发

场景 D:暂停/恢复

ttsSpeak()
  → onStart()
  → (播放中...)
  → 用户调用 ttsPause()    ← 不触发任何回调
  → (一段时间...)
  → 用户调用 ttsResume()
  → onStart()              ← 从断点恢复
  → (继续播放剩余文本...)
  → onDone() → success() → complete()

4.3 各场景回调触发表

场景 onStart onDone onStop success fail complete
正常播放完成
手动 ttsStop
新播放覆盖旧播放 ✅(旧)
ttsPause
暂停后恢复完成
播放出错

4.4 fail 回调的 err 对象

fail: (err) => {
  err.errCode  // number — 错误码,如 9020003
  err.errMsg   // string — 人类可读的错误描述,可直接展示
  err.detail   // string | null — 技术详情,用于排查
}

所有错误码及含义见 第六节:错误处理


五、实用场景示例

场景 1:播放/停止切换按钮

一个按钮,点一下播放,再点一下停止:

import { ref } from 'vue'
import { ttsSpeak, ttsStop } from '@/uni_modules/sehochen-tts'

const isPlaying = ref(false)

function toggle(text) {
  if (isPlaying.value) {
    ttsStop()
    isPlaying.value = false
    return
  }

  isPlaying.value = true
  ttsSpeak({
    text,
    onDone:  () => { isPlaying.value = false },
    onStop:  () => { isPlaying.value = false },
    fail:    () => { isPlaying.value = false }
  })
}

场景 2:顺序朗读列表

const playlist = [
  { text: "第一段文字", lang: "zh-CN" },
  { text: "Second paragraph", lang: "en-US" },
  { text: "第三段文字", lang: "zh-CN" }
]

let index = 0

function playNext() {
  if (index >= playlist.length) {
    console.log("全部播放完毕")
    return
  }

  const item = playlist[index]
  ttsSpeak({
    text: item.text,
    lang: item.lang,
    onDone: () => {
      index++
      setTimeout(playNext, 300)  // 段与段之间间隔 0.3 秒
    },
    fail: (err) => {
      console.error(`第 ${index + 1} 段播放失败:`, err.errMsg)
      index++
      playNext()  // 跳过失败的继续
    }
  })
}

playNext()

场景 3:带暂停/恢复的播放器

import { ref } from 'vue'
import { ttsSpeak, ttsStop, ttsPause, ttsResume } from '@/uni_modules/sehochen-tts'

const isPlaying = ref(false)
const isPaused = ref(false)

// 开始播放
function play(text) {
  isPlaying.value = true
  isPaused.value = false

  ttsSpeak({
    text,
    onStart: () => { isPlaying.value = true },
    onDone:  () => { resetState() },
    onStop:  () => { resetState() },
    fail:    () => { resetState() }
  })
}

// 暂停/恢复切换
function togglePause() {
  if (!isPlaying.value) return

  if (isPaused.value) {
    ttsResume()
    isPaused.value = false
  } else {
    ttsPause()
    isPaused.value = true
  }
}

// 停止
function stop() {
  ttsStop()
  resetState()
}

function resetState() {
  isPlaying.value = false
  isPaused.value = false
}

场景 4:朗读并高亮当前句子

import { ref } from 'vue'

const sentences = ref([
  '欢迎使用TTS语音合成。',
  '这是一个多语言支持的插件。',
  '感谢你的使用。'
])
const currentIndex = ref(-1)

function speakWithHighlight() {
  currentIndex.value = 0
  readCurrent()
}

function readCurrent() {
  if (currentIndex.value >= sentences.value.length) {
    currentIndex.value = -1
    console.log('全部读完')
    return
  }

  ttsSpeak({
    text: sentences.value[currentIndex.value],
    onDone: () => {
      currentIndex.value++
      setTimeout(readCurrent, 200)
    },
    fail: (err) => {
      console.error('朗读失败:', err.errMsg)
      currentIndex.value++
      readCurrent()
    }
  })
}
<!-- 模板中配合使用 -->
<view v-for="(s, i) in sentences" :key="i"
  :style="{ color: i === currentIndex ? '#007aff' : '#333', fontWeight: i === currentIndex ? 'bold' : 'normal' }">
  {{ s }}
</view>

场景 5:离开页面时清理

import { onUnload } from '@dcloudio/uni-app'
import { ttsStop, ttsShutdown } from '@/uni_modules/sehochen-tts'

onUnload(() => {
  ttsStop()       // 先停止正在播放的内容
  ttsShutdown()   // 再释放引擎资源
})

场景 6:引擎状态轮询

首次初始化是异步的,可以轮询等待就绪:

import { ttsGetEngineInfo, ttsSpeak } from '@/uni_modules/sehochen-tts'

function waitAndSpeak(text, maxRetry = 10) {
  let retry = 0

  const timer = setInterval(() => {
    const info = ttsGetEngineInfo()

    if (info.initStatus === 2) {
      // 引擎就绪,开始播放
      clearInterval(timer)
      ttsSpeak({ text })
    } else if (info.initStatus === -1 || retry >= maxRetry) {
      // 初始化失败或超时
      clearInterval(timer)
      uni.showToast({ title: 'TTS 引擎初始化失败', icon: 'none' })
    }

    retry++
  }, 300)  // 每 300ms 检查一次
}

场景 7:多语言一键朗读

const langMap = {
  zh: { code: 'zh-CN', text: '你好,世界' },
  en: { code: 'en-US', text: 'Hello, world' },
  ja: { code: 'ja-JP', text: 'こんにちは、世界' },
  ko: { code: 'ko-KR', text: '안녕하세요, 세계' },
  fr: { code: 'fr-FR', text: 'Bonjour le monde' },
  de: { code: 'de-DE', text: 'Hallo Welt' },
  es: { code: 'es-ES', text: 'Hola mundo' },
  ru: { code: 'ru-RU', text: 'Привет, мир' },
  it: { code: 'it-IT', text: 'Ciao mondo' },
  pt: { code: 'pt-BR', text: 'Olá mundo' }
}

function speakLang(key) {
  const item = langMap[key]
  if (!item) return

  ttsSpeak({
    text: item.text,
    lang: item.code,
    fail: (err) => {
      uni.showToast({
        title: `${key} 不可用: ${err.errMsg}`,
        icon: 'none',
        duration: 2500
      })
    }
  })
}

六、错误处理

6.1 错误码完整列表

错误码 常量含义 触发原因 解决方法
9020001 初始化失败 设备没有安装 TTS 引擎,或引擎初始化异常 安装 Google TTS / 讯飞语记
9020002 播放失败 引擎在播放过程中内部出错 重试或重启 APP
9020003 语言不可用 指定语言的语言包未安装 去系统设置下载语言包
9020004 文本为空 text 参数为空字符串或未传 确保传入有效文字
9020005 参数错误 rate/pitch 不在 0.5~2.0 范围 检查参数值

6.2 推荐的错误处理写法

function speak(text, lang) {
  ttsSpeak({
    text,
    lang,
    fail: (err) => {
      switch (err.errCode) {
        case 9020001:
          // TTS 引擎未安装
          uni.showModal({
            title: 'TTS 不可用',
            content: '您的手机未安装文字转语音引擎,请安装 Google 文字转语音或讯飞语记。',
            showCancel: false
          })
          break

        case 9020003:
          // 语言包未安装
          uni.showModal({
            title: '语言不支持',
            content: `您的手机未安装 ${lang} 语言包。\n请前往 设置 → 语言和输入法 → 文字转语音 → 安装语音数据 下载。`,
            showCancel: false
          })
          break

        case 9020004:
          // 没输入文字
          uni.showToast({ title: '请输入要朗读的文字', icon: 'none' })
          break

        case 9020005:
          // 参数错误
          console.error('TTS 参数错误,请检查 rate/pitch 范围')
          break

        default:
          // 其他错误
          uni.showToast({ title: err.errMsg || '播放失败', icon: 'none', duration: 3000 })
      }
    }
  })
}

6.3 防御性调用

避免在引擎未就绪时出错:

import { ttsGetEngineInfo, ttsSpeak } from '@/uni_modules/sehochen-tts'

function safeSpeak(text) {
  const info = ttsGetEngineInfo()

  if (info.initStatus === -1) {
    uni.showToast({ title: 'TTS 引擎不可用', icon: 'none' })
    return
  }

  // 即使 initStatus 为 0 或 1,ttsSpeak 也会自动处理初始化
  ttsSpeak({
    text,
    fail: (err) => {
      uni.showToast({ title: err.errMsg, icon: 'none' })
    }
  })
}

七、平台差异

Android

项目 说明
最低版本 Android 5.0 (API 21)
依赖 需要安装 TTS 引擎(Google TTS / 讯飞语记等)
初始化 首次调用 ttsSpeak 时异步初始化,约 1~3 秒
语言包 不同语言可能需要单独下载
暂停/恢复 模拟实现(stop + 估算断点续播),非原生 API
恢复精度 按时间估算(中文约 4 字/秒),短文本基本准确,长文本有 1~3 字偏差
最大输入 约 4000 字符(取决于引擎)
注意 部分国产 ROM 可能限制了 TTS 功能

iOS

项目 说明
最低版本 iOS 12.0
依赖 无需安装,系统内置 AVSpeechSynthesizer
初始化 几乎瞬间完成
静音开关 ⚠️ 硬件静音开关打开时没有声音! 这是 iOS 系统行为
语速映射 插件自动将 0.5~2.0 映射到 AVSpeechUtterance.rate 范围
暂停/恢复 ✅ 原生支持,精确暂停/恢复
最大输入 无明确限制

鸿蒙

项目 说明
最低版本 鸿蒙 NEXT 4.0+
依赖 使用系统内置 CoreSpeechKit
初始化 首次调用时异步初始化
语言包 取决于系统配置
暂停/恢复 模拟实现(同 Android),非原生 API

八、常见问题排查

Q1: 完全没有声音?

按顺序排查

  1. 手机媒体音量开了吗?(注意:不是铃声音量,是媒体音量)
  2. iOS 用户:检查侧边的硬件静音开关是否关闭(橙色=静音)
  3. Android 用户:去 设置 → 语言和输入法 → 文字转语音输出 看看有没有安装引擎
  4. 监听 fail 回调,看有没有错误信息
  5. 调用 ttsGetEngineInfo()initStatus 是否为 2(已就绪)

Q2: 中文能读,英文/日文不行?

手机缺少对应语言的语音包。

  • Android设置 → 语言和输入法 → 文字转语音输出 → 点引擎旁的齿轮 ⚙ → 安装语音数据 → 下载需要的语言
  • iOS设置 → 辅助功能 → 朗读内容 → 声音 → 下载需要的语言

Q3: 怎么知道设备上有哪些 TTS 引擎?

import { ttsGetInstalledEngines } from '@/uni_modules/sehochen-tts'

const engines = ttsGetInstalledEngines()
console.log(engines)  // 如 ["com.google.android.tts"]

Q4: 怎么知道引擎是否就绪?

import { ttsGetEngineInfo } from '@/uni_modules/sehochen-tts'

const info = ttsGetEngineInfo()
if (info.initStatus === 2) {
  console.log('引擎已就绪,可以播放')
} else {
  console.log('引擎状态:', info.initStatus, '(0=未初始化, 1=初始化中, -1=失败)')
}

Q5: 连续播放多段文字怎么做?

利用 onDone 回调串行播放。参见 场景 2:顺序朗读列表

Q6: 为什么第一次播放要等一会儿?

Android TTS 引擎首次启动需要 1~3 秒初始化。后续播放不需要等待。

如果想在播放前确认引擎就绪,参见 场景 6:引擎状态轮询

Q7: 暂停后恢复,位置准确吗?

平台 精度
iOS ✅ 精确,原生 API 支持
Android ⚠️ 按时间估算,短文本基本准确,长文本有 1~3 字偏差
鸿蒙 ⚠️ 同 Android,按时间估算

Q8: 能同时读两段话吗?

不能。新播放会自动停掉旧的(QUEUE_FLUSH 模式)。

Q9: 为什么 ttsSpeak 后立刻 ttsStop 没效果?

首次调用 ttsSpeak 时引擎还在初始化中(异步),此时 ttsStop 可能因为还没开始播放而无效果。建议在 onStart 回调触发后再进行控制操作。

Q10: 页面退出时要不要手动释放?

建议在 onUnload 中调用 ttsStop() + ttsShutdown()。不调用也不会崩溃,但会占用一些内存。


九、Android 安装 TTS 引擎指南

国内手机(华为、小米、OPPO、vivo)通常自带 TTS 引擎,不需要额外安装。如果提示没有引擎,按以下步骤操作:

方法一:安装 Google 文字转语音

  1. 打开手机设置
  2. 找到语言和输入法(部分手机在"更多设置"或"系统设置"中)
  3. 文字转语音输出(或"TTS"、"语音合成")
  4. 如果列表为空,去应用商店搜索 "Google 文字转语音" 安装
  5. 安装后回到第 3 步的页面,确认引擎已出现
  6. 点引擎旁的齿轮图标 ⚙ → 安装语音数据 → 下载需要的语言包

方法二:使用国内引擎

如果 Google 服务不可用,可以使用国内 TTS 引擎:

  • 讯飞语记:在应用商店搜索安装
  • 安装后在系统设置的"文字转语音输出"中选择它作为默认引擎

下载语言包

选中引擎后,点齿轮图标进入引擎设置,可以下载各种语言的语音数据:

  • 中文(简体)- 通常预装
  • 英语(美国)- 可能需要下载
  • 日语、韩语等 - 按需下载

十、API 速查

API 做什么 参数 返回值 最简示例
ttsSpeak 播放文字 TTSSpeakOptions void ttsSpeak({ text: "你好" })
ttsStop 停止播放 void ttsStop()
ttsPause 暂停播放 void ttsPause()
ttsResume 恢复播放 void ttsResume()
ttsGetEngineInfo 查看引擎状态 TTSEngineInfo const info = ttsGetEngineInfo()
ttsGetInstalledEngines 引擎列表 string[] const list = ttsGetInstalledEngines()
ttsShutdown 释放引擎 void ttsShutdown()

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。