如何让HTML5 video在Node.js+Electron中直接播放内存解密的MP4数据
加密MP4内存播放改造方案
两种实现方案均无需写入本地磁盘,直接在内存中完成解密播放:
方案1:全量解密后生成Blob URL(适合中小体积视频)
无需转码、实现简单,性能远高于当前的base64实现方案,直接将内存中的解密数据生成临时访问地址赋值给播放器即可。
const crypto = require('crypto'); const fs = require('fs'); const key = "12345678asdfghjk"; const keyBuf = Buffer.from(key); const inputStream = fs.createReadStream(file + "c"); const cipher = crypto.createDecipher("aes-128-cbc", keyBuf); // 建议后续替换为createDecipheriv const decryptedChunks = []; inputStream.on('data', (data) => { const buf = cipher.update(data); decryptedChunks.push(buf); }); inputStream.on('end', () => { try { const finalBuf = cipher.final(); decryptedChunks.push(finalBuf); // 拼接所有解密后的buffer const fullDecryptedBuf = Buffer.concat(decryptedChunks); // 转成Blob生成内存临时访问地址 const videoBlob = new Blob([fullDecryptedBuf], { type: 'video/mp4' }); const videoUrl = URL.createObjectURL(videoBlob); const introVideo = document.getElementById("introVideo"); introVideo.src = videoUrl; introVideo.play(); // 视频播放完成后释放内存,避免泄漏 introVideo.onended = () => { URL.revokeObjectURL(videoUrl); }; } catch (e) { console.log(e); } });
方案2:边解密边播放(适合大体积视频,低内存占用)
基于MediaSource Extensions API实现流式播放,不用等整个视频解密完成,解密一部分就喂给播放器一部分,内存占用稳定在百KB级别,适合GB级大视频。
注意:该方案需要视频为碎片化MP4(fMP4)格式,普通MP4需要先转成碎片化格式才能流式加载。
const crypto = require('crypto'); const fs = require('fs'); const key = "12345678asdfghjk"; const keyBuf = Buffer.from(key); // 按1M切片读取文件 const inputStream = fs.createReadStream(file + "c", { highWaterMark: 1024 * 1024 }); const cipher = crypto.createDecipher("aes-128-cbc", keyBuf); const introVideo = document.getElementById("introVideo"); const mediaSource = new MediaSource(); introVideo.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', () => { // 替换为你视频对应的实际编码格式 const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); let isStreamEnd = false; const bufferQueue = []; // 缓冲队列处理逻辑 const processQueue = () => { if (bufferQueue.length && !sourceBuffer.updating) { sourceBuffer.appendBuffer(bufferQueue.shift()); } else if (isStreamEnd && bufferQueue.length === 0) { mediaSource.endOfStream(); } }; sourceBuffer.addEventListener('updateend', processQueue); // 解密数据喂给播放器 inputStream.on('data', (data) => { const decrypted = cipher.update(data); bufferQueue.push(decrypted); processQueue(); }); inputStream.on('end', () => { try { const finalBuf = cipher.final(); bufferQueue.push(finalBuf); isStreamEnd = true; processQueue(); } catch (e) { console.log(e); } }); // 播放结束释放内存 introVideo.onended = () => { URL.revokeObjectURL(introVideo.src); }; });
通用注意事项
- 原代码中使用的
new Buffer()和crypto.createDecipher()均为Node.js废弃API,建议后续替换为Buffer.from()和crypto.createDecipheriv(),显式传入16字节初始化向量(IV)提升安全性 - 所有生成的
objectURL使用完成后必须调用URL.revokeObjectURL释放,否则会出现内存泄漏问题
内容的提问来源于stack exchange,提问作者chris_p
相关产品推荐
相关产品推荐

