WebRTC 入門指南:實時通信完全解析
🚀 簡介
WebRTC(Web 實時通信)是一項強大的技術,支持瀏覽器和移動應用實時交換音視頻與數據——無需中間服務器中轉。它是現代視頻通話、屏幕共享工具及實時協作平台的核心底層技術。
本文將完整覆蓋 WebRTC 技術流程:從獲取用户媒體到建立安全的點對點(P2P)連接,並提供基於 TypeScript 風格的 JavaScript 實戰示例。
🎥 捕獲媒體流
什麼是媒體流(Media Stream)?
流(Stream)是連續的數據傳輸流——在 WebRTC 中,特指實時傳輸的音頻或視頻數據。
使用 getUserMedia 捕獲音視頻
通過 navigator.mediaDevices.getUserMedia() 方法可請求訪問用户的麥克風和攝像頭,示例如下:
const constraints = { audio: true, video: true }; // 配置:同時捕獲音頻和視頻
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => {
console.log('成功獲取媒體流:', mediaStream);
})
.catch((err) => {
console.error('獲取媒體流失敗:', err);
});
💡 注意:瀏覽器會先彈出權限請求,僅在用户允許後才會提供音視頻訪問權限。
在 \<video\> 元素中顯示視頻
首先在 HTML 中定義用於顯示視頻的元素:
<video autoplay playsinline id="local-video"></video>
<!-- autoplay:自動播放;playsinline:在頁面內播放(避免全屏) -->
再通過 JavaScript 將捕獲到的媒體流綁定到視頻元素:
const videoElement = document.getElementById('local-video') as HTMLVideoElement;
navigator.mediaDevices.getUserMedia({ video: true }) // 僅捕獲視頻
.then((stream) => {
videoElement.srcObject = stream; // 將媒體流賦值給視頻元素
});
🎛 枚舉與選擇設備
枚舉所有設備
通過以下方法可列出設備上所有可用的音視頻輸入/輸出設備(如麥克風、攝像頭、揚聲器):
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}:${device.label}`);
// 示例輸出:videoinput:USB 攝像頭、audioinput:內置麥克風
});
});
監聽設備變化
當有新設備(如外接攝像頭)連接或設備斷開時,可通過事件監聽實時更新設備列表:
navigator.mediaDevices.addEventListener('devicechange', () => {
console.log('設備列表已更新!');
// 可在此處重新調用 enumerateDevices() 刷新設備列表
});
🧪 使用媒體約束(Media Constraints)
媒體約束允許精細化配置音視頻參數,例如指定使用某台攝像頭、設置視頻分辨率或幀率:
const preferredDeviceId = 'abc123'; // 從 enumerateDevices() 獲取的目標設備 ID
const constraints = {
video: {
deviceId: { exact: preferredDeviceId }, // 精確指定使用某台設備
width: { ideal: 1280 }, // 理想寬度:1280px
height: { ideal: 720 }, // 理想高度:720px(720P)
frameRate: { ideal: 30 }, // 理想幀率:30fps
},
audio: {
echoCancellation: true, // 開啓回聲消除
},
};
// 按約束條件獲取媒體流
navigator.mediaDevices.getUserMedia(constraints);
🖥 捕獲屏幕
通過 getDisplayMedia() 方法可捕獲屏幕內容(如整個屏幕、特定窗口或應用),示例如下:
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true, // 屏幕捕獲僅支持視頻(無音頻)
});
// 將屏幕流綁定到視頻元素顯示
document.querySelector('video').srcObject = screenStream;
📌 注意:瀏覽器會彈出選擇窗口,要求用户指定要捕獲的屏幕、窗口或應用。
🎚 管理媒體軌道(Media Tracks)
每個媒體流(Stream)包含一個或多個軌道(Track),分別對應音頻軌道或視頻軌道。可對軌道進行單獨禁用、停止等操作:
// 獲取僅含視頻的媒體流
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
// 提取流中的所有軌道
const tracks = stream.getTracks();
// 1. 禁用軌道(臨時關閉,可重新啓用)
tracks[0].enabled = false; // 禁用第一個軌道(此處為視頻軌道)
// 2. 完全停止軌道(釋放設備資源,無法恢復)
tracks.forEach((track) => track.stop());
🌐 建立對等連接(Peer Connection)
WebRTC 的核心是 RTCPeerConnection 接口,它負責在兩個對等端(如兩台設備)之間建立直接連接:
// 配置 ICE 服務器(用於穿透 NAT,建立 P2P 連接)
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // 谷歌公共 STUN 服務器
],
};
// 創建對等連接實例
const peerConnection = new RTCPeerConnection(config);
📡 ICE、STUN、TURN 概念解析
WebRTC 依賴以下三種技術實現對等端之間的網絡連接:
- ICE(Interactive Connectivity Establishment,交互式連接建立):核心框架,負責尋找對等端之間可用的網絡路徑。
- STUN(Session Traversal Utilities for NAT,NAT 會話穿越工具):幫助設備發現自身在公網中的 IP 地址和端口(解決 NAT 遮擋問題)。
- TURN(Traversal Using Relays around NAT,通過中繼穿越 NAT):當 P2P 直接連接失敗時,作為中繼服務器轉發音視頻數據(確保通信不中斷)。
含 TURN 服務器的配置示例
const config = {
iceServers: [
// 優先使用 STUN 嘗試直接連接
{ urls: ['stun:stun.l.google.com:19302'] },
// 備用 TURN 服務器(需自行部署或使用商業服務)
{
urls: 'turn:turn.example.com', // TURN 服務器地址
username: 'user', // 認證用户名
credential: 'pass' // 認證密碼
},
],
};
🔄 ICE 候選者交換
ICE 候選者(ICE Candidate)是包含設備網絡信息(如 IP、端口、傳輸協議)的數據,對等端需交換候選者才能找到可通信的路徑。交換需通過信令服務器(如 WebSocket)完成:
發送 ICE 候選者(本地 → 遠程)
// 監聽本地 ICE 候選者生成事件
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 通過信令服務器將候選者發送給遠程對等端
signalingServer.send('ice-candidate', event.candidate);
}
};
接收 ICE 候選者(遠程 → 本地)
// 監聽信令服務器的 ICE 候選者消息
signalingServer.on('ice-candidate', async (candidate) => {
// 將遠程候選者添加到本地對等連接
await peerConnection.addIceCandidate(candidate);
});
🤝 提議/應答交換(SDP)
對等連接建立前,需交換 SDP(Session Description Protocol,會話描述協議)信息,用於協商媒體格式、編碼方式等參數。交換過程分為“提議(Offer)”和“應答(Answer)”兩步:
1. 發起方創建併發送提議
// 1. 創建提議(包含本地媒體配置)
const offer = await peerConnection.createOffer();
// 2. 將提議設置為本地描述(保存本地配置)
await peerConnection.setLocalDescription(offer);
// 3. 通過信令服務器發送提議給接收方
signalingServer.send('offer', offer);
2. 接收方處理提議併發送應答
// 1. 接收並設置遠程描述(保存發起方的配置)
peerConnection.setRemoteDescription(offer).then(async () => {
// 2. 創建應答(包含接收方的媒體配置)
const answer = await peerConnection.createAnswer();
// 3. 將應答設置為本地描述(保存接收方配置)
await peerConnection.setLocalDescription(answer);
// 4. 通過信令服務器發送應答給發起方
signalingServer.send('answer', answer);
});
🎤 向連接添加本地媒體
將本地捕獲的音視頻流添加到對等連接,才能向遠程對等端傳輸媒體:
// 1. 獲取本地音視頻流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// 2. 將流中的所有軌道添加到對等連接
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
接收遠程媒體
監聽 track 事件,獲取遠程對等端發送的媒體流並顯示:
peerConnection.addEventListener('track', (event) => {
// 從事件中提取遠程媒體流
const remoteStream = event.streams[0];
// 將遠程流綁定到視頻元素(顯示對方畫面)
document.getElementById('remote-video').srcObject = remoteStream;
});
🔁 動態管理軌道
連接建立後,可動態調整媒體軌道(如關閉麥克風、切換攝像頭):
切換麥克風(禁用/啓用音頻軌道)
// 1. 找到音頻軌道的發送器(Sender)
const audioSender = peerConnection
.getSenders()
.find((sender) => sender.track?.kind === 'audio');
// 2. 禁用/啓用音頻軌道
if (audioSender) audioSender.track.enabled = false; // 禁用(靜音)
// if (audioSender) audioSender.track.enabled = true; // 啓用(取消靜音)
連接後添加新軌道
// 假設已獲取新的視頻軌道(如切換攝像頭後的軌道)
const newVideoTrack = stream.getVideoTracks()[0];
// 將新軌道添加到對等連接
peerConnection.addTrack(newVideoTrack, stream);
❌ 關閉 WebRTC 連接
結束通信時,需停止所有媒體軌道並關閉對等連接,釋放資源:
// 1. 停止所有發送器的軌道
peerConnection.getSenders().forEach((sender) => sender.track.stop());
// 2. 關閉對等連接
peerConnection.close();
👥 羣組通話:Mesh、SFU 與 MCU 對比
當通話參與者超過 2 人時,需選擇合適的架構方案。以下是三種主流羣組通話架構的對比:
Mesh 架構
- 原理:每個參與者直接與其他所有參與者建立 P2P 連接(如 3 人通話需建立 3 條連接)。
- 優點:架構簡單,無需專用媒體服務器,延遲低。
- 缺點:參與者數量越多,CPU 佔用和網絡帶寬消耗呈指數級增長(僅適合 4 人以下小規模通話)。
SFU(選擇性轉發單元)
- 原理:所有參與者將媒體流發送到 SFU 服務器,服務器不解碼媒體,僅根據需求將流轉發給其他參與者(如只轉發説話者的流)。
- 優點:客户端負載低(僅需處理 1 條上傳流和 N 條下載流),支持中等規模羣組(20 人以內)。
- 主流方案:LiveKit、mediasoup、Mirotalk。
MCU(多點控制單元)
- 原理:所有參與者將媒體流發送到 MCU 服務器,服務器解碼並混合所有流(如將多個人的畫面合成一個分屏畫面),再將混合後的單一流轉發給所有參與者。
- 優點:客户端負載極低(僅需處理 1 條上傳流和 1 條下載流)。
- 缺點:服務器解碼/混合需大量計算資源,延遲較高,服務器成本高(適合大規模但對延遲不敏感的場景)。
🧩 總結
WebRTC 僅需幾行 JavaScript 代碼,就能實現實時音視頻與數據傳輸。雖然入門簡單,但它涉及 ICE、SDP、信令等深層技術概念。
無論你是想開發類似 Zoom 的視頻會議工具,還是實時代碼面試平台——理解如何捕獲、發送和接收點對點媒體流,都是邁出的第一步。
擴展鏈接
SpreadJS+GCExcel全棧解決方案--協同編輯的框架搭建(三)
BI使用WebSocket創建物聯網數據
低代碼對接WebSocket