语音听写(流式版)WebAPI 文档
参考了uniapp社区插件:语音转文字,科大讯飞,实时展示文字,流示输出
参考文档:
开发参考文档
app 安卓端录音授权
【uniapp】使用permission获取录音权限及实现录音功能
要点:
由于是web端的sdk,所以uniapp中是采用renderjs实现
renderjs可以支持我们 在视图层操作dom,运行 for web 的 js库
renderjs uniapp官网解释
使用
参考uniapp插件的作者写的步骤使用就行,下面是我自己基于作者的插件稍微改一点
组件
<template>
<view>
<view id="record" class="record" :listeningRecordingBegins="recordFlag" :change:listeningRecordingBegins="record.listeningRecordingBeginsHandler"
:scriptPath="scriptPath" :change:scriptPath="record.scriptPathHandler" :options="options" :change:options="record.optionsHandler"></view>
</view>
</template>
<script>
// import log from '../../api/log';
export default {
data() {
return {
msg: '',
recordFlag: null,
scriptPath: ''
};
},
props: {
options: {
type: Object,
default: () => {
return {
receordingDuration: 60,
APPID: '',
API_SECRET: '',
API_KEY: ''
};
}
}
},
created() {
// #ifdef APP
this.scriptPath = 'file://' + plus.io.convertLocalFileSystemURL('/');
// #endif
},
methods: {
start() {
console.log('start', this.recordFlag);
if (this.recordFlag == 'START') return;
this.recordFlag = 'START';
},
end() {
console.log('end', this.recordFlag);
if (this.recordFlag == 'END') return;
this.recordFlag = 'END';
},
resultMsg(e) {
this.$emit('result', e);
this.msg = e;
},
endCallback(e) {
this.$emit('onStop', e);
this.end();
},
startCallback(e) {
this.$emit('onStart', e);
},
seconds(e) {
this.$emit('countDown', e);
},
change(e) {
this.$emit('change', e);
},
}
};
</script>
<script lang="renderjs" module="record">
import CryptoJS from 'crypto-js';
let APPID = "4c12ff51";
let API_SECRET = "ZTUxMjIyMjZiOGRjMGNhNTMwMTgwOTM1";
let API_KEY = "f946eb15c37c18de1f7f7e2b69daba69";
let receordingDuration = 60
let ws = null;
let resultText = "";
let resultTextTemp = "";
let timer = null;
let tapeStatus = {
CONNECTING: 'CONNECTING',
OPEN: 'OPEN',
CLOSING: 'CLOSING',
CLOSED: 'CLOSED'
}
export default {
data() {
return {
recorder: null,
recorderPath: '',
}
},
methods: {
// 监听recordFlag的值的处理
listeningRecordingBeginsHandler(flag) {
if (flag == null) return
if (flag == 'START') {
this.connectWebSocket()
} else if (flag == 'END') {
this.recorder.stop()
}
},
// 动态标签引入js
scriptPathHandler(path) {
var recordScript = document.getElementById("recordScript");
if (recordScript) {
var script = document.getElementById('recordScript');
this.recorder = new RecorderManager(path + 'static/dist')
console.log('有标签了', script, this.recorder);
this.initListen()
} else {
var script = document.createElement('script');
script.id = 'recordScript'
script.src = `./static/dist/index.umd.js`;
document.body.appendChild(script);
script.onload = () => {
this.recorder = new RecorderManager(path + 'static/dist')
this.initListen()
}
}
},
optionsHandler(options) {
APPID = options.APPID
API_SECRET = options.API_SECRET
API_KEY = options.API_KEY
receordingDuration = options.receordingDuration
},
initListen() {
this.recorder.onStart = () => {
this.changeStatus(tapeStatus.OPEN);
}
this.recorder.onFrameRecorded = ({
isLastFrame,
frameBuffer
}) => {
if (ws.readyState === ws.OPEN) {
ws.send(
JSON.stringify({
data: {
status: isLastFrame ? 2 : 1,
format: "audio/L16;rate=16000",
encoding: "raw",
audio: this.toBase64(frameBuffer),
},
})
);
if (isLastFrame) {
this.changeStatus("CLOSING");
}
}
};
this.recorder.onStop = () => {
clearInterval(timer);
};
},
connectWebSocket() {
const websocketUrl = this.getWebSocketUrl();
if ("WebSocket" in window) {
ws = new WebSocket(websocketUrl);
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(websocketUrl);
} else {
console.log("不支持WebSocket");
return;
}
this.changeStatus(tapeStatus.CONNECTING);
ws.onopen = () => {
console.log('this.recorder', this.recorder);
this.recorder.start({
sampleRate: 16000,
frameSize: 1280,
})
const params = {
common: {
app_id: APPID,
},
business: {
language: "zh_cn",
domain: "iat",
accent: "mandarin",
vad_eos: 5000,
dwa: "wpgs",
},
data: {
status: 0,
format: "audio/L16;rate=16000",
encoding: "raw",
}
}
ws.send(JSON.stringify(params));
}
ws.onmessage = (e) => {
console.log('---message----', e);
this.renderResult(e.data);
};
ws.onerror = (e) => {
console.error(e);
this.recorder.stop();
this.changeStatus(tapeStatus.CLOSED)
};
ws.onclose = (e) => {
this.recorder.stop();
this.changeStatus(tapeStatus.CLOSED)
};
},
getWebSocketUrl() {
let url = "wss://iat-api.xfyun.cn/v2/iat";
const host = "iat-api.xfyun.cn";
const apiKey = API_KEY;
const apiSecret = API_SECRET;
const date = new Date().toGMTString();
const algorithm = "hmac-sha256";
const headers = "host date request-line";
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
const signature = CryptoJS.enc.Base64.stringify(signatureSha);
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
const authorization = btoa(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
return url;
},
toBase64(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
},
renderResult(resultData) {
let jsonData = JSON.parse(resultData);
if (jsonData.data && jsonData.data.result) {
let data = jsonData.data.result;
let str = "";
let ws = data.ws;
for (let i = 0; i < ws.length; i++) {
str = str + ws[i].cw[0].w;
}
this.$ownerInstance.callMethod('resultMsg', str || '')
// if (data.pgs) {
// if (data.pgs === "apd") {
// resultText = resultTextTemp;
// }
// resultTextTemp = resultText + str;
// } else {
// resultText = resultText + str;
// }
// this.$ownerInstance.callMethod('resultMsg', resultTextTemp || resultText || '')
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
ws.close();
}
if (jsonData.code !== 0) {
ws.close();
console.error(jsonData);
}
},
changeStatus(status) {
let statusText = ''
if (status === "CONNECTING") {
statusText = '建立连接中'
resultText = "";
resultTextTemp = "";
} else if (status === "OPEN") {
statusText = '开始录音'
this.$ownerInstance.callMethod('startCallback', {
status,
msg: statusText
})
this.countdown();
} else if (status === "CLOSING") {
statusText = '关闭连接中'
} else if (status === "CLOSED") {
statusText = "录音已关闭";
this.$ownerInstance.callMethod('endCallback', {
status,
msg: statusText
})
}
this.$ownerInstance.callMethod('change', {
status,
msg: statusText
})
},
countdown() {
let seconds = receordingDuration
// btnControl.innerText = `录音中(${seconds}s)`;
timer = setInterval(() => {
seconds = seconds - 1;
if (seconds <= 0) {
clearInterval(timer);
this.recorder.stop();
} else {
this.$ownerInstance.callMethod('seconds', seconds)
// btnControl.innerText = `录音中(${seconds}s)`;
}
}, 1000);
}
}
}
</script>
<style></style>
页面使用
<template>
<view class="page-container">
<AppNavbar navBarTitle="语音转写"></AppNavbar>
<view class="page-content main-content">
<view class="content">
<!-- <button @touchstart.stop="start" @touchend.stop="end">按下说话,松开停止</button> -->
<button @click="start">开始说话</button>
<button @click="end">停止说话1</button>
<view style="color:deeppink;font-size: 30rpx;">
{{msg}}
</view>
<!-- <yimo-AudioTrans ref="yimoAudioTransRefs" :options="options" @countDown="countDown" @result="resultMsg" @onStop="onStop" @onOpen="onOpen"
@change="change"></yimo-AudioTrans> -->
<AudioTrans ref="yimoAudioTransRefs" :options="options" @countDown="countDown" @result="resultMsg" @onStop="onStop" @onOpen="onOpen" @change="change">
</AudioTrans>
</view>
</view>
</view>
</template>
<script>
import AppNavbar from '@/components/AppNavbar/navbar.vue';
import AudioTrans from '@/components/AudioTrans/AudioTrans.vue';
export default {
components: {
AppNavbar,
AudioTrans
},
data() {
return {
title: 'Hello',
msg: '1233',
options: {
receordingDuration: 60,
APPID: '4c12ff51',
API_SECRET: 'ZTUxMjIyMjZiOGRjMGNhNTMwMTgwOTM1',
API_KEY: 'f946eb15c37c18de1f7f7e2b69daba69'
},
};
},
onLoad() {
uni.getSystemInfo({
success: function(res) {
const platform = res.platform.toLowerCase();
if (platform === 'devtools') {
console.log('-----浏览器环境-----');
} else if (platform === 'ios' || platform === 'android') {
//监听授权
plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], function(e) {
if (e.deniedAlways.length > 0) { //权限被永久拒绝
// 弹出提示框解释为何需要权限,引导用户打开设置页面开启
console.log('权限被永久拒绝' + e.deniedAlways.toString());
}
if (e.deniedPresent.length > 0) { //权限被临时拒绝
// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限
console.log('权限被临时拒绝' + e.deniedPresent.toString());
}
if (e.granted.length > 0) { //权限被允许
console.log('权限被允许' + e.granted.toString());
}
});
} else if (platform === 'wechat') {
console.log('-----微信环境-----');
} else {
console.log('-----无法确定当前环境-----');
}
},
fail: function(error) {
console.log('-----getSystemInfo error-----');
}
});
},
omShow() {},
methods: {
start() {
this.$refs.yimoAudioTransRefs.start();
},
end() {
this.$refs.yimoAudioTransRefs.end();
},
countDown(e) {
console.log('countDown', e);
},
onStop(e) {
console.log('onStop', e);
},
onOpen(e) {
console.log('onOpen', e);
},
change(e) {
console.log('change', e);
},
resultMsg(e) {
this.msg = e;
console.log('resultMsg', e);
}
}
};
</script>
<style lang="scss" scoped>
.main-content {
padding-top: 10%;
}
</style>
遇到的问题:
在浏览器端正常运行,运行到app端,一直报错 [object DOMException] at static/dist/index.umd.js:1
折磨了好久,后面才发现是安卓端没有授权的问题
授权参考:
manifest.json中加入permissions
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
app模块配置打开Record录音
页面鉴权:
uni.getSystemInfo({
success: function(res) {
const platform = res.platform.toLowerCase();
if (platform === 'devtools') {
console.log('-----浏览器环境-----');
} else if (platform === 'ios' || platform === 'android') {
//监听授权
plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], function(e) {
if (e.deniedAlways.length > 0) { //权限被永久拒绝
// 弹出提示框解释为何需要权限,引导用户打开设置页面开启
console.log('权限被永久拒绝' + e.deniedAlways.toString());
}
if (e.deniedPresent.length > 0) { //权限被临时拒绝
// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限
console.log('权限被临时拒绝' + e.deniedPresent.toString());
}
if (e.granted.length > 0) { //权限被允许
console.log('权限被允许' + e.granted.toString());
}
});
} else if (platform === 'wechat') {
console.log('-----微信环境-----');
} else {
console.log('-----无法确定当前环境-----');
}
},
fail: function(error) {
console.log('-----getSystemInfo error-----');
}
});