Web呼叫網路攝像頭及各類錯誤處理
阿新 • • 發佈:2020-12-10
最近由於業務的原因,需要在Web端頁面接入除錯各類的網路攝像頭,遇到了很多匪夷所思的問題(說的就是讀得出攝像頭的品牌,讀不出攝像頭的解析度)。於是整理了這篇文章作為備忘錄,也希望能幫到有類似的小夥伴們。
### 基礎程式碼
```javascript
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()
})
```
### 相容性
![getUserMedia相容性](https://img2020.cnblogs.com/blog/2113379/202012/2113379-20201210111749073-1047592196.png)
從[caniuse](https://www.caniuse.com/?search=getUserMedia)的相容性來看,整體相容性一般,IE系列瀏覽器完全不支援,iOS不僅需要iOS 11以上的版本,而且在APP的嵌入式頁面也無法通過api進行呼叫。
### 開發遇到的各種問題
1. 瀏覽器控制檯提示`mediaDevices.getUserMedia is not a function`
由於受瀏覽器的限制,`navigator.mediaDevices.getUserMedia`在`https`協議下是可以正常使用的,而在`http`協議下只允許`localhost`/`127.0.0.1`這兩個域名訪問,因此在開發時應做好容災處理,上線時則需要確認生產環境是否處於`https`協議下。
```javascript
let mediaDevices = navigator.mediaDevices || null
if( mediaDevices === null ) {
console.warn(`請確定是否處於https協議環境下`)
return
}
mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => {})
```
2. 獲取攝像頭的硬體引數
我在專案開發中需要用到的硬體引數主要有兩種:品牌,解析度。獲取攝像頭的品牌名稱相對來說比較簡單,可直接通過`mediaDevices.enumerateDevices()`獲取電腦上可使用的外設列表,通過`kind`欄位過濾出攝像頭。
```javascript
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原文([連結](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia)):
> 由於隱私保護的原因,無法訪問使用者的攝像頭和麥克風資訊
但也並不是完全無法獲取到,由於可以通過`video`標籤在網頁上播放攝像頭中所錄取到的內容,而`video`標籤會預設將大小設定為與攝像頭相同的大小,因此通過獲取`video`的大小來獲取攝像頭的解析度。
經過測試,獲取到的值不受樣式的影響,所以可以通過樣式控制`video`的大小,但是不會影響到解析度。
```javascript
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)
})
```
3. 無攝像頭/無使用許可權等錯誤的處理
`getUserMedia`本身集成了幾個比較常見的錯誤提示,比如常見的無攝像頭、無使用許可權等,通過`catch`能處理大部分類似的錯誤。
```javascript
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() ] || '未知錯誤')
})
```
4. 攝像頭拔出檢查
手機端由於攝像頭是手機自帶的,所以一般不需要對攝像頭是否拔出進行檢查。但在PC上有拔出攝像頭資料線的情況發生,這種時候就需要對攝像頭的狀態進行監控。
最開始想到的是,`getUserMedia`在攝像頭拔出時可能會通過`catch`報錯。然而經過多次的實驗,`getUserMedia`在攝像頭拔出時,不會響應找不到攝像頭的錯誤,想通過`catch`直接監控這種方法並不可行。
在幾乎沒有思路的時候,在`getUserMedia`文件上看到了這麼一句話:
> `getUserMedia`返回一個 [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) , 這個Promise成功後的回撥函式帶一個 [`MediaStream`](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStream) 物件作為其引數。
`MediaStream`是接收多媒體(包括音訊、視訊)內容流的一個物件,在谷歌瀏覽器(其他瀏覽器未測試)的控制檯上列印之後,其屬性值如下:
id是`MediaStream`物件的唯一識別符號,active是當前內容流是否處於活動狀態,下面幾個欄位則是谷歌瀏覽器提供的鉤子。
![MediaStream下的方法](https://img2020.cnblogs.com/blog/2113379/202012/2113379-20201210111834412-1629644532.png)
在攝像頭拔出的一瞬間,active會從true變更為false,同時觸發`oninactive`鉤子,有了狀態監聽之後事情就簡單了許多。程式碼經過測試後發現,對使用者變更攝像頭許可權也有效。
```javascript
// 判斷攝像頭是否線上
let cameraIsOnline = false
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()
cameraIsOnline = true
}).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() ] || '未知錯誤')
})
}
```
不過,相容性也非常地捉急,也有很多欄位都是提案階段,開發階段建議做好相容性處理,防止生產環境出現