createVirtualBackgroundProcessor

expérimentaldepuis 0.1.0@legba-core/ui

createVirtualBackgroundProcessor

Ce que ça fait

Construit le pipeline d’arrière-plan virtuel (LGB-062) : intercepte une piste caméra déjà acquise, fait tourner une segmentation image par image (MediaPipe ImageSegmenter, chargé à la demande depuis un CDN, jamais dans le bundle principal), compose le résultat sur un <canvas> avec le fond choisi (image statique ou GIF animé — le navigateur anime nativement un <img> déjà décodé), et rend une nouvelle MediaStreamTrack prête à publier à la place de la caméra brute.

Ne publie jamais rien sur LiveKit lui-même — @legba-core/realtime ne sait rien de cette fonctionnalité (décision prise à l’étape 0, dépend entièrement du navigateur). Voir la recette virtual-background.md pour le câblage complet jusqu’à la publication réelle.

Signature

function createVirtualBackgroundProcessor(
  sourceTrack: MediaStreamTrack,
  options: VirtualBackgroundOptions,
): Promise<VirtualBackgroundProcessor>

interface VirtualBackgroundOptions {
  backgroundImageUrl: string;
  frameBudgetMs?: number; // défaut 33 (30 images/s)
  onPerformanceDegraded?: () => void;
  segmenterFactory?: SegmenterFactory; // échappatoire de test, vaut le vrai chargeur MediaPipe en production
}

interface VirtualBackgroundProcessor {
  readonly outputTrack: MediaStreamTrack;
  setBackground(url: string): void;
  stop(): void;
}

Paramètres

NomTypeRequisDéfautDescription
sourceTrackMediaStreamTrackouiPiste caméra déjà acquise (ex. getUserMedia) — jamais acquise par ce module lui-même
options.backgroundImageUrlstringouiURL de l’image ou du GIF de fond ; doit être servie avec des en-têtes CORS corrects (voir Pièges)
options.frameBudgetMsnumbernon33Budget de trame (ms) au-delà duquel l’effet se désactive proprement
options.onPerformanceDegraded() => voidnonAppelé une fois, après que le pipeline se soit déjà arrêté (outputTrack.readyState === "ended")
options.segmenterFactory() => Promise<Segmenter>nonchargeur MediaPipe réelInjection de test, même patron que roomFactory sur <legba-call>

Retour

Une Promise<VirtualBackgroundProcessor> :

  • outputTrack — la piste vidéo traitée, à publier via l’échappatoire .raw (voir la recette).
  • setBackground(url) — change le fond sans reconstruire le pipeline, jamais de coupure de la piste publiée.
  • stop() — arrête proprement l’effet (outputTrack passe à "ended") ; ne touche jamais sourceTrack, qui reste sous la responsabilité de l’appelant.

Erreurs

Aucune erreur typée : une Promise rejetée reflète directement un échec du navigateur (chargement WASM/modèle échoué, contexte 2D indisponible, etc.), jamais retraduite.

Exemple minimal

import { createVirtualBackgroundProcessor } from "@legba-core/ui";

async function apply(cameraTrack: MediaStreamTrack) {
  const processor = await createVirtualBackgroundProcessor(cameraTrack, {
    backgroundImageUrl: "/backgrounds/plage.jpg",
  });
  return processor.outputTrack;
}

Exemple complet

Voir la recette virtual-background.md — câblage complet avec <legba-background-select> et la publication réelle via room.raw.localParticipant.publishTrack().

Pièges

  • sourceTrack doit déjà être une caméra publiée. Ce module ne fait jamais de getUserMedia lui-même — principe retenu dès l’étape 0, pas un oubli.
  • Le fond doit être servi avec CORS. Le pipeline lit les pixels du fond via getImageData() pour la composition, pas seulement un rendu visuel — sans en-têtes CORS corrects, la composition lève une SecurityError.
  • Horodatage vidéo strictement croissant, en entier. Trouvaille réelle de l’étape 0 : ImageSegmenter.segmentForVideo() exige ça — performance.now() brut (flottant, peut piétiner) déclenche une vraie erreur MediaPipe. Géré en interne (nextVideoTimestampMs), sans action requise de l’appelant.
  • Une trame d’amorçage non chronométrée précède la boucle surveillée. La toute première inférence inclut la compilation du shader/l’initialisation du modèle (~100ms+, mesuré réellement) — sans cet amorçage, ce pic unique fausse le p95 de la fenêtre glissante de performance dès les premières trames, déclenchant une fausse dégradation. Trouvaille faite en testant ce module dans un vrai navigateur, pas anticipée depuis le banc de mesure de l’étape 0 (qui l’excluait déjà, mais séparément, en post-traitement).
  • onPerformanceDegraded signale un arrêt déjà survenu, jamais une demande — outputTrack est déjà "ended" au moment de l’appel. L’application doit elle-même republier la caméra brute.
  • @legba-core/realtime reste totalement étranger à ce module — aucune modification de LegbaClientRoom/publishCamera() pour ce billet ; la publication de outputTrack se fait via l’échappatoire .raw déjà établie.
  • Qualité du masque et appareils bas de gamme non vérifiés en conditions réelles. L’étape 0 n’a mesuré que le débit d’inférence (canevas synthétique). Un smoke test réel (navigateur + vrai MediaPipe + piste synthétique) a confirmé le pipeline stable de bout en bout, mais ni la qualité de la découpe sur un vrai visage, ni le comportement sur un appareil réellement faible n’ont encore été vérifiés.

Voir aussi