更新记录
1.0.0(2026-07-30) 下载此版本
initIflytekAikitAwaken(options) 的参数如下:
appId讯飞开放平台应用AppIDapiKey讯飞开放平台APIKeyapiSecret讯飞开放平台APISecretabilityId唤醒能力 ID,默认使用e867a88f2wakeWords唤醒词数组,例如["你好小店", "小店小店"]logEnabled是否开启 SDK 日志
说明:
wakeWords不是 SDK 自动下发的,而是你自己传入的- 插件会把这些词写入
keyword.txt,再通过loadData/specifyDataSet加载到引擎中
平台兼容性
uni-app(4.83)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | - | - | - | √ | - | 5.0 | - | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | × | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| × | × | √ |
iflytek-aikit-awaken 中文文档
iflytek-aikit-awaken 是一个面向 uni-app 的 UTS API 插件,用于在 App Android 端接入讯飞 AIKit 离线语音唤醒能力。
这份插件主要解决三件事:
- 在
uni-app Vue页面里直接调用讯飞离线唤醒 SDK - 支持运行时动态更换唤醒词
- 把
AIKit.aar和离线资源一起封装进uni_modules,减少集成工作量
1. 功能概览
当前插件支持:
- SDK 初始化
- 麦克风实时离线唤醒
- 本地 PCM 文件唤醒测试
- 动态更新唤醒词
- 鉴权、唤醒、音量、停止、错误等事件回调
当前插件限制:
- 仅支持
uni-app App Android - 不支持
H5 - 不支持
iOS - 首次鉴权仍可能需要联网,鉴权成功后才适合离线使用
2. 插件目录
插件目录位于:
uni_modules/iflytek-aikit-awaken
核心结构如下:
iflytek-aikit-awaken/
├── package.json
├── README.md
├── README.zh-CN.md
├── index.d.ts
├── CHANGELOG.md
└── utssdk/
├── index.uts
├── interface.uts
└── app-android/
├── config.json
├── index.uts
├── IflytekAikitAwakenNative.kt
├── libs/
│ └── AIKit.aar
└── assets/
└── iflytek/ivw/
其中:
libs/AIKit.aar是讯飞 AIKit Android SDKassets/iflytek/ivw/是离线唤醒资源IflytekAikitAwakenNative.kt是 Android 原生桥接实现app-android/index.uts是 UTS 对外导出层
3. 集成方式
把整个目录复制到你的项目:
uni_modules/iflytek-aikit-awaken
然后使用:
- 自定义基座
- 或云打包
注意:
- 如果新增或修改了原生插件,通常需要重新制作自定义基座
- 如果真机上提示插件不存在,优先检查当前运行包是否重新编译并包含了该插件
4. Android 权限
插件声明了以下权限:
android.permission.RECORD_AUDIOandroid.permission.INTERNETandroid.permission.ACCESS_NETWORK_STATE
其中:
RECORD_AUDIO是开始麦克风唤醒前必须具备的运行时权限INTERNET/ACCESS_NETWORK_STATE主要用于首次鉴权
5. 工作目录与离线资源
本插件不会使用旧 Demo 里的 /sdcard/iflytek/ 方案。
运行时会自动把离线资源复制到应用私有目录,工作目录可通过:
getIflytekAikitAwakenWorkDir()
获取。
当前工作目录格式为:
<App files dir>/iflytek/
这样做的好处是:
- 不依赖外部存储
- 不需要广泛的读写存储权限
- 更适合
uni-app插件分发
6. 页面调用示例
import {
setIflytekAikitAwakenListener,
initIflytekAikitAwaken,
startIflytekAikitAwaken,
stopIflytekAikitAwaken,
updateIflytekWakeWords,
releaseIflytekAikitAwaken,
getIflytekAikitAwakenWorkDir
} from "@/uni_modules/iflytek-aikit-awaken"
setIflytekAikitAwakenListener((event) => {
console.log("iflytek awaken event", event)
if (event.type === "auth" && event.code === 0) {
console.log("auth success")
}
if (event.type === "wakeup") {
console.log("wake matched", event.raw)
}
})
const code = initIflytekAikitAwaken({
appId: "your_app_id",
apiKey: "your_api_key",
apiSecret: "your_api_secret",
abilityId: "e867a88f2",
wakeWords: ["你好小店", "小店小店"],
logEnabled: true
})
if (code === 0) {
console.log("init call success, wait auth event")
}
开始监听:
const startCode = startIflytekAikitAwaken()
停止监听:
const stopCode = stopIflytekAikitAwaken()
释放资源:
const releaseCode = releaseIflytekAikitAwaken()
7. 初始化参数说明
initIflytekAikitAwaken(options) 的参数如下:
appId讯飞开放平台应用AppIDapiKey讯飞开放平台APIKeyapiSecret讯飞开放平台APISecretabilityId唤醒能力 ID,默认使用e867a88f2wakeWords唤醒词数组,例如["你好小店", "小店小店"]logEnabled是否开启 SDK 日志
说明:
wakeWords不是 SDK 自动下发的,而是你自己传入的- 插件会把这些词写入
keyword.txt,再通过loadData/specifyDataSet加载到引擎中
8. 怎么判断当前有几个唤醒词
当前插件没有单独提供“查询已加载唤醒词数量”的 API。
你可以从两个层面判断:
- 页面侧
如果你传入的是:
["你好小店", "小店小店", "欢迎光临"]
那么当前准备加载的唤醒词数量就是:
3
- 插件侧
插件最终会把唤醒词写到 keyword.txt,一行一个。
因此从实现角度看:
- 数组长度 = 计划写入数量
keyword.txt行数 = 实际写入数量
9. 动态更换唤醒词
这是本插件的核心能力之一。
调用方式:
updateIflytekWakeWords(["你好小店", "欢迎回家"], true)
第二个参数 restartIfListening 的含义:
true如果当前正在监听,则自动停止旧会话,重建关键词文件,并重新开始监听false仅更新内存中的唤醒词配置,不主动重启当前监听
推荐做法:
- 用户改了唤醒词后,直接调用
updateIflytekWakeWords(newWords, true)
10. 事件回调说明
通过:
setIflytekAikitAwakenListener((event) => {})
注册长期监听。
事件对象格式示例:
{
type: "wakeup",
code: 0,
message: "wake-word matched",
raw: "{...sdk json...}",
volume: 0
}
常见 type:
authSDK 鉴权结果listening开始监听stopped当前监听会话结束wakeup命中唤醒preWakeup预唤醒命中volume当前音量值log普通日志error错误事件
其中最重要的是:
auth+code === 0表示鉴权成功,此后才能稳定启动监听wakeup表示真正命中唤醒词
11. 文件测试模式
如果你不想先接麦克风,也可以用 PCM 文件做测试:
import { detectIflytekWakeupFromFile } from "@/uni_modules/iflytek-aikit-awaken"
detectIflytekWakeupFromFile("/storage/emulated/0/Download/test.pcm")
PCM 要求:
16 kHz- 单声道
16-bit PCM
12. 常见问题
12.1 插件找不到
如果出现类似:
Iflytek AIKit Awaken not found
通常表示:
- 当前运行包没有包含该原生插件
- 新增插件后没有重做自定义基座
- 真机安装的仍是旧包
- 当前不是
App Android环境
优先检查:
- 是否在
App Android真机运行 - 是否重新制作自定义基座
- 是否重新打包并安装了新包
12.2 回调函数已释放
如果出现类似:
回调函数已释放,不能再次执行
这是因为监听器属于长期回调,UTS 侧必须使用 keepAlive 机制。
本插件已经按长期监听方式处理 setIflytekAikitAwakenListener。
12.3 首次鉴权失败
请重点检查:
appId/apiKey/apiSecret是否正确- 首次运行时设备是否可联网
- 讯飞控制台能力是否已开通
12.4 麦克风无法启动
请检查:
RECORD_AUDIO是否已授予- 是否已有其他模块占用了麦克风
13. 推荐接入流程
推荐页面逻辑如下:
- 用户填写
appId/apiKey/apiSecret - 调用
initIflytekAikitAwaken(...) - 监听
auth事件 - 当
auth.code === 0时,再允许用户点击“开始监听” - 需要改唤醒词时,调用
updateIflytekWakeWords(newWords, true) - 页面销毁时执行:
stopIflytekAikitAwaken()
releaseIflytekAikitAwaken()
setIflytekAikitAwakenListener(null)
14. 当前版本说明
当前版本重点是:
- 跑通 Android UTS 原生桥接
- 支持离线唤醒
- 支持动态更新唤醒词
暂未覆盖:
- iOS
- 可视化组件封装
- 唤醒词数量查询专用 API
如果后续继续扩展,比较合适的方向是:
- 增加
getWakeWords()/getWakeWordCount()API - 增加鉴权状态查询 API
- 增加更完整的错误码说明
15.11实例
<template>
<view class="page">
<view class="hero">
<view class="hero__title">讯飞语音唤醒测试</view>
<view class="hero__desc">仅支持 uni-app App Android。先初始化,等鉴权成功后再开始监听。</view>
</view>
<view class="card">
<view class="card__title">SDK 配置</view>
<view class="field">
<view class="field__label">App ID</view>
<input v-model="form.appId" class="field__input" placeholder="请输入 appId" />
</view>
<view class="field">
<view class="field__label">API Key</view>
<input v-model="form.apiKey" class="field__input" placeholder="请输入 apiKey" />
</view>
<view class="field">
<view class="field__label">API Secret</view>
<input v-model="form.apiSecret" class="field__input" placeholder="请输入 apiSecret" password />
</view>
<view class="field">
<view class="field__label">Ability ID</view>
<input v-model="form.abilityId" class="field__input" placeholder="默认 e867a88f2" />
</view>
<view class="field">
<view class="field__label">唤醒词</view>
<textarea
v-model="wakeWordsText"
class="field__textarea"
placeholder="每行一个唤醒词,例如: 你好小店 小店小店"
/>
</view>
</view>
<view class="card">
<view class="card__title">运行状态</view>
<view class="status-grid">
<view class="status-item">
<view class="status-item__label">初始化</view>
<view class="status-item__value">{{ initStatusText }}</view>
</view>
<view class="status-item">
<view class="status-item__label">监听中</view>
<view class="status-item__value">{{ listening ? '是' : '否' }}</view>
</view>
<view class="status-item">
<view class="status-item__label">最近事件</view>
<view class="status-item__value">{{ lastEventType || '暂无' }}</view>
</view>
<view class="status-item">
<view class="status-item__label">命中次数</view>
<view class="status-item__value">{{ wakeCount }}</view>
</view>
</view>
<view class="workdir">工作目录:{{ workDir || '未获取' }}</view>
</view>
<view class="card">
<view class="card__title">控制面板</view>
<view class="actions">
<button class="action-btn action-btn--primary" :disabled="busy" @click="handleInit">初始化</button>
<button class="action-btn" @click="handleStart">开始监听</button>
<button class="action-btn" :disabled="busy" @click="handleStop">停止监听</button>
<button class="action-btn" :disabled="busy || !initialized" @click="handleUpdateWakeWords">更新唤醒词</button>
<button class="action-btn action-btn--danger" :disabled="busy" @click="handleRelease">释放资源</button>
</view>
</view>
<view class="card">
<view class="card__title">事件日志</view>
<scroll-view scroll-y class="log-list">
<view v-for="item in logs" :key="item.id" class="log-item">
<view class="log-item__head">
<text class="log-item__type">{{ item.type }}</text>
<text class="log-item__time">{{ item.time }}</text>
</view>
<view class="log-item__body">{{ item.message }}</view>
<view v-if="item.raw" class="log-item__raw">{{ item.raw }}</view>
</view>
<view v-if="!logs.length" class="log-empty">暂无事件</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
import {
getIflytekAikitAwakenWorkDir,
initIflytekAikitAwaken,
isIflytekAikitAwakenListening,
releaseIflytekAikitAwaken,
setIflytekAikitAwakenListener,
startIflytekAikitAwaken,
stopIflytekAikitAwaken,
updateIflytekWakeWords
} from '@/uni_modules/iflytek-aikit-awaken'
import type { IflytekAikitAwakenEvent } from '@/uni_modules/iflytek-aikit-awaken'
type LogItem = {
id: string
type: string
message: string
raw: string
time: string
}
const form = reactive({
appId: '901e339d',
apiKey: 'e34c678f97b83fba3f89324f399a4736',
apiSecret: 'NDEwZWQwMmQzOGM3OGM5Mzg2ODRmNTZh',
abilityId: 'e867a88f2'
})
const wakeWordsText = ref('你好小店\n小店小店')
const busy = ref(false)
const initialized = ref(false)
const authReady = ref(false)
const listening = ref(false)
const lastEventType = ref('')
const wakeCount = ref(0)
const workDir = ref('')
const logs = ref<LogItem[]>([])
const initStatusText = computed(() => {
if (listening.value) return '监听中'
if (authReady.value) return '鉴权成功'
if (initialized.value) return '已初始化'
return '未初始化'
})
function nowText() {
const date = new Date()
const hh = `${date.getHours()}`.padStart(2, '0')
const mm = `${date.getMinutes()}`.padStart(2, '0')
const ss = `${date.getSeconds()}`.padStart(2, '0')
return `${hh}:${mm}:${ss}`
}
function pushLog(type: string, message: string, raw = '') {
logs.value.unshift({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
type,
message,
raw,
time: nowText()
})
if (logs.value.length > 80) {
logs.value.length = 80
}
}
function parseWakeWords() {
return wakeWordsText.value
.split(/\r?\n/)
.map((item) => item.trim())
.filter(Boolean)
}
function syncListeningStatus() {
listening.value = isIflytekAikitAwakenListening()
}
function validateInitForm() {
if (!form.appId.trim() || !form.apiKey.trim() || !form.apiSecret.trim()) {
uni.showToast({
title: '请先填写 appId/apiKey/apiSecret',
icon: 'none'
})
return false
}
if (!parseWakeWords().length) {
uni.showToast({
title: '请至少填写一个唤醒词',
icon: 'none'
})
return false
}
return true
}
setIflytekAikitAwakenListener((event: IflytekAikitAwakenEvent) => {
lastEventType.value = event.type || ''
if (event.type === 'auth' && event.code === 0) {
authReady.value = true
initialized.value = true
workDir.value = getIflytekAikitAwakenWorkDir()
}
if (event.type === 'listening') {
listening.value = true
}
if (event.type === 'stopped' || event.type === 'error') {
listening.value = false
}
if (event.type === 'wakeup') {
wakeCount.value += 1
}
const message = event.message || `code=${event.code ?? ''}`
pushLog(event.type || 'log', message, event.raw || '')
})
async function handleInit() {
if (busy.value) return
if (!validateInitForm()) return
busy.value = true
try {
authReady.value = false
initialized.value = false
wakeCount.value = 0
const code = initIflytekAikitAwaken({
appId: form.appId,
apiKey: form.apiKey,
apiSecret: form.apiSecret,
abilityId: 'e867a88f2',
wakeWords: parseWakeWords(),
logEnabled: true
})
pushLog('init', `init code=${code}`)
if (code === 0) {
initialized.value = true
workDir.value = getIflytekAikitAwakenWorkDir()
} else {
uni.showToast({
title: `初始化失败: ${code}`,
icon: 'none'
})
}
} finally {
busy.value = false
}
}
async function handleStart() {
if (busy.value) return
busy.value = true
try {
const code = startIflytekAikitAwaken()
syncListeningStatus()
pushLog('start', `start code=${code}`)
if (code !== 0) {
uni.showToast({
title: `启动失败: ${code}`,
icon: 'none'
})
}
} finally {
busy.value = false
}
}
function handleStop() {
if (busy.value) return
const code = stopIflytekAikitAwaken()
syncListeningStatus()
pushLog('stop', `stop code=${code}`)
}
function handleUpdateWakeWords() {
if (busy.value) return
const wakeWords = parseWakeWords()
if (!wakeWords.length) {
uni.showToast({
title: '请至少填写一个唤醒词',
icon: 'none'
})
return
}
const code = updateIflytekWakeWords(wakeWords, true)
pushLog('update', `update wake words code=${code}`)
if (code !== 0) {
uni.showToast({
title: `更新失败: ${code}`,
icon: 'none'
})
}
}
function handleRelease() {
if (busy.value) return
stopIflytekAikitAwaken()
const code = releaseIflytekAikitAwaken()
listening.value = false
initialized.value = false
authReady.value = false
workDir.value = ''
pushLog('release', `release code=${code}`)
}
onBeforeUnmount(() => {
stopIflytekAikitAwaken()
releaseIflytekAikitAwaken()
setIflytekAikitAwakenListener(null)
})
</script>
<style lang="less">
.page {
min-height: 100vh;
padding: 24rpx;
background: linear-gradient(180deg, #eef6ff 0%, #f7fbff 38%, #f4f6f8 100%);
box-sizing: border-box;
}
.hero {
padding: 30rpx 28rpx;
margin-bottom: 20rpx;
border-radius: 28rpx;
background: linear-gradient(135deg, #0f4c81, #1d79bf);
color: #ffffff;
box-shadow: 0 18rpx 40rpx rgba(15, 76, 129, 0.18);
}
.hero__title {
font-size: 36rpx;
font-weight: 800;
line-height: 1.3;
}
.hero__desc {
margin-top: 10rpx;
font-size: 24rpx;
line-height: 1.5;
color: rgba(255, 255, 255, 0.88);
}
.card {
padding: 24rpx;
margin-bottom: 20rpx;
border-radius: 24rpx;
background: #ffffff;
box-shadow: 0 12rpx 30rpx rgba(15, 23, 42, 0.06);
}
.card__title {
margin-bottom: 20rpx;
font-size: 28rpx;
font-weight: 800;
color: #0f172a;
}
.field + .field {
margin-top: 18rpx;
}
.field__label {
margin-bottom: 10rpx;
font-size: 24rpx;
font-weight: 700;
color: #334155;
}
.field__input,
.field__textarea {
width: 100%;
padding: 18rpx 20rpx;
border: 2rpx solid #dbe7f3;
border-radius: 18rpx;
background: #f8fbff;
box-sizing: border-box;
font-size: 24rpx;
color: #0f172a;
}
.field__textarea {
min-height: 180rpx;
}
.status-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
}
.status-item {
padding: 20rpx;
border-radius: 18rpx;
background: #f8fbff;
}
.status-item__label {
font-size: 22rpx;
color: #64748b;
}
.status-item__value {
margin-top: 8rpx;
font-size: 28rpx;
font-weight: 800;
color: #0f172a;
}
.workdir {
margin-top: 18rpx;
font-size: 22rpx;
line-height: 1.5;
color: #475569;
word-break: break-all;
}
.actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
}
.action-btn {
height: 82rpx;
line-height: 82rpx;
border: none;
border-radius: 18rpx;
background: #e7eef6;
color: #0f172a;
font-size: 26rpx;
font-weight: 700;
}
.action-btn::after {
border: none;
}
.action-btn--primary {
background: linear-gradient(135deg, #0f4c81, #1d79bf);
color: #ffffff;
}
.action-btn--danger {
background: #fee2e2;
color: #b91c1c;
}
.log-list {
max-height: 720rpx;
}
.log-item {
padding: 18rpx 0;
border-bottom: 1rpx solid rgba(148, 163, 184, 0.2);
}
.log-item:last-child {
border-bottom: none;
}
.log-item__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.log-item__type {
font-size: 24rpx;
font-weight: 800;
color: #0f4c81;
}
.log-item__time {
font-size: 20rpx;
color: #94a3b8;
}
.log-item__body {
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #334155;
}
.log-item__raw {
margin-top: 8rpx;
padding: 14rpx 16rpx;
border-radius: 14rpx;
background: #f8fafc;
font-size: 20rpx;
line-height: 1.5;
color: #475569;
word-break: break-all;
}
.log-empty {
padding: 40rpx 0 20rpx;
text-align: center;
font-size: 24rpx;
color: #94a3b8;
}
</style>

收藏人数:
下载插件并导入HBuilderX
下载插件ZIP
赞赏(0)
下载 0
赞赏 0
下载 12467892
赞赏 1936
赞赏
京公网安备:11010802035340号