微信小程序实现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

相关文章

SpringBoot实现微信小程序支付功能

《SpringBoot实现微信小程序支付功能》小程序支付功能已成为众多应用的核心需求之一,本文主要介绍了SpringBoot实现微信小程序支付功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录一、引言二、准备工作(一)微信支付商户平台配置(二)Spring Boot项目搭建(三)配置文件

基于Python实现高效PPT转图片工具

《基于Python实现高效PPT转图片工具》在日常工作中,PPT是我们常用的演示工具,但有时候我们需要将PPT的内容提取为图片格式以便于展示或保存,所以本文将用Python实现PPT转PNG工具,希望... 目录1. 概述2. 功能使用2.1 安装依赖2.2 使用步骤2.3 代码实现2.4 GUI界面3.效

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in