headless-mode
Mode headless : construire sa propre interface sans @legba-core/ui
Ce que ça fait
@legba-core/ui (Lit, Web Components) est une interface possible, pas
la seule. Le socle réel — connexion, publication micro/caméra, présence des
participants, pistes distantes — vit entièrement dans
createLegbaRoom() (@legba-core/realtime/client), qui ne dépend d’aucun
moteur de rendu : ni Lit, ni React, ni aucun DOM particulier. Une
application qui veut son propre design construit sa propre interface
directement sur createLegbaRoom(), sans jamais importer @legba-core/ui.
Exemple complet et vérifié : examples/headless-call
— une interface d’appel minimale (DOM/CSS entièrement maison), testée en
E2E contre un vrai serveur LiveKit (examples/headless-call/e2e/).
Exemple
import { createLegbaRoom, type LegbaClientRoom } from "@legba-core/realtime/client";
async function joinCall(serverUrl: string, token: string, container: HTMLElement): Promise<LegbaClientRoom> {
const room = createLegbaRoom();
room.on("trackSubscribed", ({ participantIdentity, kind, track }) => {
if (kind !== "video") return;
const video = document.createElement("video");
video.autoplay = true;
video.srcObject = new MediaStream([track]);
video.dataset.identity = participantIdentity;
container.appendChild(video);
});
room.on("trackUnsubscribed", ({ participantIdentity }) => {
container.querySelector(`video[data-identity="${participantIdentity}"]`)?.remove();
});
await room.connect({ serverUrl, token });
await room.publishMicrophone();
await room.publishCamera();
return room;
}
Rien de plus n’est requis : pas de composant à enregistrer, pas de Shadow
DOM, pas de jeton --legba-* à respecter — l’application décide entièrement
de sa propre structure DOM et de son propre CSS.
Pièges
createLegbaRoom()n’a jamais dépendu de Lit. Ce n’est pas une extraction faite pour ce billet : c’était déjà vrai depuis LGB-013 (packages/realtime/src/legba-room.ts), simplement jamais démontré par un exemple dédié avant LGB-034.getRemoteParticipants()reflète l’état déjà connu à tout instant — utile pour un rendu initial (participants déjà présents avant queparticipantConnectedn’ait la moindre chance de se déclencher pour eux, voir le piège documenté sur cet événement), en plus des événements pour le temps réel.- La tuile locale ne doit jamais avoir de sortie audio (se réécouter
soi-même produit un larsen immédiat) —
getRemoteParticipants()exclut déjà l’identité locale par construction (voirremote-participants.ts), mais une interface headless doit quand même veiller à ne jamais construire elle-même un<audio>à partir de sa propre piste micro locale. .rawreste la même échappatoire typée que pour<legba-call>(voirraw-escape-hatch.md) pour tout ce queLegbaClientRoomn’enveloppe pas encore (ex. lire la piste caméra locale déjà publiée).- Aucune fonctionnalité de
@legba-core/uin’est reconstruite automatiquement. Tableau blanc, modération, partage d’écran, arrière-plan virtuel : chacun a sa propre logique dans@legba-core/realtime, mais le rendu (comme<legba-whiteboard>) reste à reconstruire pour une interface headless qui en aurait besoin — rien de spécifique au mode headless lui-même, juste plus de travail d’intégration.