Skip to content

Filters#73

Open
thinkter wants to merge 23 commits into
ACM-VIT:mainfrom
thinkter:3dfacefilters
Open

Filters#73
thinkter wants to merge 23 commits into
ACM-VIT:mainfrom
thinkter:3dfacefilters

Conversation

@thinkter

Copy link
Copy Markdown
Contributor

read greptile
see vid sent on grp

@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

@thinkter is attempting to deploy a commit to the Outreach - ACM Team on Vercel.

A member of the Team first needs to authorize it.

@thinkter thinkter changed the title 3dfacefilters Filters May 19, 2026
@greptile-apps

greptile-apps Bot commented May 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a camera-effects pipeline (blur, party-hat, cat-ears, 3d-glasses) to the web app, including a new filter-picker drawer for both the in-meeting controls bar and pre-join screens, and wires the selected effect through useMeetMedia so it is applied to the live WebRTC video producer.

  • New camera-effects library (mediapipe.ts, blur.ts, face-2d.ts, glasses-3d.ts, index.ts): lazy-loads MediaPipe and Three.js, wraps each effect in a ManagedCameraTrack abstraction with explicit stop() / RAF cleanup, and falls back to raw camera on setup failure.
  • CameraFiltersDrawer / JoinCameraFiltersDrawer: filter-picker UI with live preview; stale-request counters prevent track leaks on rapid effect changes.
  • useMeetMedia + join screens: backgroundEffect is threaded into createManagedCameraTrackForCurrentSettings and guarded by an in-flight ref so concurrent quality/effect switches are serialised.

Confidence Score: 3/5

The core camera-effects pipeline has two defects that silently degrade the user experience and leak GPU resources under reachable conditions.

In mediapipe.ts, a single failed dynamic import of @mediapipe/tasks-vision permanently poisons visionModulePromise. The individual segmenter/landmarker promises reset themselves on failure, but they re-enter loadVisionModule() and get the same cached rejected promise every time — so blur and all face filters silently fall back to raw camera for the rest of the session with no recovery path short of a page refresh. In glasses-3d.ts, the THREE.WebGLRenderer is created before waitForVideoReady, which can now reject after 2.5 s (the timeout added in this PR). When it does, renderer.dispose() is never called, leaking a WebGL context; browsers cap concurrent contexts, so repeated filter switches on slow hardware will exhaust them.

apps/web/src/app/lib/camera-effects/mediapipe.ts and apps/web/src/app/lib/camera-effects/filters/glasses-3d.ts need attention before merging.

Important Files Changed

Filename Overview
apps/web/src/app/lib/camera-effects/mediapipe.ts New file: initializes MediaPipe segmenter and face landmarker with retry-on-failure and timeout wrappers. Bug: visionModulePromise is never cleared on rejection, so a single failed module import permanently disables all camera effects for the session.
apps/web/src/app/lib/camera-effects/filters/glasses-3d.ts New file: 3D glasses overlay using Three.js + MediaPipe face landmarks. Fixed threeModulePromise and threeModelPromises rejection-caching issues from prior review. Bug: WebGL renderer is created before waitForVideoReady, which can now reject (2.5 s timeout), leaking the renderer and GPU context.
apps/web/src/app/lib/camera-effects/filters/blur.ts New file: background blur using MediaPipe image segmentation and canvas compositing with RAF loop. Resource management looks correct; stop() cleans up RAF, srcObject, and streams.
apps/web/src/app/lib/camera-effects/filters/face-2d.ts New file: 2D canvas-based face overlays (party hat, cat ears) using face landmark detection. Cleanup looks correct; no WebGL resources to leak.
apps/web/src/app/lib/camera-effects/index.ts New file: createManagedCameraTrack / createManagedCameraTrackFromTrack — orchestrates camera stream acquisition and routes to the correct filter. Falls back to raw camera on filter setup failure.
apps/web/src/app/components/CameraFiltersDrawer.tsx New component: in-meeting filter picker with live preview. Uses a stale-request counter to prevent race conditions on rapid effect changes. Previous pointer-events conflict is resolved.
apps/web/src/app/hooks/useMeetMedia.ts Adds background-effect state, createManagedCameraTrackForCurrentSettings, and updateVideoQuality in-flight guard. The early return when transport is null after creating a managed track still leaks resources (flagged in a prior outside-diff comment).
apps/web/src/app/components/JoinScreen.tsx Integrates backgroundEffect into the pre-join camera preview; uses stale-request counter to guard concurrent enableCameraPreview calls, addressing the previously reported concurrent-call leak.
apps/web/src/app/components/mobile/MobileJoinScreen.tsx Mobile counterpart to JoinScreen: adds backgroundEffect and stale-request guard for enableCameraPreview, mirroring the desktop fix.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User selects filter] --> B[createManagedCameraTrack]
    B --> C{effect type?}
    C -->|none| D[Raw camera stream]
    C -->|blur| E[createBlurredTrack]
    C -->|party-hat / cat-ears| F[createFaceOverlayTrack]
    C -->|3d-glasses| G[createThreeFaceOverlayTrack]

    E --> H[getImageSegmenterWithTimeout\nMediaPipe segmenter]
    F --> I[getFaceLandmarkerWithTimeout\nMediaPipe face landmarker]
    G --> J[loadThreeModule\nThree.js + GLTFLoader]
    G --> K[loadThreeModel\nglasses.glb]
    G --> I

    H --> L[waitForVideoReady\n2.5s timeout]
    I --> L
    J --> L
    K --> L

    L -->|ready| M[RAF render loop\ncanvas captureStream]
    L -->|timeout / error| N[throw -> caller catches\nfallback: raw camera]

    N --> O[glasses-3d: WebGLRenderer\nnot disposed on timeout]

    M --> P[ManagedCameraTrack\nstream + track + stop]
    P --> Q[useMeetMedia\nupdateVideoQuality -> WebRTC producer]
Loading

Reviews (4): Last reviewed commit: "fix: camerafilterdrawer fix" | Re-trigger Greptile

Comment thread apps/web/src/app/lib/camera-effects/filters/glasses-3d.ts Outdated
Comment thread apps/web/src/app/hooks/useMeetMedia.ts
Comment thread apps/web/src/app/lib/camera-effects/media.ts
Comment on lines +12 to +13
export type BackgroundEffect = CameraEffect;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 BackgroundEffect alias includes face effects, misleading consumers

BackgroundEffect is typed as an exact alias for CameraEffect, which means it includes "party-hat", "cat-ears", and "3d-glasses" — none of which are background effects. Everywhere BackgroundEffect is used (props, state, hooks), readers expecting a background-only type will be surprised that face filters are valid. If the intent is to keep a single enum for all camera effects, renaming the alias to something like CameraEffectOption throughout, or narrowing BackgroundEffect to only "none" | "blur", would reduce confusion.

Comment thread apps/web/src/app/components/JoinScreen.tsx
Comment thread apps/web/src/app/lib/camera-effects/filters/glasses-3d.ts
Comment thread apps/web/src/app/components/CameraFiltersDrawer.tsx
Comment on lines +19 to +25
const loadVisionModule = async (): Promise<VisionModule> => {
if (!visionModulePromise) {
visionModulePromise = import("@mediapipe/tasks-vision");
}

return visionModulePromise;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 visionModulePromise is never cleared on rejection. If the dynamic import("@mediapipe/tasks-vision") fails (chunk load error, CDN hiccup), the rejected promise is cached permanently. Both segmenterPromise and faceLandmarkerPromise correctly reset themselves to null in their .catch handlers, but when they retry they call loadVisionModule() again, which returns the same permanently-rejected visionModulePromise. The result is that blur and every face filter silently falls back to raw camera for the rest of the session — a page refresh is required to recover. This is the identical pattern that was already fixed for threeModulePromise in glasses-3d.ts.

Suggested change
const loadVisionModule = async (): Promise<VisionModule> => {
if (!visionModulePromise) {
visionModulePromise = import("@mediapipe/tasks-vision");
}
return visionModulePromise;
};
const loadVisionModule = async (): Promise<VisionModule> => {
if (!visionModulePromise) {
visionModulePromise = import("@mediapipe/tasks-vision").catch((error) => {
visionModulePromise = null;
throw error;
});
}
return visionModulePromise;
};

Comment on lines +304 to +309
try {
await video.play();
} catch {}
await waitForVideoReady(video);

const capturedStream = outputCanvas.captureStream(frameRate);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The THREE.WebGLRenderer is created before waitForVideoReady is called, but there is no cleanup path if waitForVideoReady rejects (it now times out after 2.5 s via VIDEO_READY_TIMEOUT_MS). When the timeout fires, the error propagates uncaught out of createThreeFaceOverlayTrack, and renderer.dispose() is never called — leaking a WebGL context and the associated GPU memory. Browsers cap the number of concurrent WebGL contexts (typically 16–32), so repeated filter switches on sluggish devices or after transient camera failures will eventually exhaust available contexts.

Suggested change
try {
await video.play();
} catch {}
await waitForVideoReady(video);
const capturedStream = outputCanvas.captureStream(frameRate);
try {
await video.play();
} catch {}
try {
await waitForVideoReady(video);
} catch (err) {
renderer.dispose();
stopMediaStream(sourceStream);
throw err;
}
const capturedStream = outputCanvas.captureStream(frameRate);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant