如何实现浏览器WebRTC与Node.js服务器作为对等端的音频流传输
嘿,刚好之前折腾过把Node.js作为WebRTC对等端和浏览器做音频通话的需求,你已经有浏览器自连的基础了,其实核心就是把其中一个浏览器端换成Node.js就行——不过Node本身没有浏览器自带的WebRTC API,得借助第三方库来实现。下面给你一步步拆解具体操作:
1. 给Node.js端补上WebRTC能力
Node.js原生没有RTCPeerConnection、MediaStream这些WebRTC核心API,我们需要用第三方库来模拟浏览器的WebRTC环境,最推荐的是wrtc——它完全实现了标准的浏览器WebRTC API,这样你原来写的浏览器端代码改动极小。
首先安装依赖:
npm install wrtc ws
(顺带装了ws,用来做信令的WebSocket通道,你原来浏览器自连应该也用到了类似的信令机制)
2. 改造信令逻辑:让Node同时做信令服务器+对等端
WebRTC必须通过信令交换SDP(会话描述)和ICE候选(网络地址信息),所以Node.js要同时扮演两个角色:接收浏览器信令的服务器,以及和浏览器对等的WebRTC客户端。
核心流程梳理
- 浏览器和Node建立WebSocket连接,作为信令通道
- 浏览器获取本地音频流,创建Offer并通过WebSocket发给Node
- Node接收Offer后设置为自己的远程描述,生成Answer发回浏览器
- 双方各自收集ICE候选,通过信令通道发给对方并添加到PeerConnection
- 连接建立后,双方就能交换音频流了
3. 核心代码示例
Node.js端代码(信令+WebRTC对等端)
const { RTCPeerConnection, MediaRecorder } = require('wrtc'); const WebSocket = require('ws'); // 启动WebSocket信令服务器 const wss = new WebSocket.Server({ port: 8080 }); console.log('信令服务器启动在ws://localhost:8080'); wss.on('connection', async (ws) => { // 创建Node端的PeerConnection实例,配置STUN服务器(用于NAT穿透) const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); // 监听ICE候选,一旦生成就发给浏览器 pc.onicecandidate = (event) => { if (event.candidate) { ws.send(JSON.stringify({ type: 'iceCandidate', candidate: event.candidate })); } }; // 监听浏览器发来的信令消息 ws.on('message', async (data) => { const message = JSON.parse(data); switch (message.type) { case 'offer': // 接收浏览器的Offer,设置为远程描述 await pc.setRemoteDescription(new RTCPeerConnection.RTCSessionDescription(message.offer)); // 生成Answer并发送给浏览器 const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); ws.send(JSON.stringify({ type: 'answer', answer: answer })); break; case 'iceCandidate': // 添加浏览器发来的ICE候选 await pc.addIceCandidate(new RTCPeerConnection.RTCIceCandidate(message.candidate)); break; } }); // 接收浏览器发来的音频流,这里可以选择录制或者处理 pc.ontrack = (event) => { const remoteAudioStream = event.streams[0]; console.log('收到浏览器的音频流'); // 示例:把音频流录制为文件 const recorder = new MediaRecorder(remoteAudioStream); recorder.ondataavailable = (e) => { require('fs').writeFileSync('browser-audio.wav', e.data); console.log('音频已保存到browser-audio.wav'); }; recorder.start(); }; // 错误处理 pc.onerror = (err) => { console.error('PeerConnection错误:', err); }; });
浏览器端代码(改动自你原有的自连代码)
// 连接Node的WebSocket信令服务器 const ws = new WebSocket('ws://localhost:8080'); const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); // 获取本地音频流并添加到PeerConnection navigator.mediaDevices.getUserMedia({ audio: true }) .then((localStream) => { // 本地预览(可选) document.getElementById('local-audio').srcObject = localStream; // 添加本地音频轨道到PeerConnection localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); // 创建Offer并发送给Node服务器 pc.createOffer() .then(offer => pc.setLocalDescription(offer)) .then(() => { ws.send(JSON.stringify({ type: 'offer', offer: pc.localDescription })); }); }); // 发送ICE候选到Node服务器 pc.onicecandidate = (event) => { if (event.candidate) { ws.send(JSON.stringify({ type: 'iceCandidate', candidate: event.candidate })); } }; // 接收Node服务器的信令消息 ws.onmessage = async (event) => { const message = JSON.parse(event.data); switch (message.type) { case 'answer': await pc.setRemoteDescription(new RTCSessionDescription(message.answer)); break; case 'iceCandidate': await pc.addIceCandidate(new RTCIceCandidate(message.candidate)); break; } }; // 接收Node服务器发来的音频流并播放 pc.ontrack = (event) => { const remoteStream = event.streams[0]; document.getElementById('remote-audio').srcObject = remoteStream; };
4. 关键注意事项
- 公网部署需配置TURN服务器:如果你的服务要部署到公网,仅靠STUN可能无法穿透复杂NAT,需要自己搭建coturn服务器或者使用第三方TURN服务,在PeerConnection的iceServers里添加TURN配置。
- 安全问题:线上环境一定要用
wss://(WebSocket over HTTPS),避免信令被窃听,同时确保服务器的HTTPS证书有效。 - 音频编码兼容性:默认的opus编码基本能兼容浏览器和Node端,但如果遇到音频无法播放的情况,可以检查双方支持的编码集,手动指定编码。
- 错误处理:一定要给WebSocket和PeerConnection添加
onerror监听,避免因为网络波动或配置错误导致程序崩溃。
内容的提问来源于stack exchange,提问作者moimoi
相关产品推荐
相关产品推荐

