Rumah hujung hadapan web tutorial js Membina pengalaman penjejakan mata masa nyata dengan Supabase dan WebGazer.js

Membina pengalaman penjejakan mata masa nyata dengan Supabase dan WebGazer.js

Dec 28, 2024 am 11:06 AM

TL;DR:

  • Dibina dengan Supabase, React, WebGazer.js, Motion One, anime.js, Audio Stabil
  • Memanfaatkan Kehadiran & Siaran Masa Nyata Supabase (tiada jadual pangkalan data digunakan sama sekali!)
  • Repositori GitHub
  • Tapak web
  • Video demo

Satu lagi Hackathon Minggu Pelancaran Supabase dan satu lagi projek percubaan, yang dipanggil Merenung ke dalam Abyss. Ini akhirnya menjadi salah satu projek paling mudah dan kompleks pada masa yang sama. Nasib baik saya telah menikmati Cursor agak baru-baru ini, jadi saya mempunyai beberapa bantuan untuk berjaya! Saya juga ingin mengesahkan soalan dalam fikiran saya: adakah mungkin untuk menggunakan hanya ciri masa nyata daripada Supabase tanpa sebarang jadual pangkalan data? Jawapannya (mungkin agak jelas) ialah: ya, ya (sayangi awak, pasukan Realtime ♥️). Jadi mari kita menyelami sedikit lebih mendalam tentang pelaksanaannya.

Idea

Saya hanya satu hari secara rawak hanya memikirkan petikan Nietzsche tentang jurang dan bahawa adalah bagus (dan sejuk) untuk benar-benar memvisualisasikannya entah bagaimana: anda merenung ke dalam skrin gelap dan sesuatu merenung anda kembali. Tiada apa-apa lagi!

Membina projek

Pada mulanya saya mempunyai idea bahawa saya akan menggunakan Three.js untuk membuat projek ini, namun saya menyedari ini bermakna saya perlu mencipta atau mencari beberapa aset percuma untuk mata 3D. Saya memutuskan bahawa ia agak terlalu banyak, terutamanya kerana saya tidak mempunyai terlalu banyak masa untuk mengerjakan projek itu sendiri, dan sebaliknya memutuskan untuk melakukannya dalam 2D ​​dengan SVG.

Saya juga tidak mahu ia hanya visual: ia akan menjadi pengalaman yang lebih baik dengan beberapa audio juga. Jadi saya mempunyai idea bahawa ia akan menjadi hebat jika peserta boleh bercakap dengan mikrofon dan orang lain boleh mendengarnya sebagai bisikan yang tidak layak atau angin yang berlalu. Ini, bagaimanapun, ternyata sangat mencabar dan memutuskan untuk menggugurkannya sepenuhnya kerana saya tidak dapat menyambungkan WebAudio dan WebRTC bersama-sama dengan baik. Saya mempunyai komponen sisa dalam pangkalan kod yang mendengar mikrofon tempatan dan mencetuskan "bunyi angin" untuk pengguna semasa jika anda ingin melihatnya. Mungkin sesuatu untuk ditambah pada masa hadapan?

Bilik masa nyata

Sebelum mengerjakan sebarang bahan visual, saya ingin menguji persediaan masa nyata yang saya fikirkan. Memandangkan terdapat beberapa batasan dalam ciri masa nyata, saya mahu ia berfungsi supaya:

  • Terdapat maks. 10 peserta dalam satu saluran pada satu masa
    • bermakna anda perlu menyertai saluran baharu jika saluran itu penuh
  • Anda sepatutnya hanya melihat mata peserta lain

Untuk ini saya menghasilkan persediaan useEffect di mana ia bergabung secara rekursif ke saluran masa nyata seperti:

Salin selepas log masuk
Salin selepas log masuk

JoinRoom ini tinggal di dalam cangkuk useEffect dan dipanggil apabila komponen bilik dipasang. Satu kaveat yang saya dapati semasa mengusahakan ciri ini ialah param currentPresences tidak mengandungi sebarang nilai dalam acara sertai walaupun ia tersedia. Saya tidak pasti sama ada ia adalah pepijat dalam pelaksanaan atau berfungsi seperti yang dimaksudkan. Oleh itu perlu melakukan pengambilan bilik secara manual.presenceState untuk mendapatkan bilangan peserta dalam bilik apabila pengguna menyertainya.

Kami menyemak kiraan peserta dan sama ada berhenti melanggan bilik semasa dan cuba menyertai bilik lain, atau kemudian meneruskan dengan bilik semasa. Kami melakukan ini dalam acara sertai kerana penyegerakan akan terlambat (ia tercetus selepas menyertai atau meninggalkan acara).

Saya telah menguji pelaksanaan ini dengan membuka banyak tab dalam penyemak imbas saya dan semuanya kelihatan membengkak!

Selepas itu saya ingin menyahpepijat penyelesaian dengan kemas kini kedudukan tetikus dan dengan cepat menghadapi beberapa isu menghantar terlalu banyak mesej dalam saluran! Penyelesaian: pendikit panggilan.

/**
 * Creates a throttled version of a function that can only be called at most once 
 * in the specified time period.
 */
function createThrottledFunction<T extends (...args: 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)
    }
  }
}

Salin selepas log masuk
Salin selepas log masuk

Kursor muncul dengan pencipta fungsi pendikit kecil ini dan saya menggunakannya dengan siaran penjejakan mata seperti ini:

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
})
Salin selepas log masuk
Salin selepas log masuk

Ini banyak membantu! Selain itu, dalam versi awal saya mempunyai mesej penjejakan mata yang dihantar dengan kehadiran namun siaran membenarkan lebih banyak mesej sesaat, jadi saya menukar pelaksanaan kepada itu. Ia amat penting dalam penjejakan mata kerana kamera akan merakam segala-galanya sepanjang masa.

Penjejakan mata

Saya telah berjumpa dengan WebGazer.js suatu ketika dahulu apabila saya mula-mula mendapat idea untuk projek ini. Ia adalah projek yang sangat menarik dan berfungsi dengan baik!

Keupayaan pengesanan mata keseluruhan dilakukan dalam satu fungsi dalam cangkuk 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 < imageData.data.length; i += 16) {
              // Convert RGB to grayscale
              const r = imageData.data[i]
              const g = imageData.data[i + 1]
              const b = imageData.data[i + 2]
              total += (r + g + b) / 3
            }
            return total / (imageData.width * imageData.height / 4)
          }

          const rightEyeBrightness = calculateBrightness(patches.right.patch)
          const leftEyeBrightness = calculateBrightness(patches.left.patch)
          const avgBrightness = (rightEyeBrightness + leftEyeBrightness) / 2

          // Update rolling average
          if (brightnessSamples.current.length >= 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)
        }
      })
Salin selepas log masuk

Mendapatkan maklumat di mana pengguna melihat adalah mudah dan berfungsi seperti mendapatkan kedudukan tetikus pada skrin. Walau bagaimanapun, saya juga ingin menambahkan pengesanan kelipan sebagai ciri (sejuk), yang memerlukan melompat melalui beberapa gelung.

Apabila anda menggoogle maklumat tentang WebGazer dan pengesanan berkelip, anda boleh melihat beberapa baki pelaksanaan awal. Seperti terdapat komen keluar kod dalam sumber walaupun. Malangnya keupayaan seperti ini tidak keluar di perpustakaan. Anda perlu melakukannya secara manual.

Selepas banyak percubaan dan ralat, Cursor dan saya dapat menghasilkan penyelesaian yang mengira piksel & tahap kecerahan daripada data patch mata untuk menentukan masa pengguna berkelip. Ia juga mempunyai beberapa pelarasan pencahayaan dinamik kerana saya perhatikan bahawa (sekurang-kurangnya bagi saya) kamera web tidak selalu mengenali apabila anda berkelip bergantung pada pencahayaan anda. Bagi saya ia berfungsi lebih teruk apabila gambar/bilik saya lebih terang dan lebih baik dalam pencahayaan yang lebih gelap (pergi angka).

Semasa menyahpepijat keupayaan penjejakan mata (WebGazer mempunyai panggilan .setPredictionPoints yang sangat bagus yang memaparkan titik merah pada skrin untuk menggambarkan di mana anda sedang melihat), saya mendapati penjejakan itu tidak begitu tepat melainkan anda menentukur itu. Yang mana projek meminta anda lakukan sebelum menyertai mana-mana bilik.

Salin selepas log masuk
Salin selepas log masuk
/**
 * Creates a throttled version of a function that can only be called at most once 
 * in the specified time period.
 */
function createThrottledFunction<T extends (...args: 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)
    }
  }
}

Salin selepas log masuk
Salin selepas log masuk

Ia adalah pengalaman yang sangat menarik untuk melihat ini dalam tindakan! Saya menggunakan pendekatan yang sama pada garisan sekeliling dan mengarahkan Kursor untuk "meruntuhkan"nya ke arah tengah: yang ia lakukan hampir sekali dengan sekali jalan!

Mata kemudiannya akan dipaparkan dalam grid CSS ringkas dengan sel dijajarkan supaya bilik penuh kelihatan seperti mata besar.

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
})
Salin selepas log masuk
Salin selepas log masuk

Sentuhan akhir

Kemudian tampar beberapa skrin pengenalan dan muzik latar belakang yang bagus dan projek itu boleh dilaksanakan!

Audio sentiasa meningkatkan pengalaman semasa anda mengerjakan perkara seperti ini, jadi saya menggunakan Audio Stabil untuk menjana muzik latar belakang apabila pengguna "memasuki jurang". Gesaan yang saya gunakan untuk muzik itu ialah yang berikut:

Ambien, menyeramkan, muzik latar belakang, bunyi bisikan, angin, tempo perlahan, ngeri, jurang

Saya juga berpendapat bahawa skrin hitam biasa agak membosankan, jadi saya menambah beberapa bahan penapis SVG animasi pada latar belakang. Selain itu, saya menambah bulatan gelap dan kabur di tengah-tengah skrin untuk mempunyai kesan pudar yang bagus. Saya mungkin boleh melakukan ini dengan penapis SVG, namun saya tidak mahu menghabiskan terlalu banyak masa untuk perkara ini. Kemudian untuk mempunyai lebih banyak pergerakan, saya membuat latar belakang berputar pada paksinya. Kadangkala membuat animasi dengan penapis SVG agak sukar, jadi saya memutuskan untuk melakukannya dengan cara ini.

 <div>



<h2>
  
  
  Kesimpulan
</h2>

<p>Jadi begitulah: lihat secara lurus ke hadapan tentang cara melaksanakan penjejakan mata yang digayakan dengan keupayaan masa nyata Supabase. Secara peribadi saya dapati percubaan ini sangat menarik dan tidak mengalami terlalu banyak gangguan semasa mengerjakannya. Dan yang menghairankan saya tidak perlu melakukan sepanjang malam untuk malam terakhir sebelum menyerahkan projek!</p>

<p>Sila lihat projek atau video demo bagaimana hasilnya. Mungkin terdapat beberapa isu jika sekumpulan orang menggunakannya pada masa yang sama (sangat sukar untuk diuji kerana memerlukan berbilang peranti & kamera web untuk melakukannya dengan betul), tetapi saya rasa itu dalam fesyen projek hackathon? Dan jika anda mengujinya, ingat bahawa jika anda melihat mata, ia adalah orang lain yang memerhati anda di suatu tempat melalui internet!</p>


          

            
        
Salin selepas log masuk

Atas ialah kandungan terperinci Membina pengalaman penjejakan mata masa nyata dengan Supabase dan WebGazer.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

<🎜>: Bubble Gum Simulator Infinity - Cara Mendapatkan dan Menggunakan Kekunci Diraja
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Sistem Fusion, dijelaskan
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers of the Witch Tree - Cara Membuka Kunci Cangkuk Bergelut
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Tutorial Java
1664
14
Tutorial PHP
1269
29
Tutorial C#
1249
24
Enjin JavaScript: Membandingkan Pelaksanaan Enjin JavaScript: Membandingkan Pelaksanaan Apr 13, 2025 am 12:05 AM

Enjin JavaScript yang berbeza mempunyai kesan yang berbeza apabila menguraikan dan melaksanakan kod JavaScript, kerana prinsip pelaksanaan dan strategi pengoptimuman setiap enjin berbeza. 1. Analisis leksikal: Menukar kod sumber ke dalam unit leksikal. 2. Analisis Tatabahasa: Menjana pokok sintaks abstrak. 3. Pengoptimuman dan Penyusunan: Menjana kod mesin melalui pengkompil JIT. 4. Jalankan: Jalankan kod mesin. Enjin V8 mengoptimumkan melalui kompilasi segera dan kelas tersembunyi, Spidermonkey menggunakan sistem kesimpulan jenis, menghasilkan prestasi prestasi yang berbeza pada kod yang sama.

Python vs JavaScript: Keluk Pembelajaran dan Kemudahan Penggunaan Python vs JavaScript: Keluk Pembelajaran dan Kemudahan Penggunaan Apr 16, 2025 am 12:12 AM

Python lebih sesuai untuk pemula, dengan lengkung pembelajaran yang lancar dan sintaks ringkas; JavaScript sesuai untuk pembangunan front-end, dengan lengkung pembelajaran yang curam dan sintaks yang fleksibel. 1. Sintaks Python adalah intuitif dan sesuai untuk sains data dan pembangunan back-end. 2. JavaScript adalah fleksibel dan digunakan secara meluas dalam pengaturcaraan depan dan pelayan.

Dari C/C ke JavaScript: Bagaimana semuanya berfungsi Dari C/C ke JavaScript: Bagaimana semuanya berfungsi Apr 14, 2025 am 12:05 AM

Peralihan dari C/C ke JavaScript memerlukan menyesuaikan diri dengan menaip dinamik, pengumpulan sampah dan pengaturcaraan asynchronous. 1) C/C adalah bahasa yang ditaip secara statik yang memerlukan pengurusan memori manual, manakala JavaScript ditaip secara dinamik dan pengumpulan sampah diproses secara automatik. 2) C/C perlu dikumpulkan ke dalam kod mesin, manakala JavaScript adalah bahasa yang ditafsirkan. 3) JavaScript memperkenalkan konsep seperti penutupan, rantaian prototaip dan janji, yang meningkatkan keupayaan pengaturcaraan fleksibiliti dan asynchronous.

JavaScript dan Web: Fungsi teras dan kes penggunaan JavaScript dan Web: Fungsi teras dan kes penggunaan Apr 18, 2025 am 12:19 AM

Penggunaan utama JavaScript dalam pembangunan web termasuk interaksi klien, pengesahan bentuk dan komunikasi tak segerak. 1) kemas kini kandungan dinamik dan interaksi pengguna melalui operasi DOM; 2) pengesahan pelanggan dijalankan sebelum pengguna mengemukakan data untuk meningkatkan pengalaman pengguna; 3) Komunikasi yang tidak bersesuaian dengan pelayan dicapai melalui teknologi Ajax.

JavaScript in Action: Contoh dan projek dunia nyata JavaScript in Action: Contoh dan projek dunia nyata Apr 19, 2025 am 12:13 AM

Aplikasi JavaScript di dunia nyata termasuk pembangunan depan dan back-end. 1) Memaparkan aplikasi front-end dengan membina aplikasi senarai TODO, yang melibatkan operasi DOM dan pemprosesan acara. 2) Membina Restfulapi melalui Node.js dan menyatakan untuk menunjukkan aplikasi back-end.

Memahami Enjin JavaScript: Butiran Pelaksanaan Memahami Enjin JavaScript: Butiran Pelaksanaan Apr 17, 2025 am 12:05 AM

Memahami bagaimana enjin JavaScript berfungsi secara dalaman adalah penting kepada pemaju kerana ia membantu menulis kod yang lebih cekap dan memahami kesesakan prestasi dan strategi pengoptimuman. 1) aliran kerja enjin termasuk tiga peringkat: parsing, penyusun dan pelaksanaan; 2) Semasa proses pelaksanaan, enjin akan melakukan pengoptimuman dinamik, seperti cache dalam talian dan kelas tersembunyi; 3) Amalan terbaik termasuk mengelakkan pembolehubah global, mengoptimumkan gelung, menggunakan const dan membiarkan, dan mengelakkan penggunaan penutupan yang berlebihan.

Python vs JavaScript: Komuniti, Perpustakaan, dan Sumber Python vs JavaScript: Komuniti, Perpustakaan, dan Sumber Apr 15, 2025 am 12:16 AM

Python dan JavaScript mempunyai kelebihan dan kekurangan mereka sendiri dari segi komuniti, perpustakaan dan sumber. 1) Komuniti Python mesra dan sesuai untuk pemula, tetapi sumber pembangunan depan tidak kaya dengan JavaScript. 2) Python berkuasa dalam bidang sains data dan perpustakaan pembelajaran mesin, sementara JavaScript lebih baik dalam perpustakaan pembangunan dan kerangka pembangunan depan. 3) Kedua -duanya mempunyai sumber pembelajaran yang kaya, tetapi Python sesuai untuk memulakan dengan dokumen rasmi, sementara JavaScript lebih baik dengan MDNWebDocs. Pilihan harus berdasarkan keperluan projek dan kepentingan peribadi.

Python vs JavaScript: Persekitaran dan Alat Pembangunan Python vs JavaScript: Persekitaran dan Alat Pembangunan Apr 26, 2025 am 12:09 AM

Kedua -dua pilihan Python dan JavaScript dalam persekitaran pembangunan adalah penting. 1) Persekitaran pembangunan Python termasuk Pycharm, Jupyternotebook dan Anaconda, yang sesuai untuk sains data dan prototaip cepat. 2) Persekitaran pembangunan JavaScript termasuk node.js, vscode dan webpack, yang sesuai untuk pembangunan front-end dan back-end. Memilih alat yang betul mengikut keperluan projek dapat meningkatkan kecekapan pembangunan dan kadar kejayaan projek.

See all articles