擷取 HTML5 中的音訊和視訊

簡介

長期以來,音訊/視訊擷取功能一直是網頁開發的「聖杯」。多年來,我們都必須仰賴瀏覽器外掛程式 (FlashSilverlight) 才能完成工作。把握機會!

HTML5 就能派上用場。雖然可能不明顯,但 HTML5 的興起已帶來裝置硬體存取權的激增。地理位置 (GPS)、方向 API (加速度計)、WebGL (GPU) 和 Web Audio API (音訊硬體) 都是絕佳範例。這些功能非常強大,可公開高層級的 JavaScript API,並位於系統的基本硬體功能之上。

本教學課程將介紹新的 GetUserMedia API,讓網路應用程式存取使用者的攝影機和麥克風。

getUserMedia() 的發展歷程

如果您不瞭解這段歷史,我們如何開發出 getUserMedia() API 的過程相當有趣。

過去幾年來,已演變出多種「媒體擷取 API」。許多人意識到需要能夠在網路上存取原生裝置,但這導致大家紛紛提出新的規格。情況變得非常混亂,因此 W3C 最終決定成立工作組。他們的唯一目的?快來體驗這場瘋狂的賽事!裝置 API 政策 (DAP) 工作團隊的任務是整合並標準化大量提案。

我會盡量總結 2011 年發生的情況…

第 1 輪:HTML 媒體擷取

HTML 媒體擷取是 DAP 首次嘗試在網路上標準化媒體擷取作業。方法是多載 <input type="file">,並為 accept 參數新增值。

如要讓使用者透過網路攝影機拍攝自己的快照,可以使用 capture=camera

<input type="file" accept="image/*;capture=camera">

錄製影片或音訊的步驟類似:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

很棒吧?我特別喜歡它重複使用檔案輸入內容。就語意而言,這很有道理。這個「API」的缺點是無法即時套用效果 (例如將即時網路攝影機資料算繪至 <canvas> 並套用 WebGL 篩選器)。HTML 媒體擷取功能只允許您錄製媒體檔案或拍攝即時快照。

支援:

  • Android 3.0 瀏覽器 - 首批實作項目之一。請觀看這部影片,瞭解實際做法。
  • Android 版 Google Chrome (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari 和 Chrome (部分支援)

第 2 回合:裝置元素

許多人認為 HTML 媒體擷取功能限制太多,因此出現了支援任何類型 (未來) 裝置的新規格。毫不意外,這項設計需要一個新元素,也就是 <device> 元素,而這個元素後來成為 getUserMedia() 的前身。

Opera 是首批根據 <device> 元素建立初始實作的瀏覽器之一,不久後 (確切來說是同一天),WhatWG 決定捨棄 <device> 標記,改用另一個新興技術,這次是名為 navigator.getUserMedia() 的 JavaScript API。一週後,Opera 發布了新版本,支援更新後的 getUserMedia() 規格。同年稍晚,Microsoft 也加入行列,發布了支援新規格的 IE9 實驗室

<device> 的內容如下所示:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

支援:

很抱歉,沒有任何已發布的瀏覽器包含 <device>。 我想這樣就少了一個需要擔心的 API :) 不過 <device> 確實有兩項優點:1.) 語意明確,2.) 容易擴充,可支援音訊/視訊裝置以外的裝置。

深呼吸。這個領域的變化速度很快!

第 3 回合:WebRTC

<device> 元素最終也難逃被淘汰的命運。

由於WebRTC (WebRTC) 專案規模擴大,我們加快了尋找合適擷取 API 的速度。該規格由 W3C WebRTC 工作小組監督。Google、Opera、Mozilla 和其他幾家公司都已實作這項功能。

getUserMedia() 與 WebRTC 相關,因為這是該組 API 的閘道。可存取使用者的本機攝影機/麥克風串流。

支援:

getUserMedia() 自 Chrome 21、Opera 18 和 Firefox 17 起支援。

開始使用

有了 navigator.mediaDevices.getUserMedia(),我們終於可以存取網路攝影機和麥克風輸入內容,不必使用外掛程式。 現在只要撥打電話就能存取攝影機,不必安裝任何應用程式。這項功能直接內建於瀏覽器中,心動了嗎?

特徵偵測

功能偵測是簡單的檢查,用來確認 navigator.mediaDevices.getUserMedia 是否存在:

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

取得輸入裝置的存取權

如要使用網路攝影機或麥克風,我們需要要求權限。 navigator.mediaDevices.getUserMedia() 的第一個參數是物件,用於指定要存取每種媒體類型的詳細資料和需求。舉例來說,如要存取網路攝影機,第一個參數應為 {video: true}。如要同時使用麥克風和攝影機,請傳遞 {video: true, audio: true}

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

好的。這到底是怎麼一回事?媒體擷取是新 HTML5 API 共同運作的絕佳範例。它會與其他 HTML5 好友 <audio><video> 搭配運作。 請注意,我們並未設定 src 屬性,也沒有在 <video> 元素中加入 <source> 元素。我們不是提供媒體檔案的網址給影片,而是將 srcObject 設為代表網路攝影機的 LocalMediaStream 物件。

我也要告訴 <video> autoplay,否則它會停留在第一個影格。新增 controls 的運作方式也符合預期。

設定媒體限制 (解析度、高度、寬度)

getUserMedia() 的第一個參數也可以用來指定傳回媒體串流的更多需求 (或限制)。舉例來說,除了指出您需要影片的基本存取權 (例如 {video: true}),您還可以要求串流為 HD:

const hdConstraints = {
  video: { width: { exact:  1280} , height: { exact: 720 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

如需更多設定,請參閱限制 API

選取媒體來源

MediaDevices 介面的 enumerateDevices() 方法會要求提供可用媒體輸入和輸出裝置的清單,例如麥克風、攝影機、耳機等。傳回的 Promise 會以描述裝置的 MediaDeviceInfo 物件陣列解析。

在這個範例中,系統會選取最後找到的麥克風和攝影機做為媒體串流來源:

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

請參閱 Sam Dutton 的精彩示範,瞭解如何讓使用者選取媒體來源。

安全性

呼叫 navigator.mediaDevices.getUserMedia() 時,瀏覽器會顯示權限對話方塊,讓使用者選擇授予或拒絕相機/麥克風存取權。舉例來說,以下是 Chrome 的權限對話方塊:

Chrome 中的權限對話方塊
Chrome 中的權限對話方塊

提供備用項

如果使用者不支援 navigator.mediaDevices.getUserMedia(),其中一個選項是,如果 API 不受支援和/或呼叫因故失敗,則回退至現有影片檔案:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}