6.8 KiB
6.8 KiB
Android 设备录音兼容性问题
🐛 问题描述
在某些 Android 设备上,recorderManager.onStop 回调中的 res 对象缺少 duration 和 fileSize 字段:
{
"tempFilePath": "_doc/uniapp_temp_xxx/recorder/xxx.pcm"
// duration: undefined ❌
// fileSize: undefined ❌
}
但是文件实际上已经生成了,只是这两个字段没有返回。
🔍 根本原因
这是 uni-app 在某些 Android 设备上的已知 bug:
- 录音文件确实生成了
- 但是
duration和fileSize字段没有正确返回 - 特别是使用 PCM 格式时更容易出现
✅ 解决方案
修复前的代码(会失败)
recorderManager.onStop((res) => {
if (!res.duration || res.duration < 500) {
// ❌ 在某些设备上 res.duration 是 undefined
// 导致这里总是返回,无法继续
console.error('录音时长太短')
return
}
// 永远不会执行到这里
fs.readFile({ filePath: res.tempFilePath, ... })
})
修复后的代码(兼容)
recorderManager.onStop((res) => {
// 只检查文件路径是否存在
if (!res.tempFilePath) {
console.error('没有录音文件路径')
return
}
// ⚠️ 跳过 duration 和 fileSize 的检查
// 因为在某些设备上这些字段可能为 undefined
// 但文件实际上已经生成了
// 直接尝试读取文件
fs.readFile({
filePath: res.tempFilePath,
success: (fileRes) => {
// 文件读取成功后,可以从 fileRes.data 获取实际大小
console.log('实际文件大小:', fileRes.data.byteLength, 'bytes')
// 继续处理...
}
})
})
📊 兼容性对比
修复前
| 设备类型 | duration | fileSize | 结果 |
|---|---|---|---|
| iOS | ✅ 有值 | ✅ 有值 | ✅ 正常 |
| Android (部分) | ✅ 有值 | ✅ 有值 | ✅ 正常 |
| Android (部分) | ❌ undefined | ❌ undefined | ❌ 失败 |
修复后
| 设备类型 | duration | fileSize | 结果 |
|---|---|---|---|
| iOS | ✅ 有值 | ✅ 有值 | ✅ 正常 |
| Android (部分) | ✅ 有值 | ✅ 有值 | ✅ 正常 |
| Android (部分) | ❌ undefined | ❌ undefined | ✅ 正常(跳过检查) |
🔧 完整的修复代码
recorderManager.onStop((res) => {
console.log('⏹️ 录音已停止')
console.log('📋 完整的 res 对象:', JSON.stringify(res))
// 只检查文件路径
if (!res.tempFilePath) {
console.error('❌ 没有录音文件路径!')
uni.showToast({
title: '录音失败:没有生成文件',
icon: 'none'
})
return
}
// ⚠️ 某些 Android 设备上 res.duration 和 res.fileSize 可能为 undefined
// 这是 uni-app 的已知问题,我们跳过这个检查,直接尝试读取文件
if (res.duration !== undefined && res.duration < 500) {
console.error('❌ 录音时长太短:', res.duration, 'ms')
uni.showToast({
title: '录音太短,请至少说 2 秒',
icon: 'none'
})
return
}
console.log('✅ 录音文件路径有效,准备读取文件...')
// 检查 WebSocket 状态
if (!this.socketTask || this.socketTask.readyState !== 1) {
console.error('❌ WebSocket 未连接')
return
}
// 读取文件
const fs = uni.getFileSystemManager()
fs.readFile({
filePath: res.tempFilePath,
success: (fileRes) => {
console.log('✅ 文件读取成功')
console.log('📊 实际文件大小:', fileRes.data.byteLength, 'bytes')
console.log('📊 预计录音时长:', (fileRes.data.byteLength / 32000).toFixed(2), '秒')
// 验证文件大小
if (fileRes.data.byteLength < 32000) {
console.error('❌ 文件太小,可能录音失败')
uni.showToast({
title: '录音文件太小,请重试',
icon: 'none'
})
return
}
// 确保是 ArrayBuffer
if (!(fileRes.data instanceof ArrayBuffer)) {
console.error('❌ 数据不是 ArrayBuffer')
return
}
// 发送音频数据
this.sendAudioInChunks(fileRes.data)
},
fail: (err) => {
console.error('❌ 文件读取失败:', err)
uni.showToast({
title: '文件读取失败',
icon: 'none'
})
}
})
})
🎓 经验总结
关键点
-
不要依赖
res.duration和res.fileSize- 这两个字段在某些设备上可能为 undefined
- 只检查
res.tempFilePath是否存在
-
从文件内容获取实际大小
- 读取文件后,使用
fileRes.data.byteLength获取实际大小 - 计算录音时长:
byteLength / 32000秒(PCM 16kHz 单声道)
- 读取文件后,使用
-
添加文件大小验证
- 读取文件后检查大小
- 如果太小(< 32000 bytes,即 < 1 秒),提示用户重试
最佳实践
// ✅ 好的做法
if (!res.tempFilePath) {
return // 只检查文件路径
}
// 读取文件后验证
fs.readFile({
success: (fileRes) => {
const size = fileRes.data.byteLength
const duration = size / 32000 // 计算时长
if (duration < 2) {
console.error('录音太短')
return
}
// 继续处理...
}
})
// ❌ 不好的做法
if (!res.duration || res.duration < 500) {
return // 在某些设备上会失败
}
📱 测试结果
修复后,应该看到:
⏹️ 录音已停止
📋 完整的 res 对象: {"tempFilePath":"..."}
📁 文件路径: _doc/uniapp_temp_xxx/recorder/xxx.pcm
⏱️ 录音时长: undefined ms ← 可能是 undefined
📦 文件大小: undefined bytes ← 可能是 undefined
✅ 录音文件路径有效,准备读取文件...
✅ 文件读取成功
📊 实际文件大小: 320000 bytes ← 从文件内容获取
📊 预计录音时长: 10.00 秒 ← 计算得出
📦 开始分片发送(官方推荐参数)
...
🎉 预期结果
修复后,即使 res.duration 和 res.fileSize 为 undefined,也能:
- ✅ 正确读取录音文件
- ✅ 获取实际文件大小
- ✅ 计算录音时长
- ✅ 分片发送音频数据
- ✅ 完成整个对话流程
问题: res.duration 和 res.fileSize 为 undefined
原因: uni-app 在某些 Android 设备上的已知 bug
解决: 跳过这些字段的检查,直接读取文件并从文件内容获取实际大小
状态: ✅ 已修复