You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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. 测试流程

  1. 启动信令服务器:node server.js
  2. 将前端代码保存为HTML文件,在两个不同的浏览器窗口(或不同设备)打开
  3. 其中一方点击「开始共享」,选择要共享的屏幕/窗口
  4. 另一方点击「开始共享」后,双方就能互相看到对方的共享内容

关键说明

  • 信令服务器:这里实现的是极简版本,仅做消息转发,实际生产环境需要处理房间管理、用户认证等逻辑,但两人场景足够用。
  • STUN服务器:使用谷歌的公共STUN服务器,帮助获取公网ICE候选,实现NAT穿越,大部分场景下无需自己搭建。
  • 流处理:每次开始共享时都会创建新的PeerConnection,停止时关闭连接,确保资源释放。

内容的提问来源于stack exchange,提问作者Krishna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 18:20:37