开发一个完整的录音功能,首先需要了解不同平台的权限机制。Android和iOS对于录音权限的处理方式截然不同,这也是很多新手开发者容易踩坑的地方。我在实际项目中遇到过不少因为权限配置不当导致功能无法正常使用的情况,下面就来详细说说如何正确配置。
对于Android平台,我们需要在manifest.json文件中明确声明录音权限。这个文件相当于整个应用的"身份证",所有需要用到的系统权限都必须在这里提前声明。具体配置如下:
json复制{
"permission": [
{
"android.permission.RECORD_AUDIO",
"uses-permission": "用于录音功能"
}
]
}
iOS平台的配置则更为复杂一些,不仅需要在manifest.json中声明,还需要在Xcode工程中进行额外设置。这是因为iOS对用户隐私的保护更加严格。在manifest.json中的配置是这样的:
json复制{
"ios": {
"privacyDescription": {
"NSMicrophoneUsageDescription": "需要使用麦克风进行录音"
}
}
}
这里有个小技巧:iOS的权限描述文字(NSMicrophoneUsageDescription)会直接显示在系统弹窗中,所以最好写得清晰易懂,让用户明白为什么需要这个权限。我见过很多应用因为描述不清导致用户拒绝授权,这点要特别注意。
配置完静态权限后,接下来就是动态申请了。这里有个重要原则:永远不要假设用户已经授权。我在实际开发中遇到过不少因为假设用户已经授权而导致应用崩溃的情况。
对于Android平台,我们可以封装一个统一的权限请求方法:
javascript复制async function requestRecordPermission() {
try {
const status = await uni.authorize({
scope: 'scope.record'
});
if (status.authSetting['scope.record']) {
return true;
} else {
await uni.showModal({
title: '提示',
content: '需要录音权限才能使用该功能',
showCancel: false
});
return false;
}
} catch (err) {
console.error('权限请求失败:', err);
return false;
}
}
iOS平台的处理稍微复杂一些,因为涉及到两次权限检查:
javascript复制function checkIOSPermission() {
return new Promise((resolve) => {
const audioSession = plus.ios.import('AVAudioSession');
const session = audioSession.sharedInstance();
const permission = session.recordPermission();
if (permission === 0) { // 未决定
session.requestRecordPermission((granted) => {
resolve(granted);
});
} else if (permission === 1) { // 已授权
resolve(true);
} else { // 已拒绝
resolve(false);
}
});
}
这里有个经验之谈:iOS的权限弹窗只会自动弹出一次,如果用户拒绝了,后续再调用requestRecordPermission也不会再弹出系统弹窗。这时候就需要引导用户去系统设置中手动开启权限。我通常会这样处理:
javascript复制if (!await checkIOSPermission()) {
const res = await uni.showModal({
title: '提示',
content: '录音权限被拒绝,是否去设置开启?',
confirmText: '去设置',
cancelText: '取消'
});
if (res.confirm) {
plus.runtime.openURL(UIApplicationOpenSettingsURLString);
}
}
权限搞定后,就可以开始实现录音功能了。uniapp提供了uni.getRecorderManager()接口,用起来相当方便。不过在实际使用中,我发现有几个细节需要特别注意。
首先初始化录音管理器:
javascript复制const recorderManager = uni.getRecorderManager();
recorderManager.onStart(() => {
console.log('录音开始');
this.recording = true;
});
recorderManager.onStop((res) => {
console.log('录音结束', res);
this.recording = false;
this.audioPath = res.tempFilePath;
});
recorderManager.onError((err) => {
console.error('录音出错:', err);
this.recording = false;
});
开始录音时,可以配置一些参数:
javascript复制function startRecording() {
recorderManager.start({
format: 'mp3', // 音频格式
sampleRate: 44100, // 采样率
numberOfChannels: 1, // 声道数
encodeBitRate: 96000, // 编码码率
duration: 60000 // 最长录音时长(ms)
});
}
这里有几个参数调优的经验分享:
停止录音就很简单了:
javascript复制function stopRecording() {
if (this.recording) {
recorderManager.stop();
}
}
录音完成后,接下来就是播放功能了。uniapp提供了createInnerAudioContext来实现音频播放,但实际使用中有不少坑需要注意。
首先初始化音频上下文:
javascript复制const audioContext = uni.createInnerAudioContext();
audioContext.onPlay(() => {
console.log('开始播放');
this.playing = true;
});
audioContext.onEnded(() => {
console.log('播放结束');
this.playing = false;
});
audioContext.onError((err) => {
console.error('播放出错:', err);
this.playing = false;
});
播放录音文件:
javascript复制function playRecording() {
if (!this.audioPath) return;
audioContext.src = this.audioPath;
audioContext.play();
}
在实际项目中,我还会添加一些额外功能来提升用户体验:
比如实现播放进度显示可以这样:
javascript复制audioContext.onTimeUpdate(() => {
this.currentTime = audioContext.currentTime;
this.duration = audioContext.duration;
});
// 进度条拖动
function onSliderChange(e) {
audioContext.seek(e.detail.value * audioContext.duration);
}
对于录音文件的管理,我建议使用uni.saveFile将临时文件保存为永久文件,否则在应用关闭后临时文件可能会被清理:
javascript复制function saveRecording() {
uni.saveFile({
tempFilePath: this.audioPath,
success: (res) => {
this.savedFilePath = res.savedFilePath;
uni.showToast({ title: '保存成功' });
}
});
}
在实际开发中,我遇到过不少录音功能的坑,这里分享几个典型问题的解决方案。
问题1:Android设备录音音量太小
这是因为没有正确配置音频源。可以在start参数中添加audioSource配置:
javascript复制recorderManager.start({
// ...其他参数
audioSource: 'mic', // 明确指定使用麦克风
});
问题2:iOS设备第一次录音失败
这是因为iOS的音频会话(AVAudioSession)需要正确配置。可以在App启动时初始化:
javascript复制// 在App.vue的onLaunch中添加
if (plus.os.name === 'iOS') {
const audioSession = plus.ios.import('AVAudioSession');
const session = audioSession.sharedInstance();
session.setCategoryerror('AVAudioSessionCategoryPlayAndRecord');
session.setActiveerror(true);
}
问题3:长时间录音导致内存占用过高
解决方案是分片录音,定期保存:
javascript复制let chunkIndex = 0;
recorderManager.onFrameRecorded((res) => {
if (res.frameBuffer.byteLength > 1024 * 1024) { // 每1MB保存一次
saveChunk(res.frameBuffer);
chunkIndex++;
}
});
function saveChunk(buffer) {
// 实现分片保存逻辑
}
性能优化方面,我有几个建议:
uniapp最大的优势就是跨平台,但不同平台的差异还是需要特别注意。下面是我总结的几个关键差异点:
权限弹窗时机:
录音格式支持:
文件路径处理:
后台录音:
针对这些差异,我通常会封装一个统一的录音服务:
javascript复制class RecordService {
constructor() {
this.platform = uni.getSystemInfoSync().platform;
this.recorder = uni.getRecorderManager();
// 初始化平台特定配置
}
start() {
if (this.platform === 'android') {
// Android特有配置
} else {
// iOS特有配置
}
this.recorder.start(this.options);
}
// 其他统一接口...
}
一个好的录音功能不仅需要技术实现,还需要考虑用户体验。下面分享几个我在实际项目中积累的经验:
可视化反馈:
在录音时显示声波动画,让用户直观感受到麦克风在工作。可以使用canvas实现简单的波形图:
javascript复制function drawWaveform(volume) {
const ctx = uni.createCanvasContext('waveform');
// 根据音量值绘制波形
ctx.draw();
}
recorderManager.onFrameRecorded((res) => {
const volume = calculateVolume(res.frameBuffer);
drawWaveform(volume);
});
录音时长限制:
建议设置合理的时长限制,并提供倒计时提示:
javascript复制let duration = 0;
let timer = null;
recorderManager.onStart(() => {
duration = 0;
timer = setInterval(() => {
duration++;
this.remainingTime = MAX_DURATION - duration;
if (duration >= MAX_DURATION) {
this.stopRecording();
}
}, 1000);
});
降噪处理:
虽然uniapp原生API不直接支持降噪,但可以通过以下方式改善:
多段录音管理:
对于需要录制多段音频的场景,可以实现录音列表:
javascript复制this.recordings = [];
function saveRecording() {
this.recordings.push({
path: this.audioPath,
duration: this.duration
});
}
错误友好提示:
对各种错误情况提供明确的解决方案:
javascript复制recorderManager.onError((err) => {
let message = '录音出错';
if (err.code === '1001') {
message = '麦克风被占用,请关闭其他录音应用';
} else if (err.code === '1002') {
message = '存储空间不足,请清理后重试';
}
uni.showModal({
title: '提示',
content: message,
showCancel: false
});
});
录音功能的测试需要特别注意,因为涉及硬件和权限,很多问题在模拟器上无法复现。下面是我的测试经验分享:
真机测试清单:
常见测试场景:
调试技巧:
可以在开发时增加详细的日志:
javascript复制recorderManager.onProcess((res) => {
console.log('录音进度:', res);
debug.log(`采样点: ${res.samplePoints}, 音量: ${res.volume}`);
});
性能监控:
使用uni.getPerformance()监控录音时的性能指标:
javascript复制setInterval(() => {
const perf = uni.getPerformance();
console.log('内存使用:', perf.memory);
console.log('CPU使用:', perf.cpu);
}, 5000);
自动化测试:
对于核心功能,建议编写自动化测试用例:
javascript复制describe('录音功能测试', () => {
it('应该成功获取录音权限', async () => {
const granted = await requestRecordPermission();
assert.equal(granted, true);
});
it('应该能够录制10秒音频', (done) => {
recorderManager.onStop((res) => {
assert.ok(res.tempFilePath);
done();
});
recorderManager.start();
setTimeout(() => recorderManager.stop(), 10000);
});
});