微信小程序实现websokect语音对话,实现后端实时返回片段音频,前端播放+心跳检测

本文主要是介绍微信小程序实现websokect语音对话,实现后端实时返回片段音频,前端播放+心跳检测,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇微信小程序实现和AI语音对话功能
1.目的:之前项目实现跟ai语音对话,因为API语音结果生成缓慢,返给前端大概在10左右,所以领导要求使用websokect,实时接受后端反的片段音频,前端播放。这样生成的时间就会快很多。
2.代码片段

// pages/ai/aiVoice/index.ts
import {timeExChange,copyText,openSetting,startMonitoringNetwork,
} from '../../../utils/utils'
//引入插件:微信同声传译
const plugin = requirePlugin('WechatSI');
//获取全局唯一的语音识别管理器recordRecoManager
const manager = plugin.getRecordRecognitionManager();
// const VUE_APP_API_URL_MSG = "wss://地址/";
let isOpen = false; // 使用布尔值来表示连接状态
let socketTask = null;
let reconnectAttempts = 0;
let maxReconnectAttempts = 10; // 最大重连次数
let reconnectTimer = null;
let reconnectInterval = 1000;//重置重连间隔
Page({/*** 页面的初始数据*/innerAudioContext: null,data: {audioCtx: null,// 存放从后端接收到的语音地址数组voiceUrls: [],// 当前播放的语音索引currentVoiceIndex: 0,startTime: 0,endTime: 0,elapsedTime: 0, //计时器isplay: true,onstops: true, //默认执行onStopisFlag: false, //是否点击录音到获取结果之间状态longPressTimer: null, // 用于存储长按定时器的变量touchStartTime: 0, //长按开始touchEndTime: 0, //松开结束 authsetting: false, //是否获取授权url: "地址",openid: null,islongPress: false, //是否长按ParentValue: 'Parent',loginShow: false, //登录弹窗默认关闭annimationFlag: false, //logo动画默认关闭touchstart: false, //默认没有按下toView: null,scrollTop: 0,src: '', //语音地址resultobj: {result: "",tempFilePath: ""},msgText: 1, //1默认初始化  2对话进行中   3结束对话  4对话出现问题flag: 1,haveflag: false, //防止重复点击recordState: false, //麦克风默认关闭状态msglist: [], //聊天记录showModal: false, //历时消息记录heartBeatIntervalId:null,},/*** 生命周期函数--监听页面加载*/onLoad() {// 关闭主页按钮wx.hideHomeButton();wx.setNavigationBarTitle({title: "AI语音对话"})// 获取语音授权this.getSeeting(1);//识别语音this.initRecord();const authset = wx.getStorageSync('AUTHSETTING');// console.log("初始化语音权限", authset);if (!authset) { //没有获取录音权限// 重新获取录音权限this.getSeeting(1);}},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {reconnectAttempts = 0;//检测网络情况startMonitoringNetwork();//  判断用户是否登录this.isLogin();},// 链接sokectconnectWebSocket() {let that = this;console.log("拿到openidlema", wx.getStorageSync('OPENID'))if (socketTask) {// 如果已有 WebSocket 连接,则关闭它socketTask.close();}socketTask = wx.connectSocket({url: VUE_APP_API_URL_MSG + wx.getStorageSync('OPENID'),header: {'content-type': 'application/json'},// protocols: ['protocolOne', 'protocolTwo'] // 可选,使用特定协议});socketTask.onOpen(function (res) {console.log('WebSocket连接成功', socketTask);isOpen = true; // 更新连接状态reconnectAttempts = 0; // 连接成功后重置重连尝试次数reconnectInterval = 1000; // 连接成功后重置重连间隔that.startHeartbeat(); // 启动心跳机制});socketTask.onMessage(function (res) {console.log("获取消息成功", res);if(res.data=="ping"){return false}if (that.data.isplay) {wx.hideLoading();//设置语音that.setData({isFlag: false,})const result = res.data;// console.log("处理过后的数据", result);that.data.voiceUrls.push(result);// 判断是不是第一次收到消息且目前没有播放===才调用播放音频方法if (that.data.voiceUrls.length == 1 && !that.data.audioCtx) {console.log("第一次调用")that.setData({msgText: 2, //正在对话`annimationFlag: true,haveflag: true})that.playNextVoice();}} else {wx.hideLoading();wx.showToast({title: res.data.msg,icon: 'none',duration: 2000})that.setData({msgText: 1,voiceUrls: []})}});socketTask.onClose(function (res) {console.log('WebSocket连接已关闭', res);that.socketClose();// isOpen = false; // 更新连接状态// socketTask = null; // 清除 WebSocket 实例that.stopHeartbeat(); // 停止心跳机制// that.reconnectWebSocket(); // 尝试重连});socketTask.onError(function (res) {console.log('WebSocket连接错误:', res);isOpen = false; // 更新连接状态that.stopHeartbeat(); // 停止心跳机制that.reconnectWebSocket(); // 尝试重连});},// 启动心跳机制
startHeartbeat() {let that = this;if (isOpen) {that.data.heartBeatIntervalId = setInterval(() => {if (isOpen) {console.log('发送心跳消息');let data = {msg:"ping"}socketTask.send({data: JSON.stringify(data),success(res) {console.log('心跳消息发送成功');}});} else {clearInterval(that.data.heartBeatIntervalId);}}, 60000); // 每 10 秒发送一次心跳消息}
},// 停止心跳机制
stopHeartbeat() {if (this.data.heartBeatIntervalId) {console.log("停止心跳机制");clearInterval(this.data.heartBeatIntervalId);this.data.heartBeatIntervalId = null; // 清除定时器 IDthis.setData({msgText: 1, //初始化annimationFlag: false,haveflag: false,touchstart: false})}
},// 尝试重新连接 WebSocket
reconnectWebSocket() {if (!reconnectTimer) { // 确保没有重复的重连定时器reconnectTimer = setTimeout(() => {console.log('尝试重新连接 WebSocket');this.connectWebSocket(); // 重新初始化 WebSocketreconnectTimer = null; // 清除重连定时器 ID}, 6000); // 重连间隔时间(毫秒)}
},// 播放下一个音频playNextVoice() {if (this.data.voiceUrls.length > 0) {const url = this.data.voiceUrls.shift();this.playVoice(url);} else {this.setData({audioCtx: null,msgText: 1,annimationFlag: false,haveflag: false,src: ""});}},// 播放音频playVoice(url) {if (!this.data.audioCtx) {this.data.audioCtx = wx.createInnerAudioContext();this.data.audioCtx.onError((res) => {console.log("音频播放错误", res);this.setData({msgText: 1,annimationFlag: false,haveflag: false,touchstart: false});});this.data.audioCtx.onEnded(() => {this.playNextVoice();});}this.data.audioCtx.src = url;this.data.audioCtx.play();},// 预加载音频preloadVoices(voiceUrls) {voiceUrls.forEach(url => {const audio = wx.createInnerAudioContext();audio.src = url;audio.preload = 'auto'; // 设置为自动预加载audio.onCanplay(() => {console.log(`Audio ${url} 预加载`);});audio.onError((res) => {console.error(`错误预加载 ${url}`, res);});});},// 初始化播放音频playVoices(voiceUrls) {this.preloadVoices(voiceUrls); // 预加载音频this.setData({voiceUrls}); // 更新音频列表this.playNextVoice(); // 开始播放音频},playVoice(url) {if (!this.data.audioCtx) {this.data.audioCtx = wx.createInnerAudioContext();this.data.audioCtx.onError((res) => {console.log("音频播放错误", res);this.setData({msgText: 1,annimationFlag: false,haveflag: false,touchstart: false});});this.data.audioCtx.onEnded(() => {this.playNextVoice();});}this.data.audioCtx.src = url;this.data.audioCtx.play();},// 关闭当前正在播放的语音stopVoice() {if (this.data.audioCtx) {this.data.audioCtx.stop(); // 停止播放this.data.audioCtx.destroy(); // 销毁音频上下文,释放资源this.setData({voiceUrls: [],audioCtx: null,msgText: 1, //初始化annimationFlag: false,haveflag: false,src: ""});}},// 发送数据wsSend() {// 参数let obj = {question: this.data.resultobj.result,// question: "请给我写300字的有关妈妈的作文",openId: wx.getStorageSync('OPENID'),}console.log("发送数据", obj)socketTask.send({data: JSON.stringify(obj),success(res) {// wx.hideLoading();console.log('发送数据帧成功', res);},fail(res) {wx.hideLoading();console.log('发送数据帧失败', res);}})},// 重连websokectattemptReconnect() {if (reconnectAttempts >= maxReconnectAttempts) {console.error('已达最大重连次数,停止重连');return;}reconnectAttempts++;reconnectInterval *= 2; // 指数退避,每次重连间隔加倍console.log(`尝试重新连接,间隔 ${reconnectInterval} 毫秒`);setTimeout(() => {this.connectWebSocket();}, reconnectInterval);},// 关闭sokectsocketClose() {console.log("关闭sokect",socketTask)if (socketTask) {socketTask.close(); // 关闭 WebSocket 连接socketTask = null; // 清除 WebSocket 实例isOpen = false; // 更新连接状态reconnectTimer = null;reconnectAttempts = 0;reconnectInterval = 1000;this.setData({heartBeatIntervalId:null})}},// 判断用户是否登录isLogin() {// 获取本地存储中的 OPENIDconst openid = wx.getStorageSync('OPENID');if (openid) {// 如果本地存储中存在 OPENID,则说明用户已经登录过,可以直接使用 OPENID 进行后续操作this.setData({loginShow: false})console.log("连接 WebSocket")// 连接 WebSocketthis.connectWebSocket();// 这里可以进行其他操作,比如直接跳转到主页面} else {// 如果本地存储中没有 OPENID,则需要调用登录接口获取 OPENIDthis.setData({loginShow: true,openid: openid})}},onChildEvent(event) {this.setData({loginShow: event.detail.login_show})// 连接 WebSocketthis.connectWebSocket();},//暂停语音backIndex() {let that = this;// 关闭音频this.stopVoice();// that.innerAudioContext.src = "";// that.innerAudioContext.stop(); //暂停音频that.setData({msgText: 1, //初始化touchstart: false, //按钮恢复初始状态annimationFlag: false,haveflag: false,'resultobj.tempFilePath': "",isplay: false,resultText: "",src: ""})// 如何判断当前是语音录制识别状态console.log("暂停语音", that.data.isFlag)if (that.data.isFlag) {that.setData({onstops: false, //是否执行onStop})wx.showLoading({title: '关闭中...',icon: 'none',mask: true})// 停止识别manager.stop();}wx.vibrateShort({type: 'heavy',fail: function (err) {console.error('短振动失败', err)}})},//识别语音 -- 初始化initRecord() {const that = this;// 有新的识别内容返回,则会调用此事件manager.onRecognize = function (res) {console.log("有新的识别内容返回,则会调用此事件")}// 正常开始录音识别时会调用此事件manager.onStart = function (res) {console.log("成功开始录音识别", res)that.setData({// annimationFlag:true})}//识别结束事件manager.onStop = function (res) {if (!that.data.isplay) {wx.showToast({title: "听不清楚,请再说一遍!",icon: 'success',image: '/assets/image/no_voice.png',duration: 1000,success: function (res) {that.setData({haveflag: false,isFlag: false})},fail: function (res) {console.log(res);}});return false}if (res.result == '') {wx.hideLoading();// wx.showToast({//   title: '听不清楚,请重新说一遍!',//   icon: 'none',//   duration: 2000// })// that.setData({//   msgText: 1, //初始化//   haveflag: false,//   isFlag: false,// })that.showRecordEmptyTip()return;} else {// wx.showLoading({//   title: '正在思考...',//   icon: 'none',// })console.log("获取翻译", res.result)that.setData({resultobj: {result: res.result,tempFilePath: res.tempFilePath,},msgText: 2, //正在对话// annimationFlag: true})//  调用接口// that.resultTextApi();// 给websoket发送数据that.wsSend();}}// 识别错误事件manager.onError = function (res) {console.log("error msg", res);wx.hideLoading();wx.showToast({icon: "none",title: '请重新开始~'})that.setData({haveflag: false,msgText: 1,annimationFlag: false,isFlag: false, //当前录制语音识别状态touchstart: false})}},// 根据wx.getSetting判断用户是否打开了录音权限,如果没有打开,则通过wx.authorize,向用户打开授权请求,如果用户拒绝了,就给用户打开授权设置页面。getSeeting(type) {// wx.showLoading({//   title: '获取录音权限',//   icon: 'none',//   mask: true// })const _this = thiswx.getSetting({ //获取用户当前设置success: res => {// wx.hideLoading();// console.log('获取权限', res);if (res.authSetting['scope.record']) { //查看是否授权了录音设置// console.log('获取权限1111');const authset = wx.setStorageSync('AUTHSETTING', true);_this.setData({authsetting: true})if (type == 2) {wx.showToast({title: '获取录音权限成功,点击重新开始!',icon: 'none',duration: 2000})}} else {// 用户还没有授权,向 用户发起授权请求wx.authorize({ //提前向用户发起授权请求,调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口scope: 'scope.record',success() { //用户同意授权摄像头// console.log("同意授权");// wx.showToast({//   title: '获取录音权限成功',//   icon: 'none',//   duration: 2000// })},fail() { //用户不同意授权摄像头openSetting()}})}},fail() {// console.log('获取用户授权信息失败');wx.showToast({title: '获取权限失败',icon: 'none',duration: 2000})}})},//获取文字接口resultTextApi() {//调用接口const formdata = {question: this.data.resultobj.result,// question:"请给我写300字的有关妈妈的作文",openId: wx.getStorageSync('OPENID'),};const requestTask = wx.request({url: this.data.url + '/cyjgVoice/ans',method: 'POST',//请求头需要改为stream模式header: {'content-type': 'application/x-www-form-urlencoded' // 或者 'application/json' 如果你的后端能接受JSON格式},data: formdata, // 直接使用formdata对象success: res => {console.log("语音1111", res);}})},//语音  --按住说话touchStart(e) {// 判断是否获取录音权限// if (!(this.data.authsetting)) {//   // console.log("获取语音权限")//   this.getSeeting(2);//   return// }this.setData({resultobj: {result: "",tempFilePath: "",}})console.log('按住说话', isOpen);if(!isOpen){//代表websokect未连接上wx.showToast({title: '正在建立语音通信,稍后请重试',icon: 'none',duration: 3000})return false}if (this.data.haveflag) { //true 请先结束语音console.log("没关闭haveflag")wx.showToast({title: '请先关闭语音!',icon: 'none',duration: 2000})return false}// 当前正在识别语音,还没结束上一次识别,请先关闭再进行录音if (this.data.isFlag) { //true 请先结束语音console.log("没关闭isFlag")wx.showToast({title: '请先关闭语音!',icon: 'none',duration: 2000})return false}// this.innerAudioContext.src = "";// this.innerAudioContext.stop(); //暂停音频wx.vibrateShort({type: 'heavy',fail: function (err) {console.error('短振动失败', err);}})this.setData({islongPress: true,isplay: true})var flag = Number(e.currentTarget.dataset.flag)this.setData({recordState: true, //录音状态flag: flag,touchstart: true, //按下msgText: 2, //初始化状态})// 语音开始识别manager.start({lang: 'zh_CN', // 识别的语言})},//语音  --松开结束touchEnd(e) {if (!(this.data.islongPress)) { //如果是长按执行下面内容console.log('松开1')return false}wx.showLoading({title: '正在思考...',icon: 'none',})if (this.data.haveflag) { //true 请先结束语音console.log('松开2');this.setData({touchstart: false,})wx.hideLoading();// wx.showToast({//   title: '请先关闭语音111!',//   icon: 'none',//   duration: 2000// })return false}console.log('松开结束3');this.setData({touchstart: false,recordState: false,islongPress: false, //长按初始状态isFlag: true, //判断从松手到识别录音期间状态haveflag: true})// this.resultTextApi();// this.wsSend();// 语音结束识别manager.stop();},// 关闭弹窗closeModal() {this.setData({showModal: false})},showRecordEmptyTip() {console.log("请说话1")this.setData({msgText: 1, //初始化haveflag: false,isFlag: false,})wx.showToast({title: "听不清楚,请再说一遍!",icon: 'success',image: '/assets/image/no_voice.png',duration: 1000,success: function (res) {},fail: function (res) {console.log(res);}});},/*** 生命周期函数--监听页面隐藏*/onHide() {// this.innerAudioContext.stop();reconnectAttempts = 0;this.stopVoice();this.socketClose();reconnectTimer = null;socketTask.close();},/*** 生命周期函数--监听页面卸载*/onUnload() {// this.innerAudioContext.stop();reconnectAttempts = 0;this.stopVoice();this.socketClose();reconnectTimer = null;socketTask.close();},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh() {},/*** 页面上拉触底事件的处理函数*/onReachBottom() {},/*** 用户点击右上角分享*/onShareAppMessage() {}
})

这篇关于微信小程序实现websokect语音对话,实现后端实时返回片段音频,前端播放+心跳检测的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094845

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象