// MediaPipe Face Landmarker wrapper (browser only) — the web counterpart of // app/face_tracker.py. Loads @mediapipe/tasks-vision from CDN; detect() returns // blendshapes + head euler angles + normalized landmarks, or null when no face. import { CDN_URL, WASM_BASE, MODEL_URL, eulerFromMatrix } from "./trackconfig.js?v=20260729.6"; // Load MediaPipe only when tracking or image auto-detection is requested. A // static CDN import prevents the entire authoring UI from starting when the // user is offline or the CDN is temporarily unavailable. let tasksVisionPromise = null; let tasksVisionAttempt = 0; async function loadTasksVision() { if (!tasksVisionPromise) { // Browsers cache a rejected module import by URL. Give retries a distinct // URL so the retry button really performs another CDN request instead of // immediately replaying the earlier offline failure. const url = tasksVisionAttempt ? `${CDN_URL}?retry=${tasksVisionAttempt}` : CDN_URL; tasksVisionPromise = import(url).catch((err) => { tasksVisionPromise = null; // a later retry may succeed after reconnecting tasksVisionAttempt += 1; throw new Error(`could not load MediaPipe tasks-vision from CDN (offline?): ${err.message}`); }); } return tasksVisionPromise; } export async function createTracker() { let vision; let FaceLandmarker, FilesetResolver; try { ({ FaceLandmarker, FilesetResolver } = await loadTasksVision()); vision = await FilesetResolver.forVisionTasks(WASM_BASE); } catch (e) { throw new Error(e.message ?? `could not load MediaPipe tasks-vision wasm from CDN (offline?)`); } const options = { baseOptions: { modelAssetPath: MODEL_URL, delegate: "GPU" }, runningMode: "VIDEO", numFaces: 1, outputFaceBlendshapes: true, outputFacialTransformationMatrixes: true, }; let landmarker; try { landmarker = await FaceLandmarker.createFromOptions(vision, options); } catch (gpuErr) { try { options.baseOptions.delegate = "CPU"; // GPU delegate unavailable — retry on CPU landmarker = await FaceLandmarker.createFromOptions(vision, options); } catch (cpuErr) { throw new Error(`could not create FaceLandmarker (model download failed?): ${cpuErr.message}`); } } return { /** Detect one face in a