文件切片处理 js
为了优化一些文件的加载、预览、上传速度,对于大文件就要采取一些对应的措施。此处记录一下文件切片处理已经分片预览。
核心就是利用Blob的slice特性切割文件,然后按照顺序存入数组,再按照需要去对文件进行上传,预览等操作。
// 获取文件对象(如通过 input 标签) const fileInput = document.getElementById('file-input'); const file = fileInput.files[0]; // 定义分片大小(例如 1MB) const CHUNK_SIZE = 1024 * 1024; let currentChunk = 0; const chunks = []; // 切片处理 while (currentChunk * CHUNK_SIZE < file.size) { const start = currentChunk * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); // 关键切片方法 chunks.push(chunk); currentChunk++; } // 示例:通过 FormData 上传分片 async function uploadChunks(chunks) { for (let i = 0; i < chunks.length; i++) { const formData = new FormData(); formData.append('file', chunks[i]); formData.append('chunkIndex', i); formData.append('totalChunks', chunks.length); // 发送分片到服务端 await fetch('/upload', { method: 'POST', body: formData, }); console.log(`分片 ${i} 上传成功`); } console.log('所有分片上传完成'); } // 调用上传 uploadChunks(chunks);
如果需要可以加一个校验:主要用于确保断点续传时文件不会重复以及验证是否被篡改
// 读取分片内容(含错误处理) function readChunk(chunk) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target.result); reader.onerror = (e) => reject(e.target.error); reader.readAsArrayBuffer(chunk); }); } // 计算 MD5(使用 CryptoJS) async function calculateChunkMD5WithCryptoJS(chunk) { try { const buffer = await readChunk(chunk); const uint8Array = new Uint8Array(buffer); const wordArray = CryptoJS.lib.WordArray.create(uint8Array); return CryptoJS.MD5(wordArray).toString(); } catch (error) { console.error('分片哈希计算失败:', error); throw error; } } // 计算 MD5(使用 SubtleCrypto,更高效) async function calculateChunkMD5WithNativeAPI(chunk) { try { const buffer = await readChunk(chunk); const hashBuffer = await crypto.subtle.digest('MD5', buffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } catch (error) { console.error('分片哈希计算失败:', error); throw error; } }
图片预览:
这里顺便记录下文件格式
首先是Blob,Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,比如img标签中你直接放置Blob可以直接被解析为图片。
arrayBuffer,类似一个格式中转站,不可以直接操作,但是可以转换为其他形式。读取可以用fileReader().fileAsArrayBuffer读取。
Buffer 是 node 中的类型 Buffer 继承于 Uint8Array,
Uint8Array则是继承于 TypedArray。
const canvas = document.getElementById('preview-canvas'); const ctx = canvas.getContext('2d'); async function loadImagePreview() { while (offset < file.size) { const chunk = file.slice(offset, offset + chunkSize); const arrayBuffer = await chunk.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); // 将分块数据追加到 Canvas(需根据图片格式处理) // 此处仅为示意,实际需要解析图片二进制数据 ctx.putImageData(new ImageData(uint8Array, width, height), 0, 0); offset += chunkSize; } }
文本预览:
// 示例:分块加载文本内容 const file = input.files[0]; const chunkSize = 1024 * 100; // 100KB 一个块 let offset = 0; const previewDiv = document.getElementById('text-preview'); async function loadTextPreview() { while (offset < file.size) { const chunk = file.slice(offset, offset + chunkSize); const text = await chunk.text(); previewDiv.textContent += text; offset += chunkSize; } } loadTextPreview();
pdf:
// 示例:分块加载 PDF(需引入 PDF.js 库) const loadingTask = pdfjsLib.getDocument({ url: URL.createObjectURL(file), disableStream: false, // 启用流式加载 disableRange: false, // 启用范围请求 }); loadingTask.promise.then((pdf) => { pdf.getPage(1).then((page) => { const canvas = document.getElementById('pdf-canvas'); page.render({ canvasContext: canvas.getContext('2d') }); }); });
视频预览:
// 获取视频元素和文件输入 const video = document.getElementById('preview-video'); const fileInput = document.getElementById('file-input'); const file = fileInput.files[0]; // 创建 MediaSource 并绑定到 video const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); // 错误处理函数 function handleError(error) { console.error('发生错误:', error); video.removeAttribute('src'); // 清除无效视频源 mediaSource.endOfStream('error'); // 通知媒体流终止 } mediaSource.addEventListener('sourceopen', async () => { try { // 动态获取视频编码信息(示例需根据实际文件调整) const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; if (!MediaSource.isTypeSupported(mimeCodec)) { throw new Error('浏览器不支持该视频编码格式'); } const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); let offset = 0; const chunkSize = 1024 * 1024; // 1MB 分片 // 监听 sourceBuffer 错误 sourceBuffer.addEventListener('error', handleError); // 递归追加分片(确保顺序) function appendNextChunk() { if (offset >= file.size) { mediaSource.endOfStream(); // 全部加载完成 console.log('视频数据加载完成'); return; } const chunk = file.slice(offset, Math.min(offset + chunkSize, file.size)); const reader = new FileReader(); reader.onload = () => { try { if (sourceBuffer.updating) return; // 安全校验 sourceBuffer.appendBuffer(reader.result); offset += chunk.size; } catch (err) { handleError(err); } }; reader.onerror = handleError; reader.readAsArrayBuffer(chunk); } // 监听 updateend 事件以追加下一块 sourceBuffer.addEventListener('updateend', appendNextChunk); appendNextChunk(); // 开始处理第一个分片 } catch (err) { handleError(err); } }); // 监听 MediaSource 错误 mediaSource.addEventListener('error', handleError);