前端獲取設(shè)備視頻流踩坑實戰(zhàn)記錄
前言
眾所周知,當使用http訪問設(shè)備攝像頭、麥克風等,需要在瀏覽器中配置安全策略。但是也會有其他問題。
最近,博主遇到了這樣一個問題,在本地開發(fā)時,可以正常訪問攝像頭和麥克風,在局域網(wǎng)內(nèi),其他用戶通過IP訪問我的開發(fā)環(huán)境,在配置了瀏覽器安全策略后,也可以訪問麥克風攝像頭。但是當項目上線時,在同樣未配置證書的情況下,使用http訪問前端,則無法訪問攝像頭,調(diào)用截圖方法時卻能正確調(diào)用,可以拿到當前視頻幀,瀏覽器也沒有任何報錯。
本來是通過https訪問的,但是由于后端未通過加密通道傳輸,項目也沒有正式上線,只是處于demo狀態(tài),所以還是改為http訪問。如果一定要通過https訪問,那么訪問接口的地址也必須是https/wss,否則屬于混合內(nèi)容,會被瀏覽器攔截。 使用https的情況下,有兩種解決方案:
- 后端需要支持TLS握手并提供“可信證書 + 主體匹配”(證書的 SAN 需匹配域名/IP)。否則會變成 TLS/證書錯誤(如NET::ERR_CERT_AUTHORITY_INVALID)
- 在前端同源的網(wǎng)關(guān)/Ingress/Nginx 暴露一個 WSS 路徑(同源同證書),再反向代理到后端。
排除問題
一、檢查元素大小是否被影響?
通過devtools發(fā)現(xiàn)元素布局正常,大小無變化,未被遮擋。
二、沒有正確設(shè)置autoplay/playsinline屬性
在一些瀏覽器中,尤其是移動端,如果沒有 autoplay(頁面加載后“嘗試”自動播放視頻。)、muted(將音軌靜音)、playsinline(允許在頁面內(nèi)聯(lián)播放) 屬性,視頻流會卡在“有流但不播放”的狀態(tài),導致黑屏但能截圖。 但是我全加了,排除
三、檢查 CSP、安全頭或者 iframe sandbox 限制
如果視頻是在 iframe 或某個帶有 CSP 頭的環(huán)境中(例如 Content-Security-Policy: default-src 'self'),可能媒體流被允許但渲染被限制。
查看頭部信息,未發(fā)現(xiàn)任何限制
四、video 還沒進入播放狀態(tài)
在以上答案都排除后,發(fā)現(xiàn)有流但是未被播放(因為截圖能拿到視頻幀),考慮是video標簽為正確綁定流。 在控制臺中打印video的狀態(tài):
const video = document.querySelector('video');
console.log(video.readyState, video.paused);
獲得的結(jié)果:
0 true
補充說明 readyState 的取值含義:
值 含義 說明 0 HAVE_NOTHING 還沒有任何關(guān)于視頻的信息 1 HAVE_METADATA 讀取到了元數(shù)據(jù)(寬高、時長等) 2 HAVE_CURRENT_DATA 有當前幀數(shù)據(jù),但可能不足以播放 3 HAVE_FUTURE_DATA 有未來幀,但可能不夠流暢 4 HAVE_ENOUGH_DATA 可以正常播放 理想情況下,應(yīng)該看到
readyState === 4,并且video.paused === false。 如果readyState長期卡在 2 或 3,而paused是true,那就是 video 播放沒有真正觸發(fā)。這種情況下就算截圖有幀,頁面也會一直黑屏。
也就是說,srcObject 并沒有被正確賦值或者 video 元素還沒在文檔流中,或者被瀏覽器靜默攔截。
我之前的代碼為:
useEffect(() => {
let isMounted = true
setIsLoading(true)
const startCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false
})
if (!isMounted) return
streamRef.current = stream
if (videoRef.current) {
videoRef.current.srcObject = stream
await videoRef.current.play().catch(() => {})
}
} catch {
setError('無法訪問攝像頭,請檢查權(quán)限')
} finally {
setIsLoading(false)
}
}
startCamera()
return () => {
isMounted = false
if (streamRef.current) {
streamRef.current.getTracks().forEach(track => track.stop())
streamRef.current = null
}
}
}, [])
首先考慮是否自動播放被攔截了,由于await videoRef.current.play().catch(() => {})catch塊里沒有任何處理,如果是被攔截了不會報錯,我們加上錯誤處理后,發(fā)現(xiàn)并沒有走到catch塊了,證明并非是播放被攔截。
查看整個代碼邏輯,如果沒有任何報錯,srcObject也賦值了視頻流。那就只有一個可能:video標簽掛載時間比獲取媒體流的時間要晚,尤其是在dom結(jié)構(gòu)上,設(shè)置了loading狀態(tài)結(jié)束后才渲染video標簽,更能證明這一點。
{isLoading ? (
<div className='text-white'>加載中...</div>
) : error ? (
<div className='text-white gap-4 flex flex-col items-center'>
<SvgIcon iconName='Error' />
{error}
</div>
) : (
<>
<video
ref={videoRef}
autoPlay
playsInline
muted
className='w-full h-full object-contain rounded-2xl'
/>
</>
)}
但是為什么開發(fā)環(huán)境下可以正常訪問呢?就是“時序競態(tài)”問題: 在生產(chǎn)環(huán)境里 getUserMedia 返回得比<video>掛載更早,首次調(diào)用發(fā)生在 videoRef 還為 null 時,綁定被丟失;開發(fā)環(huán)境因為 StrictMode/HMR 導致 effect 被再次執(zhí)行或整體更慢,恰好補上了這次遺漏,所以看不出問題。 按照我的理解,通過設(shè)置斷點:
useEffect(() => {
let isMounted = true
setIsLoading(true)
debugger // 1
const startCamera = async () => {
debugger // 2
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false
})
console.log(isLoading)
debugger // 3
setVideoId(videoId + 1)
if (!isMounted) return
streamRef.current = stream
if (videoRef.current) {
videoRef.current.srcObject = stream
await videoRef.current.play().catch(() => {})
}
} catch {
setError('無法訪問攝像頭,請檢查權(quán)限')
} finally {
debugger //4
setIsLoading(false)
}
}
startCamera()
return () => {
debugger // 5
isMounted = false
if (streamRef.current) {
streamRef.current.getTracks().forEach(track => track.stop())
streamRef.current = null
}
}
}, [])
開發(fā)環(huán)境執(zhí)行順序為:
- 執(zhí)行第一次useEffct,執(zhí)行到debugger2處,遇到await掛起,effect返回。
- 執(zhí)行第二次useEffct,依舊執(zhí)行到debugger2處,遇到await掛起
- 等待第一次await恢復,執(zhí)行到setLoading(false)處,video掛載。
- 等待第二次await恢復,執(zhí)行到debugger3處,videoRef.current此時已有值,成功掛載視頻流。
總結(jié)
到此這篇關(guān)于前端獲取設(shè)備視頻流踩坑實戰(zhàn)的文章就介紹到這了,更多相關(guān)前端獲取設(shè)備視頻流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于JavaScript實現(xiàn)網(wǎng)紅太空人表盤的完整代碼
這篇文章主要介紹了基于JavaScript實現(xiàn)網(wǎng)紅太空人表盤的完整代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
echarts圖表設(shè)置寬度100%結(jié)果為100px的解決辦法
在開發(fā)一個前端項目時需要用到Element-ui的el-tabs組件和Echart開源庫,當兩者嵌套使用時,我給Echart中的圖表寬度設(shè)置為了100%,但是實際的寬度卻只有100px,這篇文章主要給大家介紹了關(guān)于echarts圖表設(shè)置寬度100%結(jié)果為100px的解決辦法,需要的朋友可以參考下2022-12-12
layui table checked獲取選中數(shù)據(jù)方式
這篇文章主要介紹了layui table checked獲取選中數(shù)據(jù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
使用JavaScript動態(tài)設(shè)置樣式實現(xiàn)代碼(2)
使用onmouseover和onmouseout事件實現(xiàn)不同的效果而且是使用js動態(tài)實現(xiàn),本文有利于鞏固你js與css方面的知識,感興趣的你可以了解下哦,希望本文對你有所幫助2013-01-01
JavaScript與Image加載事件(onload)、加載狀態(tài)(complete)
以前寫過一個圖片等比縮放的Js函數(shù),缺陷是要等到所有圖片都加載完畢了,才能進行等比縮放。2011-02-02
uni-app h5端在jenkins構(gòu)建報錯解決
這篇文章主要為大家介紹了uni-app h5端在jenkins構(gòu)建報錯解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06

