大多數智能手機都有前置和后置攝像頭,當你在創建視頻應用時你可能想要選擇或者切換前置、后置攝像頭。
如果你開發的是一款聊天應用,你很可能會想調用前置攝像頭,但如果你開發的是一款拍照軟件,那么你會更傾向於使用后置攝像頭。在這篇文章中我們將探討如何通過 mediaDevices API 和 media constraints (媒體約束) 選擇或者切換攝像頭。
准備工作
要跟着本文一起動手實踐你需要:
-
一款擁有兩個可供測試的攝像頭的 iOS 或 Android 設備,如果你的電腦有兩個攝像頭那也可以
-
ngrok 以便你能通過移動設備輕松訪問到你的項目(也因為我覺得 ngrok 炒雞棒)
-
這個 GitHub 庫 的代碼讓你起步
要獲取代碼,先把這個項目 clone 下來然后 checkout 到 initial-project tag 下。
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 http 5000`
好了你現在可以公網訪問這個站點了,你可以在移動設備上打開這個網站,這樣接下來就可以測試啦。確保你打開的是 HTTPS 的 URL,因為我們用的 API 只能在安全環境下使用。

網站看起來像這樣:

獲取 media stream
我們的第一個任務是從任意攝像頭獲取視頻流顯示到屏幕上。完成這個之后我們再調研如何選擇特定攝像頭。打開 app.js , 我們以從 DOM 中選擇按鈕和 video 元素開始:
// app.js
const video = document.getElementById('video');
const button = document.getElementById('button');
當用戶點擊或觸摸按鈕時,我們要使用 mediaDevices API 請求攝像頭權限。要這樣做,我們要調用 navigator.mediaDevices.getUserMedia ,傳遞 media constraints 對象。讓我們從簡單的 constraints 開始,我們只需要視頻,因此我們把 video 設置為 true,audio 設置為 false。
getUserMedia 會返回一個 promise,當 resolve 的時候我們就可以訪問到攝像頭的媒體流了。把媒體流賦值給 video 元素的 srcObj 屬性,我們就能從屏幕上看到視頻了。
button.addEventListener('click', event => {
const constraints = {
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 函數來為元素:
const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');
enumerateDevices 會返回一個 promise,所以讓我們寫一個用來接受 promise 結果的函數吧。這個函數接收一個 media device 數組作為參數。
首先要做的是清空元素看是否選擇了特定的設備,然后基於此構造 media constraints 對象。
這樣修改按鈕的點擊處理函數和 video constraints:
button.addEventListener('click', event => {
if (typeof currentStream !== 'undefined') {
stopMediaTracks(currentStream);
}
const videoConstraints = {};
if (select.value === '') {
videoConstraints.facingMode = 'environment';
} else {
videoConstraints.deviceId = { exact: select.value };
}
const constraints = {
video: videoConstraints,
audio: false
};
當我們想通過 deviceId 來選擇設備時,使用 exact 約束。 可是對於 facingMode,我們沒有使用 exact 約束, 否則在一個無法識別有沒有用戶或環境模式的設備上將會失敗,導致我們什么媒體設備也拿不到。
當我們獲得使用視頻的權限時,在點擊處理函數內,我們還要修改一些別的東西。把傳遞給函數的新流賦值給 currentStream 以便后續調用 stop,觸發另一次 enumerateDevices 的調用。
enumerateDevices 返回一個 promise,所以在我們的 then 函數中可以直接返回它,然后鏈式創建一個新的 then 把結果傳遞給 gotDevices 函數處理。
用以下代碼替換現有的 getUserMedia 調用:
button.addEventListener('click', event => {
if (typeof currentStream !== 'undefined') {
stopMediaTracks(currentStream);
}
const videoConstraints = {};
if (select.value === '') {
videoConstraints.facingMode = 'environment';
} else {
videoConstraints.deviceId = { exact: select.value };
}
const constraints = {
video: videoConstraints,
audio: false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
currentStream = stream;
video.srcObject = stream;
return navigator.mediaDevices.enumerateDevices();
})
.then(gotDevices)
.catch(error => {
console.error(error);
});
});
當你添加完所有的代碼,你的 app.js 應該看起來像這個文件一樣。刷新頁面然后你就能愉快地選擇和改變攝像頭了。這個頁面在移動設備和電腦上都有效。

下一步
我們已經看到如何通過使用 facingMode 和 deviceId 約束來選擇用戶的攝像頭。記住,在你有權限使用攝像頭之前,facingMode 更可靠,但是選擇 deviceId 更加精確。你可以從 GitHub 倉庫 中得到所有本文中的代碼,你也可以從這里嘗試在線版的應用。
如果你正在使用 Twilio Video 構建視頻應用,你可以在調用 connect 或者 createLocalVideoTrack的時候使用這些 constraints。
對於視頻聊天來說,選擇和切換攝像頭是非常有用的功能,允許用戶在你的應用界面准確地選擇他們想用的攝像頭,並且還能做到在視頻通話時分享你的屏幕。
