Platform

Everything under the hood

A management layer over a LiveKit SFU: multi-protocol ingest, sub-second WebRTC, live HLS, recording to your own S3, a REST API and a drop-in SDK. Here is the full technical picture — the head-to-head with Ant Media, every feature, the API and the SDK.

Architecture

One stream in, thousands out

Follow the signal end to end: a device publishes once, the origin runs it through your app and its plugins, replicates it to edge nodes and fans it out to every viewer in sub-second WebRTC or HLS.

StreamHub topology: an ingest device (camera, phone or encoder) publishes a single stream over RTMP, WHIP or SRT to the origin node running StreamHub Core (LiveKit SFU, REST API, multi-tenant apps). Inside the origin the stream flows through a tenant app whose plugins — yolo, deface, scheduled-live, live-chat — process and augment it. The origin then replicates the stream to the edge nodes of the cluster, and each edge fans it out to many viewers at once over sub-second WebRTC and HLS.

StreamHub streaming architecture Animated diagram: an ingest device streams to the origin node, plugins process the feed, the origin replicates it to edge nodes, and the edges fan the stream out to many viewers. RTMP · WHIP · SRT replication WebRTC · HLS Ingest device Origin — StreamHub Core LiveKit SFU · API · apps app: cams Tenant app PLUGINS yolo deface scheduled-live live-chat Edge nodes edge-01 edge-02 Viewers
Ingest

One stream in — RTMP, WHIP or SRT from any camera, phone or encoder.

Process

The tenant app and its plugins augment the feed: detection, privacy masking, scheduling, chat.

Fan out

The origin replicates to edge nodes; every viewer gets sub-second WebRTC or live HLS.

StreamHub vs Ant Media Enterprise

Everything you run Ant Media EE for, open source and self-hosted

StreamHub covers the Ant Media Server Enterprise feature set for real-world streaming — measured, not promised — and adds the pieces we always missed: signed webhooks, per-app tenancy and a plugin marketplace. AGPL-licensed, free while in beta, and your media never leaves your infra.

0.19s

WebRTC latency — p50 measured over 601 samples in production

$0

license cost — open source (AGPL). Ant Media EE: ~$109/mo per instance

1 line

to install — curl | sudo bash on Ubuntu 24.04 / 26.04 LTS

License & price

StreamHub · Open source (AGPL). No per-instance license — free while in beta.

Ant Media EE · Proprietary. ~$109/mo (~$1,068/yr) per instance.

WebRTC latency

StreamHub · 0.19s p50, measured end-to-end over 601 samples in production.

Ant Media EE · ~0.5s per its marketing. Both are genuinely sub-second.

Webhooks

StreamHub · Every event is POSTed with an HMAC-SHA256 signature you verify in your backend.

Ant Media EE · Webhooks without a signature — you trust whoever calls.

Recording → S3

StreamHub · MP4 to your own bucket per app + periodic snapshots, indexed VODs with pagination and presigned download.

Ant Media EE · Records MP4/WebM to S3 as well — parity here.

Multi-tenant

StreamHub · Each app is a real tenant: isolated rooms, S3, database, tokens, quotas, teams and roles.

Ant Media EE · Applications scope settings and streams.

Migration SDK

StreamHub · @streamhub/adaptor shims Ant Media's WebRTCAdaptor: same callbacks and methods, no rewrite.

Ant Media EE · Its own WebRTCAdaptor SDK.

Plugins

StreamHub · Marketplace with working verticals: YOLO detection, CCTV cockpit, radio, watermark, live chat.

Ant Media EE · Java plugin SDK — you build your own.

Cluster & install

StreamHub · Origin + edge; an edge joins with one command and a token. Idempotent one-liner installer, TLS included.

Ant Media EE · Origin + edge cluster (EE feature); script install plus license setup.

Observability

StreamHub · Native Prometheus /metrics + Grafana, and logs with 30-day retention right in the panel.

Ant Media EE · Kafka-based monitoring pipeline into Grafana.

No smoke

Ant Media EE is a mature product and still ahead on config granularity (~170 per-app settings), native mobile SDKs and multi-destination restreaming — that last one sits at the top of our roadmap. If those are your blockers today, we'd rather tell you now.

Coming from Ant Media?

Swap the SDK import — your callbacks keep working.

Features

A complete media server, self-hosted

WebRTC, RTMP, WHIP, RTSP and HLS; recording to your S3; realtime, multi-tenant, signed webhooks and a lightweight WebRTC SDK. Built on LiveKit, self-hosted.

Streaming

Multi-protocol ingest and output

Come in however you want, go out in low-latency WebRTC or embeddable HLS.

Low-latency WebRTC

sub-second

WebRTC player on LiveKit with sub-second latency for "watch live now" and video calls. Ephemeral per-room subscribe tokens.

RTMP ingest

RTMP · OBS

Push from OBS/ffmpeg to rtmp://…:1935/live/<key>. Stream key + optional password: StreamHub drops any push that wasn't validated first.

WHIP

WebRTC-HTTP

WHIP endpoint to publish over WebRTC straight from the browser or modern encoders, without going through RTMP.

RTSP relay

RTSP pull

Pull from a remote source (rtsp://camera/stream) and republish it into the room. Ideal for IP cameras and NVRs.

Live HLS

video.js

Segmented HLS egress served at /hls/<app>/<room>/index.m3u8 (video.js, open CORS). ~6-15s, embeddable anywhere.

Recording & output

Record to your own S3, restream anywhere

The media is yours: it lands in your bucket. Outputs go to YouTube, Twitch or your CDN.

Recording to YOUR S3

AWS · Wasabi · MinIO

MP4 egress uploaded to your own S3 bucket (AWS, Wasabi, MinIO). One bucket/prefix per app; keys never live in the YAML.

Ready-to-play VOD

VOD

Every recording lands as a VOD in your S3 with a snapshot, metadata (duration/resolution/codec) and a public or presigned URL.

Split MP4 by parts

split N min

Cut the recording every N minutes: each part is its own MP4 = its own VOD, indexed and with a recording_part_ready callback.

Periodic snapshots

JPEG every Ns

Capture a JPEG every N seconds during recording, uploaded to your S3. On-demand snapshots of any room too.

Webcam broadcast → RTMP

YouTube · Twitch

Forward a room to an external RTMP/RTMPS (YouTube/Twitch/custom) with the Broadcast widget: browser webcam → restream.

Realtime

Live interaction and events

Chat, reactions and viewers over data-channels; signed webhooks for everything that happens.

Live chat

data-channel

Chat over LiveKit data-channels (with emojis). The backend can inject messages and it fires the chat_message callback.

Reactions

reactions

Floating animated reactions on the reaction topic: real-time hearts and likes, with their reaction callback.

Viewer counter

live

Real subscriber count per live stream, excluding publishers and hidden/QC participants. Exported to Prometheus.

Signed webhooks

HMAC-SHA256

StreamHub posts a signed JSON (HMAC-SHA256) for EVERY event: room, participants, ingress/egress, recording, VOD and HLS.

Embeddable players

/play · /embed

Public /play and /embed pages + iframe snippet. WebRTC and HLS players without auth, with a chat/reactions/viewers panel included.

Scale

Multi-tenant, adaptive and observable

Isolated apps, adaptive transcoding with optional GPU and Prometheus metrics for everything.

Adaptive transcoding

720 / 480 / 240

Rendition ladder via LiveKit simulcast + multi-layer ingress for an adaptive player per app, with no extra config.

Optional GPU

NVENC · VAAPI

Per-node GPU detection (NVIDIA nvidia-smi / VAAPI /dev/dri) and auto/gpu/cpu hwaccel per app. Never fails when there's no GPU.

Isolated multi-tenant apps

apps

Each app is a tenant: namespaced rooms/streams, its own S3, its own VODs/DB, its own tokens and callbacks — fully isolated.

Teams, roles and quotas

RBAC · quotas

Teams isolated per tenant, roles/permissions and quotas: apps, concurrent streams, recording minutes and GB of egress per month.

Observability

Prometheus

Prometheus metrics at /metrics (streams, viewers, VODs, S3 uploads, callbacks, GPU…), health probe, stats and queryable logs.

Per-app SQLite

SQLite

Global streamhub.db registry + one vods.db per app. No heavy database to operate: SQLite per tenant and media in S3 per app.

Operations

A backoffice that runs the whole fleet

Everything you used to SSH for, now in the panel: per-app dashboards, logs, VODs, cluster, server settings and backups.

Per-app dashboard

stats · filters

Each app gets its own board: active streams, live viewers, ingress/egress and recording activity, filterable per tenant.

Log management

30-day retention

Global and per-app logs with 30-day rotation, queryable and paginated from both the API and the panel. No SSH needed.

VOD library + download

paginated · presigned

Paginated VOD listing per app with snapshots and metadata, plus one-click download via presigned S3 URLs.

Cluster manager

origin + edge

Every node with its stats and health, right in the dashboard. An edge joins the cluster with a token and one command.

Server settings panel

no YAML edits

Server-wide configuration from the backoffice, Ant Media style: tune the server without touching files over SSH.

Backups + runbook

restore · rollback

Automated backups of the SQLite databases and per-app config, with a documented restore and rollback path.

Devs

API, drop-in SDK and open source

Everything over an API, everything self-hostable. Drop in the SDK and start publishing in a few lines.

Drop-in JS SDK

@streamhub/adaptor

A lightweight JavaScript SDK: pull in @streamhub/adaptor (npm or a <script> tag) and StreamHubAdaptor lets you publish and subscribe over WebRTC in a few lines.

REST API

/api/v1

API under /api/v1 with an { data, error } envelope. Apps, ingress, recording, VODs, tokens, broadcast and HLS: all over HTTP.

Auth + API tokens

JWT · sk_ tokens

Built-in auth (JWT signup/login, teams, superadmin) + sk_ API tokens for server-to-server. Bearer on every endpoint.

Radio / audio-only

audio-only

Audio-only rooms (Discord-style voice) + radio mode: one host publishes and listeners join hidden, subscribe-only, with a public listen-token.

Open source or hosted

self-host · hosted

Self-hostable end to end (Docker + LiveKit). In beta and free. Don't want to run it? We host it for you.

Documentation

Docs & API

A complete, self-hostable stack — documented end to end. A REST API (global and per-app), signed webhooks, per-app config, native integrations and a one-command deploy. Every route lives behind a single domain, with a live OpenAPI explorer.

Features

Per-feature deep-dives, from WebRTC to quotas.

  • WebRTC · ingest · HLS-live
  • Recording → S3 · VOD
  • Broadcast · transcoding/GPU
  • Chat · reactions · viewers

Global API

Server-wide REST surface, guarded by sk_ tokens.

  • GET /health · /stats
  • CRUD /apps
  • POST /tokens (mint sk_)
  • GET /logs
Open in Swagger →

Per-app API

Everything scoped to a single tenant app.

  • tokens · ingress · recording
  • vods · streams · snapshots
  • HLS start/stop
  • config · transcoding layers
Open in Swagger →

Webhooks & callbacks

Incoming LiveKit events and outbound signed POSTs.

  • HMAC-SHA256 signature
  • stream_started / _ended
  • vod_ready · recording_failed
  • chat_message · reaction

Config reference

The per-app config.yaml, secrets stripped.

  • recording · rtmp · webrtc
  • per-app S3 bucket
  • features block
  • callbacks url + secret

Integrations

Native and device clients, and the protocol each speaks.

  • Android (livekit-android)
  • iOS (LiveKitClient)
  • C++ (ffmpeg → RTMP)
  • ESP32-CAM → relay → RTMP

Self-hosting

One curl | sudo bash — or docker compose up.

  • one-liner installer (Ubuntu LTS)
  • join-cluster with a token
  • runbook · backups · rollback
  • Prometheus + Grafana

Architecture

How it is built and where it is heading.

  • mono-Node core + LiveKit
  • per-app SQLite data model
  • origin + edge cluster
  • WebRTC vs HLS/CDN

The REST API in five calls

Authenticate with a sk_ token. Room names are namespaced under the app prefix (live + demolive-demo).

base · https://streamhub.digitalhub.com.ar/api/v1
# Scaffold an isolated tenant: config, per-app SQLite, S3 prefix, samples
curl -s -X POST https://streamhub.digitalhub.com.ar/api/v1/apps \
  -H "Authorization: Bearer $STREAMHUB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"live","displayName":"Live","roomPrefix":"live"}'
# One call returns the LiveKit token + wsUrl + play/embed URLs
curl -s -X POST https://streamhub.digitalhub.com.ar/api/v1/apps/live/tokens \
  -H "Authorization: Bearer $STREAMHUB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"room":"demo","identity":"viewer-1","canPublish":false,"ttl":"10m"}'
# → { "data": { "token": "<jwt>", "wsUrl": "wss://media.digitalhub.com.ar",
#              "room": "live-demo", "playUrl": "...", "embedUrl": "...", "iframe": "..." } }
# RTMP push endpoint (also: whip, or url for RTSP/HLS pull)
curl -s -X POST https://streamhub.digitalhub.com.ar/api/v1/apps/live/ingress \
  -H "Authorization: Bearer $STREAMHUB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inputType":"rtmp","room":"demo","enableTranscoding":true}'
# → { "data": { "ingressId": "IN_abc123", "streamKey": "sk-9f3c...", "roomName": "live-demo" } }

# then push with any RTMP encoder (OBS, ffmpeg, a drone...)
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac \
  -f flv "rtmp://media.digitalhub.com.ar:1935/live/<streamKey>"
# Room-composite MP4 → the app's own bucket (AWS / Wasabi / MinIO) → VOD + snapshot
curl -s -X POST https://streamhub.digitalhub.com.ar/api/v1/apps/live/recording/start \
  -H "Authorization: Bearer $STREAMHUB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"roomName":"live-demo"}'
# → { "data": { "vodId": 12, "egressId": "EG_xyz789", "status": "recording" } }

# stop it (by VOD id or egress id); a vod_ready webhook fires when the upload finishes
curl -s -X POST https://streamhub.digitalhub.com.ar/api/v1/apps/live/recording/12/stop \
  -H "Authorization: Bearer $STREAMHUB_TOKEN"
# Start a live HLS egress for a stream (video.js-compatible, embeddable)
curl -s -X POST \
  https://streamhub.digitalhub.com.ar/api/v1/apps/live/streams/live-demo%2Fcamera-1/hls/start \
  -H "Authorization: Bearer $STREAMHUB_TOKEN"
# → { "data": { "playlistUrl":
#       "https://streamhub.digitalhub.com.ar/hls/live/live-demo/index.m3u8" } }

# the playlist itself needs no auth — point any HLS player at it
open https://streamhub.digitalhub.com.ar/hls/live/live-demo/index.m3u8
Signed webhooks HMAC-SHA256
// Every callback is a signed POST. Verify the HMAC-SHA256 over the raw body.
import crypto from 'node:crypto';

app.post('/streamhub', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.header('X-StreamHub-Signature');                 // "sha256=<hex>"
  const expected =
    'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
  if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
    return res.status(401).end();

  const evt = JSON.parse(req.body.toString('utf8'));              // { event, app, data }
  switch (evt.event) {
    case 'stream_started':   /* a publisher/ingress went live */  break;
    case 'vod_ready':        /* recording uploaded to your S3 */  break;
    case 'recording_failed': /* upload failed, local file kept */ break;
  }
  res.status(200).end();
});
Self-host in one command curl | sudo bash
# Open source — one command provisions a full node (Ubuntu 24.04/26.04 LTS):
curl -fsSL https://www.streamhub.studio/install.sh | sudo bash
# docker + LiveKit + ingress/egress + core + nginx/TLS + seeded API token. Idempotent.

# Prefer to wire the pieces yourself? (non-interactive install)
curl -fsSL https://www.streamhub.studio/install.sh | sudo bash -s -- \
  --non-interactive --domain media.example.com --email you@example.com
# then edit /opt/streamhub/.env  (LiveKit keys, per-app S3, PUBLIC_BASE_URL)

# Swagger / OpenAPI:  /api/v1/docs      Metrics:  /metrics      Health:  /api/v1/health

This is a tour, not the whole map — the full reference covers architecture, operations, testing and every environment variable. Explore it live in the OpenAPI docs, or read the streamhub-docs in the repo.

streamhub-adaptor

A lightweight WebRTC SDK — publish and subscribe in a few lines

@streamhub/adaptor is a lightweight JavaScript SDK for publishing and subscribing over WebRTC, built on livekit-client. It speaks the same language as Ant Media's WebRTCAdaptor — same string callbacks, same methods — so an existing Ant Media front-end migrates by swapping the import, not rewriting it.

migrating from Ant Media: swap one import
- import { WebRTCAdaptor } from "@antmedia/webrtc_adaptor";
+ import { StreamHubAdaptor } from "@streamhub/adaptor";

// Same callbacks, same methods — your Ant Media front-end keeps working.
const adaptor = new StreamHubAdaptor({ /* config below */ });
Install npm
npm install @streamhub/adaptor livekit-client
import { StreamHubAdaptor } from "@streamhub/adaptor";

const adaptor = new StreamHubAdaptor({
  // StreamHub mints the LiveKit token for you (prefer a pre-minted token + wsUrl in prod)
  streamhubApiUrl:   "https://streamhub.digitalhub.com.ar/api/v1",
  appName:           "live",
  streamhubApiToken: STREAMHUB_TOKEN,        // dev only — mint server-side for production
  mediaConstraints:  { video: true, audio: true },
  localVideoId:      "localVideo",           // <video id="localVideo"> preview element
  callback: (info, obj) => {
    if (info === "initialized")        adaptor.joinRoom("demo", "camera-1");
    else if (info === "joinedTheRoom") adaptor.publish(obj.streamId);   // send cam + mic
    else if (info === "publish_started") console.log("on air");
  },
  callbackError: (err, msg) => console.warn(err, msg),
});
import { StreamHubAdaptor } from "@streamhub/adaptor";

const viewer = new StreamHubAdaptor({
  streamhubApiUrl:   "https://streamhub.digitalhub.com.ar/api/v1",
  appName:           "live",
  streamhubApiToken: STREAMHUB_TOKEN,
  isPlayMode:        true,                    // subscribe-only: no camera / mic
  callback: (info, obj) => {
    if (info === "initialized")        viewer.joinRoom("demo", "viewer-1");
    else if (info === "joinedTheRoom") viewer.play(obj.ATTR_ROOM_NAME);
    else if (info === "newStreamAvailable") {
      const el = document.createElement("video");
      el.autoplay = el.playsInline = true;
      el.srcObject = new MediaStream([obj.track]);   // obj.track is a MediaStreamTrack
      document.getElementById("remote").append(el);
    }
  },
});
No build step? Drop in one tag IIFE · CDN
<!-- No build step? Drop in one script tag and you're ready. -->
<script src="https://streamhub.digitalhub.com.ar/sdk/streamhub-adaptor.global.js"></script>
<script>
  const viewer = new StreamHubAdaptor({ isPlayMode: true, /* ...config */ });
</script>
Embeddable player <iframe>
<!-- The mint call returns a ready-to-paste embed. Public player, no SDK needed. -->
<iframe
  src="https://streamhub.digitalhub.com.ar/embed/live/live-demo"
  width="640" height="360" frameborder="0"
  allow="autoplay; fullscreen; camera; microphone"
  allowfullscreen>
</iframe>

Drop-in for Ant Media's WebRTCAdaptor

same callbacks — initialized, publish_started, newStreamAvailable, data_received… — swap the import, keep your code

Methods

joinRoom, publish, play, sendData, switchVideoCameraCapture, enableStats…

Device & bitrate control

swap camera or mic on the fly and tune bitrate per sender, live

Data channels

chat and reactions ride LiveKit DataPackets; canPublishData is granted on every token

LiveKit handles simulcast and adaptive streaming natively. For production, mint tokens server-side and pass token + wsUrl so the management token never reaches the browser.

Ready to run it?

Install a full node on Ubuntu LTS with one command, or open the managed app.