webrtc
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWebRTC
WebRTC
Peer Connection Setup
对等连接设置
typescript
const config: RTCConfiguration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.example.com', username: 'user', credential: 'pass' },
],
};
const pc = new RTCPeerConnection(config);
// Get local media
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
// Display remote stream
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
// ICE candidates — send to remote peer via signaling
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingChannel.send({ type: 'ice-candidate', candidate: event.candidate });
}
};typescript
const config: RTCConfiguration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.example.com', username: 'user', credential: 'pass' },
],
};
const pc = new RTCPeerConnection(config);
// 获取本地媒体
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
// 显示远程流
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
// ICE候选者 — 通过信令发送给远程对等端
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingChannel.send({ type: 'ice-candidate', candidate: event.candidate });
}
};Signaling (via Socket.IO)
信令(基于Socket.IO)
typescript
// Caller
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
socket.emit('signal', { type: 'offer', sdp: offer });
// Callee — on receiving offer
socket.on('signal', async (msg) => {
if (msg.type === 'offer') {
await pc.setRemoteDescription(msg.sdp);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit('signal', { type: 'answer', sdp: answer });
} else if (msg.type === 'answer') {
await pc.setRemoteDescription(msg.sdp);
} else if (msg.type === 'ice-candidate') {
await pc.addIceCandidate(msg.candidate);
}
});typescript
// 呼叫方
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
socket.emit('signal', { type: 'offer', sdp: offer });
// 被叫方 — 收到offer时
socket.on('signal', async (msg) => {
if (msg.type === 'offer') {
await pc.setRemoteDescription(msg.sdp);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit('signal', { type: 'answer', sdp: answer });
} else if (msg.type === 'answer') {
await pc.setRemoteDescription(msg.sdp);
} else if (msg.type === 'ice-candidate') {
await pc.addIceCandidate(msg.candidate);
}
});Data Channels
数据通道
typescript
const dataChannel = pc.createDataChannel('chat', { ordered: true });
dataChannel.onopen = () => dataChannel.send('Hello!');
dataChannel.onmessage = (e) => console.log('Received:', e.data);
// Remote side
pc.ondatachannel = (event) => {
const channel = event.channel;
channel.onmessage = (e) => console.log('Received:', e.data);
};typescript
const dataChannel = pc.createDataChannel('chat', { ordered: true });
dataChannel.onopen = () => dataChannel.send('Hello!');
dataChannel.onmessage = (e) => console.log('Received:', e.data);
// 远程端
pc.ondatachannel = (event) => {
const channel = event.channel;
channel.onmessage = (e) => console.log('Received:', e.data);
};Screen Sharing
屏幕共享
typescript
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' },
audio: true,
});
const videoTrack = screenStream.getVideoTracks()[0];
const sender = pc.getSenders().find((s) => s.track?.kind === 'video');
await sender?.replaceTrack(videoTrack);
videoTrack.onended = () => {
// User stopped sharing — switch back to camera
const cameraTrack = localStream.getVideoTracks()[0];
sender?.replaceTrack(cameraTrack);
};typescript
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' },
audio: true,
});
const videoTrack = screenStream.getVideoTracks()[0];
const sender = pc.getSenders().find((s) => s.track?.kind === 'video');
await sender?.replaceTrack(videoTrack);
videoTrack.onended = () => {
// 用户停止共享 — 切换回摄像头
const cameraTrack = localStream.getVideoTracks()[0];
sender?.replaceTrack(cameraTrack);
};LiveKit (SFU — recommended for group calls)
LiveKit(SFU — 推荐用于群组通话)
typescript
import { Room, RoomEvent } from 'livekit-client';
const room = new Room();
await room.connect(LIVEKIT_URL, accessToken);
await room.localParticipant.enableCameraAndMicrophone();
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
const element = track.attach();
document.getElementById('remote-videos')!.appendChild(element);
});
room.on(RoomEvent.ParticipantDisconnected, (participant) => {
console.log(`${participant.identity} left`);
});typescript
import { Room, RoomEvent } from 'livekit-client';
const room = new Room();
await room.connect(LIVEKIT_URL, accessToken);
await room.localParticipant.enableCameraAndMicrophone();
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
const element = track.attach();
document.getElementById('remote-videos')!.appendChild(element);
});
room.on(RoomEvent.ParticipantDisconnected, (participant) => {
console.log(`${participant.identity} left`);
});Anti-Patterns
反模式
| Anti-Pattern | Fix |
|---|---|
| No TURN server | Always configure TURN for NAT traversal |
| P2P for group calls (>4 users) | Use SFU (LiveKit, mediasoup) |
| No error handling on getUserMedia | Handle NotAllowedError, NotFoundError |
| Signaling over unencrypted channel | Use WSS (WebSocket Secure) |
| No connection state monitoring | Listen to |
| 反模式 | 修复方案 |
|---|---|
| 未配置TURN服务器 | 始终配置TURN以实现NAT穿透 |
| 群组通话(>4人)使用P2P | 使用SFU(LiveKit、mediasoup) |
| getUserMedia未做错误处理 | 处理NotAllowedError、NotFoundError |
| 信令通过未加密通道传输 | 使用WSS(WebSocket Secure) |
| 未监控连接状态 | 监听 |
Production Checklist
生产环境检查清单
- TURN server deployed and configured
- SFU for group video (>3 participants)
- Fallback UI when media access denied
- Connection quality monitoring
- Bandwidth adaptation (simulcast)
- Recording capability if needed
- 已部署并配置TURN服务器
- 为群组视频通话(>3名参与者)配置SFU
- 媒体访问被拒绝时的备用UI
- 连接质量监控
- 带宽自适应(simulcast)
- 按需配置录制功能