webrtc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WebRTC

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-PatternFix
No TURN serverAlways configure TURN for NAT traversal
P2P for group calls (>4 users)Use SFU (LiveKit, mediasoup)
No error handling on getUserMediaHandle NotAllowedError, NotFoundError
Signaling over unencrypted channelUse WSS (WebSocket Secure)
No connection state monitoringListen to
connectionstatechange
event
反模式修复方案
未配置TURN服务器始终配置TURN以实现NAT穿透
群组通话(>4人)使用P2P使用SFU(LiveKit、mediasoup)
getUserMedia未做错误处理处理NotAllowedError、NotFoundError
信令通过未加密通道传输使用WSS(WebSocket Secure)
未监控连接状态监听
connectionstatechange
事件

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)
  • 按需配置录制功能