feat(frontend-live): ✨ Introduce DisguiseVideoParticipantVideo component for visual participant anonymization with blur/mask effects
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
02ca33562c
commit
d98eced76d
1 changed files with 22 additions and 17 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, type CSSProperties } from 'react';
|
||||
import { useEffect, useRef, type CSSProperties, type ReactElement } from 'react';
|
||||
import { useFaceDetection } from '../hooks/useFaceDetection';
|
||||
import { applyBlur } from '../renderers/BlurRenderer';
|
||||
import { drawGimpMask } from '../renderers/GimpMaskRenderer';
|
||||
|
|
@ -36,7 +36,8 @@ export interface DisguiseVideoParticipantVideoProps {
|
|||
* The output canvas can be consumed visually or captured as a MediaStream via
|
||||
* `onCaptureStream` for WebRTC integration.
|
||||
*
|
||||
* Production deployments must set these headers for MediaPipe WASM threading:
|
||||
* Production deployments must set these response headers for MediaPipe WASM
|
||||
* thread-pool support (SharedArrayBuffer):
|
||||
* Cross-Origin-Opener-Policy: same-origin
|
||||
* Cross-Origin-Embedder-Policy: require-corp
|
||||
*/
|
||||
|
|
@ -49,13 +50,16 @@ export function DisguiseVideoParticipantVideo({
|
|||
onCaptureStream,
|
||||
className,
|
||||
style,
|
||||
}: DisguiseVideoParticipantVideoProps): JSX.Element {
|
||||
}: DisguiseVideoParticipantVideoProps): ReactElement {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
// Stable ref for the 2D context so the render loop doesn't need to call
|
||||
// getContext() on every frame.
|
||||
const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
|
||||
const localStreamRef = useRef<MediaStream | null>(null);
|
||||
|
||||
// These refs let the render loop read the latest prop values without being
|
||||
// re-created on every render cycle.
|
||||
// Mirror the latest prop values into refs so the render loop closure can
|
||||
// read current values without being re-created on every render.
|
||||
const disguiseRef = useRef(disguise);
|
||||
disguiseRef.current = disguise;
|
||||
|
||||
|
|
@ -70,6 +74,12 @@ export function DisguiseVideoParticipantVideo({
|
|||
const detectRef = useRef(detectForVideo);
|
||||
detectRef.current = detectForVideo;
|
||||
|
||||
// ── Context initialisation ─────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return;
|
||||
ctxRef.current = canvasRef.current.getContext('2d');
|
||||
}, []);
|
||||
|
||||
// ── Stream setup ───────────────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
|
|
@ -99,8 +109,8 @@ export function DisguiseVideoParticipantVideo({
|
|||
}
|
||||
|
||||
setup().catch((_err: unknown) => {
|
||||
// Stream errors surface via the video element's error event or the
|
||||
// useFaceDetection hook — no additional handling needed here.
|
||||
// getUserMedia / play errors are surfaced to the user via the video
|
||||
// element's error event; no additional handling is needed here.
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
|
@ -113,18 +123,13 @@ export function DisguiseVideoParticipantVideo({
|
|||
|
||||
// ── Render loop ────────────────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
const canvas = canvasRef.current;
|
||||
if (!video || !canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
let animId: number;
|
||||
|
||||
function render(): void {
|
||||
// Wait until video has decoded enough data to draw.
|
||||
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||
const video = videoRef.current;
|
||||
const ctx = ctxRef.current;
|
||||
|
||||
if (video && ctx && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||
ctx.drawImage(video, 0, 0, width, height);
|
||||
|
||||
if (isReadyRef.current && disguiseRef.current !== 'none') {
|
||||
|
|
@ -146,7 +151,7 @@ export function DisguiseVideoParticipantVideo({
|
|||
|
||||
animId = requestAnimationFrame(render);
|
||||
return () => cancelAnimationFrame(animId);
|
||||
}, [width, height]); // Only restart loop when dimensions change.
|
||||
}, [width, height]); // Only restart when output dimensions change.
|
||||
|
||||
// ── Capture stream ─────────────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue