更新记录

1.0.0(2026-10-19) 下载此版本

首个正式版本,覆盖 App 三端(Android / iOS / HarmonyOS)与 H5 调试端。 全部基于系统原生能力实现,不引入任何三方 SDK。

TTS 语音合成

  • speak() 朗读,支持 rate(语速)、pitch(音调)、volume(音量)、voice(音色)、queue(排队)
  • stop() 停止、pause() 暂停、resume() 继续
  • 三端均支持暂停 / 继续:
    • iOS 使用 AVSpeechSynthesizer 原生 pauseSpeakingAtBoundary / continueSpeaking
    • Android 采用软件层续读:通过 onRangeStart 记录断点,pause() 回退到最近句子边界,resume() 从该处续读
    • 续读时进度回调使用原文绝对位置,进度条不回跳
  • getVoices() 获取系统音色列表
  • isAvailable() 能力检测
  • openSettings() 打开系统 TTS 设置(Android)
  • release() 释放引擎
  • 逐字进度回调 onProgress(Android / iOS)

ASR 语音识别

  • startRecognize() 开始识别,支持 engine: 'system' | 'external'
  • external 模式采集 16kHz / 16bit / 单声道 PCM,Base64 编码后 POST 到自建接口; 支持 url、headers、format、bodyType(json / binary)、resultPath、timeout 配置
  • 中间结果 onPartial、最终结果 onResult、状态 onStateChange、音量 onVolumeChange、结束 onEnd
  • stopRecognize() 结束并保留结果、cancelRecognize() 取消并丢弃
  • isRecognizeAvailable() 能力检测
  • releaseRecognize() 释放资源

平台实现

  • Android:TextToSpeech + SpeechRecognizer;外部接口模式使用 AudioRecord 采集 PCM
  • iOS:AVSpeechSynthesizer + Speech 框架
  • HarmonyOS:@kit.CoreSpeechKit 的 textToSpeech 与 speechRecognizer
  • H5:Web Speech API(仅调试用)

工程化

  • interface.uts 统一三端类型契约,unierror.uts 统一 22 个错误码
  • 各平台自带原生清单:Android AndroidManifest.xml、iOS Info.plist、鸿蒙 module.json5
  • 已通过 HBuilderX CLI compile app-android 编译验证

已知限制

  • Android 续读依赖 TTS 引擎的 onRangeStart 回调;不提供该回调的引擎(AOSP 未强制要求) 断点始终为 0,续读会从头重读整段
  • Android 续读会回退到句首,已念过的小半句会重念一次(避免从词语中间切入)
  • HarmonyOS 的 CoreSpeechKit 不支持暂停恢复,pause() 会退化为 stop()
  • HarmonyOS 的 TTS 不提供逐字进度与音调控制
  • Android 外部接口模式的静音断句采用固定阈值,未实现逐帧音量波形
  • iOS 的 engine: 'external' 暂未内置音频转码链路,会返回提示改用系统识别
  • HarmonyOS 暂不支持外部识别接口

平台兼容性

uni-app(5.21)

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

uni-app x(5.21)

Chrome Safari Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本 微信小程序
- - 9.0 1.0.0 17 1.0.0 21 1.0.0 -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

xw-tts

App 三端(Android / iOS / HarmonyOS)原生语音合成(TTS)与语音识别(ASR) UTS 插件。

  • 不引入任何三方 SDK,全部基于系统原生能力,无额外体积、无需申请 AppKey
  • TTS 支持语速 / 音调 / 音量 / 音色切换、暂停继续、逐字进度回调
  • ASR 支持系统识别与外部 HTTP 接口识别两种引擎,后者可对接自建的精准识别服务
  • 统一的三端 API 与错误码,业务代码不用写平台判断

目录


平台支持

能力 Android iOS HarmonyOS H5
TTS 朗读 ✅ TextToSpeech ✅ AVSpeechSynthesizer ✅ CoreSpeechKit ✅ Web Speech API
暂停 / 继续 ✅ 软件层续读 ✅ 原生支持 ⚠️ 退化为停止 ✅
逐字进度 ✅ onRangeStart ✅ willSpeakRange ❌ 只有开始/结束 ✅ onboundary
音色切换 ✅ ✅ ✅(内置音色) ✅
ASR 系统识别 ✅ SpeechRecognizer ✅ Speech 框架 ✅ CoreSpeechKit ✅ Webkit
ASR 外部接口 ✅ ⚠️ 见说明 ❌ ❌

H5 端仅用于本地调试,正式能力以 App 三端为准。


快速开始

1. 安装插件

在插件市场点击「使用 HBuilderX 导入插件」,或把整个 xw-tts 目录放到项目的 uni_modules 下:

项目根目录
└── uni_modules
    └── xw-tts

2. 引入

import {
  speak, stop, pause, resume, getVoices, isAvailable, release,
  startRecognize, stopRecognize, cancelRecognize, isRecognizeAvailable, releaseRecognize
} from '@/uni_modules/xw-tts'

注意:只能从插件根目录导入,不能写 @/uni_modules/xw-tts/utssdk/xxx。

3. 最简示例

// 朗读
speak({
  text: '你好,世界',
  onComplete: (res) => console.log('读完了', res),
  onError: (err) => console.error(err.errCode, err.errMsg)
})

// 识别
startRecognize({
  onResult: (res) => console.log('识别到:', res.text),
  onError: (err) => console.error(err.errCode, err.errMsg)
})

TTS 语音合成

speak(options): string

朗读文本,返回本次请求的 requestId。

参数 类型 默认 说明
text string 必填 要朗读的文本
rate number 1.0 语速,0.1 ~ 4.0
pitch number 1.0 音调,0.1 ~ 2.0(鸿蒙忽略)
volume number 1.0 音量,0.0 ~ 1.0
voice string — 音色 ID,来自 getVoices()
language string 'zh-CN' 语言标签,传了 voice 时以 voice 为准
queue boolean false true 排队等前一条结束;false 打断当前
onStart (res) => void — 开始朗读
onProgress (res) => void — 进度:{ progress, total, percent }
onComplete (res) => void — 结束:{ interrupt, progress }
onError (res) => void — 失败:{ errCode, errMsg, detail }
speak({
  text: '这一段会比较慢,方便听清楚。',
  rate: 0.7,
  pitch: 1.0,
  volume: 1.0,
  queue: false,
  onStart: (res) => console.log('开始', res.requestId),
  : (res) => console.log(`进度 ${res.percent}%`),
  onComplete: (res) => {
    // interrupt 为 true 表示被 stop() 或新的一次 speak 打断
    console.log(res.interrupt ? '被打断' : '正常读完')
  },
  onError: (err) => uni.showToast({ title: err.errMsg, icon: 'none' })
})

stop(requestId?)

停止朗读。传 requestId 只停指定请求,不传停全部。 被停止的请求会收到一次 onComplete({ interrupt: true })。

pause() / resume()

三端都支持「暂停 → 继续」,但实现方式不同,行为也有差别:

平台 实现方式 续读精度
iOS AVSpeechSynthesizer 原生 pause / continue ⭐⭐⭐ 精确到词,无缝
Android 软件层「记录断点 + 截断重读」 ⭐⭐ 回退到句首,会重念半句话
HarmonyOS CoreSpeechKit 不支持,退化为 stop ⭐ 从开头重读

Android 的续读原理

Android 的 TextToSpeech 本身没有 pause/continue,所以插件在软件层模拟:

  1. 朗读过程中,onRangeStart 持续报告「当前念到第几个字符」,插件始终记录这个位置
  2. 调用 pause() 时,从这个位置向前回退到最近的句子边界(。!?; 或英文 .!?; 或换行)
  3. 停掉引擎,把「剩余文本 + 续读起点 + 原参数」存进会话
  4. 调用 resume() 时,从那个句子开头重新朗读剩余部分
speak({ text: '第一句话。第二句话。第三句话。' })

// 念到第二句中间时暂停
pause()

// 从「第二句话」开头重新念,而不是从半个词切入
resume()

为什么回退到句首:Android 的引擎按词而非按字符回调,如果严格从断点字符续读,很可能从一个词的中间开始念,听感很差。回退到句首会重念已念过的小半句,但听起来自然。

进度连续性:onProgress 上报的 progress 是原文中的绝对位置,续读后进度条不会回跳。

已知限制

  • 部分 TTS 引擎不提供 onRangeStart(AOSP 只在引擎主动调用 SynthesisCallback.rangeStart 时才回调)。这种情况下断点位置始终为 0,pause() 后再 resume() 会从头重读整段。Google TTS、多数国产引擎都支持,可实测确认。
  • Android pause() 后不会触发 onComplete(这是「暂停」而非「结束」)。只有 stop() 或自然读完才回调 onComplete。
  • HarmonyOS 的 CoreSpeechKit 无此能力,pause() 等同 stop()。

如果你的业务要求三端行为完全一致且精确,建议用按句切分 + queue: true 的方案自行管理句子下标,这是唯一可在三端做到完全一致的可靠做法。

getVoices(options?)

获取系统可用音色。

getVoices({
  language: 'zh',            // 可选,按语言前缀过滤
  success: (res) => {
    // res.voices: Array<{ voiceId, name, language, local, isDefault }>
    console.log(res.voices)
  },
  fail: (err) => console.error(err)
})

Android 注意:系统 TextToSpeech.getVoices() 在引擎初始化完成后才有值,且部分机型需要先调一次 speak() 才会加载语音包。插件内部已做初始化等待,若仍为空,可用 openSettings() 引导用户下载语音包。

isAvailable(options?)

检测是否有可用 TTS 引擎。

isAvailable({
  success: (res) => console.log(res.available, res.voiceCount)
})

openSettings()

跳转到系统 TTS 设置页,引导用户安装 / 切换语音引擎。 仅 Android 有效,iOS / 鸿蒙为空实现。

release()

释放引擎资源,建议在页面 onUnmounted 调用。


ASR 语音识别

startRecognize(options): string

开始识别,返回 requestId。

参数 类型 默认 说明
engine 'system' \| 'external' 'system' 识别引擎
language string 'zh-CN' 识别语言
maxDuration number 60000 最长录音时长(ms)
silenceTimeout number 10000 静音多久自动结束(ms)
partialResult boolean true 是否返回中间结果(边说边出字)
external object — 外部接口配置,见下
onPartial (res) => void — 中间结果 { text }
onResult (res) => void — 最终结果 { text, engine, duration }
onStateChange (res) => void — 状态 { state }:preparing / listening / recognizing / stopping / end
onVolumeChange (res) => void — 音量 { volume }:0 ~ 100,可画波形
onError (res) => void — 失败
onEnd (res) => void — 结束 { success },成功失败都会触发一次
startRecognize({
  engine: 'system',
  language: 'zh-CN',
  partialResult: true,
  onStateChange: (res) => {
    // 用 state 驱动 UI:录音按钮动画、波形等
    console.log('状态', res.state)
  },
  onPartial: (res) => {
    // 边说边出字
    this.tempText = res.text
  },
  onResult: (res) => {
    this.finalText = res.text
  },
  : (res) => {
    this.volume = res.volume
  },
  onError: (err) => {
    if (err.errCode === 1203) {
      uni.showToast({ title: '请先允许麦克风权限', icon: 'none' })
    }
  },
  onEnd: (res) => {
    this.recording = false
  }
})

stopRecognize(requestId?)

结束识别并保留结果(用户点「说完了」)。最终结果通过 onResult 回调。

cancelRecognize(requestId?)

取消识别并丢弃结果。

isRecognizeAvailable(options?)

isRecognizeAvailable({
  engine: 'system',
  success: (res) => console.log(res.state === 'available')
})

releaseRecognize()

释放识别引擎。


外部识别接口协议

当 engine: 'external' 时,插件会:

  1. 用麦克风采集 16kHz / 16bit / 单声道 PCM
  2. 静音超时或达到 maxDuration 后自动停止
  3. 把音频 Base64 编码后 POST 到你的接口
  4. 按 resultPath 从响应 JSON 中取出文本

配置项

external: {
  url: 'https://your-domain.com/asr',   // 必填
  headers: { 'Authorization': 'Bearer xxx' },
  format: 'pcm',        // pcm | wav | amr
  bodyType: 'json',     // json | binary
  resultPath: 'data.text',  // 从响应里取文本的字段路径
  timeout: 15000
}

bodyType: 'json'(默认)请求体

{
  "format": "pcm",
  "rate": 16000,
  "lang": "zh-CN",
  "audio": "<Base64 编码的音频>"
}

bodyType: 'binary' 请求体

Content-Type: application/octet-stream,body 直接是音频字节。

服务端响应示例

{ "code": 0, "data": { "text": "今天天气不错" } }

配合 resultPath: 'data.text' 即可取到 今天天气不错。

最小服务端示例(Node.js / Express)

app.post('/asr', express.json({ limit: '20mb' }), async (req, res) => {
  const pcm = Buffer.from(req.body.audio, 'base64')
  // 转成你所用云厂商要求的格式后调用,例如:
  // const text = await callYourAsr(pcm, { sampleRate: 16000, format: 'pcm' })
  res.json({ code: 0, data: { text: '识别结果' } })
})

提示:外部识别对精度提升最明显。系统识别在嘈杂环境、专业术语场景下效果有限,把业务词表放到自己的服务端做热词增强是常见做法。


错误码

错误码 常量名 含义
1001 UNSUPPORTED 当前平台不支持该能力
1002 INVALID_PARAM 参数不合法
1003 NOT_INITIALIZED 引擎未初始化
1004 BUSY 上次操作尚未结束
1005 PERMISSION_DENIED 用户拒绝授权
1006 NETWORK_ERROR 网络异常
1007 TIMEOUT 请求超时
1099 UNKNOWN 未知错误
1101 TTS_TEXT_EMPTY 朗读文本为空
1102 TTS_INIT_FAILED 引擎初始化失败
1103 TTS_NOT_AVAILABLE 系统无可用合成引擎
1104 TTS_VOICE_NOT_FOUND 音色不存在
1105 TTS_SPEAK_FAILED 朗读失败
1106 TTS_LANGUAGE_MISSING 缺少语音包
1201 ASR_INIT_FAILED 识别引擎初始化失败
1202 ASR_NOT_AVAILABLE 识别服务不可用
1203 ASR_PERMISSION_DENIED 没有麦克风权限
1204 ASR_MIC_BUSY 麦克风被占用
1205 ASR_NO_SPEECH 未识别到内容
1206 ASR_MAX_DURATION 超出最大时长
1207 ASR_EXTERNAL_ERROR 外部接口返回错误
1208 ASR_EXTERNAL_NOT_CONFIGURED 未配置外部接口

平台差异

TTS

行为 Android iOS HarmonyOS
底层 android.speech.tts.TextToSpeech AVSpeechSynthesizer @kit.CoreSpeechKit textToSpeech
语速映射 直接设置 0.1~4.0 映射到系统 0~1 extraParams.speed
音调 原生支持 pitchMultiplier(0.5~2.0) 忽略
暂停继续 ✅ 软件层续读 ✅ 原生 ❌
逐字进度 ✅ ✅ ❌
离线 取决于系统语音包 ✅ 混合(online: 1)

ASR

行为 Android iOS HarmonyOS
底层 SpeechRecognizer Speech 框架 speechRecognizer
中间结果 ✅ ✅ ✅
音量回调 ✅ ⚠️ 未实现 ⚠️ 未实现
外部接口 ✅ ⚠️ 见下 ❌

iOS 外部接口说明:iOS 上把 AVAudioPCMBuffer 逐帧转换成 16k PCM 再 Base64 需要 AVAudioConverter,UTS 层处理二进制较重。当前 iOS 端 engine: 'external' 会返回 1001 并提示改用系统识别。若必须走自建接口,建议在业务层用 uni.getRecorderManager() 录音拿到文件,再 uni.uploadFile() 上传到你的服务端,这样三端逻辑统一且更省心。


权限说明

权限总览

插件遵循「最小权限」原则:语音合成(TTS)不需要任何权限,只有语音识别(ASR)需要麦克风。

能力 需要的权限
TTS 朗读、暂停、音色、进度 无
ASR 系统识别 麦克风 + 网络
ASR 外部接口识别 麦克风 + 网络

三端申请的权限清单

Android

权限 用途 触发时机
android.permission.RECORD_AUDIO 语音识别时采集麦克风音频 调用 startRecognize() 时动态申请
android.permission.INTERNET 系统识别与外部接口识别需要联网 安装时授予
android.permission.ACCESS_NETWORK_STATE 读取网络可用状态,用于识别失败时给出准确错误 安装时授予

插件同时声明了包可见性 queries(android.speech.RecognitionService 与 android.intent.action.TTS_SERVICE)。 这是 Android 11(API 30)的硬性要求,缺失会导致 isRecognitionAvailable() 误判为不可用。

RECORD_AUDIO 是运行时权限,插件内部已封装申请流程: 调用 startRecognize() 时若未授权,会自动弹出系统授权框, 拒绝后走 onError 回调,错误码 1203 ASR_PERMISSION_DENIED。

iOS

键 用途
NSMicrophoneUsageDescription 麦克风用途说明,缺失则无法录音
NSSpeechRecognitionUsageDescription 语音识别用途说明,缺失则无法识别

插件刻意不声明 UIBackgroundModes: audio。 AVAudioSession.Category.playback 已能在静音键打开时正常播放 TTS, 声明后台音频只会在上架审核时被要求额外说明后台用途。

HarmonyOS

权限 用途 usedScene
ohos.permission.MICROPHONE 语音识别录音 inuse(仅使用时)
ohos.permission.INTERNET 系统识别与外部接口识别 inuse(仅使用时)

权限声明文件位置

插件已内置各端原生清单,导入插件后无需手动编写:

utssdk/app-android/AndroidManifest.xml   Android 权限 + queries
utssdk/app-ios/Info.plist                iOS 用途说明
utssdk/app-harmony/module.json5          鸿蒙权限 + resources 文案

接入方仍需做的事

Android

RECORD_AUDIO 最终以项目的 manifest.json 为准,需补充:

"app-plus": {
  "distribute": {
    "android": {
      "permissions": [
        "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>"
      ]
    }
  }
}

iOS

若项目自己的 manifest.json 也配置了 infoPlist,两处会合并。保持文案语义一致,否则审核可能被质疑用途描述与实际行为不符:

"app-plus": {
  "distribute": {
    "ios": {
      "privacyDescription": {
        "NSMicrophoneUsageDescription": "需要使用麦克风进行语音识别",
        "NSSpeechRecognitionUsageDescription": "需要语音识别能力将你的语音转换为文字"
      }
    }
  }
}

HarmonyOS

插件 module.json5 已自动合并,无需额外配置。

用户拒绝授权时怎么处理

startRecognize({
  onError(err) {
    if (err.errCode === 1203) {
      // 用户拒绝了麦克风权限
      uni.showModal({
        title: '需要麦克风权限',
        content: '语音识别需要使用麦克风,是否前往设置开启?',
        success(res) {
          if (res.confirm) {
            // 各端跳设置的写法不同,按需实现
          }
        }
      })
    }
  }
})

数据与隐私

  • TTS:文本交给系统语音引擎在本地合成,插件不上传任何文本
  • ASR 系统识别:音频由系统语音服务处理(部分引擎会走厂商云端,取决于系统实现)
  • ASR 外部接口:音频会 POST 到开发者自己配置的 URL,插件不内建服务器、不内置统计或上报逻辑
  • 插件不采集设备标识、不申请通讯录/位置/存储等无关权限

打包配置

自定义基座

UTS 插件在真机运行时需要打自定义基座(HBuilderX:运行 → 运行到手机或模拟器 → 制作自定义调试基座)。标准基座不含本插件。


常见问题

Q1:getVoices() 返回空数组

  • Android:系统未安装 TTS 引擎或中文语音包。用 openSettings() 引导用户去系统设置下载;也可以在 speak() 成功后再次调用 getVoices()。
  • iOS:首次启动时系统音色列表可能尚未加载,稍等再取。

Q2:Android 上朗读没有声音

排查顺序:

  1. 手机媒体音量是否为 0(插件用的是 STREAM_MUSIC)
  2. 系统是否装了 TTS 引擎(部分国产 ROM 默认没有)
  3. 调 isAvailable() 看 available 是否为 true
  4. 调 speak() 看是否返回 1102 / 1106 错误码

Q3:识别一直返回 1205(未识别到内容)

  • 检查麦克风权限是否真的授予了(ASR_PERMISSION_DENIED 是 1203,1205 通常是权限正常但没说话 / 环境太吵)
  • 外部接口模式下常见于音频太短(不足 300ms)或格式与服务端要求不匹配

Q4:朗读和识别同时用会互相干扰

麦克风和扬声器会互相影响(回声)。建议:

  • 朗读期间不要开启识别
  • 或者用外部接口模式,并在服务端做回声消除(AEC)

Q5:如何做「边播边听」的实时对话

推荐链路:

识别(系统) → 拿到文本 → 请求大模型 → 文本分句 → speak({ queue: true })

要点:按句切分再 speak,不要一次喂很长的文本,这样能显著降低首句延迟。

Q6:长文本朗读中途要支持暂停

直接调 pause() / resume() 即可,三端都有支持(Android 是软件层续读,见 pause / resume)。

如果发现 Android 上续读总是从头开始,多半是当前系统 TTS 引擎不回调 onRangeStart。这时改用「按句切分 + queue: true」的方案,自己维护句子下标,三端行为完全一致。

Q7:Android 上暂停后继续,会重念一点内容?

这是预期行为。Android 引擎按词回调,为了不从半个词切入,resume() 会回退到最近的句子开头,因此已念过的小半句会重念一次。如果这个体验不可接受,用分句方案。


许可证

MIT

隐私、权限声明

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

Android:android.permission.RECORD_AUDIO(语音识别录音)、android.permission.INTERNET(系统识别与外部接口)、android.permission.ACCESS_NETWORK_STATE(读取网络状态);iOS:NSMicrophoneUsageDescription(麦克风用途说明)、NSSpeechRecognitionUsageDescription(语音识别用途说明);HarmonyOS:ohos.permission.MICROPHONE(语音识别录音)、ohos.permission.INTERNET(系统识别与外部接口)。语音合成(TTS)三端均不需要任何权限。

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

插件本身不采集、不上传任何用户数据。语音合成(TTS)完全在设备本地由系统引擎完成;语音识别(ASR)默认使用系统识别能力,音频由系统语音服务处理。若开发者将 engine 配置为 'external',插件会把采集到的音频提交至开发者自行指定的服务器地址,该地址与数据处理方式由开发者决定,插件不内建任何服务器、不内置任何第三方统计或上报逻辑。

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

无

许可协议

MIT License

Copyright (c) 2026 xw-uniapp-plugins

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

暂无用户评论。