如何在Amazon S3中合并多个MediaRecorder视频分片为单个WebM文件?
合并S3中WebM视频分片的方案
核心思路
MediaRecorder每秒生成的WebM分片都带有独立文件头,直接二进制拼接会导致播放异常。推荐用FFmpeg工具完成合并,步骤如下:
- 按录制顺序下载S3目标文件夹内的所有分片到服务器临时目录
- 用FFmpeg将分片合并为标准单文件WebM
- 将合并后的文件上传回S3(可选:清理原分片文件)
- 清除服务器临时文件
具体实现(Node.js)
1. 安装依赖
先安装所需工具包:
npm install @aws-sdk/client-s3 fluent-ffmpeg fs-extra
2. 代码实现
const { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectsCommand } = require('@aws-sdk/client-s3'); const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs-extra'); const path = require('path'); // 初始化S3客户端 const s3Client = new S3Client({ region: '你的S3区域', credentials: { accessKeyId: '你的AccessKey', secretAccessKey: '你的SecretKey' } }); async function mergeWebMChunks(bucketName, folderPrefix, outputS3Key) { // 1. 拉取目标文件夹下的所有分片文件 const listCmd = new ListObjectsV2Command({ Bucket: bucketName, Prefix: folderPrefix // 示例:"recordings/user_123/" }); const listResult = await s3Client.send(listCmd); const chunkObjects = listResult.Contents.filter(obj => !obj.Key.endsWith('/')); // 2. 按分片序号排序(需匹配你的分片命名规则,示例为提取part后的数字) chunkObjects.sort((a, b) => { const partA = parseInt(a.Key.match(/part(\d+)/)[1]); const partB = parseInt(b.Key.match(/part(\d+)/)[1]); return partA - partB; }); // 3. 创建临时目录 const tempDir = path.join(__dirname, `temp_merge_${Date.now()}`); await fs.ensureDir(tempDir); try { // 4. 批量下载分片到本地 const downloadTasks = chunkObjects.map(async (obj, idx) => { const tempPath = path.join(tempDir, `chunk_${idx}.webm`); const getCmd = new GetObjectCommand({ Bucket: bucketName, Key: obj.Key }); const s3Res = await s3Client.send(getCmd); await fs.writeFile(tempPath, s3Res.Body); return tempPath; }); const localChunkPaths = await Promise.all(downloadTasks); // 5. 用FFmpeg合并分片(用文件列表避免命令行参数过长) const outputTempPath = path.join(tempDir, 'merged_final.webm'); const listFilePath = path.join(tempDir, 'chunk_list.txt'); const listContent = localChunkPaths.map(p => `file '${p}'`).join('\n'); fs.writeFileSync(listFilePath, listContent); await new Promise((resolve, reject) => { ffmpeg() .input(listFilePath) .inputOptions('-f', 'concat') .output(outputTempPath) .outputOptions('-c', 'copy') // 直接复制流,不重新编码,速度快 .on('end', resolve) .on('error', reject) .run(); }); // 6. 上传合并后的文件到S3 const mergedFile = await fs.readFile(outputTempPath); const putCmd = new PutObjectCommand({ Bucket: bucketName, Key: outputS3Key, // 示例:"recordings/user_123/final.webm" Body: mergedFile, ContentType: 'video/webm' }); await s3Client.send(putCmd); // 7. 可选:删除原分片文件 const deleteCmd = new DeleteObjectsCommand({ Bucket: bucketName, Delete: { Objects: chunkObjects.map(obj => ({ Key: obj.Key })) } }); await s3Client.send(deleteCmd); console.log('合并完成,文件已上传至S3'); } catch (err) { console.error('合并失败:', err); throw err; } finally { // 8. 清理临时文件 await fs.remove(tempDir); } } // 调用示例 mergeWebMChunks( 'your_bucket_name', 'recordings/user_123/', 'recordings/user_123/final.webm' );
关键注意事项
- 分片排序:必须严格按录制顺序合并,否则视频会错乱,需根据你的分片命名规则调整排序逻辑
- FFmpeg参数:
-c copy参数直接复用原有编码,合并速度极快且无画质损失 - 临时文件清理:无论合并成功或失败,都要清理临时目录,避免服务器磁盘被占用
- 无FFmpeg替代方案:若无法安装FFmpeg,需手动解析WebM的Matroska结构,移除除第一个分片外的所有文件头再拼接,但兼容性差,不推荐
内容的提问来源于stack exchange,提问作者KaZaT
相关产品推荐
相关产品推荐

