跳至主要內容
Skip to content

WebRTC 實戰 (1) —— 瀏覽器影音採集

在即時通訊的領域中,一切的起點都源自於「資料源」。在 WebRTC 中,這通常意味著使用者的攝影機影像與麥克風聲音。

這一篇我們不只是要獲取影音,更要深入探討如何「專業地」控制這些硬體資源。


️ 瀏覽器媒體核心:MediaDevices 家族

在 JavaScript 中,所有的硬體通訊都圍繞著 navigator.mediaDevices 介面展開。在動手寫 Code 前,我們先來看看這套 API 的全貌:

方法名稱用途描述常用場景
getUserMedia()請求存取「本機」攝影機或麥克風。視訊會議、直播採集。
getDisplayMedia()請求存取「螢幕」畫面。簡報共享、螢幕錄製。
enumerateDevices()獲取硬體設備清單。切換外部鏡頭或麥克風。
getSupportedConstraints()查詢瀏覽器支援的硬體控制項。檢查是否支援降噪或 1080p。

NOTE

還有一個較新的 selectAudioOutput()(目前在 Chrome 實驗中),能讓使用者直接選擇特定的喇叭输出,這對於專業音訊應用非常有用。


一、 核心門票:getUserMedia()

瀏覽器基於隱私安全,必須在取得使用者明確同意後,才能存取硬體設備。

1. 基本語法

這是一個非同步操作,會回傳一個 MediaStream 物件。

javascript
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() 來獲取所有可用的硬體清單。

javascript
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:

javascript
navigator.mediaDevices.addEventListener("devicechange", async (event) => {
  console.log("偵測到硬體變動,正在重新獲取清單...");
  await getDevices(); // 重新整理設備選單
});

三、 進階控制:約束條件 (Constraints)

你不需要每次都獲取最高畫質,過大的流量會讓連線變得不穩定。

1. 解析度與幀率控制

javascript
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 是獨立的。

javascript
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)。這能確保視訊會議或錄影的品質一致。
javascript
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 回退序列

javascript
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。

javascript
// 計算真實輸出的影格數
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 寫一個自動探測器,在頁面開啟時就告訴使用者他的瀏覽器最適合哪種錄影品質。


延伸閱讀與資源

返回專案首頁 | 下一章:信令伺服器與連線中心