1. 前端代码(HTML + JavaScript)
创建一个 index.html
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC 视频通话</title>
<style>
body { font-family: Arial, sans-serif; }
#video-container { display: flex; gap: 20px; }
video { width: 400px; height: 300px; border: 1px solid #ccc; }
button { padding: 10px; margin: 5px; }
</style>
</head>
<body>
<h1>WebRTC 视频通话</h1>
<div id="video-container">
<video id="local-video" autoplay muted></video>
<video id="remote-video" autoplay></video>
</div>
<button id="start-btn">开启摄像头</button>
<button id="call-btn" disabled>发起通话</button>
<button id="hangup-btn" disabled>挂断</button>
<script>
// 获取DOM元素
const localVideo = document.getElementById('local-video');
const remoteVideo = document.getElementById('remote-video');
const startBtn = document.getElementById('start-btn');
const callBtn = document.getElementById('call-btn');
const hangupBtn = document.getElementById('hangup-btn');
// 全局变量
let localStream;
let peerConnection;
const socket = new WebSocket('ws://localhost:3000'); // 连接到信令服务器
// 1. 开启摄像头
startBtn.onclick = async () => {
try {
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localVideo.srcObject = localStream;
startBtn.disabled = true;
callBtn.disabled = false;
} catch (err) {
console.error("获取媒体设备失败:", err);
}
};
// 2. 创建RTCPeerConnection并设置ICE候选
function createPeerConnection() {
const config = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
peerConnection = new RTCPeerConnection(config);
// 添加本地流
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 监听远程流
peerConnection.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
// ICE候选处理
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
}
};
}
// 3. 发起通话
callBtn.onclick = async () => {
createPeerConnection();
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.send(JSON.stringify({ type: 'offer', offer }));
hangupBtn.disabled = false;
};
// 4. 挂断通话
hangupBtn.onclick = () => {
peerConnection.close();
peerConnection = null;
remoteVideo.srcObject = null;
hangupBtn.disabled = true;
callBtn.disabled = false;
};
// 5. WebSocket信令处理
socket.onmessage = async (event) => {
const message = JSON.parse(event.data);
if (!peerConnection) createPeerConnection();
if (message.type === 'offer') {
await peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.send(JSON.stringify({ type: 'answer', answer }));
}
else if (message.type === 'answer') {
await peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer));
}
else if (message.type === 'candidate') {
await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
</script>
</body>
</html>
2. 后端信令服务器(Node.js)
创建一个 server.js
文件:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
let clients = [];
wss.on('connection', (ws) => {
clients.push(ws);
console.log(`新用户连接,当前用户数: ${clients.length}`);
// 转发消息给其他客户端
ws.on('message', (message) => {
clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
// 清理断开连接的客户端
ws.on('close', () => {
clients = clients.filter(client => client !== ws);
console.log(`用户断开,剩余用户数: ${clients.length}`);
});
});
console.log('信令服务器运行在 ws://localhost:3000');
3. 如何运行
-
启动信令服务器:
npm install ws # 安装WebSocket依赖
node server.js
-
打开前端页面:
-
直接双击
index.html
或用 Live Server 打开。 -
打开两个浏览器窗口(或两个设备),分别点击 开启摄像头 → 发起通话。
-
关键点说明
-
信令服务器:负责交换 SDP(Offer/Answer)和 ICE 候选。
-
STUN 服务器:用于获取公网 IP(Google 的公共 STUN 服务器)。
-
媒体流:通过
getUserMedia
获取摄像头和麦克风权限。 -
RTCPeerConnection:WebRTC 的核心 API,处理连接和流传输。
扩展功能
-
房间管理:信令服务器可加入房间逻辑(如 Socket.io 房间)。
-
文字聊天:通过 WebSocket 发送文本消息。
-
屏幕共享:替换
getUserMedia
为getDisplayMedia