如何用JavaScript实现远程参与者间的双向屏幕共享?
两人屏幕互享实现方案
要实现两名参与者互相共享屏幕,核心依赖WebRTC进行点对点媒体流传输,同时需要一个信令服务器来交换连接协商信息(SDP、ICE候选)。下面分信令服务和前端代码两部分说明:
1. 简易信令服务器实现(Node.js + WebSocket)
WebRTC无法直接完成端到端的连接发现,需要中间服务器传递信令。这里用ws库实现一个极简的WebSocket服务器:
首先初始化项目并安装依赖:
npm init -y npm install ws
创建server.js:
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); // 存储房间内的客户端,两人房间用简单计数管理 let clients = []; wss.on('connection', (ws) => { console.log('新客户端连接'); clients.push(ws); // 转发信令给房间内的其他客户端 ws.on('message', (data) => { clients.forEach(client => { if (client !== ws && client.readyState === WebSocket.OPEN) { client.send(data); } }); }); ws.on('close', () => { console.log('客户端断开连接'); clients = clients.filter(client => client !== ws); }); }); console.log('信令服务器运行在 ws://localhost:8080');
启动服务器:
node server.js
2. 前端代码改造
基于你已有的本地屏幕捕获代码,扩展WebRTC相关逻辑,实现双向流传输:
HTML结构(新增远程视频元素)
<div> <h3>本地共享</h3> <video id="localVideo" autoplay playsinline></video> <button id="start">开始共享</button> <button id="stop">停止共享</button> </div> <div> <h3>对方共享</h3> <video id="remoteVideo" autoplay playsinline></video> </div>
JavaScript代码
const localVideo = document.getElementById("localVideo"); const remoteVideo = document.getElementById("remoteVideo"); const startElem = document.getElementById("start"); const stopElem = document.getElementById("stop"); // 屏幕捕获配置(保留你的原配置) const displayMediaOptions = { video: { cursor: "always", height: 1000, width: 1200 }, audio: false }; // WebRTC相关变量 let peerConnection; const ws = new WebSocket('ws://localhost:8080'); // 公共STUN服务器(用于获取ICE候选,实现NAT穿越) const iceConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }; // 初始化PeerConnection function initPeerConnection() { peerConnection = new RTCPeerConnection(iceConfig); // 监听ICE候选,发送给对方 peerConnection.onicecandidate = (event) => { if (event.candidate) { ws.send(JSON.stringify({ type: 'ice', candidate: event.candidate })); } }; // 接收对方的媒体流,渲染到远程视频 peerConnection.ontrack = (event) => { remoteVideo.srcObject = event.streams[0]; }; } // 信令消息处理 ws.onmessage = (event) => { const message = JSON.parse(event.data); switch (message.type) { case 'offer': // 收到对方的Offer,创建Answer并发送 peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer)) .then(() => peerConnection.createAnswer()) .then(answer => peerConnection.setLocalDescription(answer)) .then(() => { ws.send(JSON.stringify({ type: 'answer', answer: peerConnection.localDescription })); }); break; case 'answer': // 收到对方的Answer,设置为远程描述 peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer)); break; case 'ice': // 收到对方的ICE候选,添加到PeerConnection peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate)); break; } }; // 开始捕获并共享屏幕 async function startCapture() { try { // 初始化PeerConnection(如果还没创建) if (!peerConnection) initPeerConnection(); // 捕获屏幕流 const stream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); localVideo.srcObject = stream; // 将屏幕流的轨道添加到PeerConnection,发送给对方 stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); // 创建Offer并发送给对方 const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); ws.send(JSON.stringify({ type: 'offer', offer: offer })); dumpOptionsInfo(stream); } catch (err) { console.error("捕获错误: " + err); } } // 停止共享 function stopCapture() { // 停止本地屏幕捕获轨道 if (localVideo.srcObject) { localVideo.srcObject.getTracks().forEach(track => track.stop()); localVideo.srcObject = null; } // 关闭PeerConnection if (peerConnection) { peerConnection.close(); peerConnection = null; } // 清空远程视频 remoteVideo.srcObject = null; } // 打印轨道信息(保留你的原方法,修改参数适配新逻辑) function dumpOptionsInfo(stream) { const videoTrack = stream.getVideoTracks()[0]; console.info("轨道设置:"); console.info(JSON.stringify(videoTrack.getSettings(), null, 2)); console.info("轨道约束:"); console.info(JSON.stringify(videoTrack.getConstraints(), null, 2)); } // 绑定按钮事件 startElem.addEventListener("click", startCapture); stopElem.addEventListener("click", stopCapture);
3. 测试流程
- 启动信令服务器:
node server.js - 将前端代码保存为HTML文件,在两个不同的浏览器窗口(或不同设备)打开
- 其中一方点击「开始共享」,选择要共享的屏幕/窗口
- 另一方点击「开始共享」后,双方就能互相看到对方的共享内容
关键说明
- 信令服务器:这里实现的是极简版本,仅做消息转发,实际生产环境需要处理房间管理、用户认证等逻辑,但两人场景足够用。
- STUN服务器:使用谷歌的公共STUN服务器,帮助获取公网ICE候选,实现NAT穿越,大部分场景下无需自己搭建。
- 流处理:每次开始共享时都会创建新的PeerConnection,停止时关闭连接,确保资源释放。
内容的提问来源于stack exchange,提问作者Krishna
相关产品推荐
相关产品推荐

