微信小程序实现语音录制,上传,播放
发表时间:2021-1-5
发布人:葵宇科技
浏览次数:149
记录技术,分享技术,做个伟大的搬运工。
框架选用开发微信小程序,使用的是unapp。 为什么要选用这个,因为他比较成熟,之前用过mpvue,kbone都是特别的不太成熟。后来经过各种选型选定了uniapp。
遇到的问题
- 录音授权。
- 长按录音,判断手指是否划出长按的区域
- 文件上传。
- 多录制语音播放。
初始化工作
- 定义全局录音对象和audio对象,并且格式化自己想要的音频格式
const recorderManager = uni.getRecorderManager(); // 创建录音对象
const audioContext = wx.createInnerAudioContext(); // 创建audio对象
const options = {
duration: 600000, //指定录音的时长,单位 ms,最大为10分钟(600000),默认为1分钟(60000)
sampleRate: 16000, //采样率
numberOfChannels: 1, //录音通道数
encodeBitRate: 96000, //编码码率
format: 'mp3', //音频格式,有效值 aac/mp3 等
frameSize: 50 //指定帧大小,单位 KB
};
复制代码
录音授权
录音收取主要从两个方面考虑
- 申请授权用户直接同意授权
- 用户拒绝授权后,打开设置,用户收到授权。
uni.authorize({
scope: 'scope.record',
success() {
// 用户已经同意授权,可以进行录音
},
fail() {
// 此处用户授权失败,需要打开设置,去手动授权。 在某些情况下openSetting无法打开
// 可以通过fail的方式,通过弹窗用户打开设置。
uni.openSetting({
success: res => {
// 没有授权的情况下,弹窗提示
if (!res.authSetting['scope.record']) {
//未设置录音授权
uni.showModal({
title: '提示',
content: '您未授权录音,功能将无法使用',
showCancel: false
});
} else {
// 第二次才成功授权
// 用户已经同意授权,可以进行录音
}
},
fail: function() {
uni.showModal({
title: '提示',
content: '您未授权录音,功能将无法使用',
showCancel: false,
success: function(res) {
uni.openSetting();
}
});
}
});
}
});
复制代码
官方api链接
接口调整链接
长按录音 / 滑动区域 / 停止长按
uniapp中的方法
@longpress 长按(手指触摸超过350ms)
@longtap 长按
@tap 点击
@touchcancel 手指触摸被打断,如来电提醒,弹窗
@touchend 手指触摸动作结束,如松开按钮
@touchmove 手指触摸后移动
@touchstart 手指触摸动作开始
复制代码
录制操作
- 获取当前点击元素距离顶部的距离
- 长按的同时,要开始计时,已经授权的时候要开始录制语音。
// 小程序中获取当前点击元素的距离和其他的有所不同,以下是获取的方法
const query = uni.createSelectorQuery().in(this);
query
.select('.record-button')
.boundingClientRect(data => {
// data 当前元素的各个信息
})
.exec();
复制代码
- 通过event获取当前各项信息
- 滑动超出的时候要进行停止录音,并且要清空计时器。
- 滑回来的时候要继续录音,继续定时器。
- 此处需要加个中间状态,通过监听来进行继续录制录音和停止录制语音
let touches = e.touches[0];
// 超出的时候
if (touches.pageY < this.reocrdLocation.top) {
clearInterval(this.timerInfo);
recorderManager.pause();
} else {
recorderManager.resume();
}
复制代码
停止长按
- 判断录音时长太短的话,不进行上传,
- 符合条件的进行上传,并且清空定时器,停止录制
recorderManager.stop();
// 监听停止事件
recorderManager.onStop(res => {
if (this.duration < 1) {
uni.showToast({
title: '录制时间太短',
duration: 1000,
icon: 'none'
});
return;
}
// 符合条件的,推进数组。
this.voiceList.push({
size: res.fileSize, // 本地的进度
progress: -1,//-1 没有上传, -100 上传失败, 100 上传成功, 0 ~ 100上传中
path: res.tempFilePath, // 线上路径
duration: this.duration // 录音时长
});
// 核对上传
this.checkUploadVoice();
});
clearInterval(this.timerInfo);
复制代码
文件上传
- 通过设置每个录音的状态,来记录各个状态(-1 没有上传, -100 上传失败, 100 上传成功, 0 ~ 100上传中)。
- 上传失败可以实现重新上传。所以上传文件前,要进行核对文件(核对各个状态)。
let obj;
for (let i = 0; i < this.voiceList.length; i++) {
let item = this.voiceList[i];
if (item.progress == -1 || item.progress == -100) {
obj = await this.uploadFiles(item, i); // 等待文件上传完成后,获取信息
// 修改语音数组 通过set
this.$set(this.voiceList, i, {
name: item.name,
size: item.size,
progress: obj.progress,
path: obj.path, //
duration: item.duration,
nowPlay: false,
text: '',
translateStatus: false, // 此处记录是否转化为文字
});
this.duration = 0; // 文件上传后,时间记录要清0
}
}
// 上传文件
uploadFiles(item, i) {
return new Promise((resolve, reject) => {
const uploadTask = uni.uploadFile({
url: url, // 上传图片的地址
filePath: item.path, // 录音后拿到的地址
name: 'file',
header: {
'Content-Type': 'multipart/form-data',
accept: 'application/json'
},
success: upRes => {
console.log(upRes);
let dataInfo = JSON.parse(upRes.data);
let { code, data } = dataInfo;
// 成功后要返回成功的信息
resolve({
path: data.fileUrl,
progress: 100
});
},
fail: function(err) {
// 上传失败的时候要记录状态
resolve({
path: '',
progress: -100
});
}
});
uploadTask.onProgressUpdate(res => {
// 此处获取上传的进度,并且实时展示
this.$set(this.voiceList, {
name: item.name,
size: item.size,
progress: res.progress,
path: item.path,
duration: item.duration
});
// console.log('上传进度', res.progress);
// console.log('已经上传的数据长度', res.totalBytesSent);
// console.log(
// '预期需要上传的数据总长度',
// res.totalBytesExpectedToSend
// );
});
});
},
复制代码
多条录音播放
- 播放的时候只保持一条正在播放(最主要的问题)
- 停止其他正在播放的语音
- 播放当前点击的语音
// 播放语音
async playVoice(item, index) {
uni.showLoading({
title: '录音播放加载中'
});
// 同时点击当前的语音两次,需要先把上一个停止到,再进行播放新的
if (item.nowPlay && this.nowPlayItem.nowPlay) {
this.stopVoice(item);
return;
}
// 两条播放的语音不一样, 停止上一条,改变播放的状态,然后播放当前的
if (!item.nowPlay && this.nowPlayItem.nowPlay) {
let status = await this.stopVoice(item);
let obj = Object.assign({}, this.nowPlayItem.item, {
nowPlay: false
});
/**
// 记录全局播放的音频(停止上次点击的音频,播放新的点击的音频,两次点击的音频不一样)
nowPlayItem: {
index: -1, // index 为当前播放的索引值
nowPlay: false, // 当前是否有正在播放的语音
item: null
},
*/
this.$set(this.voiceList, this.nowPlayItem.index, obj);
}
audioContext.src = http://www.wxapp-union.com/item.path; // 播放的录音地址
audioContext.play();
this.nowPlayItem.index = index;
this.nowPlayItem.item = item;
// 开始播放监听
audioContext.onPlay(res => {
uni.hideLoading();
console.log('play');
item.nowPlay = true;
this.nowPlayItem.nowPlay = true;
});
// 停止播放监听(当前的播放是否停止)
audioContext.onPause(res => {
console.log('pause');
item.nowPlay = false;
this.nowPlayItem.nowPlay = false;
// 监听音频,要和取消监听同时存在,
audioContext.offPlay();
audioContext.offPause();
audioContext.offStop();
audioContext.offEnded();
});
// 监听音频自然播放至结束的事件,真机调试会存在问题,微信开发者工具这块不会有问题
audioContext.onEnded(res => {
console.log('ended');
item.nowPlay = false;
this.nowPlayItem.nowPlay = false;
// 监听音频,要和取消监听同时存在,
audioContext.offPlay();
audioContext.offPause();
audioContext.offStop();
audioContext.offEnded();
});
audioContext.onError(res => {
console.log('error');
// 播放音频失败的回调
console.log(res);
});
},
// 只有当前的播放停止时,才进行下一个播放开始
stopVoice(item) {
return new Promise((resolve, reject) => {
audioContext.stop();
audioContext.onStop(res => {
// 播放音频失败的回调
this.nowPlayItem.nowPlay = false;
if (item) {
item.nowPlay = false;
}
audioContext.offPlay();
audioContext.offPause();
audioContext.offStop();
audioContext.offEnded();
resolve(true);
});
});
},
复制代码
此处容易出错的地方
- 音频监听事件要和取消事件同时存在
- 改变语音播放状态的时候,必须要等到上一条停止,才可以进行后续工作
总结
- 模拟器和真机的差别还是挺大的,真机上各个事件比模拟器会慢一点。
- 学海无涯,持之以恒。