uniapp - 接入科大讯飞语音评测
# 一、简介
科大讯飞语音评测可以对字、词、句、篇章等题型进行多维度评分(准确度、流畅度、完整度、声韵调型等),支持中文和英文。最新的流式版使用 webSocket 调用接口,开发者可以边录音边上边音频数据(录音与评测同时进行),可以缩短用户等待评测结果的时间,大大提高用户体验。
- 语音评测官方介绍:https://www.xfyun.cn/services/ise (opens new window)
- 语音评测(流式版)API 文档:https://www.xfyun.cn/doc/Ise/IseAPI.html (opens new window)
科大讯飞语音评测提供了多种平台的 SDK 与 Demo,但是没有提供微信小程序版本的 SDK 与 Demo,不过,科大讯飞语音评测【WebApi】有提供浏览器版本的 Demo,理论上可以对它进行适当的改造,就能运行到微信小程序环境下,所以本文的主要内容就是对科大讯飞语音评测【WebApi】的浏览器版本 Demo 进行改造和封装。
温馨提示:着急伸手拿最终成品的,可以直接滚到 【文章末尾】 查看。
# 二、分析
- API 文档调用示例:https://www.xfyun.cn/doc/Ise/IseAPI.html#调用示例 (opens new window)
- 语音评测流式 API demo js 语言:https://xfyun-doc.cn-bj.ufileos.com/static/16546792913730431/ise_ws_js_demo.zip (opens new window)
打开科大讯飞语音评测 API 文档页面,在 调用示例
章节处,找到 语音评测流式API demo js语言
并下载,解压后使用 vscode 打开,查看 index.js
,可以看到开头有如下注释:
// 1. websocket连接:判断浏览器是否兼容,获取websocket url并连接,这里为了方便本地生成websocket url
// 2. 获取浏览器录音权限:判断浏览器是否兼容,获取浏览器录音权限,
// 3. js获取浏览器录音数据
// 4. 将录音数据处理为文档要求的数据格式:采样率16k或8K、位长16bit、单声道;该操作属于纯数据处理,使用webWork处理
// 5. 根据要求(采用base64编码,每次发送音频间隔40ms,每次发送音频字节数1280B)将处理后的数据通过websocket传给服务器,
// 6. 实时接收websocket返回的数据并进行处理
// ps: 该示例用到了es6中的一些语法,建议在chrome下运行
通过这个注释说明,可以直观的了解到该 js demo 的大致流程,下面会按照注释里的顺序,逐个分析,并对应到微信小程序里的实现。
注意:因为本人使用的是 uniapp 开发,所以以下关于微信小程序代码实现的部分,
并非
传统的js + wxss + wxml
,而是vue3 + typescript
。
# 1、创建 webSocket
官方 demo 是运行在浏览器环境下的,而不同的浏览器对 webSocket 的创建方式不太一样,所以这里做兼容:
// 连接websocket
connectWebSocket() {
return getWebSocketUrl().then(url => {
let iseWS
if ('WebSocket' in window) {
iseWS = new WebSocket(url)
} else if ('MozWebSocket' in window) {
iseWS = new MozWebSocket(url)
} else {
alert('浏览器不支持WebSocket')
return
}
this.webSocket = iseWS
iseWS.onopen = e => {
...
}
iseWS.onmessage = e => {
...
}
iseWS.onerror = e => {
...
}
iseWS.onclose = e => {
...
}
})
}
微信小程序内创建 webSocket 就很简单了,使用 uni.connectSocket
即可:
/* socket相关 */
private socketTask: UniApp.SocketTask | null = null;
async connect() {
const url = await this.getWebSocketUrl();
const newUrl = encodeURI(url);
this.socketTask = uni.connectSocket({
url: newUrl,
// 如果希望返回一个 socketTask 对象,需要至少传入 success / fail / complete 参数中的一个
complete: () => {},
});
this.socketTask.onOpen((res) => {
...
});
this.socketTask.onMessage((res) => {
...
});
this.socketTask.onError((err) => {
...
});
this.socketTask.onClose(() => {
...
});
}
注意:这里有一个坑,
uni.connectSocket()
如果不指定success/fail/complete
参数中的一个,则返回的不是 SocketTask,而是一个 Promise!我们需要的是 SocketTask,所以这里指定了一个空的 complete 回调函数。
- 论坛帖子:https://ask.dcloud.net.cn/question/63162 (opens new window)
- 官方文档:https://uniapp.dcloud.net.cn/api/request/websocket.html (opens new window)
上述代码中有一处十分重要的函数调用,即 getWebSocketUrl()
,它负责生成科大讯飞语音评测【WebApi】的 webSocket 链接,涉及到参数加密:
import CryptoJS from "crypto-js";
import { Base64 } from "js/base64js.js";
/**
* 获取websocket url
* 该接口需要后端提供,这里为了方便前端处理
*/
function getWebSocketUrl() {
return new Promise((resolve, reject) => {
// 请求地址根据语种不同变化
var url = "wss://ise-api.xfyun.cn/v2/open-ise";
var host = "ise-api.xfyun.cn";
var apiKey = API_KEY;
var apiSecret = API_SECRET;
var date = new Date().toGMTString();
var algorithm = "hmac-sha256";
var headers = "host date request-line";
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/open-ise HTTP/1.1`;
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
var authorization = btoa(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
resolve(url);
});
}
js/base64js.js
是 demo 工程里 js 目录下的一个文件(采用 CommonJs 导入规范),而 crypto-js
是使用 npm 安装的一个第三方模块,理论上只需要将这 2 个模块直接拷贝或 npm 安装集成到项目中就好了,但是我的这个工程用的构建工具是 Vite
,只支持 ES 模块导入规范,并且项目中使用了 TypeScript,所以,为了更好的编码体验,这里替换为支持 ES 模块导入规范且支持 TypeScript 的另外 2 个模块(js-base64
、crypto-es
),与 demo 中的那 2 个模块功能完全相同:
// npm i crypto-es -S
// npm i js-base64 -S
import CryptoES from "crypto-es";
import { Base64 } from "js-base64";
/**
* @returns 生成wss链接
*/
protected getWebSocketUrl(): Promise<string> {
if (this.apiKey === "" || this.apiSecret === "") {
throw new Error("apiKey、apiSecret must not be empty !!!");
}
return new Promise<string>((resolve, reject) => {
// 请求地址根据语种不同变化
let url = "wss://ise-api.xfyun.cn/v2/open-ise";
const host = "ise-api.xfyun.cn";
const date = (new Date() as any).toGMTString();
const algorithm = "hmac-sha256";
const headers = "host date request-line";
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/open-ise HTTP/1.1`;
const signatureSha = CryptoES.HmacSHA256(signatureOrigin, this.apiSecret);
const signature = CryptoES.enc.Base64.stringify(signatureSha);
const authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
const authorization = Base64.encode(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
resolve(url);
});
}
Date#toGMTString()
是一个过时的方法,在 TypeScript 中不被识别,从而导致编译不通过,除了像上述代码中通过强转 any
来规避外,还可以在 src/env.d.ts
文件中进行如下声明解决,interface Date { toGMTString(): string; }
,如果工程中频繁使用该方法的话,建议用第二种方法,这样就不必每处都写一次强转代码了。
注意:
getWebSocketUrl()
函数内在生成 webSocket url 时,会使用到API_KEY
和API_SECRET
,这 2 个参数需要你自己注册一个科大讯飞的开发者账号之后,在开发者账号后台获取,为了防止被别人盗用,这个 url 的生成逻辑应该放置到自己的业务后端去实现。
# 2、录音上下文与权限
录音上下文的创建,以及获取录音权限的方式在不同浏览器环境中各不相同,所以官方 Demo 中做了大量兼容判断:
// 初始化浏览器录音
recorderInit() {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
// 创建音频环境
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
this.audioContext.resume()
} catch (e) {
if (!this.audioContext) {
alert('浏览器不支持webAudioApi相关接口')
return
}
}
// 获取浏览器录音权限
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
...
.then(stream => { getMediaSuccess(stream) })
.catch(e => { getMediaFail(e) })
} else if (navigator.getUserMedia) {
navigator.getUserMedia(
...,
stream => { getMediaSuccess(stream) },
function(e) { getMediaFail(e) }
)
} else {
...
alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
this.audioContext && this.audioContext.close()
return
}
// 获取浏览器录音权限成功的回调
let getMediaSuccess = stream => {
console.log('getMediaSuccess')
// 创建一个用于通过JavaScript直接处理音频
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
this.scriptProcessor.onaudioprocess = e => {
// 去处理音频数据
...
}
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
this.mediaSource = this.audioContext.createMediaStreamSource(stream)
// 连接
this.mediaSource.connect(this.scriptProcessor)
this.scriptProcessor.connect(this.audioContext.destination)
...
}
let getMediaFail = (e) => {
alert('请求麦克风失败')
this.audioContext && this.audioContext.close()
this.audioContext = undefined
...
}
}
而在微信小程序环境下,获取录音上下文与权限就简单多了,通过 uni.getRecorderManager()
即可获取录音上下文(管理器),然后调用 start(option)
时会自动询问用户是否授权录音权限:
const recordManager = uni.getRecorderManager();
/**
* 开始录音
*/
const startRecord = () => {
recordManager.onStart(() => {
console.log("recorder start");
...
});
recordManager.onPause(() => {
console.log("recorder pause");
});
recordManager.onStop((res) => {
// tempFilePath String 录音文件的临时路径
console.log("recorder stop", res);
...
});
recordManager.onError((err) => {
// errMsg String 错误信息
console.log("recorder err", err);
});
recordManager.onFrameRecorded((res) => {
// frameBuffer ArrayBuffer 录音分片结果数据
// isLastFrame Boolean 当前帧是否正常录音结束前的最后一帧
const { frameBuffer } = res;
...
});
recordManager.start(option);
};
# 3、获取录音数据
浏览器的录音数据是通过 scriptProcessor.onaudioprocess
回调函数获得的:
// 获取浏览器录音权限成功的回调
let getMediaSuccess = (stream) => {
...
this.scriptProcessor.onaudioprocess = (e) => {
// 去处理音频数据
transWorker.postMessage(e.inputBuffer.getChannelData(0));
};
...
};
微信小程序的录音数据是通过 recordManager.onFrameRecorded()
回调函数获得:
recordManager.onFrameRecorded((res) => {
// frameBuffer ArrayBuffer 录音分片结果数据
// isLastFrame Boolean 当前帧是否正常录音结束前的最后一帧
const { frameBuffer } = res;
pushAudioData(frameBuffer); // 将每一帧音频保存起来
});
# 4、录音数据格式
从上面的 语音评测接口要求
中可以知道,科大讯飞语音评测接口对录音数据的格式是有要求的:
内容 | 说明 |
---|---|
音频属性 | 采样率 16k、位长 16bit、单声道 |
音频格式 | pcm、wav、mp3(需更改 aue 的值为 lame)、speex-wb; |
音频大小 | 音频数据发送会话时长不能超过 5 分钟 |
官方 Demo 中,通过 scriptProcessor.onaudioprocess
回调拿到的录音数据是双声道的 PCM 数据,而接口要求的是单声道,所以通过代码 e.inputBuffer.getChannelData(0)
提取出第 1 个声道的数据,并交给 transWorker 去处理成 采样率 16k
、位长 16bit
的 PCM 数据:
// index.js
this.scriptProcessor.onaudioprocess = (e) => {
// 去处理音频数据
transWorker.postMessage(e.inputBuffer.getChannelData(0));
};
// transcode.worker.js
(function(){
self.onmessage = function(e){
transAudioData.transcode(e.data)
}
let transAudioData = {
transcode(audioData) {
let output = transAudioData.to16kHz(audioData)
output = transAudioData.to16BitPCM(output)
...
},
to16kHz(audioData) {
...
return newData
},
to16BitPCM(input) {
...
return dataView
},
}
})()
微信小程序中在启动录音时需要传入一个配置参数 option
,在这个 option
中我们可以指定采样率、通道数等配置,之后通过 recordManager.onFrameRecorded()
回调拿到的就已经是 采样率 16k
、位长 16bit
的单声道 PCM 数据了:
const recordManager = uni.getRecorderManager();
const option = {
duration: duration, // 录音的时长,单位 ms,最大值 600000(10 分钟)
sampleRate: 16000, // 采样率(pc不支持)
numberOfChannels: 1, // 录音通道数
// encodeBitRate: 48000, // 编码码率(默认就是48000)
frameSize: 1, // 指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3、pcm 格式。
format: "pcm", // 音频格式,默认是 aac
}
const startRecord = () => {
...
recordManager.onFrameRecorded((res) => {
// frameBuffer ArrayBuffer 录音分片结果数据
// isLastFrame Boolean 当前帧是否正常录音结束前的最后一帧
const { frameBuffer } = res;
pushAudioData(frameBuffer); // 将每一帧音频保存起来
});
recordManager.start(option);
};
注意:微信小程序录音时长最大值为 10 分钟,而科大讯飞语音评测录音时长最大值为 5 分钟!!
# 5、发送 webSocket 数据
官方 Demo 中,使用 1 个 audioData 数组来存放每一帧经过 transWorker 处理过的 PCM "散装"
数据(音频流数据);在 webSocket 连接开启的 500ms 之后,开始发送音频流数据:
class IseRecorder {
constructor({ language, accent, appId } = {}) {
// 记录音频数据
this.audioData = []
transWorker.onmessage = function (event) {
self.audioData.push(...event.data) // GitLqr: 注意这里是 "散装" 的
}
...
}
recorderStart() {
this.audioContext.resume()
this.connectWebSocket()
}
// 连接websocket
connectWebSocket() {
return getWebSocketUrl().then(url => {
...
iseWS.onopen = e => {
// 重新开始录音
setTimeout(() => {
this.webSocketSend()
}, 500)
}
})
}
// 初始化浏览器录音
recorderInit() {
...
// 获取浏览器录音权限成功的回调
let getMediaSuccess = stream => {
this.scriptProcessor.onaudioprocess = e => {
// 去处理音频数据
transWorker.postMessage(e.inputBuffer.getChannelData(0));
}
...
}
}
}
再来看 webSocketSend()
的具体实现,就是不断从 audioData 数组中取出 "1280字节的"
PCM 数据,经过 Base64 编码后发送给科大讯飞接口;PCM 数据只会在 第一帧
和 中间帧
进行发送,当 PCM 数据消费完之后,还需要再发送一个 最后帧
通知科大讯飞语音评测接口数据流已经全部发送完成;此外,需要注意第一帧的几个重要参数:
- category:题型。
read_sentence
指的是句子朗读,read_chapter
指的是篇章朗读,等等。 - auf:音频采样率。不填默认就是
audio/L16;rate=16000
。 - ent:中文填
cn_vip
;英文填en_vip
。 - text:待评测文本 utf8 编码,需要加 utf8bom 头。
注意:官方 Demo 的对比句子是
where are you
,所以 ent 填写的是en_vip
,所以,如果实际业务使用的是中文,则需要注意调整参数值。另外,第一帧参数中有用到 appid,这个跟前面的 API_KEY、API_SECRET 一样,都是从开发者账号后台拿到的。
// 向webSocket发送数据
webSocketSend() {
let audioData = this.audioData.splice(0, 1280)
var params = {
common: { app_id: this.appId },
business: {
...
category: 'read_sentence', // read_syllable/单字朗读,汉语专有 read_word/词语朗读 read_sentence/句子朗读 https://www.xfyun.cn/doc/Ise/IseAPI.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B
auf: 'audio/L16;rate=16000',
ent: 'en_vip',
text: '\uFEFF' + 'where are you'
},
data: {
status: 0, encoding: 'raw', data_type: 1,
data: this.toBase64(audioData),
},
}
this.webSocket.send(JSON.stringify(params))
this.handlerInterval = setInterval(() => {
// websocket未连接
if (this.webSocket.readyState !== 1) {
this.audioData = []
clearInterval(this.handlerInterval)
return
}
// 最后一帧
if (this.audioData.length === 0) {
if (this.status === 'end') {
this.webSocket.send(
JSON.stringify({
business: { cmd: 'auw', aus: 4, aue: 'raw' },
data: { status: 2, encoding: 'raw', data_type: 1, data: '', },
})
)
this.audioData = []
clearInterval(this.handlerInterval)
}
return false
}
audioData = this.audioData.splice(0, 1280)
// 中间帧
this.webSocket.send(
JSON.stringify({
business: { cmd: 'auw', aus: 2, aue: 'raw' },
data: {
status: 1, encoding: 'raw', data_type: 1,
data: this.toBase64(audioData),
},
})
)
}, 40)
}
微信小程序中的数据发送流程跟官方 Demo 差不多,不过对 PCM 数据的存储方式不太一样,在启动录音的时候传入的 option 参数中,frameSize
指定了 onFrameRecorded()
回调的 PCM 帧大小,比如指定为 1(k),那么每次回调时附带的就是 "1024"字节的
PCM 数据,而官方 Demo 中对 PCM 进行拆散(存储时)和切割(发送时)的操作在微信小程序中是没必要的,无形中增加了复杂度。于是我这里干脆使用了一个 audioDataList 数组,将 PCM 数据以一帧帧(每帧 1024 字节)的形式进行存放,发送时,再一帧一帧的取出:
// index.vue
const recordManager = uni.getRecorderManager();
const option = {
...
frameSize: 1, // 指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3、pcm 格式。
format: "pcm", // 音频格式,默认是 aac
}
const startRecord = () => {
...
recordManager.onFrameRecorded((res) => {
// frameBuffer ArrayBuffer 录音分片结果数据
// isLastFrame Boolean 当前帧是否正常录音结束前的最后一帧
const { frameBuffer } = res;
pushAudioData(frameBuffer); // 将每一帧音频保存起来
});
recordManager.start(option);
};
// ise-xfyun/index.ts
export default class IseXfyun {
private audioDataList: ArrayBuffer[] = []; // 音频流数据
/**
* 添加语音数据
* @param frameBuffer 帧数据
*/
pushAudioData(frameBuffer: any) {
if (frameBuffer) {
this.audioDataList.push(frameBuffer);
}
}
/**
* 发送语音数据
*/
private sendAudioData() {
const audioData = this.audioDataList.splice(0, 1);
const params = {
common: { app_id: this.appId },
business: {
...
category: this.category,
ent: "cn_vip", // 中文
text: "\uFEFF" + this.chapter,
},
data: {
status: 0, encoding: "raw", data_type: 1,
data: this.toBase64(audioData[0]),
},
};
this.socketTask.send({ data: JSON.stringify(params) });
this.handlerInterval = setInterval(() => {
// websocket未连接
if (!this.socketTask) { return this.clearHandlerInterval(); }
// 最后一帧
if (this.audioDataList.length === 0) {
const params = ...;
this.socketTask.send({ data: JSON.stringify(params) });
this.audioDataList = [];
return this.clearHandlerInterval();
}
// 中间帧
const audioData = this.audioDataList.splice(0, 1);
const params = {
business: { cmd: "auw", aus: 2, aue: "raw" },
data: {
status: 1, encoding: "raw", data_type: 1,
data: this.toBase64(audioData[0]),
},
};
this.socketTask.send({ data: JSON.stringify(params) });
}, 40);
}
private toBase64(buffer: ArrayBuffer) {
return uni.arrayBufferToBase64(buffer);
}
...
}
注意:这里的
toBase64()
也与官方 Demo 中的实现不一样,官方 Demo 使用的是js/base64js.js
中 Base64 来对数据进行编码,但是实际测试在微信小程序环境中编码会出问题,具体表现为调用科大讯飞语音评测接口时,会返回68675
错误码,改用uni.arrayBufferToBase64()
产出的 base64 则可被科大讯飞正常识别。
# 6、接收 webSocket 消息
官方 Demo 在创建好 webSocket 之后,指定了消息回调处理函数,具体处理逻辑交由 result(resultData)
实现,回调次数为多次,比如:帧数据接收成功消息,数据流中途错误消息,以及最终的评测结果消息。需要注意的是,如果在中途收到了科大讯飞接口的错误消息,那么应该直接停止数据流发送并提示给用户,因为一旦接口返回了错误消息,那么后续发送的中间帧数据科大讯飞接口是不会受理的,纯属浪费用户的流量和时间:
// 连接websocket
connectWebSocket() {
return getWebSocketUrl().then(url => {
let iseWS
...
iseWS.onmessage = e => {
this.result(e.data)
}
})
}
result(resultData) {
// 识别结束
let jsonData = JSON.parse(resultData)
if (jsonData.data && jsonData.data.data) {
let data = Base64.decode(jsonData.data.data)
let grade = parser.parse(data, {
attributeNamePrefix: '',
ignoreAttributes: false
})
// 显示成绩
...
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
this.webSocket.close()
}
if (jsonData.code !== 0) {
this.webSocket.close()
console.log(`${jsonData.code}:${jsonData.message}`)
}
}
微信小程序接收并处理 webSocket 消息的逻辑与官方 Demo 类似,不多赘述:
import { Base64 } from "js-base64";
import { XMLParser } from "fast-xml-parser";
export default class IseXfyun {
/**
* 连接科大讯飞wss接口
*/
async connect() {
this.socketTask = uni.connectSocket(...);
this.socketTask.onMessage((res) => {
this.onMessage(res.data.toString());
});
...
}
private onMessage(resultData: string) {
const jsonData = JSON.parse(resultData);
this.log("收到消息:", jsonData);
if (jsonData.data && jsonData.data.data) {
const data = Base64.decode(jsonData.data.data);
const parser = new XMLParser({
attributeNamePrefix: "",
ignoreAttributes: false,
allowBooleanAttributes: true,
});
const grade = parser.parse(data);
// 显示成绩
...
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
this.disconnect();
}
if (jsonData.code !== 0) {
this.callback.onError(jsonData);
this.log(`${jsonData.code}:${jsonData.message}`);
this.disconnect();
}
}
...
}
# 三、封装
以上就是对科大讯飞语音评测官方 js Demo 代码逻辑的逐步分析,官方 Demo 把录音与 webSocket 接口调用写在一个文件中,感觉比较混乱,为了更好的复用,我将 录音 与 语音评测 两者进行拆分,把 语音评测 的部分封装到 IseXfyun
类中,大致类结构如下,内容基本上就是上面分析过程中微信小程序端的实现,为缩短篇幅,方法体内代码以省略号表示:
import { Base64 } from "js-base64";
import { XMLParser } from "fast-xml-parser";
import CryptoES from "crypto-es";
/**
* 科大讯飞 语音评测(流式版)封装
*
* 注意:需要设置wss域名白名单 wss://ise-api.xfyun.cn
* 建议:apiKey、apiSecret 不要存放在本地,应该放在后端,防止被破解。
* 最佳做法:继承 IseXfyun,重写 getWebSocketUrl(),从后端获取 socket 链接,即 webSocketUrl 的计算规则由后端实现。
*
* @author GitLqr
* @since 2022-08-30
*/
export default class IseXfyun {
/**
* 科大讯飞的语音评测只支持 16k 16bit 单通道
* @param duration 录音时长
* @returns 录音配置
*/
getAudioRecordOption(duration: number) { ... }
/**
* 添加语音数据
* @param frameBuffer 帧数据
*/
pushAudioData(frameBuffer: any) { ... }
/**
* @returns 生成wss链接
*/
protected getWebSocketUrl(): Promise<string> { ... }
/**
* 连接科大讯飞wss接口
*/
async connect() { ... }
/**
* 断开连接
*/
disconnect() { ... }
/**
* 发送语音数据
*/
private sendAudioData() { ... }
private onMessage(resultData: string) { ... }
}
类 IseXfyun
与 Uniapp 完整版 Demo 的具体代码可访问下面仓库进行查看:
注意:微信小程序需要配置域名白名单
wss://ise-api.xfyun.cn
- 01
- Flutter - 子部件任意位置观察滚动数据11-24
- 02
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 03
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21