TL;博士:
- 使用 Supabase、React、WebGazer.js、Motion One、anime.js、穩定音訊建置
- 利用 Supabase 即時呈現和廣播(完全不使用資料庫表格!)
- GitHub 儲存庫
- 網站
- 示範影片
又一場 Supabase 啟動週黑客馬拉松和另一個實驗項目,名為 凝視深淵。 這最終成為了最簡單又最複雜的項目之一。幸運的是,我最近很喜歡 Cursor,所以我得到了一些幫助來完成它!我還想驗證我心中的一個問題:是否可以使用僅 Supabase 的即時功能而無需任何資料庫表? (也許有些明顯)答案是:是的,是的(愛你,即時團隊♥️)。因此,讓我們更深入地了解實現。
這個想法
有一天,我隨機想到了尼采關於深淵的名言,如果能夠以某種方式實際想像它會很好(而且很酷):你凝視著黑暗的屏幕,有東西在凝視著你。沒有更多了!
建構專案
最初我的想法是使用 Three.js 來製作這個項目,但我意識到這意味著我需要為 3D 眼睛創建或找到一些免費資源。我認為這有點太多了,特別是因為我沒有太多時間來處理專案本身,因此決定使用 SVG 進行 2D 製作。
我也不希望它只是視覺效果:如果有些音訊也會有更好的體驗。所以我有一個想法,如果參與者可以對著麥克風說話,而其他人可以聽到不合格的低語或風過時的聲音,那就太棒了。然而,這非常具有挑戰性,因此我決定完全放棄它,因為我無法將 WebAudio 和 WebRTC 很好地連接在一起。我的程式碼庫中確實有一個剩餘組件,如果您想看一下,它會監聽本地麥克風並為當前用戶觸發“風聲”。也許將來會添加一些東西?
即時房間
在處理任何視覺內容之前,我想測試一下我想要的即時設定。由於即時功能存在一些限制,我希望它能夠工作,以便:
- 最多有。一個頻道一次有 10 位參與者
- 表示如果一個新頻道已滿,您需要加入一個新頻道
- 你應該只看到其他參與者的眼睛
為此,我想出了一個 useEffect 設置,它遞歸地加入到實時通道,如下所示:
這個 joinRoom 位於 useEffect 鉤子內,並在安裝房間元件時被呼叫。我在開發此功能時發現的一個警告是,currentPresences 參數在連接事件中不包含任何值,即使它可用。我不確定這是否是實施中的錯誤或按預期工作。因此,每當用戶加入時,需要手動獲取 room.presenceState 來獲取房間中的參與者數量。
我們檢查參與者數量,然後取消訂閱當前房間並嘗試加入另一個房間,或者然後繼續當前房間。我們在加入事件中執行此操作,因為同步太晚了(它在加入或離開事件後觸發)。
我透過在瀏覽器中開啟大量分頁來測試此實現,一切看起來都很棒!
之後,我想透過滑鼠位置更新來調試解決方案,但很快就遇到了一些在頻道中發送過多訊息的問題!解決方案:限制調用。
/** * Creates a throttled version of a function that can only be called at most once * in the specified time period. */ function createThrottledFunction<t extends unknown> unknown>( functionToThrottle: T, waitTimeMs: number ): (...args: Parameters<t>) => void { let isWaitingToExecute = false return function throttledFunction(...args: Parameters<t>) { if (!isWaitingToExecute) { functionToThrottle.apply(this, args) isWaitingToExecute = true setTimeout(() => { isWaitingToExecute = false }, waitTimeMs) } } } </t></t></t>
遊標想出了這個小油門函數創建器,我將它與眼動追蹤廣播一起使用,如下所示:
const throttledBroadcast = createThrottledFunction((data: EyeTrackingData) => { if (currentChannel) { currentChannel.send({ type: 'broadcast', event: 'eye_tracking', payload: data }) } }, THROTTLE_MS) throttledBroadcast({ userId: userId.current, isBlinking: isCurrentlyBlinking, gazeX, gazeY })
這很有幫助!另外,在最初的版本中,我在存在狀態下發送了眼動追蹤訊息,但是廣播每秒允許更多訊息,所以我將實現切換到了這一點。這在眼動追蹤中尤其重要,因為相機會一直記錄一切。
眼球追蹤
當我第一次有了這個專案的想法時,我就遇到了 WebGazer.js。這是一個非常有趣的項目,而且效果出奇的好!
整個眼球追蹤功能是在 useEffect 掛鉤中的一個函數中完成的:
window.webgazer .setGazeListener(async (data: any) => { if (data == null || !currentChannel || !ctxRef.current) return try { // Get normalized gaze coordinates const gazeX = data.x / windowSize.width const gazeY = data.y / windowSize.height // Get video element const videoElement = document.getElementById('webgazerVideoFeed') as HTMLVideoElement if (!videoElement) { console.error('WebGazer video element not found') return } // Set canvas size to match video imageCanvasRef.current.width = videoElement.videoWidth imageCanvasRef.current.height = videoElement.videoHeight // Draw current frame to canvas ctxRef.current?.drawImage(videoElement, 0, 0) // Get eye patches const tracker = window.webgazer.getTracker() const patches = await tracker.getEyePatches( videoElement, imageCanvasRef.current, videoElement.videoWidth, videoElement.videoHeight ) if (!patches?.right?.patch?.data || !patches?.left?.patch?.data) { console.error('No eye patches detected') return } // Calculate brightness for each eye const calculateBrightness = (imageData: ImageData) => { let total = 0 for (let i = 0; i = SAMPLES_SIZE) { brightnessSamples.current.shift() // Remove oldest sample } brightnessSamples.current.push(avgBrightness) // Calculate dynamic threshold from rolling average const rollingAverage = brightnessSamples.current.reduce((a, b) => a + b, 0) / brightnessSamples.current.length const dynamicThreshold = rollingAverage * THRESHOLD_MULTIPLIER // Detect blink using dynamic threshold const blinkDetected = avgBrightness > dynamicThreshold // Debounce blink detection to avoid rapid changes if (blinkDetected !== isCurrentlyBlinking) { const now = Date.now() if (now - lastBlinkTime > 100) { // Minimum time between blink state changes isCurrentlyBlinking = blinkDetected lastBlinkTime = now } } // Use throttled broadcast instead of direct send throttledBroadcast({ userId: userId.current, isBlinking: isCurrentlyBlinking, gazeX, gazeY }) } catch (error) { console.error('Error processing gaze data:', error) } })
取得使用者正在查看的資訊很簡單,就像取得螢幕上的滑鼠位置一樣。然而,我還想添加眨眼檢測作為(一個很酷的)功能,這需要跳過一些環節。
當您在 google 上搜尋有關 WebGazer 和眨眼偵測的資訊時,您可以看到初始實作的一些剩餘內容。就像原始碼中甚至有註解掉的程式碼一樣。不幸的是,庫中不存在此類功能。您需要手動完成。
經過大量的試驗和錯誤,Cursor 和我想出了一個解決方案,可以根據眼罩數據計算像素和亮度級別,以確定用戶何時眨眼。它還具有一些動態照明調整功能,因為我注意到(至少對我來說)網路攝影機並不總是根據您的照明來識別您何時眨眼。對我來說,我的照片/房間越亮,效果越差,而在較暗的燈光下效果更好(見圖)。
在調試眼動追蹤功能時(WebGazer 有一個非常好的.setPredictionPoints 調用,它在屏幕上顯示一個紅點以可視化您正在看的位置),我注意到跟踪不是很準確除非您進行校準 這是專案要求您在加入任何房間之前要做的事情。
/** * Creates a throttled version of a function that can only be called at most once * in the specified time period. */ function createThrottledFunction<t extends unknown> unknown>( functionToThrottle: T, waitTimeMs: number ): (...args: Parameters<t>) => void { let isWaitingToExecute = false return function throttledFunction(...args: Parameters<t>) { if (!isWaitingToExecute) { functionToThrottle.apply(this, args) isWaitingToExecute = true setTimeout(() => { isWaitingToExecute = false }, waitTimeMs) } } } </t></t></t>
看到它的實際應用是一次非常酷的體驗!我對周圍的線條應用了相同的方法,並指示遊標將它們向中心“折疊”:它幾乎一氣呵成!
然後,眼睛將在一個簡單的 CSS 網格內渲染,單元格對齊,這樣整個房間看起來就像一隻大眼睛。
const throttledBroadcast = createThrottledFunction((data: EyeTrackingData) => { if (currentChannel) { currentChannel.send({ type: 'broadcast', event: 'eye_tracking', payload: data }) } }, THROTTLE_MS) throttledBroadcast({ userId: userId.current, isBlinking: isCurrentlyBlinking, gazeX, gazeY })
最後的潤飾
然後播放一些不錯的介紹螢幕和背景音樂,專案就可以開始了!
當您處理此類事情時,音訊總是可以改善體驗,因此我使用穩定音訊在使用者「進入深淵」時生成背景音樂。我用於音樂的提示如下:
環境、令人毛骨悚然、背景音樂、低語聲、風、慢節奏、怪異、深淵
我還覺得純黑的螢幕有點無聊,所以我在背景上添加了一些動畫 SVG 濾鏡。此外,我在螢幕中央添加了一個黑暗的、模糊的圓圈,以產生一些漂亮的淡入淡出效果。我可能可以使用 SVG 濾鏡來完成此操作,但我不想在這方面花費太多時間。然後為了有更多的運動,我讓背景繞著軸旋轉。有時使用 SVG 濾鏡製作動畫有點奇怪,所以我決定採取這種方式。
<div> <h2> 結論 </h2> <p>現在您已經了解了:相當直接地了解如何使用 Supabase 的即時功能實現程式化的眼球追蹤。就我個人而言,我發現這是一個非常有趣的實驗,並且在進行過程中沒有遇到太多問題。令人驚訝的是,在提交專案之前我不需要在最後一晚熬夜! </p> <p>請隨意查看該專案或示範影片的結果。如果一群人同時使用它,可能會出現一些問題(很難測試,因為它需要多個設備和網路攝影機才能正確完成),但我想這是黑客馬拉松專案的時尚?如果您確實進行了測試,請記住,如果您看到一隻眼睛,那就是其他人透過網路在某個地方看著您! </p> </div>
以上是使用 Supabase 和 WebGazer.js 建立即時眼動追蹤體驗的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Dreamweaver Mac版
視覺化網頁開發工具

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

禪工作室 13.0.1
強大的PHP整合開發環境