在 HTML5 中捕獲音頻和視頻
簡介
長久以來,音頻/視頻捕獲都是網絡開發中的“聖杯”。多年來,我們總是依賴於瀏覽器插件(Flash 或 Silverlight)實現這一點。快來看看吧!
現在輪到 HTML5 大顯身手了。也許看起來不是很顯眼,但是 HTML5 的崛起引發了對設備硬件訪問的激增。地理位置 (GPS)、Orientation API(加速計)、WebGL(GPU) 和 Web Audio API(視頻硬件)都是很好的例子。這些功能非常強大,展示了基於系統底層硬件功能之上的高級 JavaScript API。
本教程介紹了一種新 API:navigator.getUserMedia()
,可讓網絡應用訪問用戶的相機和麥克風。
getUserMedia() 的歷史
如果您還不知道,getUserMedia()
的歷史可謂一段有趣的故事。
過去幾年中出現過好幾種“Media Capture 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>
支持:
- Android 3.0 瀏覽器 - 首次實施的一個例子。請觀看此視頻,了解其實際使用情況。
- Android 版 Chrome 瀏覽器 (0.16)
除非您使用的是以上某個移動瀏覽器,否則我建議您不要使用該 API。供應商紛紛轉向 getUserMedia()
。其他任何人都不太可能會長期實施 HTML 媒體捕獲。
第 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>
確實有兩大優點:一是語義方面,二是可以輕松進行擴展,而不僅僅是支持音頻/視頻設備。
現在深吸一口氣。這玩意兒速度飛快!
第 3 輪:WebRTC
<device>
元素最終還是像渡渡鳥一樣銷聲匿跡了。
依靠 WebRTC(網絡即時通信)的大力協助,最近幾個月尋找合適捕獲 API 的步伐加快了很多。該規範由 W3C WebRTC 工作組負責監管。Google、Opera、Mozilla 和其他一些公司目前正致力於在自己的瀏覽器中實施該 API。
getUserMedia()
與 WebRTC 相關,因為它是通向這組 API 的門戶。它提供了訪問用戶本地相機/麥克風媒體流的手段。
支持:
在 Chrome 瀏覽器 18.0.1008 和更高版本中,可在 about:flags
下啟用 WebRTC。
使用入門
利用 navigator.getUserMedia()
,我們最終實現了在沒有插件的情況下訪問網絡攝像頭和麥克風輸入內容。相機訪問權限現在和調用有關,而不是和安裝有關。它直接內嵌在瀏覽器中。感到興奮了嗎?
啟用
getUserMedia()
API 還很新,只有 Google 和 Opera 在開發人員版本中加入了它。在 Chrome 18 和更高版本中,可通過訪問 about:flags
啟用該 API。
在 Chrome 瀏覽器的 about:flags
頁中啟用 getUserMedia()
。
對於 Opera,請下載某個實驗性 Android 和桌面計算機版本。
功能檢測
功能檢測是簡單地檢查是否存在 navigator.getUserMedia
:
function hasGetUserMedia() { // Note: Opera builds are unprefixed. return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); } if (hasGetUserMedia()) { // Good to go! } else { alert(‘getUserMedia() is not supported in your browser‘); }
獲取輸入設備的訪問權限:
要使用網絡攝像頭或麥克風,我們需要請求權限。getUserMedia()
的第一個參數用於指定您要訪問的媒體類型。例如,如果您要請求訪問網絡攝像頭,第一個參數就應該是 "video"
。要同時使用麥克風和相機,則傳遞 "video, audio"
:
<video autoplay></video> <script> var onFailSoHard=function(e) { console.log(‘Reeeejected!‘, e); }; // Not showing vendor prefixes. navigator.getUserMedia(‘video, audio‘, function(localMediaStream) { var video = document.querySelector(‘video‘); video.src = window.URL.createObjectURL(localMediaStream); // Note: onloadedmetadata doesn‘t fire in Chrome when using it with getUserMedia. // See crbug.com/110938. video.onloadedmetadata = function(e) { // Ready to go. Do some stuff. }; }, onFailSoHard); </script>
好吧,這到底是怎麽一回事呢?媒體捕獲是各種新 HTML5 API 進行協作的絕佳示例。參與協作的還有其他一些 HTML 元素,例如 <audio>
和 <video>
。請註意,我們不是要設置 src
屬性或在 <video>
元素中加入 <source>
元素。我們不會向視頻饋入媒體文件的網址,而是饋入從代表網絡攝像頭的 LocalMediaStream
對象獲得的 Blob 網址。
我還會將 <video>
設置為 autoplay
,否則它會停在第一幀。添加 controls
也能達到您預期的效果。
請註意:在 Chrome 瀏覽器中存在一個錯誤,導致僅僅傳遞“audio”無效:crbug.com/112367。我也無法在 Opera 中正常使用 <audio>
。
Opera 和 Chrome 瀏覽器實施的是該規範的不同版本。這導致實際使用起來要比預期的更有“挑戰性”。
在 Chrome 瀏覽器中:
該代碼段適用於 Chrome 18 和更高版本(在 about:flags
中啟用):
navigator.webkitGetUserMedia(‘audio, video‘, function(localMediaStream) { var video = document.querySelector(‘video‘); video.src = window.webkitURL.createObjectURL(localMediaStream); }, onFailSoHard);
在 Opera 中:
Opera 開發人員版本不支持該規範的更新版本。該代碼段適用於 Opera:
navigator.getUserMedia({audio: true, video: true}, function(localMediaStream) { video.src = localMediaStream; }, onFailSoHard);
關鍵的區別之處在於:
getUserMedia()
是無前綴的。- 對象作為第一個參數而不是字符串列表進行傳遞。
- 將
video.src
直接設置為LocalMediaStream
對象,而不是 Blob 網址。據我所知,Opera 最終會更新此設置,改為要求 Blob 網址。
對於這兩者:
如果您希望能跨瀏覽器通用(但是這樣很容易出問題),請嘗試如下方法:
var video = document.querySelector(‘video‘); if (navigator.getUserMedia) { navigator.getUserMedia({audio: true, video: true}, function(stream) { video.src = stream; }, onFailSoHard); } else if (navigator.webkitGetUserMedia) { navigator.webkitGetUserMedia(‘audio, video‘, function(stream) { video.src = window.webkitURL.createObjectURL(stream); }, onFailSoHard); } else { video.src = ‘somevideo.webm‘; // fallback. }
請務必查看 Mike Taylor 和 Mike Robinson 的 gUM Shield。它可以很好地將各瀏覽器實施之間的不一致“標準化”。
安全
將來,瀏覽器在調用 getUserMedia()
時可能會彈出信息欄,讓用戶選擇授予還是拒絕對其相機/麥克風的訪問權限。很遺憾,該規範在安全方面非常薄弱。目前,沒有任何瀏覽器實施了權限欄。
提供回退
對於無法獲得 getUserMedia()
支持的用戶,如果 API 不受支持且/或由於某些原因而調用失敗,可以選擇回退到現有的視頻文件:
// Not showing vendor prefixes or code that works cross-browser: function fallback(e) { video.src = ‘fallbackvideo.webm‘; } function success(stream) { video.src = window.URL.createObjectURL(stream); } if (!navigator.getUserMedia) { fallback(); } else { navigator.getUserMedia({video: true}, success, fallback); }
<canvas>
API 的 ctx.drawImage(video, 0, 0)
方法可以輕松地將 <video>
幀繪制到 <canvas>
上。當然,既然我們通過 getUserMedia()
獲得了視頻輸入,就可輕松地使用即時視頻創建照相亭應用了。
<video autoplay></video> <img src=""> <canvas style="display:none;"></canvas> var video = document.querySelector(‘video‘); var canvas = document.querySelector(‘canvas‘); var ctx = canvas.getContext(‘2d‘); var localMediaStream = null; function snapshot() { if (localMediaStream) { ctx.drawImage(video, 0, 0); // "image/webp" works in Chrome 18. In other browsers, this will fall back to image/png. document.querySelector(‘img‘).src = canvas.toDataURL(‘image/webp‘); } } video.addEventListener(‘click‘, snapshot, false); // Not showing vendor prefixes or code that works cross-browser. navigator.getUserMedia({video: true}, function(stream) { video.src = window.URL.createObjectURL(stream); localMediaStream = stream; }, onFailSoHard);
總結
總體而言,網絡上的設備訪問向來是一大難題。很多人曾經嘗試過,但是沒什麽人取得成功。大多數早期的思路從未在專有環境之外占據主導地位,也從未廣泛采用過。
真正的問題在於,網絡的安全模式與本地系統有天壤之別。例如,我可能不希望隨便什麽網站都有權訪問我的攝像機,但是這個問題很難解決。
PhoneGap 等橋接框架有助於突破這方面的限制,但這種臨時性的解決方案對於深層的根本問題而言還遠遠不夠。要讓網絡應用具備與桌面計算機應用一較高下的實力,我們需要能夠訪問本地設備。
getUserMedia()
僅僅是對新設備類型的第一波訪問。我希望在不久的將來能看到更多。
其他資源
- W3C 規範
- 布魯斯·勞森 (Bruce Lawson) 的 HTML5Doctor 文章
- 布魯斯·勞森的 dev.opera.com 文章
演示
- 實時照相亭
- 保羅·尼夫 (Paul Neave) 的 WebGL 相機效果
- Snapster
- WebGL 中的直播視頻
摘自:https://www.html5rocks.com/zh/tutorials/getusermedia/intro/
在 HTML5 中捕獲音頻和視頻