Video Canvas
A mirrored 16:9 webcam surface — a video element with a pixel-aligned transparent canvas overlay for landmark / skeleton drawing, an FPS badge, and a child slot. Powers real-time MediaPipe trackers.
Video Canvas
The Video Canvas is a mirrored 16:9 webcam surface: a <video> element with a pixel-aligned transparent <canvas> overlay for drawing landmark/skeleton annotations, an absolutely-positioned FPS-counter badge, and a child slot for status/badges. The canvas is kept sized to the video's intrinsic resolution, so the coordinates a tracker returns map 1:1 onto it; both layers share the same mirror transform, so the overlay stays aligned.
It is a shell, not an acquisition engine — the consuming app supplies the stream (getUserMedia) and wires a MediaPipe streaming tracker (createHandTracker / createPoseTracker / createFaceTracker / createGestureTracker from @localmode/mediapipe) via the experimental useStreamingTracker hook. The trackers own their own video frame loop — the app just draws each results batch onto the exposed canvas. This keeps the primitive presentational and free of device-permission logic.
When to use it: real-time hand/pose/face tracking, gesture recognition, any live-webcam annotation UI (MediaPipe Studio-style apps).
Preview
Installation
pnpm dlx shadcn@latest add @localmode/ui/media-vision/video-canvasnpx shadcn@latest add @localmode/ui/media-vision/video-canvasyarn dlx shadcn@latest add @localmode/ui/media-vision/video-canvasbunx --bun shadcn@latest add @localmode/ui/media-vision/video-canvasDependencies
-
Data source: a presentational shell — you supply the
stream(getUserMedia) and draw each tracker batch onto the exposed<canvas>; works with any frame source. Recommended LocalMode producer: the experimentaluseStreamingTrackerfrom@localmode/reactwired to a@localmode/mediapipecreate*Trackerfactory (optional). -
clsx+tailwind-merge— via the sharedcn()util (installed automatically as a registry dependency)
Files installed
video-canvas.tsx— the componentlib/utils.ts— thecn()helper (if not already present)
Props
VideoCanvas
Prop
Type
Backing hooks
Wire the experimental useStreamingTracker hook from @localmode/react to a @localmode/mediapipe streaming tracker factory (createHandTracker, createPoseTracker, createFaceTracker, createGestureTracker) running against the exposed <video> (ref.current.video). The tracker owns its own frame loop; the hook surfaces { status, results, fps, start, stop } — draw each results batch onto the exposed <canvas> (ref.current.canvas) and feed fps to the badge. (The single-shot useDetectHands / useDetectPose / useDetectFace / useRecognizeGesture hooks are for still images, not the live loop.)
Examples
Wire a webcam stream and tracker
import { VideoCanvas, type VideoCanvasHandle } from '@/components/video-canvas';
import { useStreamingTracker } from '@localmode/react';
import { createHandTracker } from '@localmode/mediapipe';
export function Example() {
const ref = useRef<VideoCanvasHandle>(null);
const [stream, setStream] = useState<MediaStream | null>(null);
const { results, fps, start } = useStreamingTracker({
video: () => ref.current?.video ?? null,
create: ({ video, onResults, onError }) =>
createHandTracker({ video, onResults, onError }),
onResults: (hands) => {
const ctx = ref.current?.canvas?.getContext('2d');
if (ctx) drawHandLandmarks(ctx, hands); // your draw helper
},
});
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true }).then((s) => {
setStream(s);
start(); // tracker owns the frame loop from here
});
}, []);
return (
<VideoCanvas ref={ref} stream={stream} fps={fps}>
<span className="rounded-full bg-emerald-500/90 px-2.5 py-1 text-xs text-white">
✋ Open palm
</span>
</VideoCanvas>
);
}useStreamingTracker is experimental: it creates the tracker lazily on the first start(), reuses it across start/stop cycles (the model stays warm), measures fps over a one-second window, and disposes the tracker on unmount.
Customization
The component owns only the video/canvas composition, the mirror transform, and the FPS badge. Set mirrored={false} for non-selfie views, hide the badge with hideFps, and restyle the 16:9 shell in the copied video-canvas.tsx. You retain full control over acquisition and the draw loop.
Real-hardware dependency: the live preview here uses a simulated marker. A real webcam stream requires
getUserMediaand a user permission grant in the browser — wire that in your app.
Image Processing Overlay
A full-bleed overlay shown over a dimmed source image while vision inference runs — spinner ring, status, optional cancel, and an animated scan variant. Renders nothing when idle.
Image Result Gallery
A responsive grid / list of image result cards — in-flight overlay, persistent metadata, confidence score, multi-select and delete — sharing one data contract across both layouts.