文件切片处理 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);

 

posted @ 2025-03-07 17:24  妄欢  阅读(97)  评论(0)    收藏  举报