使用的是: RecorderManager 全局唯一的录音管理器 和 InnerAudioContext 实例
以下为实现代码:
WXML
<view class="bg-white fs14 pb-32">
<view class="label gray-2">录音</view>
<view class="w100 audio">
<view class="bg-1 w100 radius-10 center pd-40 flex-column">
<image src="../../../assets/imgs/playAudio.png" mode="" class="audioImg" data-type="1" bindtap="playRecord" wx:if="{{recorderType == '0'}}" />
<block wx:if="{{recorderType == '1'}}">
<view class="auditBox w100 mb-20">
<view class="auditItem" wx:for="{{31}}" wx:key="index"></view>
</view>
<view class="p">{{ recorderCount }}</view>
<image src="../../../assets/imgs/pauseAudio.png" mode="" class="audioImg" data-type="2" bindtap="playRecord" />
</block>
<block wx:if="{{recorderType == '2'}}">
<view class="w100 flex">
<van-icon name="play-circle" color="{{colorStyle}}" size="28px" data-type="3" bindtap="playRecord" wx:if="{{audioType == '4'}}" />
<van-icon name="stop-circle" color="{{colorStyle}}" size="28px" data-type="4" bindtap="playRecord" wx:if="{{audioType == '3'}}" />
<view class="flex-1 plr-20">
<progress percent="{{audioCurrentLength}}" stroke-width="3" activeColor="{{colorStyle}}" />
</view>
<view class="mr-20">{{audioLength}}</view>
<van-icon name="delete-o" data-type="0" bindtap="playRecord" color="#ff0000" size="24px" wx:if="{{isAdd}}" />
</view>
<view class="center w100 mt-20">
<view class="bg-blue white radius-10 ptb-16 plr-60 size-30" data-type="3" bindtap="playRecord" wx:if="{{audioType == '4'}}">播放</view>
<view class="bg-blue white radius-10 ptb-16 plr-60 size-30" data-type="4" bindtap="playRecord" wx:if="{{audioType == '3'}}">暂停</view>
</view>
</block>
</view>
</view>
</view>
JS(其中 msToTime
秒数转换成时间方法, uploadWx
简单封装微信请求, 方法写在最后
data: {
// 全局唯一的录音管理器
recorderManager: null,
recorderType: '0', // 0-开始录音 1-停止录音
// recorderCount: 60, // 剩余秒数
recorderCount: '00:00', // 已经录制秒数
recCount: 0, // 已经录制秒数
timer: null,
soundRecordPath: '', // 录音资源
// 播放器
innerAudioContext: null,
audioCurrentLength: 0,
audioLength: "00:00",
duration: 0, //音频时长
audioType: "4", //3-开始播放 4-暂停播放
},
//定时器方法
audioTimer() {
let that = this
let { recCount } = this.data
that.data.timer = setInterval(function () {
if (++recCount == 600) {
clearInterval(that.data.timer);
that.stop();
that.setData({ recCount: 0, recorderCount: "00:00", recorderType: "2" })
} else {
console.log(recCount, msToTime(recCount * 1000));
that.setData({ recorderCount: msToTime(recCount * 1000) })
}
}, 1000)
// that.data.timer = setInterval(function () {
// console.log(recorderCount);
// if (--recorderCount == 0) {
// clearInterval(that.data.timer);
// that.stop();
// that.setData({ recorderCount: 600, recorderType: "2" })
// } else {
// that.setData({ recorderCount: recorderCount })
// }
// }, 1000)
},
// 开始录音
playRecord(e) {
let { type } = e.currentTarget.dataset
if (type == '3' || type == '4') {
this.setData({ audioType: type })
} else if (type == 2) {
// 停止录音操作
this.stop();
} else {
this.setData({ recorderType: type })
}
if (type == '1') {
// 开始录音操作
this.start();
} else if (type == '0') {
//重新录制操作
this.setData({ soundRecordPath: "", audioCurrentLength: 0, audioType: '4', duration: 0 })
this.audioPause() // 暂停
this.audioDestroy() // 释放音频资源
this.stop();
} else if (type == '3') {
// 播放音频操作
this.audioPlay();
} else if (type == '4') {
// 暂停音频操作
this.audioPause();
}
},
// 录音start
start() {
const options = {
duration: 600000,
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 192000,
format: 'mp3',
frameSize: 50
}
this.data.recorderManager.start(options)
this.data.recorderManager.onStart(() => {
this.audioTimer();
wx.showToast({ title: '录音开始', icon: 'none' });
console.log('录音开始')
})
},
// 录音stop
stop() {
let that = this
this.data.recorderManager.stop();
this.data.recorderManager.onStop((res) => {
clearInterval(that.data.timer);
console.log('recorder stop', res)
const { tempFilePath, duration } = res
console.log(tempFilePath, duration, 'duration');
that.setData({ audioLength: msToTime(duration), duration: duration })
wx.showLoading({
title: '上传中',
})
uploadWx(tempFilePath).then(response => {
if (response.statusCode == 200 && JSON.parse(response.data).code == 200) { //相当于success回调
let resp = JSON.parse(response.data)
if (resp.code == 200) {
console.log(resp, 'resp');
that.setData({ soundRecordPath: resp.url, recorderType: '2', recCount: 0, recorderCount: "00:00", duration: duration });
that.playRecorder(resp.url)
wx.showToast({ title: '上传成功' });
} else {
that.uploadError();
}
wx.hideLoading();
} else {
wx.hideLoading();
that.uploadError();
}
}).catch(() => {
wx.hideLoading();
that.uploadError();
})
})
},
uploadError(){
this.setData({ soundRecordPath: "", recorderType: '0', duration: 0, recCount: 0, recorderCount: "00:00", duration: duration });
wx.showToast({
title: "上传失败,请重新录制",
icon: 'none',
duration: 2000
})
},
// 录音器
getRecorder() {
const recorderManager = wx.getRecorderManager()
recorderManager.onPause(() => {
console.log('recorder pause')
})
recorderManager.onFrameRecorded((res) => {
const { frameBuffer } = res
console.log('frameBuffer.byteLength', frameBuffer.byteLength)
})
this.setData({ recorderManager: recorderManager });
},
// 播放器
playRecorder(url) {
const innerAudioContext = wx.createInnerAudioContext({
useWebAudioImplement: false // 是否使用 WebAudio 作为底层音频驱动,默认关闭。对于短音频、播放频繁的音频建议开启此选项,开启后将获得更优的性能表现。由于开启此选项后也会带来一定的内存增长,因此对于长音频建议关闭此选项
})
let that = this
this.setData({ innerAudioContext: innerAudioContext })
this.data.innerAudioContext.src = url
this.data.innerAudioContext.onTimeUpdate(() => {
let prop = (this.data.innerAudioContext.currentTime / (that.data.duration / 1000)) * 100
console.log(prop, this.data.innerAudioContext.currentTime, that.data.duration);
that.setData({ audioCurrentLength: prop })
})
this.data.innerAudioContext.onEnded(() => {
console.log('自然结束了');
that.setData({ audioCurrentLength: 0, audioType: '4' })
})
},
// 播放play
audioPlay() {
this.data.innerAudioContext.play() // 播放
},
// 暂停pause
audioPause() {
this.data.innerAudioContext.pause() // 暂停
},
// 播放stop
audioDestroy() {
this.data.innerAudioContext.destroy() // 释放
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getRecorder();
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
if (this.data.innerAudioContext) {
this.audioPause() // 暂停
this.audioDestroy() // 释放音频资源
}
},
WXSS
/* -------------语音音阶------------- */
.auditBox {
height: 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.auditBox .auditItem {
display: block;
background: #333333;
width: 1px;
height: 10%;
margin-right: 2.5px;
float: left;
}
.auditBox .auditItem:last-child {
margin-right: 0px;
}
.auditBox .auditItem:nth-child(1) {
animation: load 2.5s 3.0s infinite linear;
}
.auditBox .auditItem:nth-child(2) {
animation: load 2.5s 2.8s infinite linear;
}
.auditBox .auditItem:nth-child(3) {
animation: load 2.5s 2.6s infinite linear;
}
.auditBox .auditItem:nth-child(4) {
animation: load 2.5s 2.4s infinite linear;
}
.auditBox .auditItem:nth-child(5) {
animation: load 2.5s 2.2s infinite linear;
}
.auditBox .auditItem:nth-child(6) {
animation: load 2.5s 2.0s infinite linear;
}
.auditBox .auditItem:nth-child(7) {
animation: load 2.5s 1.8s infinite linear;
}
.auditBox .auditItem:nth-child(8) {
animation: load 2.5s 1.6s infinite linear;
}
.auditBox .auditItem:nth-child(9) {
animation: load 2.5s 1.4s infinite linear;
}
.auditBox .auditItem:nth-child(10) {
animation: load 2.5s 1.2s infinite linear;
}
.auditBox .auditItem:nth-child(11) {
animation: load 2.5s 1s infinite linear;
}
.auditBox .auditItem:nth-child(12) {
animation: load 2.5s 0.8s infinite linear;
}
.auditBox .auditItem:nth-child(13) {
animation: load 2.5s 0.6s infinite linear;
}
.auditBox .auditItem:nth-child(14) {
animation: load 2.5s 0.4s infinite linear;
}
.auditBox .auditItem:nth-child(15) {
animation: load 2.5s 0.2s infinite linear;
}
.auditBox .auditItem:nth-child(16) {
animation: load 2.5s 0s infinite linear;
}
.auditBox .auditItem:nth-child(17) {
animation: load 2.5s 0.2s infinite linear;
}
.auditBox .auditItem:nth-child(18) {
animation: load 2.5s 0.4s infinite linear;
}
.auditBox .auditItem:nth-child(19) {
animation: load 2.5s 0.6s infinite linear;
}
.auditBox .auditItem:nth-child(20) {
animation: load 2.5s 0.8s infinite linear;
}
.auditBox .auditItem:nth-child(21) {
animation: load 2.5s 1s infinite linear;
}
.auditBox .auditItem:nth-child(22) {
animation: load 2.5s 1.2s infinite linear;
}
.auditBox .auditItem:nth-child(23) {
animation: load 2.5s 1.4s infinite linear;
}
.auditBox .auditItem:nth-child(24) {
animation: load 2.5s 1.6s infinite linear;
}
.auditBox .auditItem:nth-child(25) {
animation: load 2.5s 1.8s infinite linear;
}
.auditBox .auditItem:nth-child(26) {
animation: load 2.5s 2.0s infinite linear;
}
.auditBox .auditItem:nth-child(27) {
animation: load 2.5s 2.2s infinite linear;
}
.auditBox .auditItem:nth-child(28) {
animation: load 2.5s 2.4s infinite linear;
}
.auditBox .auditItem:nth-child(29) {
animation: load 2.5s 2.6s infinite linear;
}
.auditBox .auditItem:nth-child(30) {
animation: load 2.5s 2.8s infinite linear;
}
.auditBox .auditItem:nth-child(31) {
animation: load 2.5s 3.0s infinite linear;
}
@keyframes load {
0% {
height: 10%;
}
25% {
height: 100%;
}
50% {
height: 20%;
}
75% {
height: 100%;
}
100% {
height: 10%;
}
}
方法
// msToTime
export const msToTime = (duration) => {
var milliseconds = parseInt((duration % 1000) / 100)
, seconds = parseInt((duration / 1000) % 60)
, minutes = parseInt((duration / (1000 * 60)) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return minutes + ":" + seconds;
}
// uploadWx
export const uploadWx = (tempFilePath) => {
return wxp.uploadFile({
url: 'yourApi',
filePath: tempFilePath,
name: 'file',
formData: {
file: tempFilePath,
},
header: {
'Content-Type': 'multipart/form-data',
// token: wx.getStorageSync('token'), // 看后台需要哪种 header
Authorization: "Bearer " + wx.getStorageSync('token'),
}
})
}