LegbaCall

expérimentaldepuis 0.1.0@legba-core/ui
Aperçu en direct — vrai composant, pas une image

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éTypeRequisDéfautDescription
roomstringoui""Nom de la salle à rejoindre
tokenEndpoint (attribut token-endpoint)stringoui""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 que connect() 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() appelle disconnect() 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’appelle disconnect() par précaution inutile.
  • "disconnected" ne survient jamais après un disconnect() volontaire (LGB-058), même si le SDK LiveKit sous-jacent émet aussi son événement disconnected dans ce cas : <legba-call> s’en protège en interne. Un intégrateur qui écoute legba-call-phase-change ne verra donc "disconnected" que pour une coupure réellement subie.
  • La piste caméra locale est lue via .raw (échappatoire typée de LegbaClientRoom, voir docs/recipes/raw-escape-hatch.md) : publishCamera() et unpublishCamera() 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 quand connect() 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 avec display: none via ::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, voir LegbaElement.

Voir aussi