Skip to content

SDK Overlay ​

Construis des overlays OBS personnalises qui reagissent a StreamooS en temps reel -- comptes a rebours, bannieres "en cours de lecture", animations d'alertes, ou tout ce que tu peux exprimer en HTML/CSS/JS.

Qu'est-ce qu'un overlay personnalise ? ​

Un overlay personnalise est une petite page web (HTML + CSS + JS) que StreamooS sert a OBS comme source navigateur. Il se connecte au serveur WebSocket local sur ws://127.0.0.1:8765 et recoit les mises a jour d'etat en direct (tics du timer, changements de piste, evenements Twitch...). Tu peux aussi envoyer des commandes pour controler les fonctionnalites depuis l'overlay.

Les overlays personnalises sont packages sous forme de repertoires .streamoos et importes via le tableau de bord StreamooS.

Ton premier overlay ​

Construisons un afficheur de timer minimal en moins de 20 lignes.

Structure des fichiers ​

my-timer.streamoos/
  manifest.json
  index.html

manifest.json ​

Chaque overlay .streamoos a besoin d'un manifest :

json
{
  "id": "a1b2c3d4-0000-0000-0000-000000000001",
  "name": "Mon Timer",
  "version": "1.0",
  "htmlPath": "index.html",
  "createdAt": "2026-01-01T00:00:00.000Z"
}
ChampTypeDescription
idUUID stringUn identifiant unique. Genere-en un sur uuidgenerator.net ou avec crypto.randomUUID() dans la console du navigateur.
namestring (1--100 car.)Nom affiche dans le gestionnaire d'overlays du tableau de bord.
version"1.0"Version du schema. Toujours "1.0" pour l'instant.
htmlPathstringChemin relatif vers le fichier HTML d'entree. Doit se terminer par .html, ne doit pas commencer par / ni contenir ...
createdAtdate ISO 8601Horodatage de creation.

index.html ​

html
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        background: transparent;
        margin: 0;
        font-family: monospace;
        color: white;
        font-size: 48px;
      }
    </style>
  </head>
  <body>
    <div id="display">--:--</div>

    <script src="https://127.0.0.1:8766/shared/websocket-client.js"></script>
    <script>
      const client = new StreamooSClient("ws://127.0.0.1:8765");

      client.onUpdate((msg) => {
        if (msg.feature !== "timer") return;
        const s = msg.data.remainingSeconds;
        const mm = String(Math.floor(s / 60)).padStart(2, "0");
        const ss = String(s % 60).padStart(2, "0");
        document.getElementById("display").textContent = mm + ":" + ss;
      });

      client.onConnected(() => {
        client.sendCommand("timer", "get_state");
      });
    </script>
  </body>
</html>

C'est tout. Quand StreamooS tourne et qu'un timer est actif, cet overlay affiche un compte a rebours en direct.

Connexion au SDK ​

Inclure le client ​

Le client WebSocket partage est disponible depuis le serveur d'assets StreamooS :

html
<script src="https://127.0.0.1:8766/shared/websocket-client.js"></script>

Si ton overlay est place directement dans le dossier app/overlays/ pendant le developpement, tu peux aussi utiliser un chemin relatif :

html
<script src="../shared/websocket-client.js"></script>

Creer un client ​

js
const client = new StreamooSClient("ws://127.0.0.1:8765");

Le constructeur accepte un second argument optionnel pour les options avancees :

OptionDefautDescription
reconnectBaseDelayMs1000Delai initial avant la premiere tentative de reconnexion.
reconnectMaxDelayMs15000Delai maximum entre les tentatives de reconnexion.
reconnectJitterMs250Jitter aleatoire ajoute a chaque delai pour eviter le thundering herd.
connectTimeoutMs5000Temps d'attente pour le handshake initial avant abandon.
autoConnecttrueMettre a false pour se connecter manuellement avec client.connect().
debugfalseSi true, le client log les evenements (cycle de vie + messages) avec le prefixe [StreamooS]. Off par defaut pour garder les overlays silencieux en production.

Ecouter les mises a jour ​

js
const off = client.onUpdate((msg) => {
  // msg.type     === 'update'
  // msg.feature  === 'timer' | 'khinsider_radio' | 'smtc' | ...
  // msg.data     === objet d'etat specifique a la fonctionnalite
});

// Detache plus tard quand tu n'as plus besoin de ce handler
off();

Chaque enregistrement on* retourne une fonction d'unsubscribe. L'appeler retire ce handler unique - pas besoin de garder une reference au callback original.

Les mises a jour sont diffusees a chaque changement d'etat d'une fonctionnalite. Filtre par msg.feature pour ne reagir qu'aux donnees qui t'interessent.

Helper en un appel : bindOverlay() ​

Pour le cas frequent "je me connecte, je filtre une fonctionnalite, je reagis a son etat", bindOverlay() supprime le boilerplate :

js
const client = bindOverlay({
  feature: "timer",
  onConnected: () => client.sendCommand("timer", "get_state"),
  onUpdate: (data) => {
    // `data` est deja deballe (msg.data pour la fonctionnalite filtree)
    document.getElementById("display").textContent = data.remainingSeconds;
  },
});

bindOverlay() retourne le StreamooSClient sous-jacent, donc tu peux toujours appeler sendCommand(), disconnect(), etc. Options disponibles :

OptionTypeDescription
featurestringSi renseigne, onUpdate ne se declenche que pour les messages dont feature correspond.
urlstringURL WebSocket personnalisee. Defaut : ws://127.0.0.1:8765.
debugbooleanActive le logging verbose. Defaut : false.
onUpdatefunction(data, msg) => void - recoit la data deballee plus le message brut.
onResponsefunctionReponse serveur a une commande envoyee.
onErrorfunctionNotification d'erreur cote serveur.
onConnectedfunctionAppele une fois le handshake WebSocket etabli.
onDisconnectedfunctionAppele a la fermeture, avec { willReconnect }.

Envoyer des commandes ​

js
client.sendCommand("timer", "start", { minutes: 5, seconds: 0 });

Parametres : sendCommand(feature, action, data). L'argument data est optionnel et vaut {} par defaut.

Evenements du cycle de vie ​

js
client.onConnected(() => {
  // Connexion WebSocket etablie
});

client.onDisconnected(({ willReconnect }) => {
  // willReconnect est true sauf si tu as appele client.disconnect()
});

client.onResponse((msg) => {
  // Reponse a une commande envoyee (msg.ok, msg.data, msg.code)
});

client.onError((msg) => {
  // Erreur cote serveur
});

Controle manuel ​

js
client.disconnect(); // Fermer et arreter la reconnexion
client.reconnectNow(); // Forcer une tentative de reconnexion immediate

Mode composite ​

Un overlay peut aussi tourner embarque dans une source composite (http://127.0.0.1:8766/overlays/composite/?preset=<id>), qui empile plusieurs overlays derriere une seule source OBS. Rien a faire pour un overlay normal - StreamooSClient detecte le parametre ?composite=1 que la page composite ajoute a ton src et bascule automatiquement en mode proxy (aucune connexion propre, relaie via l'unique connexion du parent). Ton code d'overlay et son propre reglage de position fonctionnent sans changement dans les deux cas.

Si ton overlay affiche un item transitoire, evenementiel (une alerte qui apparait, pas un widget permanent comme une horloge ou une carte now-playing), attends un slot de position avant de l'afficher, pour qu'il ne se superpose jamais a un autre overlay evenementiel place a la meme position dans le preset composite de quelqu'un :

js
await requestOverlaySlot(maPositionConfiguree, durationMs);
// affichage sur sans risque maintenant ; hors mode composite, resout immediatement

Declare-le aussi dans ton manifest.json builtin, pour qu'un preset composite sache l'arbitrer :

json
{ "kind": "event" }

Omets kind (ou mets "permanent") pour tout ce qui n'est pas une popup transitoire - jamais mis en attente. Design complet et limites actuelles : doc/internal/adr/ADR-010-overlay-composite-mode.md.

Reference des fonctionnalites et evenements ​

Chaque fonctionnalite diffuse des messages update. Voici les fonctionnalites disponibles et leurs actions :

timer ​

ActionParametresDescription
start{ minutes, seconds }Demarrer (ou reprendre) le timer.
stop--Mettre en pause le timer.
reset--Remettre a zero.
get_state--Demander l'etat actuel (declenche un update).

Forme des donnees update :

js
{
  running: true,            // boolean
  durationSeconds: 300,     // duree totale
  remainingSeconds: 120,    // secondes restantes
  endAtMs: 1760000000000    // epoch ms quand le timer atteint zero (null si en pause)
}

khinsider_radio ​

ActionParametresDescription
play--Reprendre la lecture.
pause--Mettre en pause.
next--Piste suivante.
shuffle{ enabled } (optionnel)Activer/desactiver le mode aleatoire.
volume{ value } (0--100)Regler le volume.
get_state--Demander l'etat actuel.
activate--Activer le module radio.
deactivate--Desactiver le module radio.

Forme des donnees update :

js
{
  active: true,
  isPlaying: true,
  isShuffleEnabled: false,
  volume: 75,
  title: "Main Theme",
  game: "Celeste",
  coverUrl: "https://...",
  currentTimeSeconds: 42,
  durationSeconds: 180,
  history: [{ title, game, coverUrl }, ...]
}

smtc ​

ActionParametresDescription
play--Reprendre le media systeme.
pause--Mettre en pause.
next--Piste suivante.
previous--Piste precedente.
get_state--Demander l'etat actuel.

Forme des donnees update :

js
{
  connected: true,
  isPlaying: true,
  title: "Titre du morceau",
  artist: "Artiste",
  albumTitle: "Album",
  artworkUrl: "https://...",
  progressMs: 30000,
  durationMs: 240000,
  radioSource: "smtc",    // 'khinsider' | 'smtc'
  mediaApp: "spotify",    // app id detecte, "" si inconnu
  mediaAppLabel: "Spotify"
}

twitch ​

ActionParametresDescription
status--Obtenir le statut de connexion.
get_profile--Obtenir le profil du streamer.

Forme des donnees update :

js
{
  active: true,
  connected: true,
  displayName: "NomDuStreamer",
  avatarUrl: "https://..."
}

Configuration d'overlay ​

Chaque overlay integre supporte une action set_overlay_config qui controle la visibilite, la position et le theme. Tu peux reutiliser ce pattern dans tes propres overlays pour persister les preferences utilisateur :

js
client.sendCommand("timer", "set_overlay_config", {
  enabled: true,
  position: "bottom-right",
  theme: "streamoos",
});

Positions disponibles :

top-left       top-center       top-right
middle-left    center           middle-right
bottom-left    bottom-center    bottom-right

Tester en local ​

  1. Lancer StreamooS en mode developpement : pnpm dev
  2. Placer ton dossier overlay dans app/overlays/ pendant le developpement (ex : app/overlays/my-timer/)
  3. Ouvrir dans un navigateur a http://127.0.0.1:8766/my-timer/index.html (le serveur d'assets embarque) ou directement via le chemin file://
  4. Ajouter comme source navigateur OBS :
    • Cliquer sur + puis Navigateur
    • Decocher "Fichier local" et entrer l'URL ou le chemin absolu file://
    • Resolution : 1920x1080 (ou adapter a ta scene)
    • Activer "Actualiser le navigateur quand la scene devient active"
  5. Iterer : modifier ton HTML/CSS/JS, puis rafraichir la source navigateur dans OBS (clic droit sur la source, "Actualiser")

Astuce : utilise les DevTools du navigateur (F12) pour debuguer avant de passer a OBS. La connexion WebSocket fonctionne de maniere identique dans les deux environnements.

Packager en .streamoos ​

Quand ton overlay est pret pour la distribution ou l'import :

  1. Verifie que ton dossier contient un manifest.json valide a la racine
  2. Nomme le dossier avec l'extension .streamoos : my-timer.streamoos/
  3. Importe via le tableau de bord StreamooS : ouvre la page Gestionnaire d'overlays et utilise la fonction d'import

Le tableau de bord lit le manifest, le valide et copie l'overlay dans le repertoire userData de l'application. A partir de la, il apparait dans la liste des overlays et peut etre ouvert dans OBS.

Regles de validation du manifest ​

  • id doit etre un UUID v4 valide
  • name doit contenir entre 1 et 100 caracteres
  • version doit etre exactement "1.0"
  • htmlPath doit se terminer par .html, ne doit pas contenir .. et ne doit pas commencer par /
  • createdAt doit etre une date ISO 8601 valide

Si un champ est invalide, l'import echouera avec une erreur de validation affichee dans le tableau de bord.

Depannage ​

L'overlay affiche "Connecting..." indefiniment ​

  • Verifie que StreamooS tourne et que le serveur WebSocket est actif sur le port 8765
  • Consulte la console du navigateur (F12) pour les erreurs de connexion
  • Assure-toi que l'URL est ws://127.0.0.1:8765 (pas localhost, pas wss://)

L'overlay se connecte mais ne recoit aucune mise a jour ​

  • Envoie une commande get_state apres la connexion pour demander l'etat actuel :

    js
    client.onConnected(() => {
      client.sendCommand("timer", "get_state");
    });
  • Verifie que tu filtres correctement msg.feature dans ton handler onUpdate

  • Assure-toi que la fonctionnalite est bien active (ex : un timer doit etre demarre, un media doit etre en cours de lecture)

Erreurs CORS ou d'origine ​

Le serveur WebSocket n'accepte que les connexions provenant de ces origines :

OrigineContexte
nullSource navigateur OBS (fichier local)
file://Electron en production
http://127.0.0.1:5173Serveur de dev Vite
http://127.0.0.1:8766Serveur d'assets embarque

Si tu testes depuis une autre origine (ex : http://localhost:3000), la connexion sera rejetee avec HTTP 403.

Fond transparent non fonctionnel dans OBS ​

Assure-toi que ton CSS contient :

css
body {
  background: transparent;
}

Et que le champ "CSS personnalise" de la source navigateur OBS ne surcharge pas cette propriete.

Nom de fonctionnalite non reconnu ​

Les noms de fonctionnalites sont sensibles a la casse et doivent correspondre exactement :

timer -- khinsider_radio -- smtc -- twitch -- discord_rpc -- system

Script non charge ​

Si tu utilises le client partage depuis le serveur d'assets, assure-toi d'utiliser https://127.0.0.1:8766 (HTTPS, port 8766), pas HTTP. Si tu testes via file://, utilise un chemin relatif a la place.

Pour aller plus loin ​

  • Consulte la reference de l'API WebSocket pour la matrice complete des commandes et les contrats d'etat
  • Voir Overlays OBS pour les details sur les themes, positions et la configuration des overlays integres
  • Explore les overlays integres dans app/overlays/ pour des exemples concrets (timer, KHInsider, SMTC, dock)

Released under the MIT License.