LegbaCall
LegbaCall
Ce que ça fait
Grille d’appel composée (<legba-call>) : assemble <legba-video-track>,
<legba-audio-track>, <legba-avatar>/<legba-name>,
<legba-mic-button>/<legba-camera-button>/<legba-screen-share-button>
en une tuile par participant (soi-même + chaque participant distant), à
partir d’une vraie connexion @legba-core/realtime/client. Ne se connecte
jamais au montage — connect() doit être appelé explicitement (voir
<legba-call-button>, le déclencheur normal).
Partage d’écran (LGB-064). Le bouton <legba-screen-share-button> du
contrôle local appelle startScreenShare()/stopScreenShare()
(@legba-core/realtime/client, LGB-014). Un participant (local ou distant)
qui partage son écran obtient une deuxième tuile distincte, en plus de
sa tuile caméra (jamais une superposition) — repérable via
data-kind="screen-share" (contre data-kind="camera" pour la tuile
habituelle), et visuellement plus large (part="tile-screen-share", 480px
au lieu de 240px, le contenu d’un écran partagé étant souvent illisible à
la largeur d’une tuile caméra).
token-endpoint reçoit room en paramètre de requête GET et doit répondre
{ serverUrl: string, token: string } en JSON. <legba-chat> réutilise la
même connexion via le getter clientRoom.
Signature
class LegbaCall extends LegbaElement {
room: string;
tokenEndpoint: string; // attribut HTML : token-endpoint
readonly clientRoom: LegbaClientRoom | null;
readonly connectionPhase: LegbaCallPhase; // "idle" | "connecting" | "connected" | "disconnected" | "error"
connect(): Promise<void>;
disconnect(): Promise<void>;
}
Tag : legba-call.
Paramètres
| Propriété | Type | Requis | Défaut | Description |
|---|---|---|---|---|
room | string | oui | "" | Nom de la salle à rejoindre |
tokenEndpoint (attribut token-endpoint) | string | oui | "" | URL de l’endpoint applicatif qui émet { serverUrl, token } |
Retour
N/A — un élément DOM. connect()/disconnect() rendent des Promise<void>
qui ne rejettent jamais : un échec de connexion se traduit par l’état
"error" (connectionPhase), jamais une exception qui remonte à
l’appelant. Émet legba-call-phase-change (CustomEvent<{ phase: LegbaCallPhase }>,
bubbles/composed) à chaque changement d’état — c’est ce
qu’écoute <legba-call-button> pour refléter l’état réel.
Erreurs
Aucune exception : token-endpoint injoignable, réponse malformée (champ
serverUrl/token manquant ou vide), ou connexion LiveKit refusée
basculent tous vers connectionPhase === "error" avec un message lisible
affiché (part="error", role="alert") et un bouton « Réessayer ».
Une déconnexion subie après une connexion établie (coupure réseau, salle
fermée côté serveur, échec de reconnexion — LGB-058) bascule vers
connectionPhase === "disconnected" : même structure que "error"
(role="alert", bouton actionnable), mais part="disconnected" et le
bouton porte le libellé « Se reconnecter ». Distinct d’"error" : ce n’est
pas connect() qui a échoué, la connexion a fonctionné puis s’est arrêtée.
Une déconnexion volontaire (disconnect() appelé par l’application, y
compris via disconnectedCallback) ne produit jamais cette transition —
connectionPhase retombe directement à "idle".
Exemple minimal
<legba-call id="ma-salle" room="salle-demo" token-endpoint="/api/livekit-token"></legba-call>
<legba-call-button for="ma-salle"></legba-call-button>
Exemple complet
import type { LegbaCall } from "@legba-core/ui";
function watchCallState(call: LegbaCall): void {
call.addEventListener("legba-call-phase-change", (event) => {
const { phase } = (event as CustomEvent<{ phase: string }>).detail;
console.log("état de l'appel :", phase);
});
}
// <legba-chat> réutilise la même connexion, sans en ouvrir une deuxième :
// <legba-chat for="ma-salle"></legba-chat>
Pièges
- Jamais de connexion automatique au montage. Ajouter
<legba-call>au DOM ne fait rien tant queconnect()n’a pas été appelé — c’est intentionnel (l’utilisateur doit déclencher l’appel, pas le subir). connect()est idempotent. Un second appel pendant que le premier est en cours (ou après un succès) rend la même promesse plutôt que d’ouvrir une deuxième connexion réelle.disconnectedCallback()appelledisconnect()automatiquement. Retirer<legba-call>du DOM ne laisse jamais de connexion fantôme — aucune action manuelle requise, mais explicitement documenté pour éviter qu’un intégrateur n’appelledisconnect()par précaution inutile."disconnected"ne survient jamais après undisconnect()volontaire (LGB-058), même si le SDK LiveKit sous-jacent émet aussi son événementdisconnecteddans ce cas :<legba-call>s’en protège en interne. Un intégrateur qui écoutelegba-call-phase-changene verra donc"disconnected"que pour une coupure réellement subie.- La piste caméra locale est lue via
.raw(échappatoire typée deLegbaClientRoom, voirdocs/recipes/raw-escape-hatch.md) :publishCamera()etunpublishCamera()sont enveloppés, mais la lecture de la piste déjà publiée ne l’est pas encore côté@legba-core/realtime. Même échappatoire pour la piste de partage d’écran locale. - Le message d’erreur du bouton de partage d’écran est celui de
LegbaClientError, tel quel, jamais retraduit (permission refusée, navigateur non supporté, partage déjà actif…) — un seul texte décidé côté@legba-core/realtime, jamais deux versions à faire diverger. - L’audio système d’un partage d’écran distant n’est pas encore rendu
(
screen_share_audio, limite assumée côté@legba-core/realtime— ignoré plutôt que de risquer d’écraser silencieusement l’audio du micro). Seule la vidéo du partage est diffusée pour l’instant. - Arrêter un partage via le contrôle natif du navigateur (le bandeau
« Arrêter le partage » que certains navigateurs affichent) n’est pas
détecté automatiquement par
<legba-call>: le bouton peut rester affiché « actif » jusqu’au prochain clic, qui resynchronise l’état réel — même simplification assumée que pour<legba-mic-button>/<legba-camera-button>(aucun de ces boutons ne suit un changement d’état survenu hors de l’API). - L’audio distant est lu automatiquement (LGB-038) : chaque participant
distant qui publie son micro reçoit un
<legba-audio-track>dans sa tuile, sans surface visuelle. Aucune action d’intégration n’est requise. - La tuile locale n’a jamais de sortie audio, et un participant distant portant l’identité locale est écarté : deux garanties anti-larsen portées par le modèle de tuiles lui-même, pas par une condition d’affichage qu’une régression pourrait retirer sans bruit.
- Le son peut être bloqué au démarrage. Les navigateurs refusent toute
lecture sonore tant que l’utilisateur n’a pas interagi avec la page. Quand
cela arrive, un bouton « Activer le son » (
part="unmute") apparaît et relance toutes les sorties en attente. Le parcours nominal ne le rencontre pas : rejoindre l’appel passe par un clic, ce qui accorde déjà l’activation nécessaire. Le cas se présente surtout quandconnect()est appelé par programme, sans geste utilisateur préalable. - Une région live annonce les changements importants (LGB-035,
part="announcer",role="status"/aria-live="polite") : arrivée et départ d’un participant, son bloqué par le navigateur. Elle est masquée visuellement mais toujours présente dans l’arbre d’accessibilité, et rendue dans TOUTES les phases — une région live créée en même temps que son contenu n’est annoncée par presque aucun lecteur d’écran. Ne jamais la masquer avecdisplay: nonevia::part(announcer): cela la retirerait de l’arbre d’accessibilité et la rendrait muette. - Le focus est rattrapé quand son porteur disparaît (LGB-035) : quand
l’élément focalisé est retiré par un rendu (le bouton « Activer le son »
après usage, par exemple) et que le focus retomberait sur
<body>, il est déplacé sur la grille (part="grid",tabindex="-1"). Le focus n’est jamais volé si l’utilisateur l’a lui-même déplacé ailleurs entre-temps. - Consomme les jetons
--legba-*comme tout composant@legba-core/ui: ne redéclare jamais un jeton public directement, voirLegbaElement.