学习uni-app开发,实现了一个微信聊天的demo,简单记录下其中的语音发送功能。这里只是介绍从发送到显示的过程,暂不涉及websocket做聊天对话。若有错误和不足之处留言指正,谢谢!
功能就不细说了,最终效果的UI界面如下,用户昵称头像皆为测试数据。
长按按住说话按钮弹出遮罩层按住说话,直接松开即可发送语音。
若向左上方滑动松开后取消发送当前语音。
首先给按钮是绑定事件,需要给按钮绑定三个事件
1.touchstart事件会在触摸按钮时触发,可以获取手指的初始坐标;
2.touchmove事件在触摸后移动手指时触发,可以用来计算手手指的滑动距离;
3.touchend事件在松开手指时候触发。
页面遮罩层
松开 取消
文
松开 发送
......
随后定义事件回调, 三个事件中可以在事件对象event中touches数组获取当前屏幕上所有触摸点的列表,touches数组中保存着多个手指触摸点的信息,触摸点信息中的pageX,pageY属性获取的是触摸目标在页面中的x和y坐标。
录制语音需要获取uni-app提供的全局录音管理器uni.getRecorderManager();
官方文档:录音管理 - uni-app官网 (dcloud.io)
开始录音调用该对象的recorderManager.start()方法,结束录音调用recorderManager.stop(),并在结束的回调recorderManager.onStop中获取音频的文件地址。
开始录音时需要记录保存初始的触摸坐标pageX和pageY到当前组件实例,此外由于语音需要显示时长,还需要开启计时器setInterval计算录音时长,长度保存在组件实例的length属性中,且录音的时长上限为59s。
const recorderManager = uni.getRecorderManager();
// 开始录制语音
handleTouchStart(e){
this.mask = true;
recorderManager.start();
this.length = 1;
this.startX = e.touches[0].pageX;
this.startY = e.touches[0].pageY;
this.timer = setInterval(() => {
this.length += 1;
if(this.length >= 60) {
clearInterval(this.timer);
this.handleTouchEnd()
}
},1000);
},
此外还需实现了一个细节,遮罩层中的语音条的长度是随录制时长递增的,如下图:
使用计算属性实现,录音时长和长度关系如下:
computed:{
// 计算语音条宽度
getVoiceBarWidth(){
return (230 + this.length * 4) + 'rpx';
}
},
开始录制语音后开启遮罩层,此时若用户移动手指会触发touchmove事件,在该事件的事件对象中获取当前用户手指在页面的实时坐标pageX和pageY,判断用户是否存在向左上方方向移动手指至取消发送图标行为,这里做了一些测试,取this.startX - e.touches[0].pageX > 14 && this.startY - e.touches[0].pageY > 50为判断条件,满足条件即为取消发送。
// 语音录制时滑动事件
handleTouchMove(e){
if(this.startX - e.touches[0].pageX > 14 && this.startY - e.touches[0].pageY > 50){
this.needCancel = true;
} else {
this.needCancel = false;
}
},
用户松开手指执行录制结束的回调,其中需要根据needCancel 标志判断该音频是否需要发送;在onStop回调中整理音频信息,调用提交音频的方法。
// 语音录制结束
handleTouchEnd(){
this.mask = false;
clearInterval(this.timer);
recorderManager.stop();
recorderManager.onStop((res) => {
const message = {
voice:res.tempFilePath,
length:this.length
};
if(!this.needCancel){
this.inputSubmit(message,2);
}
this.needCancel = false
});
}
这里将聊天界面底部菜单栏单独封装成一个组件bottomBar,所以在音频录制完毕后将信息提交父组件chatroom遍历展示。
inputSubmit(msg,types){
if(msg.types === 0 && this.inputText == '') return;
this.$emit('sub',msg,types);
if(this.inputText){
this.inputText = '';
}
},
语音条的页面结构如下所示
{{item.message.length}}
"
语音条长度和时长之间存在一个转换关系,10秒内的语音和大于10秒的语音长度增长比例是不一致的,这里规定在10秒的语音长度正好为可变长度的一半,随后的50秒缓慢增长到可变长度的最大值Lmax,具体转换关系如下:
// 处理语音长度
handleVoiceWidth(lenght){
lenght = lenght - 1;
let Lmin = 138;
let Lmax = 366
let barCanChangeLen = Lmax - Lmin;
// 11秒以内的语音
if (lenght < 11) {
// VoicePlayTimes 为10秒时,正好为可变长度的一半
return (Lmin + lenght * 0.05 * barCanChangeLen) + 'rpx';
} else {
// 12-60秒的语音
return (Lmin + 0.5 * barCanChangeLen + (lenght - 10) * 0.01 * barCanChangeLen) + 'rpx';
}
},
播放语音需要创建并返回内部 audio 上下文 innerAudioContext
对象;
官方文档:音频组件控制 - uni-app官网 (dcloud.io)
需要指定src属性为播放音频的链接,调用play()播放,调用stop()停止,播放自然结束触发触发方法onEnded,onStop修改播放标志。
const innerAudioContext = uni.createInnerAudioContext();
// 播放语音
handleVoicePlay(item){
item.isFirstPlay = false;
innerAudioContext.src = item.message.voice;
this.isPlay = !this.isPlay;
this.isPlay ? innerAudioContext.play() : innerAudioContext.stop();
innerAudioContext.onEnded(() => {
this.isPlay = false;
})
innerAudioContext.onStop(() => {
this.isPlay = false;
})
},