最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端獲取設(shè)備視頻流踩坑實戰(zhàn)記錄

 更新時間:2026年02月12日 11:09:14   作者:悠哉摸魚大王  
在前端開發(fā)中,使用JavaScript讀取視頻流并將其保存為視頻文件是一個常見的需求,尤其是在涉及用戶上傳、本地處理和下載的場景中,這篇文章主要介紹了前端獲取設(shè)備視頻流踩坑實戰(zhàn)的相關(guā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的情況下,有兩種解決方案:

  1. 后端需要支持TLS握手并提供“可信證書 + 主體匹配”(證書的 SAN 需匹配域名/IP)。否則會變成 TLS/證書錯誤(如NET::ERR_CERT_AUTHORITY_INVALID)
  2. 在前端同源的網(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 的取值含義:

含義說明
0HAVE_NOTHING還沒有任何關(guān)于視頻的信息
1HAVE_METADATA讀取到了元數(shù)據(jù)(寬高、時長等)
2HAVE_CURRENT_DATA有當前幀數(shù)據(jù),但可能不足以播放
3HAVE_FUTURE_DATA有未來幀,但可能不夠流暢
4HAVE_ENOUGH_DATA可以正常播放

理想情況下,應(yīng)該看到 readyState === 4,并且 video.paused === false。 如果 readyState 長期卡在 2 或 3,而 pausedtrue,那就是 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í)行順序為:

  1. 執(zhí)行第一次useEffct,執(zhí)行到debugger2處,遇到await掛起,effect返回。
  2. 執(zhí)行第二次useEffct,依舊執(zhí)行到debugger2處,遇到await掛起
  3. 等待第一次await恢復,執(zhí)行到setLoading(false)處,video掛載。
  4. 等待第二次await恢復,執(zhí)行到debugger3處,videoRef.current此時已有值,成功掛載視頻流。

總結(jié)

到此這篇關(guān)于前端獲取設(shè)備視頻流踩坑實戰(zhàn)的文章就介紹到這了,更多相關(guān)前端獲取設(shè)備視頻流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

淮南市| 霞浦县| 怀安县| 武胜县| 城口县| 舞阳县| 荔浦县| 盐源县| 新昌县| 长武县| 衡山县| 凤翔县| 岳普湖县| 濮阳县| 水富县| 五大连池市| 灌南县| 阳泉市| 重庆市| 鸡西市| 双牌县| 松滋市| 香港 | 和硕县| 丰宁| 恩平市| 石林| 灌南县| 本溪市| 锡林郭勒盟| 揭阳市| 色达县| 当阳市| 马鞍山市| 大城县| 赤城县| 独山县| 蓬溪县| 林甸县| 滦南县| 绩溪县|