录音
需求:长按进行录音,松开即录音完成,当录音时长达到2分钟时,自动结束录音
小程序录音API:wx.getRecorderManager() ,实例后主要用到2个事件监听:onStart、onStop
注意:
- 录音功能,需要在微信公众平台的 用户隐私保护指引 上勾选 麦克风 才能使用
- !!!微信开发者工具上无法录音,只能通过 真机进行 ,迟钝的我刚开始竟然get不到,浪费了一些时间
- 录音会遇到 中断 情形 onInterruptionBegin 、onInterruptionEnd ,本篇暂时不针对此情况做处理。(监听录音因为受到系统占用而被中断开始事件。以下场景会触发此事件:微信语音聊天、微信视频聊天。此事件触发后,录音会被暂停。pause 事件在此事件后触发)
step1:添加长按控制按钮
此处使用catch防止事件冒泡,按下事件touchstart,松开事件touchend
<view
class="tap-button"
catchtouchstart="recordTouchStart"
catchtouchend="recordTouchEnd"
></view>
step2:录音逻辑分析与实现
- 获取麦克风权限,用户已授权权限则直接下一步,未授权需授权才能继续以下流程
- 创建录音实例,如果当前页面已创建过实例,无需重复创建
- 按下按钮,调用录音实例 start 方法,start方法中按需设置自定义参数(也可以不设置,默认),设置onStart、onStop监听回调
- 通过 setInterval 记录录音已录制时间(小程序未看到有相关实时返回录制时间的监听事件,所以录制时间和实际录音结果时长会有些偏差)
- 监听 onStart 触发,设置状态
- 按下弹起时,结束录音 stop 方法
- 监听 onStop 触发,获取录音信息,并上传录音tempUrl到oss,清除setInterval,重置用到的状态
- 组件卸载时需要清除定时器,detached 中清除
注意:
- 按下弹起过快时,会出现弹起之后,才监听到录音 onStart 的回调方法,此处加一个trigger变量,在onStart 回调中判断此时是否已经是弹起状态,如果是的话,应该结束录音,出现此情况大概率录制不超过1s,提示时间过短,需重新录制
问题:
- setInterval记录的录制时间和实际录音时长结果有偏差,无法准确动态展示录制时间
// 长按开始
recordTouchStart () {
this.recordStart()
},
// 长按结束
recordTouchEnd () {
this.recordEnd()
},
// 开始逻辑
recordStart () {
// 长按开始
this.recorderTapping = true
// 判断用户是否同意授权麦克风,如已经同意,调用创建录音实例
if (!this.canRecord) {
this.setRecordPermission()
return
} else if (!this.audioRecorder) {
this.createAudioRecorder()
}
// 录制开始
this.recorderTapping && this.audioRecorder.start({
duration: this.maxRecordTime, // maxRecordTime:120000,最大可录音时长 2分钟 到时会自动停止
format: 'mp3' // 录音格式
})
},
// 结束逻辑
recordEnd () {
this.recorderTapping = false
// 判断是否录音中,onStart 被调用,即为录音中,录音中时,去手动停止录音
if (this.isRecording) {
this.audioRecorder.stop()
}
},
// 监听回调 - 录音开始
onRecorderStart () {
// 正在录音状态
this.isRecording = true
// 存在一种情况,按下和弹起太快了,tapend触发后,start才监听到,此时应该立刻结束掉
if (!this.recorderTapping) {
this.recordEnd()
return
}
// 此处开启定时器,实时展示已录音时长(会和实际录音结果时长有偏差),此处只是为了动态展示,不作为最后结果获取出处
this.startRecordTimer()
},
// 监听回调 - 录音结束:手动结束 和 设置最大录音时间时的超长录音自动结束
onRecorderStop (res) {
this.isRecording = false
this.clearRecordTimer()
// 结束后会返回录制时长,针对时长做处理(毫秒)
if (!res || !res.duration && res.duration < 1000) {
wx.showToast({
title: '录音时间不能小于1秒',
icon: 'none',
duration: 2000,
});
this.recordTime = 0
return
}
const { tempFilePath, duration } = res
this.recordTime = Math.round(duration / 1000).toFixed(1))
this.recorderTimeExceed()
// 非手动弹起,自动结束的情况
if (this.recorderTapping) {
this.recordEnd()
wx.showToast({
title: '已达录音时长上限',
icon: 'none',
duration: 2000,
});
}
// 上传oss
this.onUploadRecord(tempFilePath)
},
// 录制时长超过上限时,需提示,且录音会自动终止在onStop中监听到,无需手动调用stop
// 录音长按时,但录音时长超出设置的最大限度
recorderTimeExceed () {
if (this.maxRecordTime && this.recordTime >= this.maxRecordTime) {
this.recordTime = this.maxRecordTime
}
},
// 上传录音
onUploadRecord (url) {
wx.uploadFile({
filePath: url,
name: 'file',
url: this.oss.endpoint,
formData: {
name: url,
success_action_status: '200',
key: 'key',
policy: this.oss.policy,
OSSAccessKeyId: this.oss.accessKeyId,
signature: this.oss.signature
},
success: (res) => {},
fail: (e) =>{}
})
},
// 设置定时器,监听录音持续时长
startRecordTimer () {
this.clearRecordTimer()
this.recordTime = 0
this.recordTimer = setInterval(() => {
this.recordTime += 1
}, 1000);
},
// 清除定时器
clearRecordTimer () {
this.recordTimer && clearInterval(this.recordTimer)
this.recordTimer = null
},
// 授权麦克风权限, 需要同意麦克风权限,未同意不能点
setRecordPermission () {
wx.getSetting({
success: (res) => {
const auth = res.authSetting['scope.record']
if (auth === true) { // 用户已经同意授权
this.canRecord = true
} else { // 首次发起授权
wx.authorize({
scope: 'scope.record',
success: () => {
this.canRecord = true
},
fail) => {
this.canRecord = false
}
})
}
},
fail: () {
this.canRecord = false
}
})
},
// 创建录音实例,并设置监听方法 onStart、onStop
async createAudioRecorder () {
if (this.audioRecorder) return
this.audioRecorder = wx.getRecorderManager()
this.audioRecorder.onStart(() => {
this.onRecorderStart()
])
this.audioRecorder.onStop((res) => {
this.onRecorderStop(res)
})
},
音频播放
坑点:
- 未播放音频的情况下无法获取到音频时长
- 二次重新播放有问题, 最简单的办法是销毁当前音频,重新创建,但是销毁后重建在开发工具上失效,真机好使
- 预期想获取音频时长后,将音频通过seek方式定位到开头,但是偶现失效,所以采取销毁形式
音频播放流程分析:
- 创建音频播放实例
- 设置 音频 播放链接
- 设置 音频 状态回调
- 获取音频时长
- 操作音频 播放、暂停、重置
- onCanPlay回调时,触发音频自动播放(未播放音频的情况下无法获取到音频时长)
- detached 销毁音频组件
// 创建实例、绑定回调监听
createAudioPlayer () {
// 创建实例
this.myAudio = wx.createInnerAudioContext()
// 设置audio的资源地址
this.myAudio.src = this.audioSrc
this.myAudio.onError(() => {
this.status = 'fail'
})
this.myAudio.onPlay(() => {
this.status = 'playing'
})
this.myAudio.onPause(() => {
this.status = 'pause'
})
this.myAudio.onEnded(() => {
// 销毁播放器,重新创建,因为二次播放下有问题
this.handleDestroyPlayer()
})
this.myAudio.onCanplay(() => {
// 解决获取不到时长的问题,默认先播放,无声音,后续在onTimeUpdate回调中去获取时长时进行音频重置
if (this.audioSrc && !this.recordTime) {
// 只是为了获取时间的播放标志
this.getDurationTrigger = true
this.myAudio.volume = 0
this.myAudio.play()
} else {
this.myAudio.volume = 1
}
})
this.myAudio.onTimeUpdate(() => {
if (this.myAudio) {
if (this.getDurationTrigger) {
this.recordTime = this.myAudio.duration // 毫秒
// 解决可能获取时间后,开始播放时未从录音开头放的问题
this.handleDestroyPlayer()
this.getDurationTrigger = false
} else {
// 可以在播放中回调改变图标,代表播放中,并实时监听当前音频播放的进度
console.log(this.myAudio.currentTime)
}
}
})
},
handleAudioPlay () {
if (!this.playerSrc || this.status === 'fail') return
if (!this.myAudio) {
this.initAudioPlayer()
}
if (!this.recordTime) return
if (this.status === 'playing') {
this.myAudio.pause()
} else {
this.myAudio.play()
}
},
// 二次重新播放有问题, 最简单的办法销毁,再次点击音频的时候重新创建,但是不用二次获取音频时长了
handleDestroyPlayer ) {
if (!this.myAudio) return
if (this.status === 'playing') {
this.myAudio.pause()
}
this.status = ''
// 用了destroy方案,真机好使,开发工具不行
this.myAudio.destroy()
this.myAudio = null
},
注意:
如果一个页面中存在多个需要播放的音频,如果手动不进行控制,会有多个音频一起播放的情况,小程序不会帮你去停掉播放中的音频,再播放新的音频,需要自己控制。
解决办法:
- 正常通过ref即可,去调用音频播放组件的音频重置方法。
- 遇到了未准确获取到ref的数据(具体原因不详,小程序使用mpx形式),采取了其他办法:当播放音频时,将当前音频的 this 传递给上一级,在父级页面存储当前播放中的音频的 this ,当另一个音频点击播放,要替换当前的音频时,取出上次记录的播放中的音频实例,通过判断是否在播放中,调用组件中的重置方法,重置掉即可。