# Android 设备录音兼容性问题 ## 🐛 问题描述 在某些 Android 设备上,`recorderManager.onStop` 回调中的 `res` 对象缺少 `duration` 和 `fileSize` 字段: ```javascript { "tempFilePath": "_doc/uniapp_temp_xxx/recorder/xxx.pcm" // duration: undefined ❌ // fileSize: undefined ❌ } ``` 但是文件实际上已经生成了,只是这两个字段没有返回。 ## 🔍 根本原因 这是 uni-app 在某些 Android 设备上的已知 bug: - 录音文件确实生成了 - 但是 `duration` 和 `fileSize` 字段没有正确返回 - 特别是使用 PCM 格式时更容易出现 ## ✅ 解决方案 ### 修复前的代码(会失败) ```javascript recorderManager.onStop((res) => { if (!res.duration || res.duration < 500) { // ❌ 在某些设备上 res.duration 是 undefined // 导致这里总是返回,无法继续 console.error('录音时长太短') return } // 永远不会执行到这里 fs.readFile({ filePath: res.tempFilePath, ... }) }) ``` ### 修复后的代码(兼容) ```javascript 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 | ✅ 正常(跳过检查) | ## 🔧 完整的修复代码 ```javascript 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' }) } }) }) ``` ## 🎓 经验总结 ### 关键点 1. **不要依赖 `res.duration` 和 `res.fileSize`** - 这两个字段在某些设备上可能为 undefined - 只检查 `res.tempFilePath` 是否存在 2. **从文件内容获取实际大小** - 读取文件后,使用 `fileRes.data.byteLength` 获取实际大小 - 计算录音时长:`byteLength / 32000` 秒(PCM 16kHz 单声道) 3. **添加文件大小验证** - 读取文件后检查大小 - 如果太小(< 32000 bytes,即 < 1 秒),提示用户重试 ### 最佳实践 ```javascript // ✅ 好的做法 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,也能: 1. ✅ 正确读取录音文件 2. ✅ 获取实际文件大小 3. ✅ 计算录音时长 4. ✅ 分片发送音频数据 5. ✅ 完成整个对话流程 --- **问题**: res.duration 和 res.fileSize 为 undefined **原因**: uni-app 在某些 Android 设备上的已知 bug **解决**: 跳过这些字段的检查,直接读取文件并从文件内容获取实际大小 **状态**: ✅ 已修复