Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/video/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
output
.turbo
16 changes: 16 additions & 0 deletions apps/video/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="React Doctor performance diagnostics for Three.js and React Three Fiber."
/>
<title>React Doctor for Three.js</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions apps/video/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "video-workspace",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc --noEmit && vite build",
"dev": "vite",
"render": "tsc --noEmit && vite build && node scripts/render-video.mjs",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@fontsource/ibm-plex-mono": "^5.2.7",
"@react-three/fiber": "^9.7.0",
"framer-motion": "^12.23.24",
"react": "19.2.5",
"react-dom": "19.2.5",
"three": "^0.185.1"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.185.3",
"@vitejs/plugin-react": "^5.1.1",
"playwright-core": "^1.62.1",
"typescript": "^6.0.3",
"vite": "^7.3.1"
}
}
115 changes: 115 additions & 0 deletions apps/video/scripts/render-video.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { spawn } from "node:child_process";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { chromium } from "playwright-core";
import { preview } from "vite";

const VIDEO_WIDTH_PX = 1920;
const VIDEO_HEIGHT_PX = 1080;
const VIDEO_FPS = 30;
const VIDEO_FRAME_COUNT = 390;
const PREVIEW_PORT = 4173;
const FRAME_EVENT_NAME = "react-doctor:set-frame";
const CHROMIUM_EXECUTABLE_PATH = process.env.CHROMIUM_PATH ?? "/usr/bin/chromium";
const appDirectory = process.cwd();
const outputDirectory = path.join(appDirectory, "output");
const outputPath = path.join(outputDirectory, "react-doctor-three.mp4");

const runCommand = (command, argumentsList) =>
new Promise((resolve, reject) => {
const processInstance = spawn(command, argumentsList, { stdio: "inherit" });
processInstance.once("error", reject);
processInstance.once("exit", (exitCode) => {
if (exitCode === 0) {
resolve();
return;
}
reject(new Error(`${command} exited with code ${exitCode ?? "unknown"}`));
});
});

const closeServer = (previewServer) =>
new Promise((resolve, reject) => {
previewServer.httpServer.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});

await mkdir(outputDirectory, { recursive: true });
const frameDirectory = await mkdtemp(path.join(tmpdir(), "react-doctor-three-frames-"));
const previewServer = await preview({
root: appDirectory,
preview: { host: "127.0.0.1", port: PREVIEW_PORT, strictPort: true },
});
const browser = await chromium.launch({
executablePath: CHROMIUM_EXECUTABLE_PATH,
headless: true,
args: ["--disable-gpu-sandbox", "--no-sandbox"],
});

try {
const page = await browser.newPage({
deviceScaleFactor: 1,
viewport: { width: VIDEO_WIDTH_PX, height: VIDEO_HEIGHT_PX },
});
await page.goto(`http://127.0.0.1:${PREVIEW_PORT}/?real=true&manual=true`, {
waitUntil: "networkidle",
});
await page.evaluate(async () => {
await document.fonts.ready;
await new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => requestAnimationFrame(resolve))),
);
});

for (let frame = 0; frame < VIDEO_FRAME_COUNT; frame += 1) {
await page.evaluate(
async ({ eventName, nextFrame }) => {
window.dispatchEvent(new CustomEvent(eventName, { detail: nextFrame }));
await new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => requestAnimationFrame(resolve))),
);
},
{ eventName: FRAME_EVENT_NAME, nextFrame: frame },
);
await page.screenshot({
path: path.join(frameDirectory, `frame-${String(frame).padStart(4, "0")}.png`),
type: "png",
});
if ((frame + 1) % VIDEO_FPS === 0) {
process.stdout.write(
`Rendered ${(frame + 1) / VIDEO_FPS}s / ${VIDEO_FRAME_COUNT / VIDEO_FPS}s\n`,
);
}
}

await browser.close();
await runCommand("ffmpeg", [
"-y",
"-framerate",
String(VIDEO_FPS),
"-i",
path.join(frameDirectory, "frame-%04d.png"),
"-c:v",
"libx264",
"-preset",
"medium",
"-crf",
"18",
"-pix_fmt",
"yuv420p",
"-movflags",
"+faststart",
outputPath,
]);
process.stdout.write(`${outputPath}\n`);
} finally {
if (browser.isConnected()) await browser.close();
await closeServer(previewServer);
await rm(frameDirectory, { recursive: true, force: true });
}
115 changes: 115 additions & 0 deletions apps/video/src/components/doctor-face.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
export interface DoctorFaceProps {
color: string;
mood: "happy" | "neutral" | "sad";
size: number;
}

export const DoctorFace = ({ color, mood, size }: DoctorFaceProps) => {
const borderWidth = Math.max(2, size * 0.04);
const eyeSize = size * 0.12;
const eyeGap = size * 0.22;
const mouthWidth = size * 0.25;
const mouthHeight = size * 0.12;

return (
<div
style={{
width: size,
height: size,
border: `${borderWidth}px solid ${color}`,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: size * 0.12,
flexShrink: 0,
}}
>
<div style={{ display: "flex", gap: eyeGap, height: eyeSize * 1.4, alignItems: "center" }}>
{mood === "happy" ? (
<>
<div
style={{
width: eyeSize * 1.4,
height: eyeSize * 0.7,
borderBottom: `${borderWidth}px solid ${color}`,
borderRadius: `${eyeSize}px ${eyeSize}px 0 0`,
}}
/>
<div
style={{
width: eyeSize * 1.4,
height: eyeSize * 0.7,
borderBottom: `${borderWidth}px solid ${color}`,
borderRadius: `${eyeSize}px ${eyeSize}px 0 0`,
}}
/>
</>
) : (
<>
<div
style={{
width: eyeSize,
height: eyeSize,
borderRadius: "50%",
backgroundColor: color,
}}
/>
<div
style={{
width: eyeSize,
height: eyeSize,
borderRadius: "50%",
backgroundColor: color,
}}
/>
</>
)}
</div>

<div
style={{
width: mouthWidth,
height: mouthHeight + borderWidth,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{mood === "happy" && (
<div
style={{
width: mouthWidth,
height: mouthHeight,
borderBottom: `${borderWidth}px solid ${color}`,
borderLeft: `${borderWidth}px solid ${color}`,
borderRight: `${borderWidth}px solid ${color}`,
borderRadius: `0 0 ${mouthWidth}px ${mouthWidth}px`,
}}
/>
)}
{mood === "neutral" && (
<div
style={{
width: mouthWidth,
height: 0,
borderBottom: `${borderWidth}px solid ${color}`,
}}
/>
)}
{mood === "sad" && (
<div
style={{
width: mouthWidth,
height: mouthHeight,
borderTop: `${borderWidth}px solid ${color}`,
borderLeft: `${borderWidth}px solid ${color}`,
borderRight: `${borderWidth}px solid ${color}`,
borderRadius: `${mouthWidth}px ${mouthWidth}px 0 0`,
}}
/>
)}
</div>
</div>
);
};
53 changes: 53 additions & 0 deletions apps/video/src/components/playback-controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { RefObject } from "react";
import { formatPlaybackTime } from "../utils/format-playback-time";

export interface TimelinePlaybackControls {
pause: () => void;
play: () => void;
stop: () => void;
time: number;
}

export interface PlaybackControlsProps {
canAutoplay: boolean;
currentSeconds: number;
durationSeconds: number;
playbackRef: RefObject<TimelinePlaybackControls | null>;
}

export const PlaybackControls = ({
canAutoplay,
currentSeconds,
durationSeconds,
playbackRef,
}: PlaybackControlsProps) => {
const resumePlayback = () => {
const playback = playbackRef.current;
if (!playback || !canAutoplay) return;
if (playback.time >= durationSeconds) playback.time = 0;
playback.play();
};

return (
<div className="playback-controls">
<span className="playback-time">{formatPlaybackTime(currentSeconds)}</span>
<input
aria-label="Video timeline"
type="range"
min={0}
max={durationSeconds}
step={0.01}
value={currentSeconds}
onPointerDown={() => playbackRef.current?.pause()}
onChange={(event) => {
if (playbackRef.current) {
playbackRef.current.time = Number(event.currentTarget.value);
}
}}
onPointerUp={resumePlayback}
onPointerCancel={resumePlayback}
onBlur={resumePlayback}
/>
</div>
);
};
35 changes: 35 additions & 0 deletions apps/video/src/components/three-code-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { GREEN_COLOR, RED_COLOR } from "../constants";
import type { ThreeCodeLine } from "../three-constants";

export interface ThreeCodeCardProps {
isOptimized: boolean;
lines: ThreeCodeLine[];
opacity: number;
title: string;
}

export const ThreeCodeCard = ({ isOptimized, lines, opacity, title }: ThreeCodeCardProps) => (
<div
className="three-code-card"
style={{
borderColor: isOptimized ? "rgba(74, 222, 128, 0.24)" : "rgba(248, 113, 113, 0.24)",
opacity,
transform: `translateY(${(1 - opacity) * 20}px)`,
}}
>
<div className="three-code-card-header">
<span>{title}</span>
<span style={{ color: isOptimized ? GREEN_COLOR : RED_COLOR }}>
{isOptimized ? "STABLE" : "HOT PATH"}
</span>
</div>
<pre>
{lines.map((line, lineIndex) => (
<span key={line.id}>
<i>{String(lineIndex + 1).padStart(2, "0")}</i>
{line.text || " "}
</span>
))}
</pre>
</div>
);
Loading
Loading