咨询大佬们一个问题,我后台接入大模型做对话,把音频文件保存到本地,是先转成成base64,然后保存的本地。base64是正确的,用其他工具可以正常转换成音频文件。但是保存到安卓手机就不行,一直报错(报错如附件),文件大小一直是0(附件截图),也没有具体报错信息。
文件目录为:/storage/emulated/0/Android/data/io.dcloud.HBuilder/apps/HBuilder/doc/temp_1772876228130_7250.wav
安卓版本:Android13 HBuilder版本4.87。
手机是小米12
写入代码如下:
try {
// 4. 清理Base64前缀
const pureBase64 = base64Str.replace(/^data:.+;base64,/, '');
if (!pureBase64) {
reject(new Error('Base64字符串为空'));
return;
}
// 5. 生成唯一文件名 + 私有目录路径(核心:使用_doc目录)
const fileName = `temp_${Date.now()}_${Math.floor(Math.random() * 10000)}.${ext}`;
const docPath = plus.io.convertLocalFileSystemURL('_doc/'); // 私有目录
console.log('[FileHelper] 私有目录路径:', docPath);
// 6. 解析私有目录,创建文件
plus.io.resolveLocalFileSystemURL(
'_doc/',
(dirEntry) => {
// 创建/打开文件
dirEntry.getFile(
fileName,
{ create: true, exclusive: false },
(fileEntry) => {
console.log('[FileHelper] 文件创建成功:', fileEntry.fullPath);
// 7. 创建FileWriter,使用writeAsBinary写入(官方要求)
fileEntry.createWriter(
(writer) => {
// 写入成功回调
writer.onwrite = () => {
console.log('[FileHelper] 文件写入成功');
// 8. 验证文件是否真实存在且非空(官方要求)
fileEntry.getMetadata(
(metadata) => {
if (metadata.size > 0) {
// 转换为可播放的本地路径(添加 file://协议头)
const playPath = plus.io.convertLocalFileSystemURL(fileEntry.fullPath);
console.log('[FileHelper] 文件路径转换成功:', playPath);
resolve(playPath);
} else {
console.error('[FileHelper] 写入成功但文件大小为 0');
reject(new Error('写入成功但文件大小为 0'));
}
},
(err) => {
console.error('[FileHelper] 验证文件大小失败:', err);
reject(new Error(`验证文件大小失败:${err.message}`));
}
);
};
// 写入失败回调
writer.onerror = (e) => {
console.error('[FileHelper] 写入失败:', e);
const errorMsg = e.target?.error?.message || '未知错误';
console.error('[FileHelper] 详细错误信息:', errorMsg, '错误码:', e.target?.error?.code);
reject(new Error(`文件写入失败:${errorMsg}`));
};
// 9. Base64 转 ArrayBuffer,用 write 方法写入(核心修复)
try {
console.log('[FileHelper] ArrayBuffer 开始转换,原值:', pureBase64);
const arrayBuffer = uni.base64ToArrayBuffer(pureBase64); // 使用 uni-app 官方 API
console.log('[FileHelper] ArrayBuffer 创建成功,长度:', arrayBuffer.byteLength);
// 使用 write 方法写入 ArrayBuffer(官方推荐方式,兼容性更好)
writer.write(arrayBuffer);
} catch (e) {
console.error('[FileHelper] ArrayBuffer 转换失败:', e);
reject(new Error(`ArrayBuffer 转换失败:${e.message}`));
}
},
(err) => reject(new Error(`创建写入器失败:${err.message}`))
);
},
(err) => reject(new Error(`创建/打开文件失败:${err.message}`))
);
},
(err) => reject(new Error(`访问私有目录失败:${err.message}`))
);
} catch (error) {
console.error('[FileHelper] 异常:', error);
reject(new Error(`Base64转临时文件失败:${error.message}`));
}
相关权限配置如下:
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"debug" : {
"webview" : "devtools",
"webviewPort" : 9223
},
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {},
"distribute" : {
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
},
"ios" : {
"dSYMs" : false
}
}
},
"app-android" : {
"distribute" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\"/>",
"<uses-permission android:name=\"android.permission.READ_MEDIA_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\"/>",
"<uses-permission android:name=\"android.permission.WRITE_INTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>"
],
"android" : {
"package" : "com.moyin.aiteacher.chat",
"application" : [
"<application android:requestLegacyExternalStorage=\"true\" android:exported=\"true\"/>"
]
}
}
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"locale" : "zh-Hans"