如何在WebRTC+MERN视频聊天应用中实现协作白板同步?
协作白板同步解决方案
方案一:复用WebRTC DataChannel(推荐,无需额外服务)
既然你已经基于WebRTC实现了视频通话,直接用它的**数据通道(DataChannel)**传输绘图指令是最顺畅的选择,不用额外维护独立的WebSocket服务。
1. 初始化WebRTC数据通道
在管理WebRTC会话的Session.js中,创建并监听数据通道:
// Session.js let dataChannel; // 发起方创建数据通道 function createDataChannel(peerConnection) { dataChannel = peerConnection.createDataChannel('whiteboard'); setupDataChannelListeners(); } // 配置通道监听逻辑 function setupDataChannelListeners() { dataChannel.onopen = () => console.log('白板数据通道已建立'); dataChannel.onmessage = (event) => { const drawData = JSON.parse(event.data); // 将远程绘图指令传给白板实例处理 window.whiteboard.drawRemote(drawData); }; dataChannel.onclose = () => console.log('白板数据通道已关闭'); } // 接收方监听对方创建的通道 peerConnection.ondatachannel = (event) => { dataChannel = event.channel; setupDataChannelListeners(); }; // 导出发送方法,供白板调用 export function sendDrawData(drawData) { if (dataChannel && dataChannel.readyState === 'open') { dataChannel.send(JSON.stringify(drawData)); } }
2. 修改Whiteboard.js,捕获本地绘图并发送指令
在Whiteboard.js中,把每次本地绘制的动作(坐标、颜色、线宽等)打包成数据,调用上面的sendDrawData发送给对方:
// Whiteboard.js import { sendDrawData } from './Session'; class Whiteboard { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.isDrawing = false; // 存储当前绘图的核心参数 this.currentDrawData = { type: 'path', color: '#000', lineWidth: 2, points: [] }; this.setupEventListeners(); } setupEventListeners() { this.canvas.addEventListener('mousedown', this.startDrawing.bind(this)); this.canvas.addEventListener('mousemove', this.draw.bind(this)); this.canvas.addEventListener('mouseup', this.endDrawing.bind(this)); } startDrawing(e) { this.isDrawing = true; const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; this.currentDrawData.points = [[x, y]]; // 从Options.js获取当前设置的颜色、线宽 this.currentDrawData.color = Options.getCurrentColor(); this.currentDrawData.lineWidth = Options.getCurrentLineWidth(); } draw(e) { if (!this.isDrawing) return; const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; this.currentDrawData.points.push([x, y]); // 执行本地绘制 this.drawLocal(this.currentDrawData); } endDrawing() { if (!this.isDrawing) return; this.isDrawing = false; // 将完整的绘图指令发送给对方 sendDrawData(this.currentDrawData); this.currentDrawData = { ...this.currentDrawData, points: [] }; } // 本地绘制逻辑(保留你原有的实现) drawLocal(data) { this.ctx.strokeStyle = data.color; this.ctx.lineWidth = data.lineWidth; this.ctx.beginPath(); this.ctx.moveTo(...data.points[0]); for (let i = 1; i < data.points.length; i++) { this.ctx.lineTo(...data.points[i]); } this.ctx.stroke(); } // 远程绘图重现:直接复用本地绘制逻辑 drawRemote(data) { this.drawLocal(data); } } // 初始化白板实例,挂载到全局方便调用 window.whiteboard = new Whiteboard(document.getElementById('whiteboard-canvas'));
3. 验证WebRTC连接
确保你的PeerConnection已经成功建立(视频流正常传输),DataChannel会自动复用这个连接,无需额外配置。
方案二:修复WebSocket同步逻辑
如果坚持使用WebSocket,核心是确保客户端与服务器的消息收发逻辑正确:
1. 修正server.js的WebSocket处理
// server.js const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); // 存储所有在线客户端 const clients = new Set(); wss.on('connection', (ws) => { clients.add(ws); console.log('新客户端接入'); ws.on('message', (message) => { // 转发绘图指令给除发送方外的所有客户端 clients.forEach((client) => { if (client !== ws && client.readyState === WebSocket.OPEN) { client.send(message); } }); }); ws.on('close', () => { clients.delete(ws); console.log('客户端断开连接'); }); });
2. 客户端Whiteboard.js集成WebSocket
// Whiteboard.js class Whiteboard { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.isDrawing = false; this.currentDrawData = { type: 'path', color: '#000', lineWidth: 2, points: [] }; // 建立WebSocket连接 this.ws = new WebSocket('ws://localhost:8080'); this.setupWsListeners(); this.setupEventListeners(); } setupWsListeners() { this.ws.onopen = () => console.log('WebSocket连接已建立'); this.ws.onmessage = (event) => { const drawData = JSON.parse(event.data); this.drawRemote(drawData); }; this.ws.onclose = () => console.log('WebSocket连接已关闭'); } // ... 保留原有startDrawing、draw、drawLocal方法 ... endDrawing() { if (!this.isDrawing) return; this.isDrawing = false; // 发送绘图指令到服务器 this.ws.send(JSON.stringify(this.currentDrawData)); this.currentDrawData = { ...this.currentDrawData, points: [] }; } drawRemote(data) { this.drawLocal(data); } }
关键注意事项
- 精简传输数据:只传绘图的必要参数(类型、颜色、线宽、坐标点),不要传输canvas图片数据,避免性能损耗。
- 同步初始状态:新用户加入时,可以将当前白板的绘图指令历史发送给对方,或者临时转base64图片同步初始内容。
- 错误处理:添加DataChannel/WebSocket的异常捕获,比如连接断开时提示用户,或实现自动重连逻辑。
内容的提问来源于stack exchange,提问作者ProgrammerNoob
相关产品推荐
相关产品推荐

