Web調用網路攝影機及各類錯誤處理
- 2020 年 12 月 10 日
- 筆記
最近由於業務的原因,需要在Web端頁面接入調試各類的網路攝影機,遇到了很多匪夷所思的問題(說的就是讀得出攝影機的品牌,讀不出攝影機的解析度)。於是整理了這篇文章作為備忘錄,也希望能幫到有類似的小夥伴們。
基礎程式碼
navigator.mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => {
let video = document.getElementById('#video')
// 兼容性監測
if( 'srcObject' in video ) {
video.srcObject = stream
} else {
// 在支援srcObject的瀏覽器上,不再支援使用這種方式
video.src = URL.createObjectURL(stream)
}
await video.play()
})
兼容性
從caniuse的兼容性來看,整體兼容性一般,IE系列瀏覽器完全不支援,iOS不僅需要iOS 11以上的版本,而且在APP的嵌入式頁面也無法通過api進行調用。
開發遇到的各種問題
-
瀏覽器控制台提示
mediaDevices.getUserMedia is not a function
由於受瀏覽器的限制,
navigator.mediaDevices.getUserMedia
在https
協議下是可以正常使用的,而在http
協議下只允許localhost
/127.0.0.1
這兩個域名訪問,因此在開發時應做好容災處理,上線時則需要確認生產環境是否處於https
協議下。let mediaDevices = navigator.mediaDevices || null if( mediaDevices === null ) { console.warn(`請確定是否處於https協議環境下`) return } mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => {})
-
獲取攝影機的硬體參數
我在項目開發中需要用到的硬體參數主要有兩種:品牌,解析度。獲取攝影機的品牌名稱相對來說比較簡單,可直接通過
mediaDevices.enumerateDevices()
獲取電腦上可使用的外設列表,通過kind
欄位過濾出攝影機。if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { console.log("瀏覽器不支援enumerateDevices屬性") return } navigator.mediaDevices.enumerateDevices().then((devices) => { let devicesList = devices.filter((device) => device.kind === 'videoinput') // devicesList -> [{ kind: 'videoinput', name: 'FaceTime HD Camera (Built-in)', deviceId: xxx }] // 在devicesList獲取到的deviceId可以用於切換攝影機 // 具體方法:mediaDevices.getUserMedia({ audio: false, video: { deviceId } }) })
解析度則不能直接通過官方的api獲取到,從MDN上查到的理由是為了保護用戶的個人隱私,而解析度就在保護的範疇內。(個人非常好奇解析度為啥是隱私?)
MDN原文(鏈接):
由於隱私保護的原因,無法訪問用戶的攝影機和麥克風資訊
但也並不是完全無法獲取到,由於可以通過
video
標籤在網頁上播放攝影機中所錄取到的內容,而video
標籤會默認將大小設置為與攝影機相同的大小,因此通過獲取video
的大小來獲取攝影機的解析度。經過測試,獲取到的值不受樣式的影響,所以可以通過樣式控制
video
的大小,但是不會影響到解析度。let mediaDevices = navigator.mediaDevices || null if( mediaDevices === null ) { console.warn(`請確定是否處於https協議環境下`) return } mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => { let video = document.getElementById('#video') video.srcObject = stream await video.play() // 1280,720 console.log(video.videoWidth, video.videoHeight) })
-
無攝影機/無使用許可權等錯誤的處理
getUserMedia
本身集成了幾個比較常見的錯誤提示,比如常見的無攝影機、無使用許可權等,通過catch
能處理大部分類似的錯誤。let mediaDevices = navigator.mediaDevices || null if( mediaDevices === null ) { console.warn(`請確定是否處於https協議環境下`) return } mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => { let video = document.getElementById('#video') video.srcObject = stream await video.play() }).catch((error) => { let message = error.message || error, response = { 'permission denied': '瀏覽器禁止本頁面使用攝影機,請開啟相關的許可權', 'requested device not found': '未檢測到攝影機' } alert(response[ message.toLowerCase() ] || '未知錯誤') })
-
攝影機拔出檢查
手機端由於攝影機是手機自帶的,所以一般不需要對攝影機是否拔出進行檢查。但在PC上有拔出攝影機數據線的情況發生,這種時候就需要對攝影機的狀態進行監控。
最開始想到的是,
getUserMedia
在攝影機拔出時可能會通過catch
報錯。然而經過多次的實驗,getUserMedia
在攝影機拔出時,不會響應找不到攝影機的錯誤,想通過catch
直接監控這種方法並不可行。在幾乎沒有思路的時候,在
getUserMedia
文檔上看到了這麼一句話:getUserMedia
返回一個Promise
, 這個Promise成功後的回調函數帶一個MediaStream
對象作為其參數。MediaStream
是接收多媒體(包括音頻、影片)內容流的一個對象,在Google瀏覽器(其他瀏覽器未測試)的控制台上列印之後,其屬性值如下:id是
MediaStream
對象的唯一標識符,active是當前內容流是否處於活動狀態,下面幾個欄位則是Google瀏覽器提供的鉤子。在攝影機拔出的一瞬間,active會從true變更為false,同時觸發
oninactive
鉤子,有了狀態監聽之後事情就簡單了許多。程式碼經過測試後發現,對用戶變更攝影機許可權也有效。// 判斷攝影機是否在線 let cameraIsOnline = true const loadWebCamera = () => { let mediaDevices = navigator.mediaDevices || null if( mediaDevices === null ) { console.warn(`請確定是否處於https協議環境下`) return } mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => { let video = document.getElementById('#video') video.srcObject = stream // 兼容性處理 if( stream.oninactive === null ) { // 監聽流中斷,流中斷後將重新進行調用自身進行狀態監測 stream.oninactive = () => loadWebCamera() } await video.play() }).catch((error) => { let message = error.message || error, response = { 'permission denied': '瀏覽器禁止本頁面使用攝影機,請開啟相關的許可權', 'requested device not found': '未檢測到攝影機', 'could not start video source': '無法訪問到攝影機,請重新插拔後重試' } cameraIsOnline = false alert(response[ message.toLowerCase() ] || '未知錯誤') }) }
不過,兼容性也非常地捉急,也有很多欄位都是提案階段,開發階段建議做好兼容性處理,防止生產環境出現問題。