WebRTC 實戰 (1) —— 瀏覽器影音採集
在即時通訊的領域中,一切的起點都源自於「資料源」。在 WebRTC 中,這通常意味著使用者的攝影機影像與麥克風聲音。
這一篇我們不只是要獲取影音,更要深入探討如何「專業地」控制這些硬體資源。
️ 瀏覽器媒體核心:MediaDevices 家族
在 JavaScript 中,所有的硬體通訊都圍繞著 navigator.mediaDevices 介面展開。在動手寫 Code 前,我們先來看看這套 API 的全貌:
| 方法名稱 | 用途描述 | 常用場景 |
|---|---|---|
getUserMedia() | 請求存取「本機」攝影機或麥克風。 | 視訊會議、直播採集。 |
getDisplayMedia() | 請求存取「螢幕」畫面。 | 簡報共享、螢幕錄製。 |
enumerateDevices() | 獲取硬體設備清單。 | 切換外部鏡頭或麥克風。 |
getSupportedConstraints() | 查詢瀏覽器支援的硬體控制項。 | 檢查是否支援降噪或 1080p。 |
NOTE
還有一個較新的 selectAudioOutput()(目前在 Chrome 實驗中),能讓使用者直接選擇特定的喇叭输出,這對於專業音訊應用非常有用。
一、 核心門票:getUserMedia()
瀏覽器基於隱私安全,必須在取得使用者明確同意後,才能存取硬體設備。
1. 基本語法
這是一個非同步操作,會回傳一個 MediaStream 物件。
async function startCapture() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// 將串流導向 <video> 標籤
const videoElement = document.querySelector("#localVideo");
videoElement.srcObject = stream;
} catch (error) {
handleError(error);
}
}> **HTTPS 必要條件**:除了 `localhost` 以外,`getUserMedia` 只能在安全來源 (HTTPS) 下運作。這也是為什麼很多開發者在環境架設時會卡住的第一個坑。
二、 設備枚舉:你有多少個攝像頭?
一個專業的通訊軟體,應該讓使用者選擇「哪一個」攝影機或麥克風。
1. enumerateDevices
我們可以使用 enumerateDevices() 來獲取所有可用的硬體清單。
async function getDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoInputs = devices.filter((d) => d.kind === "videoinput");
videoInputs.forEach((device) => {
console.log(`${device.label} (ID: ${device.deviceId})`);
});
}2. 動態監聽:devicechange 事件
真實世界中,使用者可能會在會議中拔插耳機或接上美顏燈。我們必須即時偵測並更新 UI:
navigator.mediaDevices.addEventListener("devicechange", async (event) => {
console.log("偵測到硬體變動,正在重新獲取清單...");
await getDevices(); // 重新整理設備選單
});三、 進階控制:約束條件 (Constraints)
你不需要每次都獲取最高畫質,過大的流量會讓連線變得不穩定。
1. 解析度與幀率控制
const constraints = {
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
aspectRatio: 1.777777778, // 16:9
frameRate: { ideal: 24, max: 30 },
facingMode: "user", // "user" (鏡像) 或 "environment" (後鏡頭)
},
audio: {
echoCancellation: true, // 回音消除 (必開!)
noiseSuppression: true, // 雜訊抑制
autoGainControl: true, // 自動增益控制
},
};四、 螢幕共享:getDisplayMedia()
現代會議軟體少不了螢幕共享。雖然它回傳的也是 MediaStream,但 API 是獨立的。
async function startScreenShare() {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true,
});
// 處理螢幕串流...
} catch (err) {
console.error("螢幕分享取消或失敗", err);
}
}五、 實戰補遺:硬體控制與相容性深度指南
在真實世界中,單純的 API 呼叫往往會遇到各種邊界情況。以下是根據專案開發經驗整理的進階技巧。
1. 跨平台約束策略:ideal vs. exact
開發 WebRTC 時,行動裝置與桌面上機的行為差異是最大的坑。
- 行動端 (iOS/Android):建議多使用
ideal。因為行動裝置的攝像頭硬體層(API)會自動處理旋轉,且不同型號的解析度上限極其混亂。使用exact常導致OverconstrainedError直接掛掉。 - 桌面端 (Chrome/Safari/Edge):建議使用
exact來鎖定高品質解析度(如 1080p)。這能確保視訊會議或錄影的品質一致。
const isMobile = checkIsMobile();
const videoConstraints = isMobile
? { width: { ideal: 1080 }, height: { ideal: 1920 } } // 行動端:讓它自動適配
: { width: { exact: 1920 }, height: { exact: 1080 } }; // 桌面端:強硬要求2. 裝置方向的「羅生門」
在桌面端錄製時,瀏覽器不會因為你把筆電轉 90 度就自動交換寬高。
> **手動交換寬高**:如果你的應用程式要求「直向錄製」但使用者是在桌面端,你必須手動將 `width` 與 `height` 的約束數值交換(例如從 1920x1080 變成 1080x1920),否則請求會失敗。
3. 編碼相容性:動態探測機制
不是每台電腦都能壓製 H.264,也不是所有瀏覽器都支援 WebM。
實戰中,你應該建立一個 MIME Type 回退序列:
function getSupportedMimeType() {
const types = [
"video/mp4;codecs=avc1", // 首選:相容性最強的 H.264
"video/webm;codecs=vp9", // 次選:高品質
"video/webm;codecs=vp8", // 備選
"video/webm", // 最後的守護者
];
return types.find((type) => MediaRecorder.isTypeSupported(type)) || null;
}4. 即時 FPS 監控
有些低階手機雖然宣稱支援 30fps,但實際上錄製時會掉幀。使用 requestVideoFrameCallback (如果瀏覽器支援) 或 requestAnimationFrame 來計算真實輸出的 FPS。
// 計算真實輸出的影格數
videoElement.requestVideoFrameCallback((now, metadata) => {
frameCount++;
// 每秒計算一次即時 FPS
});5. 常見錯誤處理 (Error Handling) 百科
當你在呼叫 getUserMedia 失敗時,請不要只噴出 Error,精準的錯誤處理能讓使用者更清楚發生了什麼事:
| 錯誤名稱 | 發生原因 | 建議對策 |
|---|---|---|
NotAllowedError | 使用者拒絕授權。 | 顯示提示引導使用者手動開啟權限。 |
NotFoundError | 找不到要求的媒體型別 (例如電腦沒鏡頭)。 | 改為僅請求 audio: true 或提示檢查硬體。 |
NotReadableError | 設備被其他程式佔用 (例如開著 LINE 視訊)。 | 提示使用者關閉其他佔用鏡頭的軟體。 |
OverconstrainedError | 設定的 exact 解析度過高,硬體做不到。 | 使用 ideal 而非 exact 提升相容性。 |
AbortError | 瀏覽器或作業系統層級的硬體故障。 | 建議使用者重新開機或重連設備。 |
總結
掌握了媒體採集與真實世界的相容性處理後,我們現在已經擁有了「穩定的影音內容」。
下一篇,我們將進入 WebRTC 最神祕的地帶:信令伺服器 (Signaling Server)。
️ 進階挑戰
試著結合 MediaRecorder.isTypeSupported 寫一個自動探測器,在頁面開啟時就告訴使用者他的瀏覽器最適合哪種錄影品質。
延伸閱讀與資源
- MDN Web Docs: MediaDevices.getUserMedia()
- WebRTC.org: Getting Started