无需WebRTC,如何通过Express传输Electron桌面捕获的MediaStream?
嘿,我来帮你搞定这个问题!你说的没错,fs.createReadStream根本没法处理Electron捕获的MediaStream——前者是用来读取本地文件的静态流,后者是实时在内存里生成的媒体流,完全不是一个路数。而且不用WebRTC确实能实现,我给你两种靠谱的方案,你可以根据需求选:
方案一:MediaRecorder + Express响应流(简单易实现)
这种方式适合对延迟要求不高的场景,用MediaRecorder把桌面流编码成webm格式,再通过Express的响应对象实时推送给前端,前端直接用video标签播放即可。
后端(Electron + Express)代码修改
var express = require('express'); var app = express(); const { desktopCapturer } = require('electron'); // 先初始化桌面捕获流 let desktopStream; async function initDesktopStream() { const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] }); // 这里默认选第一个捕获源,你可以改成用户选择的目标窗口/屏幕 const selectedSource = sources[0]; desktopStream = await navigator.mediaDevices.getUserMedia({ audio: false, // 需要音频的话改成true video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: selectedSource.id, minWidth: 1280, maxWidth: 1280, minHeight: 720, maxHeight: 720 } } }); } // 启动时初始化流 initDesktopStream(); // 提供桌面流的接口 app.get('/desktop-stream', (req, res) => { if (!desktopStream) { res.status(500).send('桌面流未初始化'); return; } // 设置响应头,告诉前端这是webm格式的流式数据 res.setHeader('Content-Type', 'video/webm'); res.setHeader('Transfer-Encoding', 'chunked'); // 创建MediaRecorder,编码为vp9格式的webm const recorder = new MediaRecorder(desktopStream, { mimeType: 'video/webm; codecs=vp9' }); // 每当有编码后的数据块生成,就推送给前端 recorder.ondataavailable = (e) => { if (e.data.size > 0) { e.data.arrayBuffer().then(buffer => { res.write(Buffer.from(buffer)); }); } }; // 录制停止时结束响应 recorder.onstop = () => { res.end(); }; // 每100ms生成一个数据块(值越小延迟越低,但传输压力越大) recorder.start(100); // 客户端断开连接时停止录制,释放资源 req.on('close', () => { recorder.stop(); console.log('客户端断开连接,已停止桌面流'); }); }); // 托管前端静态页面 app.use(express.static('public')); app.listen(3000, () => { console.log('服务已启动:http://localhost:3000'); });
前端页面(public/index.html)
<!DOCTYPE html> <html> <head> <title>桌面流预览</title> </head> <body> <video id="desktop-video" autoplay muted controls style="width: 100%;"></video> <script> const video = document.getElementById('desktop-video'); // 直接把流接口地址赋值给video的src video.src = 'http://localhost:3000/desktop-stream'; </script> </body> </html>
方案二:WebSocket实时推送帧(低延迟场景)
如果需要更低的延迟,可以用WebSocket把桌面流的每一帧推送给前端,再通过MediaSource拼接成可播放的流。
后端代码修改
var express = require('express'); var app = express(); const http = require('http'); const WebSocket = require('ws'); const { desktopCapturer } = require('electron'); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); let desktopStream; async function initDesktopStream() { const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] }); const selectedSource = sources[0]; desktopStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: selectedSource.id, minWidth: 1280, maxWidth: 1280, minHeight: 720, maxHeight: 720 } } }); } initDesktopStream(); // 处理WebSocket连接 wss.on('connection', (ws) => { console.log('客户端已连接'); if (!desktopStream) { ws.send(JSON.stringify({ type: 'error', message: '桌面流未初始化' })); return; } // 创建隐藏的video元素用于捕获帧 const videoElement = document.createElement('video'); videoElement.srcObject = desktopStream; videoElement.play(); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 1280; canvas.height = 720; // 每33ms捕获一帧(约30fps),转成webp格式推送 const intervalId = setInterval(() => { ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); canvas.toBlob((blob) => { if (ws.readyState === WebSocket.OPEN) { ws.send(blob); } }, 'image/webp'); }, 33); // 客户端断开时清理资源 ws.on('close', () => { clearInterval(intervalId); videoElement.pause(); videoElement.srcObject = null; console.log('客户端已断开连接'); }); }); app.use(express.static('public')); server.listen(3000, () => { console.log('服务已启动:http://localhost:3000'); });
前端页面(public/index.html)
<!DOCTYPE html> <html> <head> <title>低延迟桌面流</title> </head> <body> <video id="desktop-video" autoplay muted controls style="width: 100%;"></video> <script> const video = document.getElementById('desktop-video'); const ws = new WebSocket('ws://localhost:3000'); const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); let sourceBuffer; // MediaSource就绪后创建源缓冲区 mediaSource.addEventListener('sourceopen', () => { sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp9"'); }); // 接收WebSocket推送的帧数据 ws.onmessage = (event) => { if (event.data instanceof Blob && !sourceBuffer.updating) { sourceBuffer.appendBuffer(event.data); } }; ws.onclose = () => { mediaSource.endOfStream(); console.log('WebSocket连接已关闭'); }; </script> </body> </html>
关键提醒
- 绝对不要用
fs.createReadStream处理MediaStream,二者的应用场景完全不同:前者针对本地静态文件,后者是内存中的实时媒体数据。 - 方案一代码更简洁,适合大多数场景;方案二延迟更低,但需要处理帧编码和MediaSource的拼接逻辑,复杂度稍高。
- 如果需要同步推送音频,记得在获取
MediaStream时开启audio: true,并修改对应的MIME类型(比如video/webm; codecs=vp9,opus)。
内容的提问来源于stack exchange,提问作者Industrial Comet
相关产品推荐
相关产品推荐

