suchen
HeimWeb-Frontendjs-TutorialErstellen Sie mit Supabase und WebGazer.js ein Echtzeit-Eye-Tracking-Erlebnis

TL;DR:

  • Erstellt mit Supabase, React, WebGazer.js, Motion One, anime.js, Stable Audio
  • Nutzt Supabase Realtime Presence & Broadcast (es werden überhaupt keine Datenbanktabellen verwendet!)
  • GitHub-Repo
  • Website
  • Demovideo

Noch ein weiterer Supabase Launch Week Hackathon und ein weiteres experimentelles Projekt namens Gaze into the Abyss. Dies war letztendlich eines der einfachsten und komplexesten Projekte zugleich. Zum Glück hat mir Cursor in letzter Zeit ziemlich viel Spaß gemacht, sodass ich einige helfende Hände dabei hatte, es durchzustehen! Ich wollte auch eine Frage in meinem Kopf validieren: Ist es möglich, nur die Echtzeitfunktionen von Supabase ohne Datenbanktabellen zu verwenden? Die (vielleicht etwas offensichtliche) Antwort lautet: Ja, ja (liebe Grüße, Realtime-Team ♥️). Tauchen wir also etwas tiefer in die Umsetzung ein.

Die Idee

Eines Tages dachte ich zufällig an Nietzsches Zitat über den Abgrund und dass es schön (und cool) wäre, es sich tatsächlich irgendwie vorzustellen: Man starrt auf einen dunklen Bildschirm und etwas starrt zurück. Mehr ist da nicht drin!

Aufbau des Projekts

Anfangs hatte ich die Idee, Three.js zu verwenden, um dieses Projekt zu erstellen, mir wurde jedoch klar, dass dies bedeuten würde, dass ich einige kostenlose Assets für die 3D-Augen erstellen oder finden müsste. Ich entschied, dass es etwas zu viel ist, vor allem, weil ich nicht viel Zeit hatte, um am Projekt selbst zu arbeiten, und habe mich stattdessen dafür entschieden, es in 2D mit SVGs zu machen.

Ich wollte auch nicht, dass es nur visuell ist: Mit etwas Audio wäre es auch ein besseres Erlebnis. Deshalb kam mir die Idee, dass es großartig wäre, wenn die Teilnehmer über ein Mikrofon sprechen könnten und andere es als unberechtigtes Flüstern oder vorbeiziehenden Wind hören könnten. Dies erwies sich jedoch als sehr herausfordernd und ich beschloss, es ganz aufzugeben, da ich WebAudio und WebRTC nicht gut miteinander verbinden konnte. Ich habe eine übrig gebliebene Komponente in der Codebasis, die auf das lokale Mikrofon hört und „Windgeräusche“ für den aktuellen Benutzer auslöst, falls Sie einen Blick darauf werfen möchten. Vielleicht gibt es in Zukunft noch etwas hinzuzufügen?

Echtzeiträume

Bevor ich an visuellen Dingen arbeitete, wollte ich das Echtzeit-Setup testen, das ich mir vorgestellt hatte. Da es bei der Echtzeitfunktion einige Einschränkungen gibt, wollte ich, dass sie so funktioniert:

  • Es sind max. 10 Teilnehmer gleichzeitig in einem Kanal
    • Das bedeutet, dass Sie einem neuen Kanal beitreten müssen, wenn einer voll ist
  • Sie sollten nur die Augen anderer Teilnehmer sehen

Dazu habe ich mir ein useEffect-Setup ausgedacht, bei dem es rekursiv mit einem Echtzeitkanal verknüpft wird, etwa so:





Dieser joinRoom befindet sich im useEffect-Hook und wird aufgerufen, wenn die Raumkomponente gemountet wird. Eine Einschränkung, die ich bei der Arbeit an dieser Funktion festgestellt habe, war, dass der Parameter „currentPresences“ keine Werte im Join-Ereignis enthält, obwohl er verfügbar ist. Ich bin mir nicht sicher, ob es sich um einen Fehler in der Implementierung handelt oder ob es wie vorgesehen funktioniert. Daher muss ein manueller Abruf von „room.presenceState“ durchgeführt werden, um die Anzahl der Teilnehmer im Raum zu ermitteln, wann immer der Benutzer beitritt.

Wir überprüfen die Teilnehmerzahl und melden uns entweder vom aktuellen Raum ab und versuchen, einem anderen Raum beizutreten, oder fahren dann mit dem aktuellen Raum fort. Wir tun dies im Beitrittsereignis, da die Synchronisierung zu spät wäre (sie wird nach Beitritts- oder Austrittsereignissen ausgelöst).

Ich habe diese Implementierung getestet, indem ich eine ganze Reihe von Tabs in meinem Browser geöffnet habe, und alles schien gut zu sein!

Danach wollte ich die Lösung mit Mauspositionsaktualisierungen debuggen und stieß schnell auf einige Probleme beim Senden zu vieler Nachrichten im Kanal! Die Lösung: Anrufe drosseln.

/**
 * 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>

Cursor hat sich diesen kleinen Drosselfunktions-Ersteller ausgedacht und ich habe ihn für die Eye-Tracking-Übertragungen wie folgt verwendet:

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
})

Das hat sehr geholfen! Außerdem habe ich in den ersten Versionen die Eye-Tracking-Nachrichten mit Präsenz gesendet, Broadcast erlaubt jedoch mehr Nachrichten pro Sekunde, also habe ich die Implementierung stattdessen darauf umgestellt. Dies ist besonders wichtig beim Eye-Tracking, da die Kamera ständig alles aufzeichnet.

Blickverfolgung

Ich bin vor einiger Zeit auf WebGazer.js gestoßen, als ich zum ersten Mal die Idee für dieses Projekt hatte. Es ist ein sehr interessantes Projekt und funktioniert überraschend gut!

Die gesamten Eye-Tracking-Funktionen werden in einer Funktion in einem useEffect-Hook ausgeführt:

    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)
        }
      })

Das Ermitteln der Informationen dort, wo der Benutzer gerade hinschaut, ist einfach und funktioniert wie das Ermitteln der Mauspositionen auf dem Bildschirm. Allerdings wollte ich auch die Blinzelerkennung als (coole) Funktion hinzufügen, was einige Hürden erforderte.

Wenn Sie Informationen zu WebGazer und Blinzelerkennung googeln, können Sie einige Überreste einer ersten Implementierung sehen. Es gibt sogar auskommentierten Code in der Quelle. Leider sind diese Funktionen in der Bibliothek nicht verfügbar. Sie müssen dies manuell tun.

Nach vielen Versuchen konnten Cursor und ich eine Lösung entwickeln, die Pixel und Helligkeitsstufen aus den Augenklappendaten berechnet, um festzustellen, wann der Benutzer blinzelt. Es gibt auch einige dynamische Beleuchtungsanpassungen, da mir aufgefallen ist, dass die Webcam (zumindest für mich) je nach Beleuchtung nicht immer erkennt, wann Sie blinzeln. Bei mir funktionierte es schlechter, je heller mein Bild/Raum war, und besser bei dunklerer Beleuchtung (siehe Abbildung).

Beim Debuggen der Eye-Tracking-Funktionen (WebGazer verfügt über einen sehr schönen .setPredictionPoints-Aufruf, der einen roten Punkt auf dem Bildschirm anzeigt, um zu visualisieren, wohin Sie schauen) ist mir aufgefallen, dass das Tracking nicht sehr genau ist es sei denn, Sie kalibrieren es. Dazu werden Sie im Projekt aufgefordert, bevor Sie einem Raum beitreten.







/**
 * 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>

Es war eine sehr coole Erfahrung, dies in Aktion zu sehen! Ich habe den gleichen Ansatz auf die umgebenden Linien angewendet und den Cursor angewiesen, sie zur Mitte hin zu „kollabieren“: was praktisch auf einmal geschah!

Die Augen würden dann in einem einfachen CSS-Raster gerendert, wobei die Zellen so ausgerichtet sind, dass ein vollständiger Raum wie ein großes Auge aussieht.

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
})

Letzte Handgriffe

Dann legen Sie noch einen schönen Einführungsbildschirm und Hintergrundmusik ein und das Projekt kann losgehen!

Audio verbessert immer das Erlebnis, wenn man an solchen Dingen arbeitet, deshalb habe ich Stable Audio verwendet, um eine Hintergrundmusik zu erzeugen, wenn der Benutzer „den Abgrund betritt“. Die Eingabeaufforderung, die ich für die Musik verwendet habe, war folgende:

Ambiente, gruselig, Hintergrundmusik, flüsternde Geräusche, Winde, langsames Tempo, unheimlich, Abgrund

Ich fand auch, dass ein einfacher schwarzer Bildschirm etwas langweilig ist, also habe ich ein paar animierte SVG-Filtersachen im Hintergrund hinzugefügt. Zusätzlich habe ich einen dunklen, unscharfen Kreis in der Mitte des Bildschirms hinzugefügt, um einen schönen Fade-Effekt zu erzielen. Ich hätte das wahrscheinlich mit SVG-Filtern machen können, aber ich wollte nicht zu viel Zeit damit verbringen. Um noch mehr Bewegung zu haben, habe ich den Hintergrund um seine Achse drehen lassen. Manchmal ist das Erstellen von Animationen mit den SVG-Filtern etwas kompliziert, deshalb habe ich mich stattdessen für diese Methode entschieden.

 <div>



<h2>
  
  
  Abschluss
</h2>

<p>Da haben Sie es also: Ein ziemlich einfacher Einblick in die Implementierung eines stilisierten Eye-Trackings mit den Echtzeitfunktionen von Supabase. Ich persönlich fand das ein sehr interessantes Experiment und hatte bei der Arbeit nicht allzu viele Probleme. Und überraschenderweise musste ich die letzte Nacht nicht durcharbeiten, bevor ich das Projekt einreichte!</p>

<p>Schauen Sie sich gerne das Projekt oder das Demo-Video an, wie es entstanden ist. Es kann zu Problemen kommen, wenn viele Leute es gleichzeitig verwenden (sehr schwer zu testen, da mehrere Geräte und Webcams erforderlich sind, um es richtig zu machen), aber ich denke, das ist bei Hackathon-Projekten üblich? Und wenn Sie es ausprobieren, denken Sie daran: Wenn Sie ein Auge sehen, ist es jemand anderes, der Sie irgendwo im Internet beobachtet!</p>


          </div>

            
        

Das obige ist der detaillierte Inhalt vonErstellen Sie mit Supabase und WebGazer.js ein Echtzeit-Eye-Tracking-Erlebnis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Die Ursprünge von JavaScript: Erforschung seiner ImplementierungsspracheDie Ursprünge von JavaScript: Erforschung seiner ImplementierungsspracheApr 29, 2025 am 12:51 AM

JavaScript stammt aus dem Jahr 1995 und wurde von Brandon Ike erstellt und realisierte die Sprache in C. 1.C-Sprache bietet Programmierfunktionen auf hoher Leistung und Systemebene für JavaScript. 2. Die Speicherverwaltung und die Leistungsoptimierung von JavaScript basieren auf C -Sprache. 3. Die plattformübergreifende Funktion der C-Sprache hilft JavaScript, auf verschiedenen Betriebssystemen effizient zu laufen.

Hinter den Kulissen: Welche Sprache macht JavaScript?Hinter den Kulissen: Welche Sprache macht JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript wird in Browsern und Node.js -Umgebungen ausgeführt und stützt sich auf die JavaScript -Engine, um Code zu analysieren und auszuführen. 1) abstrakter Syntaxbaum (AST) in der Parsenstufe erzeugen; 2) AST in die Kompilierungsphase in Bytecode oder Maschinencode umwandeln; 3) Führen Sie den kompilierten Code in der Ausführungsstufe aus.

Die Zukunft von Python und JavaScript: Trends und VorhersagenDie Zukunft von Python und JavaScript: Trends und VorhersagenApr 27, 2025 am 12:21 AM

Zu den zukünftigen Trends von Python und JavaScript gehören: 1. Python wird seine Position in den Bereichen wissenschaftlicher Computer und KI konsolidieren. JavaScript wird die Entwicklung der Web-Technologie fördern. Beide werden die Anwendungsszenarien in ihren jeweiligen Bereichen weiter erweitern und mehr Durchbrüche in der Leistung erzielen.

Python vs. JavaScript: Entwicklungsumgebungen und ToolsPython vs. JavaScript: Entwicklungsumgebungen und ToolsApr 26, 2025 am 12:09 AM

Sowohl Python als auch JavaScripts Entscheidungen in Entwicklungsumgebungen sind wichtig. 1) Die Entwicklungsumgebung von Python umfasst Pycharm, Jupyternotebook und Anaconda, die für Datenwissenschaft und schnelles Prototyping geeignet sind. 2) Die Entwicklungsumgebung von JavaScript umfasst Node.JS, VSCODE und WebPack, die für die Entwicklung von Front-End- und Back-End-Entwicklung geeignet sind. Durch die Auswahl der richtigen Tools nach den Projektbedürfnissen kann die Entwicklung der Entwicklung und die Erfolgsquote der Projekte verbessert werden.

Ist JavaScript in C geschrieben? Prüfung der BeweiseIst JavaScript in C geschrieben? Prüfung der BeweiseApr 25, 2025 am 12:15 AM

Ja, der Motorkern von JavaScript ist in C. 1) Die C -Sprache bietet eine effiziente Leistung und die zugrunde liegende Steuerung, die für die Entwicklung der JavaScript -Engine geeignet ist. 2) Die V8-Engine als Beispiel wird sein Kern in C geschrieben, wobei die Effizienz und objektorientierte Eigenschaften von C kombiniert werden.

JavaScripts Rolle: das Web interaktiv und dynamisch machenJavaScripts Rolle: das Web interaktiv und dynamisch machenApr 24, 2025 am 12:12 AM

JavaScript ist das Herzstück moderner Websites, da es die Interaktivität und Dynamik von Webseiten verbessert. 1) Es ermöglicht die Änderung von Inhalten, ohne die Seite zu aktualisieren, 2) Webseiten durch DOMAPI zu manipulieren, 3) Komplexe interaktive Effekte wie Animation und Drag & Drop, 4) die Leistung und Best Practices optimieren, um die Benutzererfahrung zu verbessern.

C und JavaScript: Die Verbindung erklärteC und JavaScript: Die Verbindung erklärteApr 23, 2025 am 12:07 AM

C und JavaScript erreichen die Interoperabilität durch WebAssembly. 1) C -Code wird in das WebAssembly -Modul zusammengestellt und in die JavaScript -Umgebung eingeführt, um die Rechenleistung zu verbessern. 2) In der Spieleentwicklung kümmert sich C über Physik -Engines und Grafikwiedergabe, und JavaScript ist für die Spiellogik und die Benutzeroberfläche verantwortlich.

Von Websites zu Apps: Die verschiedenen Anwendungen von JavaScriptVon Websites zu Apps: Die verschiedenen Anwendungen von JavaScriptApr 22, 2025 am 12:02 AM

JavaScript wird in Websites, mobilen Anwendungen, Desktop-Anwendungen und serverseitigen Programmierungen häufig verwendet. 1) In der Website -Entwicklung betreibt JavaScript DOM zusammen mit HTML und CSS, um dynamische Effekte zu erzielen und Frameworks wie JQuery und React zu unterstützen. 2) Durch reaktnatives und ionisches JavaScript wird ein plattformübergreifendes mobile Anwendungen entwickelt. 3) Mit dem Elektronenframework können JavaScript Desktop -Anwendungen erstellen. 4) Node.js ermöglicht es JavaScript, auf der Serverseite auszuführen und unterstützt hohe gleichzeitige Anforderungen.

See all articles

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

EditPlus chinesische Crack-Version

EditPlus chinesische Crack-Version

Geringe Größe, Syntaxhervorhebung, unterstützt keine Code-Eingabeaufforderungsfunktion

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

WebStorm-Mac-Version

WebStorm-Mac-Version

Nützliche JavaScript-Entwicklungstools

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Leistungsstarke integrierte PHP-Entwicklungsumgebung

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)