Skip to content

OBS Overlays ​

Overlays are vanilla HTML/CSS/JS files located in app/overlays/. They connect to the StreamooS runtime via WebSocket and are used as Browser Sources in OBS.

Available Overlays ​

Built-in overlays live under app/overlays/builtin/<id>.streamoos/. Each ships a manifest.json (auto-discovered by overlay-discovery.ts), an HTML entry, CSS and JS.

OverlayPurpose
timer.streamoosCountdown with theme variants
radio.streamoosRadio: art + title/artist of the Radio Source picked in Music (KHInsider or the system player)
twitch-alerts.streamoosTwitch alerts (follow/sub/raid/cheer/gift_sub) with optional shiny effect
twitch-events-ribbon.streamoosRecent events scrolling ribbon - see Position variants below
twitch-shoutout.streamoosShoutout: the channel /shoutout points at (avatar, last category)
twitch-goal-bar.streamoosGoal bar filled by the live counters (follows, subs, bits, viewers…)
spectrum-analyzer.streamoosLightweight music visualizer (28 bars, pulse/wave modes)
dock-obs.streamoosMinimalist 3-view live dock (Music / Timer / Chat)

Assets the user uploaded (custom alert images, etc.) are stored in SQLite and served by the overlay HTTP server at http://127.0.0.1:8766/overlays/asset/<id> - the renderer-side streamoos-asset:// protocol is only available inside Electron, so OBS browser sources use the HTTP route instead. The overlay JS rewrites streamoos-asset://<id> URIs to this path automatically (twitch_alerts.js#resolveAssetPath).

Adding an Overlay in OBS ​

  1. In OBS, click + → Browser

  2. Uncheck "Local file" and enter the absolute path:

    file:///C:/Users/you/StreamooS/app/overlays/timer/index.html

    (Linux: file:///home/you/StreamooS/app/overlays/timer/index.html)

  3. Recommended resolution: 1920×1080 or match your scene size

  4. Enable "Refresh browser when scene becomes active"

The overlay automatically connects to ws://127.0.0.1:8765 on load. No additional configuration needed.

Composite Mode (multiple overlays, one OBS source) ​

The one-URL-per-overlay setup above still works unchanged and is the default. As an alternative, a composite source embeds several chosen overlays behind a single OBS Browser Source:

http://127.0.0.1:8766/overlays/composite/?preset=<id>
  • A preset (id, name, list of {overlaySlug, kind: "permanent" | "event", position}) is stored server-side (overlay_composite_presets table) and read via the WS command overlay_composite/get_preset. Presets are created/edited via window.streamoos.sendCommand (privileged renderer IPC) - save_preset/delete_preset are intentionally not reachable over the network-facing overlay WS, same as twitch/stream:save_preset.
  • Each embedded overlay runs in its own <iframe src="../builtin/<slug>?composite=1"> - it self-positions exactly as it would in a single-source setup. The composite page never moves an iframe.
  • ?composite=1 switches that overlay's StreamooSClient into proxy mode: it opens no socket of its own and relays through the parent's one real connection instead (see below).
  • Two kind:"event" overlays configured at the same position never render at once: each calls requestOverlaySlot(position, durationMs) (from streamoos-client.js) before showing a transient item and queues (FIFO, nothing dropped) if another is already showing there. kind:"permanent" overlays (radio, timer, ribbon) never queue.

Full rationale and what's explicitly out of scope for now (a preset-editing UI, a permanent-vs-event fade handoff): doc/internal/adr/ADR-010-overlay-composite-mode.md.

Shared WebSocket Client (StreamooSClient) ​

All overlays use app/overlays/shared/websocket-client.js - a reusable class that handles connection, automatic reconnection, and update reception.

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

  // Receive state updates
  client.on("update", (msg) => {
    if (msg.feature === "timer") {
      const { remainingSeconds, running } = msg.data;
      // update the DOM
    }
  });

  // Send a command
  client.sendCommand("timer", "start", { minutes: 5, seconds: 0 });
</script>

Constructor Options ​

js
new StreamooSClient(wsUrl, options);
OptionDefaultDescription
wsUrlws://localhost:8765WS server URL
reconnectBaseDelayMs1000Initial reconnect delay
reconnectMaxDelayMs15000Maximum reconnect delay
reconnectJitterMs250Random jitter added to delay
connectTimeoutMs5000Connection timeout
autoConnecttrueAuto-connect on instantiation

Available Events ​

js
client.on("connected", () => {
  /* connected */
});
client.on("disconnected", ({ willReconnect }) => {
  /* disconnected */
});
client.on("update", (msg) => {
  /* msg.feature, msg.data */
});
client.on("response", (msg) => {
  /* command response */
});
client.on("error", (msg) => {
  /* server error */
});

Methods ​

js
client.sendCommand(feature, action, data); // send a command
client.send(rawObject); // send a raw object
client.disconnect(); // manual disconnect (no reconnect)

Automatic Reconnection ​

The client implements exponential backoff with jitter:

  • 1st attempt: ~1s
  • Progression: doubling + jitter up to 15s max
  • Infinite reconnect until explicit disconnect()

Overlay Themes ​

Each overlay supports multiple visual themes. Themes are configured via the dashboard UI or by sending a set_overlay_config command via WebSocket.

OverlayThemes
KHInsiderstreamoos, ghibli, xp, glass, noisy, minimal-dark
Timerstreamoos, minimal-dark
SMTCstreamoos, minimal-dark

Overlay Position ​

All overlays support 9 anchor positions:

TopMiddleBottom
top-left · top-center · top-rightmiddle-left · center · middle-rightbottom-left · bottom-center · bottom-right

Twitch Events Ribbon - placement variants ​

The ribbon overlay accepts a ?position= URL query param so streamers can pin it to a corner or edge instead of the default full-canvas curve:

ValueLayout
omitOriginal 1920×1080 wavy strip
corner-top-left · corner-top-right · corner-bottom-left · corner-bottom-rightCompact 45 %-scaled ribbon anchored in a corner
edge-left · edge-rightRotated 90° so the ribbon scrolls vertically down a side
band-top · band-bottomCompressed horizontal strip along the chosen edge

Add the value to the URL when configuring the OBS browser source, e.g. …/twitch-events-ribbon.streamoos/twitch_events_ribbon.html?position=edge-left.

Twitch Goal Bar - URL config ​

twitch-goal-bar.streamoos accepts these parameters:

ParameterDefaultDescription
metricfollowsTracked counter, see the table below
target50Goal, a positive integer
labelauto labelText shown on the left, 60 characters max
onReachedcelebratefreeze (bar stays full), celebrate (pulse), next (roll to the next target)
step= targetIncrement used by onReached=next
positionbottom-centertop-left · top-center · top-right · bottom-left · bottom-center · bottom-right
themeneonneon · streamoos
accent#9147ffBar colour, hex

Session and total are not the same number. "40 follows tonight" and "1000 followers total" are different metrics; the auto label says which one, a custom label will not do that for you.

metricScopeSource
follows · subscriptions · giftSubs · cheerBits · raidsSessionEventSub events of the current session
totalFollowers · totalSubsTotalHelix poll (channel-wide count)
viewersNowInstantHelix poll

The total* and viewersNow metrics stay at zero off-stream: the Helix poll only runs while live.

Spectrum Analyzer - URL config ​

spectrum-analyzer.streamoos accepts the following query params:

ParamDefaultDescription
bars28Number of frequency bars (4–64)
color#00ff88Hex bar color
modepulsepulse (beat-driven) or wave (sinusoidal only)
sensitivity5Amplitude multiplier (1–10)

Configuring via WebSocket ​

json
{
  "type": "command",
  "feature": "smtc",
  "action": "set_overlay_config",
  "data": {
    "enabled": true,
    "radioSource": "auto",
    "position": "bottom-right",
    "showArtwork": true,
    "showProgress": true,
    "theme": "glass"
  }
}

Custom Overlays ​

StreamooS supports importing custom user-created overlays via the dashboard. Custom overlays are stored in the app's userData directory and can be managed through the preload bridge:

  • listCustomOverlays() -- list all imported overlays
  • addCustomOverlay() -- import a new HTML overlay
  • removeCustomOverlay(id) -- remove a custom overlay
  • openCustomOverlay(id) -- open in an external window

Custom overlays connect to the same ws://127.0.0.1:8765 endpoint and can use app/overlays/shared/websocket-client.js.

Building a Custom Overlay ​

Minimal structure:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        background: transparent;
        margin: 0;
      }
      /* OBS-friendly styles */
    </style>
  </head>
  <body>
    <div id="display">Connecting...</div>

    <script src="../shared/websocket-client.js"></script>
    <script>
      const client = new StreamooSClient();

      client.on("connected", () => {
        document.getElementById("display").textContent = "Connected";
        client.sendCommand("timer", "get_state");
      });

      client.on("update", (msg) => {
        if (msg.feature !== "timer") return;
        const { remainingSeconds } = msg.data;
        const m = Math.floor(remainingSeconds / 60)
          .toString()
          .padStart(2, "0");
        const s = (remainingSeconds % 60).toString().padStart(2, "0");
        document.getElementById("display").textContent = `${m}:${s}`;
      });
    </script>
  </body>
</html>

Allowed WebSocket Origins ​

The server validates the Origin header on every connection:

OriginContext
nullOBS Browser Source (local file)
file://Electron production
http://127.0.0.1:5173Vite dev server

Any other origin is rejected with HTTP 403.

Released under the MIT License.