前言
最近小程序项目中需要开发录音功能,但是因为需要实现多个小程序共用一个功能,遂定使用web-view内嵌h5的方式,达到“一劳永逸”,那么真的“一劳永逸”了嘛,在这里埋个伏笔。
好的,事实上,并没有。
那么,遇到了哪些问题呢,我们来简单剖析一下。
- 用户点击某个元素,达到临界条件需要触发自动播放。
- 音频切换倍速导致ios无法触发onend事件
- 部分机型(vivo、iphone14 pro)在web-view播放的声音从听筒位置发出,即音量小,其实是因为录音的问题,录音属性配置问题echoCancellation设置为true
自动播放解决:
// 在获取到音频资源后操作dom调用load方法 audioPlayer = this.$refs.audioPlayer
audioPlayer.load()
2、3问题解决方案:最终还是将播放页写在了原生小程序页
h5录音功能实现
在录音测试过程中出现了很多问题,以下进行详细说明:
1. 在http环境即本地环境、或域名协议为http的环境下,由于浏览器安全协议的限制,需要对浏览器进行配置才能正常开始录音。
解决方法:
浏览器访问: chrome://flags/#unsafely-treat-insecure-origin-as-secure,将该选项设置为Enabled
2. 在小程序web-view中调试录音需要将代码提交到测试环境后才能进行录音,切记在https环境下测试录音。
代码如下:
import { Toast, Dialog } from 'vant'
import Recorder from 'recorder-core'
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine'
export default class Recorder {
recorderManager = null
recoderAuthStatus = false // 是否授权
duration = 600000 // 录音时长单位ms 10分钟
stopFn = null // 结束回调
changRecordStatusFn = null // 状态改变回调
recordStatus = 0 // 0未开始 1录音中 2结束 3已上传
onProcessCallback = null // 录音过程中回调
constructor(data) {
if (data && data.duration) {
this.duration = data.duration
}
if (data && data.stopFn) {
this.stopFn = data.stopFn
}
if (data && data.changRecordStatusFn) {
this.changRecordStatusFn = data.changRecordStatusFn
}
this.recorderManager = Recorder({
type: 'mp3',
sampleRate: 16000,
bitRate: 16,
audioTrackSet: {
echoCancellation: true, // 解决录音声音小的终极大招 (巨好用)
autoGainControl: true, // 增益开关 (好用)
},
onProcess: (buffers, powerLevel, duration, sampleRate) => {
if (this.onProcessCallback) this.onProcessCallback(duration)
if (duration > this.duration && this.recordStatus == 1) {
Dialog.alert({
title: '提示',
message: '已停止录音,录音时长不能超过10分钟!',
confirmButtonColor: '#2F74ED',
confirmButtonText: '确定',
})
this.stopRecorder()
}
},
})
this.getSetting().then(() => {
this.startRecorder()
})
}
// 开始录音
startRecorder() {
if (!this.recorderManager || !Recorder.IsOpen()) {
Toast.fail('未打开录音')
return
}
Toast.success('开始录音')
this.recorderManager.start()
this.changRecordStatusFn(1)
this.recordStatus = 1
}
// 结束录音
stopRecorder() {
if (!(this.recorderManager && Recorder.IsOpen())) {
Toast.fail('未打开录音')
return
}
this.recorderManager.stop(
(blob, duration) => {
this.changRecordStatusFn(2)
this.recordStatus = 2
if (duration < 1) {
this.duration = 1 //不计,未开始
Toast.fail('录音失败,录音时间太短')
return
}
const file = new File([blob], 'filename.mp3', { type: 'audio/mp3' })
file.duration = duration
this.uploadFile(file, duration)
},
(s) => {
Toast.fail('录音失败:' + s)
}
)
}
// 上传音频文件
uploadFile(file, duration) {
const loading = Toast.loading({
message: '上传中...',
forbidClick: true,
loadingType: 'spinner',
})
uploadAudio({ file, duration })
.then((res) => {
this.stopFn(res.datas.path)
this.changRecordStatusFn(3)
this.recordStatus = 3
})
.finally(() => loading.clear())
}
// 获取麦克风授权状态
getSetting() {
return new Promise((resolve, reject) => {
if (this.recoderAuthStatus) {
resolve(true)
}
this.recorderManager.open(
() => {
resolve(true)
},
(msg, isUserNotAllow) => {
this.recoderAuthStatus = false
console.log(isUserNotAllow + ':' + msg)
Toast.fail(msg)
reject(msg)
}
)
})
}
// 销毁引用数据
destory() {
if (this.recorderManager) {
this.recorderManager.close()
}
}
}
小程序录音功能实现
代码如下:
export default class Voice {
recorderManager = null;
recoderAuthStatus = 0; // 是否授权
playStatus = 0; // 音频播放状态
duration = 60000; // 录音时长单位ms 1分钟
constructor(data) {
const { duration = 60000} = data;
this.duration = duration;
this.getSetting().then(() => {
this.initRecord();
this.startRecorder();
}).catch(() => {
this.openSetting();
});
}
// 初始化音频管理器
initRecord() {
// 录音
this.recorderManager = wx.getRecorderManager();
this.recorderManager.onError(() => {
console.log('录音器加载错误');
});
this.recorderManager.onStop(res => {
const duration = res.duration; //获取录音时长
if (duration > this.duration) {
console.log('已停止录音,录音时间不能超过60秒');
} else if (duration < 1) {
this.duration = 1; //不计,未开始
console.log('录音失败,录音时间太短');
return;
}
this.duration = duration;
// this.audioValue = res.tempFilePath;(这里是录音本地文件,如果需要在其他地方播放,则需要上传转换成网络链接)
console.log(res, 'file:', res.tempFilePath);
this.uploadFile(res.tempFilePath);
});
}
// 开始录音
startRecorder() {
this.recorderManager.start({
format: 'mp3',
duration: this.duration
});
console.log('开始录音');
}
// 结束录音
stopRecorder() {
this.recorderManager.stop();
console.log('停止录音');
}
// 上传音频文件
uploadFile(path) {
wx.uploadFile({
url: "api",
method: 'POST',
filePath: path,
name: 'file',
success(res) {
const data = JSON.parse(res.data);
console.log('upload:',res, data);
},
fail(res) {
console.log('媒体文件上传失败');
return;
}
});
}
// 获取麦克风授权状态
getSetting() {
let that = this;
return new Promise((resolve, reject) => {
if (that.recoderAuthStatus) resolve();
wx.getSetting({
success(res) {
if (!res.authSetting['scope.record']) {
wx.authorize({
scope: 'scope.record',
success() {
that.recoderAuthStatus = true;
resolve();
},
fail() {
that.recoderAuthStatus = false;
reject();
}
});
} else {
that.recoderAuthStatus = true;
resolve();
}
}
});
});
}
// 打开麦克风授权
openSetting() {
let that = this;
wx.openSetting({
success(res) {
if (res.authSetting['scope.record']) {
that.recoderAuthStatus = true;
that.init();
}
}
});
}
}
小程序播放音频
initAudio() {
const innerAudioContext = wx.createInnerAudioContext({
useWebAudioImplement: true // 是否使用 WebAudio 作为底层音频驱动,默认关闭。对于短音频、播放频繁的音频建议开启此选项,开启后将获得更优的性能表现。由于开启此选项后也会带来一定的内存增长,因此对于长音频建议关闭此选项
});
//监听音频自然播放至结束的事件
innerAudioContext.volume = 1;
innerAudioContext.onPlay((res) => {
console.log('开始播放');
});
innerAudioContext.onEnded(() => {
//音频自然播放至结束的事件的回调函数
this.data.audioInfo.status = 0;
});
innerAudioContext.onError(res => {
console.log('error', res);
});
},
//播放录音
playOrPause() {
if (!this.data.audioInfo.url) {
return;
}
if (this.data.audioInfo.status != 1) {
this.data.audioInfo.status = 1;
this.data.innerAudioContext.src = this.data.audioInfo.url;
this.data.innerAudioContext.play();
} else {
//正在播放状态,则点击暂停
this.data.audioInfo.status = 2;
this.data.innerAudioContext.stop();
}
},
app.js
onShow() {
wx.setInnerAudioOption({
obeyMuteSwitch: false,
mixWithOther: false,
speakerOn: true,
success() {
console.log('set audio option success');
},
fail(e) {
console.error(e);
}
});
},