Skip to content

Commit ae3aa1c

Browse files
committed
Using youtube video instead of gif.
1 parent 87312be commit ae3aa1c

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# WebEyeTrack
22
Created by <a href="https://edavalosanaya.github.io" target="_blank">Eduardo Davalos</a>, <a href="https://scholar.google.com/citations?user=_E0SGAkAAAAJ&hl=en" target="_blank">Yike Zhang</a>, <a href="https://scholar.google.com/citations?user=GWvdYIoAAAAJ&hl=en&oi=ao" target="_blank">Namrata Srivastava</a>, <a href="https://www.linkedin.com/in/yashvitha/" target="_blank">Yashvitha Thatigolta</a>, <a href="" target="_blank">Jorge A. Salas</a>, <a href="https://www.linkedin.com/in/sara-mcfadden-93162a4/" target="_blank">Sara McFadden</a>, <a href="https://scholar.google.com/citations?user=0SHxelgAAAAJ&hl=en" target="_blank">Cho Sun-Joo</a>, <a href="https://scholar.google.com/citations?user=dZ8X7mMAAAAJ&hl=en" target="_blank">Amanda Goodwin</a>, <a href="https://sites.google.com/view/ashwintudur/home" target="_blank">Ashwin TS</a>, and <a href="https://scholar.google.com/citations?user=-m5wrTkAAAAJ&hl=en" target="_blank">Guatam Biswas</a> from <a href="https://wp0.vanderbilt.edu/oele/" target="_blank">Vanderbilt University</a>
33

4-
### [Project](https://redforestai.github.io/WebEyeTrack) | [Paper](https://) | [Demo](https://azure-olympie-5.tiiny.site)
4+
### [Project](https://redforestai.github.io/WebEyeTrack) | [Paper](https://arxiv.org/abs/2508.19544) | [Demo](https://azure-olympie-5.tiiny.site)
55

66
WebEyeTrack is a framework that uses a lightweight CNN-based neural network to predict the ``(x,y)`` gaze point on the screen. The framework provides both a Python and JavaScript/TypeScript (client-side) versions to support research/testing and deployment via TS/JS. It performs few-shot gaze estimation by collecting samples on-device to adapt the model to account for unseen persons.
77

docs/src/App.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Separator } from "./components/ui/separator";
1212
import { CodeBlock } from "./components/ui/code-block"
1313
import { InlineCode } from "./components/ui/inline-code"
1414
import { AvatarCard } from "./components/ui/avatar-card";
15+
import { YouTubeEmbed } from "./components/ui/youtube-embed";
1516

1617
// https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
1718

@@ -177,7 +178,17 @@ export default function App() {
177178
<Section id="overview" title="Overview">
178179

179180
<div className="mb-6">
180-
<img src={`${import.meta.env.BASE_URL}/demo.gif`} alt="WebEyeTrack demo screenshot" className="rounded-lg border w-full" />
181+
182+
<YouTubeEmbed
183+
src="https://www.youtube.com/watch?v=EhFJplhuQGY" // or just "YOUR_VIDEO_ID"
184+
title="WebEyeTrack demo"
185+
aspect="16/9" // or "4/3" | "1/1" | "56.25%" custom
186+
lite // renders a thumbnail until clicked (performance)
187+
autoplay // start playback after click
188+
/>
189+
190+
{/* https://youtu.be/EhFJplhuQGY */}
191+
{/* <img src={`${import.meta.env.BASE_URL}/demo.gif`} alt="WebEyeTrack demo screenshot" className="rounded-lg border w-full" /> */}
181192
<Button className="w-full mt-4" variant="outline" size="lg">
182193
<a href="https://azure-olympie-5.tiiny.site" target="_blank" rel="noreferrer noopener" className="w-full">
183194
Click to use live demo
@@ -284,7 +295,7 @@ npm run start
284295
<p> If you would like to cite this work, please use the following reference:</p>
285296
<CodeBlock
286297
language="bibtex"
287-
code={`Add BibTeX citation here`} />
298+
code={`@misc{davalos2025webeyetrackscalableeyetrackingbrowser,\ntitle={WEBEYETRACK: Scalable Eye-Tracking for the Browser via On-Device Few-Shot Personalization},\nauthor={Eduardo Davalos and Yike Zhang and Namrata Srivastava and Yashvitha Thatigotla and Jorge A. Salas and Sara McFadden and Sun-Joo Cho and Amanda Goodwin and Ashwin TS and Gautam Biswas},\nyear={2025},\neprint={2508.19544},\narchivePrefix={arXiv},\nprimaryClass={cs.CV},\nurl={https://arxiv.org/abs/2508.19544}}`} />
288299
</div>
289300
</Section>
290301

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React from "react";
2+
import clsx from "clsx";
3+
4+
export type YouTubeEmbedProps = {
5+
/** Full YouTube URL or bare video id */
6+
src: string;
7+
/** Accessible title for the iframe */
8+
title?: string;
9+
/** 16/9 by default. Provide a ratio like 16/9, 4/3, or a custom string (e.g., '56.25%'). */
10+
aspect?: "16/9" | "4/3" | "1/1" | string;
11+
/** Defer loading until scrolled into view */
12+
lazy?: boolean;
13+
/** Start time in seconds */
14+
start?: number;
15+
/** Auto-play after user interaction (applies on first load when clicking the thumbnail) */
16+
autoplay?: boolean;
17+
/** Hide related videos */
18+
rel?: 0 | 1;
19+
/** Reduce YouTube branding */
20+
modestBranding?: 0 | 1;
21+
/** Show player controls */
22+
controls?: 0 | 1;
23+
/** Additional class names on wrapper */
24+
className?: string;
25+
/** Render a lightweight click-to-play thumbnail instead of immediate iframe */
26+
lite?: boolean;
27+
/** Optional custom thumbnail URL; defaults to YouTube HQ thumbnail */
28+
thumbnailUrl?: string;
29+
};
30+
31+
function extractId(src: string) {
32+
// Accept: youtu.be/<id>, youtube.com/watch?v=<id>, youtube.com/embed/<id>, or raw id
33+
const short = /youtu\.be\/(^[\n\r\s]+)?([\w-]{11})/;
34+
const watch = /v=([\w-]{11})/;
35+
const embed = /embed\/([\w-]{11})/;
36+
const raw = /^[\w-]{11}$/;
37+
if (raw.test(src)) return src;
38+
const s = src.toString();
39+
const m1 = s.match(watch);
40+
if (m1) return m1[1];
41+
const m2 = s.match(embed);
42+
if (m2) return m2[1];
43+
const m3 = s.match(short);
44+
if (m3) return m3[2];
45+
return src; // best effort
46+
}
47+
48+
export function YouTubeEmbed({
49+
src,
50+
title = "YouTube video player",
51+
aspect = "16/9",
52+
lazy = true,
53+
start,
54+
autoplay = false,
55+
rel = 0,
56+
modestBranding = 1,
57+
controls = 1,
58+
className,
59+
lite = true,
60+
thumbnailUrl,
61+
}: YouTubeEmbedProps) {
62+
const id = React.useMemo(() => extractId(src), [src]);
63+
64+
const padTop = React.useMemo(() => {
65+
if (typeof aspect === "string" && aspect.includes("/")) {
66+
const [w, h] = aspect.split("/").map(Number);
67+
if (w && h) return `${(h / w) * 100}%`;
68+
}
69+
if (aspect.endsWith("%")) return aspect; // custom string like '56.25%'
70+
return "56.25%"; // default 16/9
71+
}, [aspect]);
72+
73+
const params = new URLSearchParams({
74+
rel: String(rel),
75+
modestbranding: String(modestBranding),
76+
controls: String(controls),
77+
playsinline: "1",
78+
});
79+
if (start) params.set("start", String(start));
80+
if (autoplay) params.set("autoplay", "1");
81+
82+
const embedUrl = `https://www.youtube-nocookie.com/embed/${id}?${params.toString()}`;
83+
const thumb =
84+
thumbnailUrl || `https://i.ytimg.com/vi_webp/${id}/hqdefault.webp`;
85+
86+
const [showIframe, setShowIframe] = React.useState(!lite);
87+
88+
return (
89+
<div
90+
className={clsx(
91+
"relative w-full overflow-hidden rounded-lg border bg-black",
92+
className
93+
)}
94+
style={{ paddingTop: padTop }}
95+
>
96+
{showIframe ? (
97+
<iframe
98+
className="absolute inset-0 h-full w-full"
99+
src={embedUrl}
100+
title={title}
101+
loading={lazy ? "lazy" : undefined}
102+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
103+
allowFullScreen
104+
referrerPolicy="strict-origin-when-cross-origin"
105+
/>
106+
) : (
107+
<button
108+
type="button"
109+
onClick={() => setShowIframe(true)}
110+
className={clsx(
111+
"absolute inset-0 grid place-items-center",
112+
"text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-2"
113+
)}
114+
aria-label="Play video"
115+
>
116+
{/* Thumbnail background */}
117+
<img
118+
src={thumb}
119+
alt="Video thumbnail"
120+
className="absolute inset-0 h-full w-full object-cover opacity-90"
121+
loading={lazy ? "lazy" : undefined}
122+
/>
123+
{/* Play button */}
124+
<span
125+
className="relative z-10 inline-block rounded-full bg-white/90 p-4 shadow-md transition hover:scale-105"
126+
>
127+
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" viewBox="0 0 16 16" className="text-black">
128+
<path d="M11.596 8.697l-6.363 3.692A.75.75 0 0 1 4 11.742V4.258a.75.75 0 0 1 1.233-.647l6.363 3.692a.75.75 0 0 1 0 1.294z"/>
129+
</svg>
130+
</span>
131+
</button>
132+
)}
133+
</div>
134+
);
135+
}
136+
137+
export default YouTubeEmbed;

0 commit comments

Comments
 (0)