更新记录

1.0.0(2026-06-16) 下载此版本

第一次上传


平台兼容性

uni-app

Vue2 Vue3 Chrome Safari app-vue app-nvue app-nvue插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本
- - - - - 1.0.0 1.0.0 1.0.0 1.0.0
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.07)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - 1.0.0 -

apex-gu-cheng-tts 操作说明

跨平台 TTS(Text-to-Speech)语音合成 UTS 模块,支持 AndroidiOSHarmonyOS


目录

  1. 快速开始
  2. API 参考
  3. 类型定义
  4. 错误码
  5. 平台差异
  6. 完整示例
  7. 注意事项

快速开始

导入

import {
  ttsInit,
  ttsSpeak,
  ttsStop,
  ttsIsSpeaking,
} from '@/uni_modules/apex-gu-cheng-tts'

最小示例

// 1. 初始化
ttsInit({
  language: 'zh-CN',
  success: () => console.log('就绪'),
  fail: (err) => console.error('初始化失败', err.errMsg),
})

// 2. 朗读
ttsSpeak({
  text: '你好,世界',
  success: () => console.log('开始朗读'),
  fail: (err) => console.error('朗读失败', err.errMsg),
})

// 3. 停止
ttsStop({
  success: () => console.log('已停止'),
})

// 4. 查询状态
if (ttsIsSpeaking()) {
  console.log('正在朗读中...')
}

API 参考

ttsInit(options)

初始化 TTS 引擎。必须先调用此方法,后续的 ttsSpeak / ttsStop 才可用。

参数 类型 必填 默认值 说明
rate number 1.0 语速,范围 0.5 ~ 2.0
pitch number 1.0 音调,范围 0.5 ~ 2.0
volume number 1.0 音量,范围 0.0 ~ 1.0
language string 'zh-CN' 语言代码
success (res: TTSInitResult) => void 初始化成功回调
fail (res: TTSFail) => void 初始化失败回调
complete (res: any) => void 完成回调(成功/失败都会触发)

注意:重复调用 ttsInit 会自动销毁旧引擎再创建新的(Android / HarmonyOS 平台)。


ttsSpeak(options)

朗读指定文本。调用前需确保 TTS 已初始化成功。

参数 类型 必填 默认值 说明
text string 要朗读的文本内容
speed number 1.0 语速,范围 0.5 ~ 2.0
pitch number 1.0 音调,范围 0.5 ~ 2.0
volume number 1.0 音量,范围 0.0 ~ 1.0
language string 'zh-CN' 语言代码
success (res: TTSSpeakResult) => void 提交成功回调
fail (res: TTSFail) => void 朗读失败回调
complete (res: any) => void 完成回调

注意success 仅表示朗读请求已提交到引擎,不代表朗读已结束。朗读是否完成需通过轮询 ttsIsSpeaking() 判断。


ttsStop(options)

立即停止当前朗读。

参数 类型 必填 说明
success (res: TTSStopResult) => void 停止成功回调
fail (res: TTSFail) => void 停止失败回调
complete (res: any) => void 完成回调

ttsIsSpeaking()

同步查询当前是否正在朗读。

  • 返回值boolean
  • true — 正在朗读中
  • false — 空闲状态

ttsShutdown() (平台内部方法)

停止朗读并释放 TTS 引擎资源。Android / iOS / HarmonyOS 平台均有导出,可用于页面销毁时清理。

// 页面 onUnload 时清理
export function ttsShutdown(): void

类型定义

TTSInitResult

type TTSInitResult = {
  success: boolean   // 是否初始化成功
  message: string    // 描述信息
}

TTSSpeakResult

type TTSSpeakResult = {
  success: boolean   // 是否成功提交朗读请求
  message: string    // 描述信息
}

TTSStopResult

type TTSStopResult = {
  success: boolean   // 是否停止成功
  message: string    // 描述信息
}

TTSFail

interface TTSFail extends IUniError {
  errCode: TTSErrorCode  // 错误码
  errMsg: string         // 错误描述
}

错误码

错误码 常量含义 说明
9020001 初始化失败 引擎创建失败,可能设备不支持 TTS
9020002 朗读失败 提交朗读请求时出错
9020003 停止失败 停止朗读时出错
9020004 未初始化 在 TTS 未就绪时调用了 speakstop

平台差异

Android

  • 底层使用系统 android.speech.tts.TextToSpeech API
  • 语言解析支持 语言-地区 格式(如 zh-CNen-USja-JP
  • ttsIsSpeaking() 直接调用 TextToSpeech.isSpeaking(),实时准确
  • 需要 Activity 上下文,请确保在 uni-app 页面生命周期内初始化

iOS

  • 底层使用 AVFoundation.AVSpeechSynthesizer
  • 语速映射:接口 speed 范围 0.5~2.0,映射到 iOS 原生 rate 范围 0.0~1.0,公式:iosRate = speed × 0.5
  • 音调使用 pitchMultiplier,范围与接口一致(0.5~2.0
  • 初始化是同步的,不会失败(除非内存不足)
  • ttsIsSpeaking() 通过 synthesizer.isSpeaking 属性获取

HarmonyOS

  • 底层使用 @kit.CoreSpeechKittextToSpeech API
  • 需要在线模式online: 1),需设备联网
  • 引擎创建是异步的(createEngine 返回 Promise)
  • 支持完整的播报生命周期回调:onStartonCompleteonStoponError
  • ttsIsSpeaking() 通过内部标志位维护,非原生查询

完整示例

以下示例来自 pages/index/index.uvue,展示了完整的 TTS 使用流程:

1. 页面初始化时启动 TTS

import { ref } from 'vue'
import { ttsInit, ttsSpeak, ttsStop, ttsIsSpeaking } from '@/uni_modules/apex-gu-cheng-tts'

const ttsReady = ref(false)
const speaking = ref(false)

ttsInit({
  rate: 1.0,
  pitch: 1.0,
  volume: 1.0,
  language: 'zh-CN',
  success: (res) => {
    ttsReady.value = true
    console.log('TTS初始化成功')
  },
  fail: (err) => {
    console.error('TTS初始化失败: ' + err.errMsg)
  },
})

2. 朗读文本(含轮询完成检测)

const textContent = ref('你好,欢迎使用TTS语音合成测试功能')
const curLang = ref('zh-CN')
const speed = ref(1.0)
const pitch = ref(1.0)
const volume = ref(1.0)

let pollTimer: number = 0

function handleSpeak(): void {
  const txt = textContent.value.trim()
  if (txt.length === 0) return

  ttsSpeak({
    text: txt,
    speed: speed.value,
    pitch: pitch.value,
    volume: volume.value,
    language: curLang.value,
    success: (res) => {
      speaking.value = true
    },
    fail: (err) => {
      console.error('朗读失败: ' + err.errMsg)
    },
    complete: () => {
      // 轮询检测朗读是否结束
      pollTimer = setInterval(() => {
        if (!ttsIsSpeaking()) {
          speaking.value = false
          console.log('朗读完成')
          clearInterval(pollTimer)
        }
      }, 300)

      // 30 秒超时保护
      setTimeout(() => {
        clearInterval(pollTimer)
      }, 30000)
    },
  })
}

3. 停止朗读

function handleStop(): void {
  ttsStop({
    success: () => {
      speaking.value = false
      console.log('已停止朗读')
    },
    fail: (err) => {
      console.error('停止失败: ' + err.errMsg)
    },
  })
}

4. 页面销毁时清理

// 在页面 onUnload 中调用
import { ttsShutdown } from '@/uni_modules/apex-gu-cheng-tts'

// 如果需要在页面销毁时释放引擎资源
// ttsShutdown()

注意事项

  1. 初始化顺序:必须先调用 ttsInit 并等待 success 回调后,才能调用 ttsSpeak / ttsStop,否则会收到 9020004(未初始化)错误。

  2. 朗读完成检测ttsSpeaksuccess 回调仅表示请求已提交。判断朗读是否真正结束,需要通过轮询 ttsIsSpeaking() 实现,建议间隔 300ms,并设置超时保护(如 30 秒)。

  3. 语言代码格式:使用 BCP-47 格式,常见值: 语言 代码
    中文(简体) zh-CN
    英文(美国) en-US
    日文 ja-JP
  4. 参数范围 参数 范围 默认值 说明
    语速 (speed/rate) 0.5 ~ 2.0 1.0 1.0 = 正常语速
    音调 (pitch) 0.5 ~ 2.0 1.0 1.0 = 正常音调
    音量 (volume) 0.0 ~ 1.0 1.0 0.0 = 静音,1.0 = 最大
  5. HarmonyOS 平台需要网络连接(使用在线 TTS 服务),Android 和 iOS 使用本地引擎,可离线使用。

  6. Android 平台重复调用 ttsInit 会自动关闭旧引擎;如果需要在页面切换时彻底释放资源,可调用 ttsShutdown()

  7. 线程安全:所有 TTS 操作应在主线程调用,UTS 模块内部未做线程调度。

  8. 正常调用模板

    
    <template>
    <view class="page-container">
        <NavBar title="TTS测试" :showBack="true" />
    
        <scroll-view scroll-y class="scroll-content">
            <view class="content-wrapper">
                <!-- 状态指示 -->
                <view class="status-bar">
                    <view class="status-item">
                        <view class="status-dot" :class="ttsReady ? 'dot-green' : 'dot-red'" />
                        <text class="status-text">{{ ttsReady ? 'TTS已就绪' : 'TTS未就绪' }}</text>
                    </view>
                    <view class="status-item">
                        <view class="status-dot" :class="speaking ? 'dot-blue' : 'dot-gray'" />
                        <text class="status-text">{{ speaking ? '正在朗读' : '空闲' }}</text>
                    </view>
                </view>
    
                <!-- 文本输入 -->
                <view class="section">
                    <text class="section-label">朗读文本</text>
                    <textarea
                        v-model="textContent"
                        class="text-input"
                        placeholder="输入要朗读的文字..."
                        :maxlength="500"
                    />
                </view>
    
                <!-- 语言选择 -->
                <view class="section">
                    <text class="section-label">语言</text>
                    <view class="lang-group">
                        <view
                            v-for="lang in langList"
                            :key="lang.value"
                            class="lang-chip"
                            :class="curLang === lang.value ? 'lang-chip-active' : ''"
        @click="curLang = (lang.value as string)"
                        >
                            <text class="lang-chip-text">{{ lang.label }}</text>
                        </view>
                    </view>
                </view>
    
                <!-- 参数滑块 -->
                <view class="section">
                    <text class="section-label">语速: {{ speed.toFixed(1) }}</text>
                    <slider
                        class="param-slider"
                        :value="speed"
                        :min="0.5"
                        :max="2.0"
                        :step="0.1"
                        @change="onSpeedChange"
                    />
                </view>
    
                <view class="section">
                    <text class="section-label">音调: {{ pitch.toFixed(1) }}</text>
                    <slider
                        class="param-slider"
                        :value="pitch"
                        :min="0.5"
                        :max="2.0"
                        :step="0.1"
                        @change="onPitchChange"
                    />
                </view>
    
                <view class="section">
                    <text class="section-label">音量: {{ volume.toFixed(1) }}</text>
                    <slider
                        class="param-slider"
                        :value="volume"
                        :min="0.0"
                        :max="1.0"
                        :step="0.1"
                        @change=""
                    />
                </view>
    
                <!-- 操作按钮 -->
                <view class="btn-group">
                    <button class="btn-speak" :class="!ttsReady ? 'btn-disabled' : ''" :disabled="!ttsReady" @click="handleSpeak">
                        朗读
                    </button>
                    <button class="btn-stop" :class="!ttsReady ? 'btn-disabled' : ''" :disabled="!ttsReady" @click="handleStop">
                        停止
                    </button>
                </view>
    
                <!-- 快捷文本 -->
                <view class="section">
                    <text class="section-label">快捷文本</text>
                    <view class="quick-group">
                        <view
                            v-for="qt in quickTexts"
                            :key="qt"
                            class="quick-chip"
                            @click="textContent = qt"
                        >
                            <text class="quick-chip-text">{{ qt }}</text>
                        </view>
                    </view>
                </view>
    
                <!-- 日志 -->
                <view class="section">
                    <text class="section-label">运行日志</text>
                    <view class="log-box">
                        <text
                            v-for="(log, i) in logs"
                            :key="i"
                            class="log-line"
                        >{{ log }}</text>
                        <text v-if="logs.length === 0" class="log-empty">暂无日志</text>
                    </view>
                </view>
    
                <view class="bottom-space" />
            </view>
        </scroll-view>
    </view>
    </template>
import { ref } from 'vue' import { ttsInit, ttsSpeak, ttsStop, ttsIsSpeaking, } from '@/uni_modules/apex-gu-cheng-tts' // 状态 const ttsReady = ref(false) const speaking = ref(false) const textContent = ref('你好,欢迎使用TTS语音合成测试功能') const curLang = ref('zh-CN') const speed = ref(1.0) const pitch = ref(1.0) const volume = ref(1.0) const logs = ref([]) let pollTimer : number = 0 // 语言列表 interface LangItem { label : string value : string } const langList = [ { label: '中文', value: 'zh-CN' }, { label: 'English', value: 'en-US' }, { label: '日本語', value: 'ja-JP' }, ] // 快捷文本 const quickTexts = [ '你好,欢迎使用TTS语音合成测试功能', 'Hello, welcome to TTS testing', 'こんにちは、TTSテストへようこそ', '请保持面部正对镜头,不要遮挡眉毛和耳朵', '拍照完成,正在处理中,请稍候', ] function addLog(msg : string) : void { const now = new Date() const ts = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0') logs.value.push('[' + ts + '] ' + msg) if (logs.value.length > 50) { logs.value.splice(0, logs.value.length - 50) } } // 初始化 TTS ttsInit({ rate: 1.0, pitch: 1.0, volume: 1.0, language: 'zh-CN', success: (res) => { ttsReady.value = true addLog('TTS初始化成功') }, fail: (err) => { addLog('TTS初始化失败: ' + err.errMsg) }, }) // 滑块事件 function onSpeedChange(e : UTSJSONObject) : void { const detail = e["detail"] as UTSJSONObject speed.value = detail["value"] as number } function onPitchChange(e : UTSJSONObject) : void { const detail = e["detail"] as UTSJSONObject pitch.value = detail["value"] as number } function (e : UTSJSONObject) : void { const detail = e["detail"] as UTSJSONObject volume.value = detail["value"] as number } // 朗读 function handleSpeak() : void { const txt = textContent.value.trim() if (txt.length === 0) { uni.showToast({ title: '请输入朗读文本', icon: 'none' }) return } addLog('开始朗读: ' + txt.substring(0, 30) + (txt.length > 30 ? '...' : '')) addLog('参数: 语速=' + speed.value.toFixed(1) + ' 音调=' + pitch.value.toFixed(1) + ' 音量=' + volume.value.toFixed(1) + ' 语言=' + curLang.value) ttsSpeak({ text: txt, speed: speed.value, pitch: pitch.value, volume: volume.value, language: curLang.value, success: (res) => { speaking.value = true addLog('朗读请求已提交') }, fail: (err) => { addLog('朗读失败: ' + err.errMsg) uni.showToast({ title: '朗读失败', icon: 'none' }) }, complete: () => { pollTimer = setInterval(() => { if (!ttsIsSpeaking()) { speaking.value = false addLog('朗读完成') clearInterval(pollTimer) } }, 300) setTimeout(() => { clearInterval(pollTimer) }, 30000) }, }) } // 停止 function handleStop() : void { ttsStop({ success: () => { speaking.value = false addLog('已停止朗读') }, fail: (err) => { addLog('停止失败: ' + err.errMsg) }, }) } <style lang="scss" scoped> .page-container { display: flex; flex-direction: column; height: 100%; width: 100%; } .scroll-content { flex: 1; } .content-wrapper { padding: 24rpx; padding-bottom: 80rpx; } .status-bar { display: flex; flex-direction: row; justify-content: space-around; padding: 24rpx; margin-bottom: 24rpx; background-color: #f8f9fa; border-radius: 12rpx; } .status-item { display: flex; flex-direction: row; align-items: center; } .status-dot { width: 16rpx; height: 16rpx; border-radius: 8rpx; margin-right: 12rpx; } .dot-green { background-color: #34c759; } .dot-red { background-color: #ff3b30; } .dot-blue { background-color: #007aff; } .dot-gray { background-color: #c7c7cc; } .status-text { font-size: 28rpx; color: #333; } .section { margin-bottom: 28rpx; } .section-label { font-size: 28rpx; color: #666; margin-bottom: 16rpx; } .text-input { width: 100%; height: 160rpx; padding: 20rpx; background-color: #f8f9fa; border-radius: 12rpx; font-size: 30rpx; color: #333; border: 1rpx solid #e5e5e5; box-sizing: border-box; } .lang-group { display: flex; flex-direction: row; } .lang-chip { padding: 12rpx 28rpx; border-radius: 40rpx; background-color: #f0f0f0; border: 2rpx solid transparent; margin-right: 16rpx; } .lang-chip-active { background-color: #e8f4fd; border-color: #007aff; } .lang-chip-text { font-size: 26rpx; color: #333; } .lang-chip-active .lang-chip-text { color: #007aff; } .param-slider { width: 100%; } .btn-group { display: flex; flex-direction: row; margin: 32rpx 0; } .btn-speak { flex: 1; height: 88rpx; line-height: 88rpx; background-color: #007aff; color: #fff; border-radius: 12rpx; font-size: 32rpx; border: none; margin-right: 24rpx; } .btn-stop { flex: 1; height: 88rpx; line-height: 88rpx; background-color: #ff3b30; color: #fff; border-radius: 12rpx; font-size: 32rpx; border: none; margin-right: 0; } .btn-disabled { opacity: 0.4; } .quick-group { display: flex; flex-direction: row; flex-wrap: wrap; } .quick-chip { padding: 12rpx 22rpx; border-radius: 8rpx; background-color: #f0f0f0; border: 1rpx solid #e5e5e5; margin-right: 16rpx; margin-bottom: 16rpx; } .quick-chip-text { font-size: 24rpx; color: #555; } .log-box { width: 100%; max-height: 360rpx; padding: 20rpx; background-color: #1c1c1e; border-radius: 12rpx; box-sizing: border-box; } .log-line { font-size: 22rpx; color: #98c379; display: flex; margin-bottom: 4rpx; font-family: monospace; } .log-empty { font-size: 22rpx; color: #666; font-family: monospace; } .bottom-space { height: 80rpx; } </style>

隐私、权限声明

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

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

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

许可协议

MIT协议