查看微信的官方文档
DownloadTask wx.downloadFile(Object object)
以 Promise 风格 调用:不支持
小程序插件:支持,需要小程序基础库版本不低于 1.9.6
微信 Windows 版:支持
微信 Mac 版:支持
微信 鸿蒙 OS 版:支持
相关文档: 网络使用说明、局域网通信
功能描述
下载文件资源到本地。客户端直接发起一个 HTTPS GET 请求,返回文件的本地临时路径 (本地路径),单次下载允许的最大文件为 200MB。使用前请注意阅读相关说明。
注意:请在服务端响应的 header 中指定合理的Content-Type字段,以保证客户端正确处理文件类型。
参数
Object object
| 属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
|---|---|---|---|---|---|
| url | string | 是 | 下载资源的 url | ||
| header | Object | 否 | HTTP 请求的 Header,Header 中不能设置 Referer | ||
| timeout | number | 60000 | 否 | 超时时间,单位为毫秒,默认值为 60000 即一分钟。 | 2.10.0 |
| filePath | string | 否 | 指定文件下载后存储的路径 (本地路径) | 1.8.0 |
文档标识只支持200MB的文件,那么我们如何下载超过200MB的文件呢
我尝试通过分块下载来实现这个逻辑,先把文件拆分成多个子文件下载下来,然后再一起合并成最终的视频。
import {authorize} from "./download"; const fs = wx.getFileSystemManager(); const fileName = 'video.mp4' // 下载的文件名 const tempFilePath = wx.env.USER_DATA_PATH + '/downloads4_' // 临时文件存储路径 // const FILE_PATH = `${wx.env.USER_DATA_PATH}/downloaded_video.mp4`; // 最终合并输出路径 const BLOCK_SIZE = 1024 * 1024 * 300; // 每块大小:5MB /** * 获取远程视频文件大小 */ function getFileSize(url) { return new Promise((resolve, reject) => { uni.request({ url, method: 'HEAD', success: (res) => { const size = parseInt(res.header['Content-Length'] || res.header['content-length']); resolve(size); }, fail: reject, }); }); } /** * 下载单个分块并写入临时 chunk 文件 */ function downloadChunk(url, start, end, index,callback) { return new Promise((resolve, reject) => { // uni.request({ // url, // header: { // Range: `bytes=${start}-${end}`, // }, // responseType: 'arraybuffer', // success: (res) => { // const chunkPath = `${wx.env.USER_DATA_PATH}/chunk_${index}`; // fs.writeFile({ // filePath: chunkPath, // data: res.data, // encoding: 'binary', // success: resolve, // fail: reject, // }); // }, // fail: reject, // }); authorize().then(() => { console.log(`${tempFilePath}${index}`) const tmpFile = wx.env.USER_DATA_PATH + '/downloads4_'+index+".mp4" const task = wx.downloadFile({ url: `${url}?t=${new Date().getTime()}`, //仅为示例,并非真实的资源 // header: { // Range: `bytes=${start}-${end}`, // }, // filePath:tmpFile, // filepath: tmpFile, success: (res) => { console.log(res); if (res.statusCode >= 200 && res.statusCode < 300) { wx.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success:function(res) { console.log(res) resolve(); // wx.hideLoading(); }, fail: (err) => { console.log(err) reject(new Error('Download chunk fail.')) } }) } else { reject(new Error('Download chunk error.')) } }, fail: (err) => { console.log(err) reject(new Error('Download chunk fail.')) } }) task?.onProgressUpdate((res) => { const { progress, totalBytesWritten, totalBytesExpectedToWrite } = res console.log('下载进度' + progress); console.log('已经下载的数据长度' + totalBytesWritten); console.log('预期需要下载的数据总长度' + totalBytesExpectedToWrite); if (callback) { callback(res); }else{ uni.showToast({ icon: "none", title: `下载进度: ${progress}%`, }); } if (totalBytesExpectedToWrite > MaxTotalSize) { const CurTotalSizeMB = (totalBytesExpectedToWrite / 1024 / 1024).toFixed(2); // FailReason.errMsg = `当前文件${CurTotalSizeMB}MB,小程序单次下载允许的最大文件仅为${MaxTotalSizeMB}MB。您可复制下载链接,切换到本地浏览器自行下载!`; task.abort(); return; } }) }) }); } /** * 合并所有 chunk 成一个完整文件 */ function mergeChunks(chunkCount, targetFilePath) { return new Promise((resolve, reject) => { try { // 清空或创建目标文件 fs.writeFileSync(targetFilePath,'', 'binary' ); for (let i = 0; i < chunkCount; i++) { // const chunkPath = `${wx.env.USER_DATA_PATH}/chunk_${i}`; const chunkPath = wx.env.USER_DATA_PATH + '/downloads4_'+i+".mp4"; console.log("chunkPath:"+chunkPath); const chunkData = fs.readFileSync(chunkPath, 'binary'); fs.appendFileSync( targetFilePath, chunkData, 'binary', ); // 删除临时 chunk fs.unlinkSync(chunkPath); } resolve(); } catch (err) { console.log(err) reject(err); } }); } // 合并已下载的分片文件 function mergeFiles(chunkCount) { const fs = wx.getFileSystemManager() const stream = fs.createWriteStream(`${wx.env.USER_DATA_PATH}/downloads4_${fileName}`) const write = (index) => { console.log("开始合并"+index); const path = wx.env.USER_DATA_PATH + '/downloads4_'+index+".mp4"; // const path = `${wx.env.USER_DATA_PATH}/chunk_${index}`; // const chunk = chunks[index] // if (!chunk.downloaded) { // console.log(`Chunk ${chunk.index} not downloaded.`) // return // } fs.readFile(path, 'binary', (err, data) => { if (err) { console.log(err) return } stream.write(data, 'binary') if (index < chunkCount - 1) { write(index + 1) } else { // 文件合并完成 stream.end() // this.resetState() } }) } write(0) } /** * 下载完整视频,带进度回调 */ async function downloadVideoWithChunks(url, callback) { const totalSize = await getFileSize(url); console.log("totalSize:"+totalSize); const chunkCount = Math.ceil(totalSize / BLOCK_SIZE); for (let i = 0; i < chunkCount; i++) { const start = i * BLOCK_SIZE; const end = Math.min(start + BLOCK_SIZE - 1, totalSize - 1); await downloadChunk(url, start, end, i,callback); // if (onProgress) { // onProgress(i + 1, chunkCount); // } } const fullPath = `${wx.env.USER_DATA_PATH}/downloads4_${fileName}` console.log('fullPath', fullPath) // await mergeChunks(chunkCount,fullPath); // await mergeFiles(chunkCount); return fullPath; } /** * 保存文件到相册,带权限检查 */ function requestPermissionAndSave(filePath) { return new Promise((resolve, reject) => { uni.getSetting({ success(settingRes) { if (!settingRes.authSetting['scope.writePhotosAlbum']) { uni.authorize({ scope: 'scope.writePhotosAlbum', success: () => { uni.saveVideoToPhotosAlbum({ filePath, success: resolve, fail: reject, }); }, fail: () => { uni.showModal({ title: '提示', content: '请授权保存到相册', success(res) { if (res.confirm) wx.openSetting(); }, }); reject(new Error('未授权')); }, }); } else { uni.saveVideoToPhotosAlbum({ filePath, success: resolve, fail: reject, }); } }, fail: reject, }); }); } /** * 主函数:分块下载 + 合并 + 保存 */ export async function downloadAndSaveVideo(url, onProgress) { try { const path = await downloadVideoWithChunks(url, onProgress); // await requestPermissionAndSave(path); uni.showToast({ title: '保存成功', icon: 'success' }); } catch (err) { console.error('下载或保存失败:', err); uni.showToast({ title: '失败', icon: 'none' }); } }但在分块的逻辑下,虽然下载子文件是没有问题,但是在最终合成大视频的环节中还是会报200mb限制大小的错误,所以这个方案还是不可行的。
最后的最后,我神奇的发现,其实wx.downloadFile 是支持超过200MB的文件,你不要自己定义filePath,你用它自己返回的文件路径就好
const Task = uni.downloadFile({
// url: url, //仅为示例,并非真实的资源
url: `${url}?t=${new Date().getTime()}`, //仅为示例,并非真实的资源
// filepath: filePath, // 之前我这里自定义了路径,这里需要注释掉
timeout: 300 * 1000, // 设置特定超时时间5分钟
success: (res) => {
你用它返回的res.tempFilePath 去保存 (要用真机调试,开发者工具做了200MB的拦截)
wx.saveVideoToPhotosAlbum({
filePath: res.tempFilePath,
success:function(res) {
resolve();
// wx.hideLoading();
},