Vue3+WebRTC实战:10分钟搞定跨浏览器视频聊天室(附完整代码)

张开发
2026/4/16 5:30:56 15 分钟阅读

分享文章

Vue3+WebRTC实战:10分钟搞定跨浏览器视频聊天室(附完整代码)
Vue3WebRTC实战10分钟搞定跨浏览器视频聊天室附完整代码最近在帮朋友改造一个在线教育平台时遇到了一个棘手的需求——需要快速实现一个跨浏览器的视频聊天功能。经过反复对比最终选择了Vue3WebRTC的技术方案不仅完美解决了兼容性问题还大幅降低了服务器带宽成本。今天就把这个实战经验分享给大家手把手教你如何用最简配置打造高性能视频聊天室。1. 环境准备与项目初始化首先确保你的开发环境已经安装了Node.js建议16.x以上版本和npm。我们使用Vite来快速搭建Vue3项目这比传统的Vue CLI启动速度更快配置更简单。npm create vitelatest webrtc-chat --template vue cd webrtc-chat npm install接下来安装核心依赖socket.io-client用于信令服务器通信vue-router管理页面路由tailwindcss快速构建UI可选npm install socket.io-client vue-router4提示WebRTC虽然可以直接在浏览器间建立连接但仍需要信令服务器来交换会话描述协议(SDP)和网络信息(ICE候选)。这里我们使用最简单的Socket.io方案。2. WebRTC核心组件封装在src/components下创建VideoChat.vue这是整个功能的核心。我们先定义基本的模板结构template div classcontainer mx-auto p-4 div classgrid grid-cols-1 md:grid-cols-2 gap-4 div classbg-gray-100 rounded-lg overflow-hidden video reflocalVideo autoplay muted classw-full/video /div div classbg-gray-100 rounded-lg overflow-hidden video refremoteVideo autoplay classw-full/video /div /div div classmt-4 flex gap-2 justify-center button clickstartCall classbtn-primary开始通话/button button clickendCall classbtn-danger结束通话/button /div /div /template3. 实现WebRTC连接流程WebRTC的连接过程主要分为以下几个步骤获取媒体设备权限访问摄像头和麦克风创建RTCPeerConnection建立点对点连接交换SDP信息通过信令服务器协商媒体能力交换ICE候选建立最佳网络路径处理媒体流显示本地和远程视频3.1 获取媒体流首先实现获取用户媒体设备的逻辑const localVideo ref(null); const remoteVideo ref(null); const localStream ref(null); const peerConnection ref(null); const getMedia async () { try { localStream.value await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); localVideo.value.srcObject localStream.value; } catch (err) { console.error(获取媒体设备失败:, err); } }; onMounted(() { getMedia(); });3.2 创建点对点连接接下来创建RTCPeerConnection实例并配置ICEconst createPeerConnection () { const configuration { iceServers: [ { urls: stun:stun.l.google.com:19302 }, { urls: turn:your-turn-server.com, username: username, credential: password } ] }; peerConnection.value new RTCPeerConnection(configuration); // 添加本地流到连接中 localStream.value.getTracks().forEach(track { peerConnection.value.addTrack(track, localStream.value); }); // 监听远程流 peerConnection.value.ontrack event { remoteVideo.value.srcObject event.streams[0]; }; // ICE候选处理 peerConnection.value.onicecandidate event { if (event.candidate) { // 通过信令服务器发送候选 socket.emit(ice-candidate, event.candidate); } }; };4. 信令服务器实现在项目根目录创建server.js作为信令服务器const express require(express); const http require(http); const { Server } require(socket.io); const app express(); const server http.createServer(app); const io new Server(server, { cors: { origin: * } }); io.on(connection, (socket) { console.log(用户连接:, socket.id); socket.on(join-room, (roomId) { socket.join(roomId); socket.to(roomId).emit(user-connected, socket.id); }); socket.on(offer, (offer, roomId) { socket.to(roomId).emit(offer, offer, socket.id); }); socket.on(answer, (answer, roomId) { socket.to(roomId).emit(answer, answer, socket.id); }); socket.on(ice-candidate, (candidate, roomId) { socket.to(roomId).emit(ice-candidate, candidate, socket.id); }); socket.on(disconnect, () { console.log(用户断开:, socket.id); }); }); server.listen(3001, () { console.log(信令服务器运行在 http://localhost:3001); });5. 完整通话流程实现回到前端组件实现完整的通话控制逻辑const roomId ref(default-room); const socket io(http://localhost:3001); const startCall async () { createPeerConnection(); socket.emit(join-room, roomId.value); const offer await peerConnection.value.createOffer(); await peerConnection.value.setLocalDescription(offer); socket.emit(offer, offer, roomId.value); }; socket.on(offer, async (offer, senderId) { if (!peerConnection.value) createPeerConnection(); await peerConnection.value.setRemoteDescription(new RTCSessionDescription(offer)); const answer await peerConnection.value.createAnswer(); await peerConnection.value.setLocalDescription(answer); socket.emit(answer, answer, roomId.value); }); socket.on(answer, async (answer) { await peerConnection.value.setRemoteDescription(new RTCSessionDescription(answer)); }); socket.on(ice-candidate, async (candidate) { try { await peerConnection.value.addIceCandidate(new RTCIceCandidate(candidate)); } catch (e) { console.error(添加ICE候选失败:, e); } }); const endCall () { if (peerConnection.value) { peerConnection.value.close(); peerConnection.value null; } localStream.value.getTracks().forEach(track track.stop()); };6. 跨浏览器兼容性处理不同浏览器对WebRTC的实现有细微差异我们需要做一些兼容处理// 统一前缀处理 const RTCPeerConnection window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; // Safari特殊处理 if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { configuration.iceServers [{ urls: stun:stun.l.google.com:19302 }]; } // 处理Firefox的getUserMedia差异 if (navigator.mediaDevices undefined) { navigator.mediaDevices {}; } if (navigator.mediaDevices.getUserMedia undefined) { navigator.mediaDevices.getUserMedia function(constraints) { const getUserMedia navigator.webkitGetUserMedia || navigator.mozGetUserMedia; return new Promise((resolve, reject) { getUserMedia.call(navigator, constraints, resolve, reject); }); }; }7. 性能优化与错误处理在实际使用中我们还需要考虑以下优化点带宽自适应根据网络状况调整视频质量断线重连处理网络中断情况日志监控记录通话质量数据// 带宽自适应示例 const updateBandwidth () { const senders peerConnection.value.getSenders(); senders.forEach(sender { if (sender.track.kind video) { const parameters sender.getParameters(); if (!parameters.encodings) { parameters.encodings [{}]; } parameters.encodings[0].maxBitrate 500000; // 500kbps sender.setParameters(parameters); } }); }; // 网络监控 setInterval(() { if (peerConnection.value) { peerConnection.value.getStats().then(stats { stats.forEach(report { if (report.type inbound-rtp report.kind video) { console.log(视频接收帧率:, report.framesPerSecond); } }); }); } }, 5000);这个方案已经在Chrome、Firefox、Edge和Safari最新版本上测试通过完整代码已上传GitHub仓库。在实际项目中部署时建议使用专业的TURN服务器来处理复杂的NAT穿透场景特别是对于企业级应用。

更多文章