Aller au contenu principal

TTS en Streaming avec WebSocket

Mis à jour le 29 juillet 2026

Pourquoi le streaming change l’expérience

En mode HTTP, le TTS ne vous rend la main qu’une fois l’intégralité de l’audio produite. Pour une notification de deux phrases, l’attente passe inaperçue. Pour un article de blog, un chapitre de livre ou un fil d’actualités qui défile en continu, elle devient le principal défaut de votre produit : l’utilisateur clique, puis regarde un indicateur de chargement pendant plusieurs secondes.

Le mode WebSocket supprime cette attente en poussant l’audio au fur et à mesure de sa synthèse. Le lecteur démarre sur les premiers mots alors que la fin du texte n’a pas encore été traitée — la différence entre une page qui semble figée et une lecture immédiate.

Établir la connexion

La connexion s’ouvre sur wss://api.x.ai/v1/tts, et toute la configuration passe par la query string de l’URL plutôt que par un message d’initialisation.

const ws = new WebSocket(
  "wss://api.x.ai/v1/tts?language=fr&voice=ara&codec=mp3&sample_rate=24000",
  {
    headers: {
      "Authorization": "Bearer " + XAI_API_KEY
    }
  }
);

Les paramètres acceptés sont les suivants :

  • language (obligatoire) : code BCP-47 ou auto
  • voice (optionnel, défaut : eve) : identifiant de la voix
  • codec (optionnel, défaut : mp3) : format audio de sortie
  • sample_rate (optionnel, défaut : 24000) : fréquence d’échantillonnage en Hz
  • bit_rate (optionnel, défaut : 128000) : débit binaire en bps (MP3 uniquement)

Ces réglages valent pour toute la durée de la session : vous ne changez pas de voix ni de codec en cours de route. Si votre application doit alterner deux narrateurs, ouvrez deux connexions.

Le protocole de messages

Côté émission, deux messages suffisent à piloter la synthèse. Le premier envoie un morceau de texte, le second annonce que le texte est complet.

{ "type": "text.delta", "delta": "Bonjour et bienvenue " }
{ "type": "text.done" }

Vous pouvez enchaîner autant de text.delta que nécessaire, chacun jusqu’à 15 000 caractères. Mieux vaut pourtant ne pas remplir ce plafond : découper en morceaux de quelques phrases fait arriver le premier chunk audio bien plus tôt, ce qui est précisément l’intérêt du streaming. Un delta unique de 15 000 caractères vous ramène au comportement du mode HTTP.

Côté réception, chaque audio.delta transporte un fragment d’audio encodé en base64, dans le codec que vous avez spécifié à la connexion. Vous le décodez et l’ajoutez à votre buffer de lecture.

{ "type": "audio.delta", "delta": "<base64 audio>" }
{ "type": "audio.done", "trace_id": "uuid-de-trace" }

L’événement audio.done marque la fin de la synthèse ; son trace_id est l’identifiant à fournir au support ou à consigner dans vos logs lorsqu’une génération se comporte mal. En cas de texte invalide ou de quota dépassé, un message d’erreur arrive à la place.

{ "type": "error", "message": "description de l'erreur" }

Une implémentation complète

L’exemple ci-dessous découpe un texte long par paragraphes, les envoie tous, puis assemble les fragments audio reçus dans un fichier MP3.

const WebSocket = require("ws");
const fs = require("fs");

const ws = new WebSocket(
  "wss://api.x.ai/v1/tts?language=fr&voice=rex&codec=mp3",
  { headers: { "Authorization": "Bearer " + process.env.XAI_API_KEY } }
);

const audioChunks = [];

ws.on("open", () => {
  const paragraphs = longText.split("\n\n");
  for (const para of paragraphs) {
    ws.send(JSON.stringify({
      type: "text.delta",
      delta: para + " "
    }));
  }
  ws.send(JSON.stringify({ type: "text.done" }));
});

ws.on("message", (data) => {
  const msg = JSON.parse(data);
  if (msg.type === "audio.delta") {
    audioChunks.push(Buffer.from(msg.delta, "base64"));
  } else if (msg.type === "audio.done") {
    fs.writeFileSync("output.mp3", Buffer.concat(audioChunks));
    ws.close();
  }
});

Notez le para + " " : sans cet espace, la fin d’un paragraphe se colle au début du suivant et le moteur enchaîne les deux mots sans respiration.

Les limites à connaître

MétriqueValeur
Texte par delta15 000 caractères max
Texte totalIllimité
Sessions simultanées50 par équipe
Durée de session (TTL)600 secondes
TimeoutAucun (tant que des messages circulent)

Le TTL de 600 secondes ferme la session au bout de dix minutes d’inactivité ; tant que des messages circulent, la connexion tient. C’est donc l’application qui ouvre une session « au cas où », en attendant que l’utilisateur clique, qui se fera couper : connectez-vous au moment où vous avez du texte à envoyer, pas au chargement de la page. La limite de 50 sessions simultanées par équipe se planifie de la même façon — une bibliothèque audio qui convertit son catalogue en tâche de fond sérialise ses travaux plutôt que de lancer deux cents connexions en parallèle.

HTTP ou WebSocket

Le mode HTTP reste le bon choix pour les textes courts, en dessous de 5 000 caractères, pour les générations ponctuelles et partout où la simplicité d’intégration prime : une requête, une réponse, aucun état à gérer. Le WebSocket s’impose dès que le texte s’allonge, que l’audio doit être écouté pendant sa production, ou que le contenu arrive lui-même en flux continu — typiquement la sortie d’un modèle de langage que vous vocalisez au fil de sa génération.

Points clés à retenir

  • Le mode WebSocket TTS permet de recevoir l’audio en streaming sans attendre la fin de la génération
  • Les paramètres de configuration se passent dans la query string de l’URL de connexion
  • Le protocole est simple : text.delta pour envoyer du texte, audio.delta pour recevoir l’audio
  • Les sessions WebSocket ont une limite de 50 simultanées par équipe et un TTL de 600 secondes
  • Privilégiez le WebSocket pour les textes longs et les expériences de lecture en temps réel