擷取 HTML5 中的音訊和視訊

簡介

音訊/視訊擷取一直是網頁開發的「Holy Grail」。多年來,我們一直仰賴瀏覽器外掛程式 (FlashSilverlight) 來完成工作。別擔心!

支援 HTML5。儘管不是顯而易見,但 HTML5 的興起也讓我們開始接觸裝置硬體。Geolocation (GPS)、Orientation API (加速計)、WebGL (GPU) 和 Web Audio API (音訊硬體) 都是完美的範例。這些功能非常強大,可公開位於系統基礎硬體功能之上層的高階 JavaScript API。

本教學課程導入了新的 GetUserMedia API,可讓網頁應用程式存取使用者的相機和麥克風。

getUserMedia() 之路

如果您不知道自己的記錄,我們抵達 getUserMedia() API 的方式是個有趣的故事。

過去幾年來,我們陸續推出數個「Media Capture API」的變化版本。 許多人都意識到,他們需要存取網路上的原生裝置,但這也導致每個人與其母親共同製定了新的規格。W3C 最終決定組成一個工作團隊,他們的唯一目的?瞭解瘋狂的感受!Device API Policy (DAP) 工作小組任務是整合並標準化提案豐富的提案。

我會總結 2011 年發生的事...

第 1 輪:HTML 媒體擷取

HTML Media Capture 是 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 瀏覽器 - 初次實作之一。請觀看這部影片,瞭解實際運作情形。
  • Google Chrome Android 版 (0.16 版)
  • Firefox 行動版 10.0
  • iOS6 Safari 和 Chrome (部分支援)

第 2 輪:裝置元素

許多人認為 HTML 媒體擷取的限制太多,因此出現了支援任何類型 (日後) 裝置的新規格。也因為如此,呼叫新元素的設計 <device> 元素,成為 getUserMedia() 的前身。

Opera 是第一個根據 <device> 元素建立影片擷取初始實作的瀏覽器。不久之後 (也就是同一天表示精確),WhatWG 決定抓取 <device> 標記,改用另一個更新標記,這次由 JavaScript API 呼叫 navigator.getUserMedia()。一週後,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> 元素最後是 Dodo 的。

憑藉更大規模的 WebRTC (網路即時通訊) 努力,加速找到合適的擷取 API 加速。該規格由 W3C WebRTC 工作小組所規範。Google、Opera、Mozilla 及其他一些工具已導入相關實作程序。

getUserMedia() 與 WebRTC 有關,因為這是進入該 API 組合的閘道。而是提供存取使用者本機相機/麥克風串流的方法。

支援:

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

開始使用

透過 navigator.mediaDevices.getUserMedia(),我們終於可以輕鬆使用網路攝影機和麥克風輸入功能,完全不需外掛程式。 相機存取權現在即可撥打電話,不必安裝。這個 Cookie 會直接內建在瀏覽器中。心動了嗎?

功能偵測

功能偵測是一種簡單的檢查,用來檢查 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);

如需其他設定,請參閱 constraints 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;
}