createVirtualBackgroundProcessor
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
| Nom | Type | Requis | Défaut | Description |
|---|---|---|---|---|
sourceTrack | MediaStreamTrack | oui | — | Piste caméra déjà acquise (ex. getUserMedia) — jamais acquise par ce module lui-même |
options.backgroundImageUrl | string | oui | — | URL de l’image ou du GIF de fond ; doit être servie avec des en-têtes CORS corrects (voir Pièges) |
options.frameBudgetMs | number | non | 33 | Budget de trame (ms) au-delà duquel l’effet se désactive proprement |
options.onPerformanceDegraded | () => void | non | — | Appelé une fois, après que le pipeline se soit déjà arrêté (outputTrack.readyState === "ended") |
options.segmenterFactory | () => Promise<Segmenter> | non | chargeur MediaPipe réel | Injection 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 (outputTrackpasse à"ended") ; ne touche jamaissourceTrack, 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
sourceTrackdoit déjà être une caméra publiée. Ce module ne fait jamais degetUserMedialui-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 uneSecurityError. - 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).
onPerformanceDegradedsignale un arrêt déjà survenu, jamais une demande —outputTrackest déjà"ended"au moment de l’appel. L’application doit elle-même republier la caméra brute.@legba-core/realtimereste totalement étranger à ce module — aucune modification deLegbaClientRoom/publishCamera()pour ce billet ; la publication deoutputTrackse fait via l’échappatoire.rawdé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.