当前位置: 首页 > web >正文

【实用案例】录音分片上传的核心逻辑和实现案例【文章附有代码】

前言

最近接到一个业务需求:录音文件上传时因为文件过大(超过100M)有报错,

(服务器端对单次文件的上传,有大小限制)。

经过讨论,最后决定使用‘分片’解决问题。

我做了一个小案例(非业务代码),用HTML代码实现前端页面用express实现后端服务

一、分片上传的核心原理​

分片上传是将大文件分割成多个小片段(分片),逐个上传到服务器,最后在服务器端合并所有分片形成完整文件的技术。这种方式的优势在于:​

  • 避免单次上传大文件导致的超时问题​
  • 支持断点续传,某分片失败只需重传该分片​
  • 减轻服务器单次处理压力,提高系统稳定性​

对于录音文件来说,由于可能包含长时间的音频数据(尤其是高清录音),文件体积往往较大,非常适合采用分片上传方案。

二、前端实现核心逻辑​

前端主要负责录音、文件分片和上传控制三个部分。

1. 录音功能实现​

使用浏览器原生的MediaRecorderAPI 实现录音功能:

// 开始录音
startRecordBtn.addEventListener('click', async () => {const stream = await navigator.mediaDevices.getUserMedia({ audio: true });mediaRecorder = new MediaRecorder(stream);audioChunks = [];mediaRecorder.ondataavailable = (event) => {audioChunks.push(event.data);};mediaRecorder.start();// 更新UI状态...
});// 停止录音
stopRecordBtn.addEventListener('click', () => {mediaRecorder.stop();mediaRecorder.stream.getTracks().forEach(track => track.stop());mediaRecorder.onstop = () => {audioBlob = new Blob(audioChunks, { type: 'audio/webm' });audioUrl = URL.createObjectURL(audioBlob);// 显示上传区域...};
});

2. 分片上传核心实现​

创建AudioChunkUploader类处理分片逻辑:

class AudioChunkUploader {constructor(options) {this.options = {chunkSize: 5 * 1024 * 1024, // 5MB每片uploadUrl: 'http://localhost:3000/upload/chunk',mergeUrl: 'http://localhost:3000/upload/merge',...options};this.state = {totalChunks: 0,uploadedChunks: 0,fileMd5: '', // 文件唯一标识isPaused: false};}// 计算文件MD5(用于标识同一文件)async _calculateFileMd5() {return new Promise((resolve) => {const fileReader = new FileReader();const spark = new SparkMD5.ArrayBuffer();fileReader.onload = (e) => {spark.append(e.target.result);this.state.fileMd5 = spark.end();resolve();};fileReader.readAsArrayBuffer(this.options.file);});}// 上传单个分片async _uploadSingleChunk(chunkIndex) {const start = chunkIndex * this.options.chunkSize;const end = Math.min(start + this.options.chunkSize, this.state.fileSize);const chunk = this.options.file.slice(start, end);const formData = new FormData();formData.append('chunk', chunkIndex);formData.append('chunks', this.state.totalChunks);formData.append('fileMd5', this.state.fileMd5);formData.append('file', chunk);return fetch(this.options.uploadUrl, {method: 'POST',body: formData}).then(response => response.json());}// 上传所有分片async _uploadChunks() {for (let i = 0; i < this.state.totalChunks; i++) {if (this.state.isPaused) {// 等待恢复上传await new Promise(resolve => {const check = setInterval(() => {if (!this.state.isPaused) {clearInterval(check);resolve();}}, 100);});}await this._uploadSingleChunk(i);this.state.uploadedChunks = i + 1;// 触发进度更新...}}// 通知服务器合并分片async _mergeChunks() {return fetch(this.options.mergeUrl, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({fileMd5: this.state.fileMd5,fileName: this.options.fileName,chunks: this.state.totalChunks})}).then(response => response.json());}
}

3. 播放控制功能​

实现带暂停 / 继续功能的音频播放:

let audioPlayer = null;
let lastPlayTime = 0;// 播放录音
playRecordBtn.addEventListener('click', () => {if (!audioPlayer) {audioPlayer = new Audio(audioUrl);if (lastPlayTime > 0) {audioPlayer.currentTime = lastPlayTime;}} else {audioPlayer.currentTime = lastPlayTime;}audioPlayer.play();// 更新按钮状态...
});// 暂停播放
stopPlayBtn.addEventListener('click', () => {lastPlayTime = audioPlayer.currentTime;audioPlayer.pause();// 更新按钮状态...
});

三、后端实现核心逻辑​

Express 后端负责接收分片、临时存储和合并文件:​

1. 分片接收接口

const multer = require('multer');
const upload = multer({ storage: multer.diskStorage({destination: (req, file, cb) => {// 为每个文件创建独立的临时目录const chunkDir = path.join(tempDir, req.body.fileMd5);if (!fs.existsSync(chunkDir)) {fs.mkdirSync(chunkDir, { recursive: true });}cb(null, chunkDir);},filename: (req, file, cb) => {// 用分片索引作为文件名cb(null, req.body.chunk);}})
});// 处理分片上传
app.post('/upload/chunk', upload.single('file'), (req, res) => {res.json({success: true,message: `分片 ${req.body.chunk} 上传成功`});
});

2. 分片合并接口

app.post('/upload/merge', (req, res) => {const { fileMd5, fileName, chunks } = req.body;const chunkDir = path.join(tempDir, fileMd5);const destPath = path.join(uploadDir, fileName);// 检查所有分片是否上传完成if (fs.readdirSync(chunkDir).length !== parseInt(chunks)) {return res.status(400).json({success: false,message: '分片不完整'});}// 合并分片const writeStream = fs.createWriteStream(destPath);let chunkIndex = 0;const mergeNextChunk = () => {const chunkPath = path.join(chunkDir, chunkIndex.toString());if (fs.existsSync(chunkPath)) {const readStream = fs.createReadStream(chunkPath);readStream.pipe(writeStream, { end: false });readStream.on('end', () => {fs.unlinkSync(chunkPath); // 删除已合并的分片chunkIndex++;mergeNextChunk();});} else {writeStream.end(); // 所有分片合并完成}};mergeNextChunk();writeStream.on('finish', () => {fs.rmdirSync(chunkDir, { recursive: true }); // 清理临时目录res.json({success: true,fileName,filePath: destPath});});
});

四、效果展示

可以在设定好存放上传文件的文件夹下看到上传的录音文件

五、总结与拓展​

录音文件分片上传方案的核心在于:​

  • 前端将录音文件分割成固定大小的分片,计算文件唯一标识​
  • 逐个上传分片,支持暂停 / 继续功能​
  • 后端接收分片并临时存储​
  • 所有分片上传完成后,后端按顺序合并成完整文件​

方案可以进一步优化:​

  • 添加断点续传功能,上传前先查询已上传的分片​
  • 实现分片上传的并发控制,提高上传速度​
  • 增加文件校验机制,确保上传文件的完整性​
  • 对大文件 MD5 计算进行优化,避免页面卡顿​

分片上传技术不仅适用于录音文件,也可推广到视频、文档等各种大文件上传场景,是 Web 开发中处理大文件的重要方案。

代码获取,后台联系。

http://www.xdnf.cn/news/17685.html

相关文章:

  • Godot ------ 平滑拖动03
  • SpringBoot 自动配置核心机制(面试高频考点)
  • Orange的运维学习日记--38.MariaDB详解与服务部署
  • JavaEE 初阶第十七期:文件 IO 的 “管道艺术”(下)
  • 《范仲淹传》读书笔记与摘要
  • 使用frp内网穿透实现远程办公
  • 基于AI量化模型的比特币周期重构:传统四年规律是否被算法因子打破?
  • Python(9)-- 异常模块与包
  • AI Coding 概述及学习路线图
  • Elasticsearch Node.js 客户端的安装
  • 【功能测试】软件集成测试思路策略与经验总结
  • FFmpeg - 基本 API大全(视频编解码相关的)
  • 【数据结构】深入理解顺序表与通讯录项目的实现
  • leetcode-hot-100 (图论)
  • CobaltStrike的搭建和使用
  • 爬虫与数据分析实战
  • 【09-神经网络介绍2】
  • 一文读懂 C# 中的 Lazy<T>
  • 第10节 大模型分布式推理典型场景实战与架构设计
  • Godot ------ 平滑拖动02
  • Apache Ignite 核心组件:GridClosureProcessor解析
  • C# 异步编程(计时器)
  • Python: configparser库 ini文件操作库
  • 使用MAS(Microsoft Activation Scripts)永久获得win10专业版和office全套
  • Edit Distance
  • react中父子数据流动和事件互相调用(和vue做比较)
  • GO学习记录三
  • 基于MongoDB/HBase的知识共享平台的设计与实现
  • 【Dv3Admin】菜单转换选项卡平铺到页面
  • Excel 连接阿里云 RDS MySQL