Ai_GirlFriend/xuniYou/components/ai.vue
2026-01-31 19:15:41 +08:00

318 lines
8.6 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="">
<!-- <button class="btn" shape="circle" type="info" @touchstart="openMedia" @touchend="stopMedia">按住
说话</button> -->
<!-- @click="handleShow" -->
<!-- <iatPopup v-model:moduleValue="show" /> -->
<!-- <view v-if="show" class="iating">
正在说话中....
</view> -->
</view>
</template>
<script>
import CryptoJS from "crypto-js";
// 科大讯飞接口配置
const config = {
hostUrl: "wss://iat-api.xfyun.cn/v2/iat",
host: "iat-api.xfyun.cn",
appid: "331874e7",
apiSecret: "YTc3MDJmYjJjMjk3NTJhMGMzMTk5YTVl", //"你的秘钥",
apiKey: "ddf92cc17ecc9cda0a8cca5d4997e897", //"你的apikey",
uri: "/v2/iat",
highWaterMark: 1280,
// file: "./16k_10.pcm",
};
// 鉴权签名
function getAuthStr(date) {
let signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`;
let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, config.apiSecret);
let signature = CryptoJS.enc.Base64.stringify(signatureSha);
let authorizationOrigin =
`api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
let authStr = CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(authorizationOrigin)
);
return authStr;
}
function getUrl() {
// 获取当前时间 RFC1123格式
let date = new Date().toUTCString();
//科大讯飞远程连接地址
let wssUrl =
config.hostUrl +
"?authorization=" +
getAuthStr(date) +
"&date=" +
encodeURIComponent(date) +
"&host=" +
config.host;
console.log("websocke科大讯飞的地址为", wssUrl);
return wssUrl;
}
let uniSocketTask = null
//发送给科大讯飞的第一帧的模板数据格式
let frame = {
common: {
app_id: config.appid,
},
business: {
language: "zh_cn",
domain: "iat",
accent: "mandarin",
dwa: "wpgs", // 可选参数,动态修正
vad_eos: 5000,
},
data: {
status: 0,
format: "audio/L16;rate=16000",
encoding: "lame"
},
};
// 录音配置项
const recordOption = {
sampleRate: 16000, // 采样率pc不支持
format: "mp3", // 音频格式,默认是 aac
};
// 录音管理器
const recordManager = uni.getRecorderManager();
console.log('recordManager:', recordManager)
export default {
data() {
return {
show: false,
resultText: "",
resultTextTemp: "",
renderText: "",
}
},
onLoad() {
},
onShow() {
},
methods: {
//打开麦克风
openMedia() {
this.connectSocket(); //创建websocket连接
},
stopMedia() {
console.log('组件录音结束',)
recordManager.stop()
},
//创建连接并返回数据
connectSocket() {
//创建socketTask实例
if (uniSocketTask === null) {
uniSocketTask = uni.connectSocket({
url: getUrl(),
success() {},
});
console.log('uniSocketTask:::', uniSocketTask)
//监听连接成功的事件
uniSocketTask.onOpen(() => {
console.log("监听到开启连接成功");
this.startRecord(); //打开录音
});
//监听连接关闭的事件
uniSocketTask.onClose((res) => {
console.log("监听到关闭连接成功", res); //Websocket关闭!服务器读取MSG超时
uniSocketTask = null;
});
uniSocketTask.onError((res) => {
console.log("监听到连接发生错误", res);
});
//监听科大讯飞消息返回
uniSocketTask.onMessage((res) => {
//收到消息
console.log('收到消息:', res)
const message = JSON.parse(res.data);
//判断是否存在数据
if (res.data) {
console.log("收到服务器消息,并开始渲染", message);
this.renderResult(message);
if (message.code === 0 && message.data.status === 2) {
//该函数为当前页唯一的关闭连接函数
console.log('最后一条', this.renderText)
this.closeSocket();
this.$emit('renderText', this.renderText)
}
//收到不正常服务器消息,返回错误到控制台
if (message.code !== 0) {
this.closeSocket();
console.error(message);
}
} else {
console.log("未监听到消息:原因:", JSON.stringify(res));
}
});
} else {
console.log("socketTask实例已存在");
}
},
//发送消息
sendMessage(sendData) {
console.log('发送', JSON.stringify(sendData))
uniSocketTask.send({
data: JSON.stringify(sendData),
success() {
console.log("发送成功");
},
fail() {
console.log("发送失败");
},
});
},
//关闭连接
closeSocket() {
console.log("开始尝试关闭连接");
// 关闭连接
uniSocketTask.close();
},
//开启录音
startRecord() {
console.log("打开录音");
recordManager.onStart(() => {
console.log("开始录音");
this.show = true
// ...
});
recordManager.onStop((res) => {
this.show = false
// tempFilePath String 录音文件的临时路径
console.log("录音停止,文件路径为:", res.tempFilePath);
// this.sendMessage(frame)
this.pathToBase64(res.tempFilePath).then(base64 => {
console.log('base64--', base64);
// 这里一定要注意当需要将base64字符串转换为Buffer时通常会去掉前面部分的数据URL标识如"data:image/png;base64,"因为这部分内容不是实际的base64编码数据。
let buff = base64.split(",")[1]
const arrayBuffer = uni.base64ToArrayBuffer(buff)
const audioString = this.toString(arrayBuffer)
console.log("文件读取成功", audioString.length);
let offset = 0;
while (offset < audioString.length) {
const subString = audioString.substring(offset, offset + 1280)
offset += 1280
const isEnd = offset >= audioString.length
let params = {
common: {
app_id: config.appid,
},
business: {
language: "zh_cn",
domain: "iat",
accent: "mandarin",
dwa: "wpgs", // 可选参数,动态修正
vad_eos: 5000,
},
data: {
status: isEnd ? 2 : 1,
format: "audio/L16;rate=16000",
encoding: "lame",
audio: btoa(subString)
}
}
this.sendMessage(params);
}
}).catch(error => {
console.error(error)
})
});
recordManager.onError((err) => {
// errMsg String 错误信息
console.log("录音出现错误", err);
});
recordManager.start(recordOption);
},
//关闭录音
stopRecord() {
recordManager.stop();
},
toString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary
},
//录音文件路径转base64
pathToBase64(path) {
return new Promise(function(resolve, reject) {
// app
if (typeof plus === 'object') {
plus.io.resolveLocalFileSystemURL(path, function(entry) {
entry.file(function(file) {
var fileReader = new plus.io.FileReader()
fileReader.onload = function(evt) {
resolve(evt.target.result)
}
fileReader.onerror = function(error) {
reject(error)
}
fileReader.readAsDataURL(file)
}, function(error) {
reject(error)
})
}, function(error) {
reject(error)
})
return
}
reject(new Error('not support'))
})
},
//讯飞回复字段拼接
renderResult(jsonData) {
console.log("开始执行渲染函数", jsonData);
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;
}
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果替换范围为rg字段
if (data.pgs) {
if (data.pgs === "apd") {
// 将resultTextTemp同步给resultText
this.resultText = this.resultTextTemp;
}
// 将结果存储在resultTextTemp中
this.resultTextTemp = this.resultText + str;
} else {
this.resultText = this.resultText + str;
}
this.renderText = this.resultTextTemp || this.resultText || "";
}
console.log("渲染后的数据为");
console.log(this.renderText);
},
}
}
</script>
<style>
page {
/* background-color: #111111; */
}
</style>
<style lang="scss" scoped>
</style>