攝像頭操作指南
大多數智能手機都有前置和後置攝像頭,當你在創建視頻應用時你可能想要選擇或者切換前置、後置攝像頭。
如果你開發的是一款聊天應用,你很可能會想調用前置攝像頭,但如果你開發的是一款拍照軟體,那麼你會更傾向於使用後置攝像頭。在這篇文章中我們將探討如何通過 mediaDevices API 和 media constraints (媒體約束) 選擇或者切換攝像頭。
準備工作
要跟著本文一起動手實踐你需要:
一款擁有兩個可供測試的攝像頭的 iOS 或 Android 設備,如果你的電腦有兩個攝像頭那也可以
ngrok (https://ngrok.com/) 以便你能通過移動設備輕鬆訪問到你的項目(也因為我覺得 ngrok 炒雞棒)
這個 GitHub 庫 (https://github.com/philnash/mediadevices-camera-selection) 的代碼讓你起步
要獲取代碼,先把這個項目 clone 下來然後 checkout 到initial-projecttag 下。
git clone https://github.com/philnash/mediadevices-camera-selection.git-b initial-project
cd mediadevices-camera-selection
這個起步項目已經為你準備好了一些 HTML 和 CSS,所以我們就可以把注意集中到 JavaScript 上了。你可以直接打開 index.html,但我建議你用一款 webserver 把這些文件託管起來。我喜歡用 npm 的 serve 模塊。我在這個庫里已經引入了 serve,要使用它你需要先用 npm 安裝依賴然後啟動這個服務。
npm install
npm start
服務運行起來後,我們要用 ngrok 開啟一條隧道。serve 用 5000 埠託管文件,要用 ngrok 開隧道通到這個埠,新開一個命令行窗口輸入以下命令:
ngrok http5000
好了你現在可以公網訪問這個站點了,你可以在移動設備上打開這個網站,這樣接下來就可以測試啦。確保你打開的是 HTTPS 的 URL,因為我們用的 API 只能在安全環境下使用。
網站看起來像這樣:
獲取 media stream
我們的第一個任務是從任意攝像頭獲取視頻流顯示到屏幕上。完成這個之後我們再調研如何選擇特定攝像頭。打開 app.js , 我們以從 DOM 中選擇按鈕和 video 元素開始:
// app.js
constvideo=document.getElementById("video");
constbutton=document.getElementById("button");
當用戶點擊或觸摸按鈕時,我們要使用 mediaDevices API 請求攝像頭許可權。要這樣做,我們要調用 navigator.mediaDevices.getUserMedia ,傳遞 media constraints 對象。讓我們從簡單的 constraints 開始,我們只需要視頻,因此我們把 video 設置為 true,audio 設置為 false。
getUserMedia 會返回一個 promise,當 resolve 的時候我們就可以訪問到攝像頭的媒體流了。把媒體流賦值給 video 元素的 srcObj 屬性,我們就能從屏幕上看到視頻了。
button.addEventListener("click",event=>{
constconstraints={
video:true,
audio:false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream=>{
video.srcObject=stream;
})
.catch(error=>{
console.error(error);
});
});
保存文件,重新載入頁面然後點擊按鈕。你應該能看到一個許可權對話框請求訪問你的攝像頭,一旦授權屏幕上就應該會出現視頻。在你的電腦和手機上試一試,我在我的 iPhone 上試了,被選擇的是前置攝像頭。
如果你用的是一部 iPhone 手機,確認你在 Safari 里嘗試,因為其他瀏覽器貌似並沒有效果。
可用攝像頭
media Devices API 為我們提供了一種枚舉所有可用音頻和視頻輸入設備的方式。我們要用 enumerateDevices 函數來為 框構建選項,這樣我們就能用它來選擇我們想看的攝像頭了。再次打開 app.js,從 DOM 中選出 元素:
constvideo=document.getElementById("video");
constbutton=document.getElementById("button");
constselect=document.getElementById("select");
enumerateDevices 會返回一個 promise,所以讓我們寫一個用來接受 promise 結果的函數吧。這個函數接收一個 media device 數組作為參數。
首先要做的是清空 現有的任何選項,然後插入一個空的 。接著循環遍歷所有設備,過濾掉非 「videoinput」類型的設備。然後我們創建一個 元素,用設備 ID 當作 option value,設備 label 當作 option text。我們還要處理一種情況,如果一個設備沒有 label 存在,生成一個簡單的 「Camera n」 作為標籤。
constvideo=document.getElementById("video");
constbutton=document.getElementById("button");
constselect=document.getElementById("select");
functiongotDevices(mediaDevices){
select.innerHTML="";
select.appendChild(document.createElement("option"));
letcount=1;
mediaDevices.forEach(mediaDevice=>{
if(mediaDevice.kind==="videoinput"){
constoption=document.createElement("option");
option.value=mediaDevice.deviceId;
constlabel=mediaDevice.label||`Camera${count++}`;
consttextNode=document.createTextNode(label);
option.appendChild(textNode);
select.appendChild(option);
}
});
}
在 app.js 末尾調用一下 enumerateDevices。
navigator.mediaDevices.enumerateDevices().then(gotDevices);
刷新頁面,看一下按鈕旁邊的下拉選擇框。如果你用的是 Android ,或者使用 Chrome 或 Firefox,你就能看到可用的攝像頭名稱了。
然而在 iPhone 上,你將看到我們函數生成的通用名字 「Camera 1」 和 「Camera 2」。在 iOS 上只有你授權至少一個攝像頭給網站,你才能看到攝像頭的名字。這讓在我們的界面上選擇攝像頭變得更不方便,因為儘管你能獲取到設備 ID,你還是不能分辨哪個攝像頭是哪個。
目前我們還沒有處理下拉選擇框來改變攝像頭。在這之前,讓我們來看另一種能改變哪個攝像頭被使用的方法。
FacingMode
FacingMode (https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode) 約束是一個可以用來選擇攝像頭的替代方法。這個方法比起通過 enumerateDevices 函數獲取 ID 來說更不那麼精確,但在移動設備上效果非常好。對於這個約束,一共有四種選項可供你選擇:用戶(user),環境(environment),左(left),右(right)。MDN 上的文檔對這個約束做了詳細介紹, 以本文的目的我們將使用用戶和環境模式,在移動設備上它們正好對應到前置和後置攝像頭。
要使用 facingMode 約束我們需要修改調用 getUserMedia 時使用的 constraints 對象。對於 video 我們需要一個對象來控制具體的約束,而不是給一個 true 值。像這樣修改代碼來使用前置攝像頭:
button.addEventListener("click",event=>{
constvideoConstraints={
facingMode:"user"
};
constconstraints={
video:videoConstraints,
audio:false
};
現在可以用你的手機測試。你應該能看到前置攝像頭被使用。更改 facingMode 為 environment 再試一次, 使用的應該是後置攝像頭。讓我們把這些代碼和上面通過 enumerateDevices 獲取到的結果放到一塊兒,只要我們獲得了讀取攝像頭數據的許可權,就能構建一個攝像頭切換器了。
切換攝像頭
現在我們有在首次選擇時挑選用戶或環境攝像頭的代碼了,但如果我們要切換攝像頭那還有一丟丟額外的工作要做。
首先,我們應該保留對當前流的引用,這樣當我們切換到另一個流時就能停止當前流。在 app.js 的最前面添加一個額外的變數和輔助函數來停止流中的軌。
constvideo=document.getElementById("video");
constbutton=document.getElementById("button");
constselect=document.getElementById("select");
letcurrentStream;
functionstopMediaTracks(stream){
stream.getTracks().forEach(track=>{
track.stop();
});
}
函數 stopMediaTracks 接收一個媒體流,循環遍歷流中的每一個媒體軌道,調用 stop 方法停止媒體軌。
我們要在點擊同一個按鈕時改變攝像頭,所以我們需要更新一下按鈕的事件監聽器了。如果當前有媒體流,我們應該先停止掉它。然後我們要檢查 元素看是否選擇了特定的設備,然後基於此構造 media constraints 對象。
這樣修改按鈕的點擊處理函數和 video constraints:
button.addEventListener("click",event=>{
if(typeofcurrentStream!=="undefined"){
stopMediaTracks(currentStream);
}
constvideoConstraints={};
if(select.value===""){
videoConstraints.facingMode="environment";
}else{
videoConstraints.deviceId={exact:select.value};
}
constconstraints={
video:videoConstraints,
audio:false
};
當我們想通過 deviceId 來選擇設備時,使用 exact 約束。 可是對於 facingMode,我們沒有使用 exact 約束, 否則在一個無法識別有沒有用戶或環境模式的設備上將會失敗,導致我們什麼媒體設備也拿不到。
當我們獲得使用視頻的許可權時,在點擊處理函數內,我們還要修改一些別的東西。把傳遞給函數的新流賦值給 currentStream 以便後續調用 stop,觸發另一次 enumerateDevices 的調用。
enumerateDevices 返回一個 promise,所以在我們的 then 函數中可以直接返回它,然後鏈式創建一個新的 then 把結果傳遞給 gotDevices 函數處理。
用以下代碼替換現有的 getUserMedia 調用:
button.addEventListener("click",event=>{
if(typeofcurrentStream!=="undefined"){
stopMediaTracks(currentStream);
}
constvideoConstraints={};
if(select.value===""){
videoConstraints.facingMode="environment";
}else{
videoConstraints.deviceId={exact:select.value};
}
constconstraints={
video:videoConstraints,
audio:false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream=>{
currentStream=stream;
video.srcObject=stream;
returnnavigator.mediaDevices.enumerateDevices();
})
.then(gotDevices)
.catch(error=>{
console.error(error);
});
});
當你添加完所有的代碼,你的 app.js 應該看起來像這個文件 (https://github.com/philnash/mediadevices-camera-selection/blob/master/app.js) 一樣。刷新頁面然後你就能愉快地選擇和改變攝像頭了。這個頁面在移動設備和電腦上都有效。
下一步
我們已經看到如何通過使用 facingMode 和 deviceId 約束來選擇用戶的攝像頭。記住,在你有許可權使用攝像頭之前,facingMode 更可靠,但是選擇 deviceId 更加精確。你可以從 GitHub 倉庫 (https://github.com/philnash/mediadevices-camera-selection) 中得到所有本文中的代碼,你也可以從這裡嘗試在線版的應用 (https://philnash.github.io/mediadevices-camera-selection/)。
如果你正在使用 Twilio Video 構建視頻應用,你可以在調用 connect 或者 createLocalVideoTrack 的時候使用這些 constraints。
對於視頻聊天來說,選擇和切換攝像頭是非常有用的功能,允許用戶在你的應用界面準確地選擇他們想用的攝像頭,並且還能做到在視頻通話時分享你的屏幕。
還有哪些其他你想看到的在視頻聊天中有用的功能?或者對這個功能有什麼疑問?歡迎在評論中留言或者在 Twitter 上 @philnash。
英文:Phil Nash 譯文:眾成翻譯/Will Liu
www.zcfy.cc/article/choosing-cameras-in-javascript-with-the-mediadevices-api


※35歲以後的程序員都沒活幹了?
※Node.js 10.0.0 正式發布,帶來大量改進和修復
TAG:JavaScript |