一、需求功能点描述
按住说话 松开发送 上滑取消 语音时长超过3分钟,自动结束录入并发送。
采用方案:使用Web API navigator.mediaDevices 实现该功能。
H5语音功能
二、开发过程
1、获取麦克风权限
起初是在按住说话开始录音阶段才获取麦克风权限,但是由于测试过程中出现bug:按住说话时获取权限当授权之后 没长按就能录音 因此前置获取权限(在文本输入框切换成语音输入框时获取麦克风权限)
function captureMicrophone() {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(() => {
})
.catch(() => { // 禁止权限
showDialog({
title: '暂无麦克风权限',
message: '推荐使用手机自带浏览器\n打开该页面',
confirmButtonText: '知道了',
}).then(() => {
// on close
})
})
}
2、开始录音
侦听touchstart 触摸元素事件,开始录音。
// 按住说话开始录语音
function startRecording(event) {
event.preventDefault() //阻止默认事件,避免了安卓设备长按会出现选中文字剪贴板
posStart.value = event.touches[0].pageY// 获取起点坐标(用于上滑取消发送语音判断)
// 语音输入
try {
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
touchStart.value = true
createTimer() // 计时三分钟
audioStream.value = stream
mediaRecorder.value = new MediaRecorder(stream)
mediaRecorder.value.start()
audioChunks.value = []
mediaRecorder.value.addEventListener('dataavailable', (event) => {
audioChunks.value.push(event.data)
})
})
.catch(() => { // 禁止权限
touchStart.value = false
resetTimer() //清理计时器
})
}
catch (error) {
// console.log("获取语音error",error);
}
}
对于 移动端长按会出现选中文字剪贴板的问题:
(1)、尝试了利用css 但是对于安卓没效果ios试了可以
-webkit-user-select: none; /*Safari */
-ms-user-select: none; /*IE 10+ and Edge */
-o-user-select: none;
-moz-user-select: none; /*火狐*/
-khtml-user-select: none; /*早期浏览器*/
user-select: none; /*Standard syntax*/
-webkit-touch-callout: none;
(2)、尝试将文字换成图片 结果会出现保存图片的弹窗,因此最终选择了 event.preventDefault()
3、松开结束并发送录音
// 结束录制语音
function stopAndSendRecording(event) {
if (mediaRecorder.value) {
mediaRecorder.value.addEventListener('stop', () => {
audioBlob.value = new Blob(audioChunks.value, { type: 'audio/mp3' })
posEnd.value = JSON.stringify(event) === '{}' ? posStart.value : event.changedTouches[0].pageY// 获取终点坐标
if (posStart.value - posEnd.value < 100) {
// 判断语音时长
if (min.value === 0 && sec.value <= 1) {
resetTimer()
showToast({
message: '说话时间太短',
icon: warnPng,
duration: 3000,
zIndex: 2026,
})
}
else {
sendAudio(min.value * 60 + sec.value) //发送语音
resetTimer()
}
}
// 释放麦克风权限
audioStream.value.getAudioTracks().forEach((track) => {
track.stop()
})
})
mediaRecorder.value.stop()
mediaRecorder.value = null
}
}
3.1语音发送
调用语音发送是遇到一个问题,传参数给后端时file传formData时,传值是对象而并非是二进制文件流(请求体如下),最终通过FormData的get方法获取到了file文件,解决了这个问题
落地代码如下:
// 语音发送
function sendAudio(duration) {
const formData = new FormData()
formData.append('file', audioBlob.value, 'audio.mp3')
uploadGuideFile({ file: formData.get('file'), groupKey: 'base-audio', uploadPlatform: 1, uploadSource: 1 }).then((res) => {
// console.log('上传语音', res)
const data = {
baseFileId: res.data.id,
audioTotalDuration: duration,
}
loadingVoice.value = false
emits('sendGuideEv', data)
})
}
4、三分钟自动发送语音相关
// 计时器三分钟
function createTimer() {
if (timer.value) {
clearInterval(timer.value)
}
timer.value = setInterval(() => {
sec.value += 1
if (sec.value === 60) {
min.value += 1
sec.value = 0
}
if (min.value === 3) {
// 三分钟自动发送
stopAndSendRecording({})
}
timeNum.value = `${(`0${min.value}`).slice(-2)}:${(`0${sec.value}`).slice(-2)}`
}, 1000)
}
// 清除计时器重置语音时长
function resetTimer() {
clearInterval(timer.value)
min.value = 0
sec.value = 0
timeNum.value = `${(`0${min.value}`).slice(-2)}:${(`0${sec.value}`).slice(-2)}`
}
三、播放语音
播放语音最初采用方案是利用H5原生new Audio创建audio播放器,但是测试过程中出现:在oppo默认浏览器中,语音无法播放的问题。页面中写了固定audio标签,也不能正常播放音频。此方案处理音频具有兼容性问题。因此最终选择Howler.js 库。
// 语音播放
function playVoice(item) {
if (activeAudio.value === item.id) { // 点击正在播放的语音
audio.value.stop()
audio.value = null
activeAudio.value = 0
}
else {
if (audio.value) { // 有播放点其他的音频
audio.value.stop()
audio.value = null
}
activeAudio.value = item.id
const audioUrl = `${import.meta.env.VITE_APP_FILE}${item.audioPath}`
audio.value = new Howl({
src: [audioUrl], // 提供多个格式以提高兼容性
autoplay: true, // 是否自动播放,默认为false
loop: false, // 是否循环播放,默认为false
volume: 0.5, // 音量大小,范围是0-1,默认为1
preload: true, // 是否预加载音频,默认为true
})
audio.value.play()
audio.value.on('end', () => {
window.console.log('音频播放结束')
audio.value = null
activeAudio.value = 0
})
}
}