@meeeetup/camera-web
v0.6.0
Published
Web SDK for meeeetup-cam — MediaPipe BlazeFace + React hooks
Readme
@meeeetup/camera-web
Browser face-capture SDK for the Meeeetup face-ID service. Detects faces with MediaPipe BlazeFace, tracks them, picks the best-quality frame per person, and hands you the images through a callback.
Transport is yours. The SDK never makes a network request and never holds a
credential: it calls onBatchCapture (passive) or onCapture (interactive)
with base64 JPEG data URLs, and sending them is your code.
Requires React 18 or 19. ESM-only. Served over HTTPS or localhost —
getUserMedia is a secure-context API.
Install
npm i @meeeetup/camera-web react react-dom@meeeetup/camera-core arrives as a dependency — add it explicitly only if you
import from it directly (this package already re-exports SelectedFace,
LiveFacePreview and PersonResult).
import "@meeeetup/camera-web/styles.css";Passive capture — continuous, batched
Faces are flushed to onBatchCapture every 10 s.
import {
MeeeetUpCamProvider,
CameraViewport,
FaceCounter,
FaceGrid,
type SelectedFace,
} from "@meeeetup/camera-web";
export function PassiveCamera({ captureKey }: { captureKey: string }) {
return (
<MeeeetUpCamProvider
mode="passive"
onBatchCapture={async (faces: SelectedFace[]) => {
const res = await fetch("https://api.example.com/capture", {
method: "POST",
headers: {
Authorization: `Bearer ${captureKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
images: faces.map((f) => ({
image: f.dataUrl,
capturedAt: new Date(f.createdAt).toISOString(),
})),
}),
});
// Throw to report failure. A missing status, <400 or >=500 is retried;
// 4xx drops that batch (resending an unchanged rejected payload
// livelocks).
if (!res.ok) throw Object.assign(new Error("send failed"), { status: res.status });
}}
>
<CameraViewport />
<FaceCounter />
<FaceGrid />
</MeeeetUpCamProvider>
);
}Interactive capture — user-triggered
import {
MeeeetUpCamProvider,
CameraViewport,
AlignmentRing,
CaptureButton,
} from "@meeeetup/camera-web";
export function KioskCamera() {
return (
<MeeeetUpCamProvider
mode="interactive"
onCapture={async (faceImage, fullCircleImage) => {
await upload(faceImage, fullCircleImage);
}}
>
<CameraViewport />
<AlignmentRing />
<CaptureButton />
</MeeeetUpCamProvider>
);
}Custom UI
useMeeeetUpCam() exposes everything the built-in components render. Narrow on
cam.mode first; it throws outside a provider.
const cam = useMeeeetUpCam();
if (cam.mode === "passive") {
// cam.ready, cam.error, cam.trackedCount, cam.totalCount,
// cam.selectedFaces, cam.livePreviews, cam.flushBatch()
}Shared across modes: ready, error, videoRef, devices, currentDeviceId,
setDeviceId, currentFacingMode, toggleCamera.
Components
| Component | Mode | Renders |
|---|---|---|
| CameraViewport | both | <video> + overlay canvas (passive) / circular viewport (interactive) |
| FaceCounter | passive | Tracked + total badge |
| FaceGrid | passive | Grid of selected face crops |
| DevicePicker | both | Camera dropdown |
| CameraToggle | both | Front/back switch |
| AlignmentRing | interactive | Alignment ring, progress, countdown |
| CaptureButton | interactive | Manual capture trigger |
Every component except AlignmentRing accepts className. The skin is CSS
custom properties (--mfc-*) with dark defaults — re-theme by setting them on
any ancestor.
Provider props
Both modes accept children and mediapipeWasmUrl; the capture gates
(cooldownMs and the three below) are passive-only.
| Prop | Mode | Type | Default |
|---|---|---|---|
| onBatchCapture | passive | (faces: SelectedFace[]) => void \| Promise<void> | required |
| onCapture | interactive | (faceImage: string, fullCircleImage: string) => void \| Promise<void> | required |
| cooldownMs | passive | number | 10000 — minimum ms between sends for the same face |
| mediapipeWasmUrl | passive | string | jsDelivr CDN — point it at a self-hosted WASM directory |
| alignmentCaptureDelayMs | interactive | number | 2000 — how long a face must stay aligned before auto-capture |
| minDetectionScore | passive | number | 0.5 — detector confidence floor, applied before any pose analysis |
| minFaceHeight | passive | number | 0 — minimum face-box height as a fraction of frame height (0–1); off by default |
| minFrontalness | passive | number | 50 — minimum pose score (0–100) a frame must reach to be captured |
| captureWindowMs | passive | number | 0 — per-track best-shot window in ms; 0 keeps settle-based capture |
With captureWindowMs set, each tracked face is searched for that long and the
best frame is published as final at expiry. useMeeeetUpCam() in passive mode
returns rearm(), which starts every window's search again.
Runtime network access
MediaPipe assets are fetched on demand, so the browser must reach:
https://cdn.jsdelivr.net— WASM runtime (override withmediapipeWasmUrl)https://storage.googleapis.com— the.tflitedetection model
A strict CSP needs both in connect-src, plus script-src 'wasm-unsafe-eval'.
Licence
Proprietary — see LICENSE. Use requires a current agreement with MeeeetUp1120.
