Low-latency WebRTC
WebRTC player on LiveKit with sub-second latency for "watch live now" and video calls. Ephemeral per-room subscribe tokens.
StreamHub is a management layer on top of LiveKit: sub-second WebRTC, multi-protocol ingest, live HLS, recording to your own S3, and a lightweight drop-in JavaScript SDK to publish and subscribe over WebRTC. Multi-tenant, with signed webhooks and observability.
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.
Come in however you want, go out in low-latency WebRTC or embeddable HLS.
WebRTC player on LiveKit with sub-second latency for "watch live now" and video calls. Ephemeral per-room subscribe tokens.
Push from OBS/ffmpeg to rtmp://…:1935/live/<key>. Stream key + optional password: StreamHub drops any push that wasn't validated first.
WHIP endpoint to publish over WebRTC straight from the browser or modern encoders, without going through RTMP.
Pull from a remote source (rtsp://camera/stream) and republish it into the room. Ideal for IP cameras and NVRs.
Segmented HLS egress served at /hls/<app>/<room>/index.m3u8 (video.js, open CORS). ~6-15s, embeddable anywhere.
The media is yours: it lands in your bucket. Outputs go to YouTube, Twitch or your CDN.
MP4 egress uploaded to your own S3 bucket (AWS, Wasabi, MinIO). One bucket/prefix per app; keys never live in the YAML.
Every recording lands as a VOD in your S3 with a snapshot, metadata (duration/resolution/codec) and a public or presigned URL.
Cut the recording every N minutes: each part is its own MP4 = its own VOD, indexed and with a recording_part_ready callback.
Capture a JPEG every N seconds during recording, uploaded to your S3. On-demand snapshots of any room too.
Forward a room to an external RTMP/RTMPS (YouTube/Twitch/custom) with the Broadcast widget: browser webcam → restream.
Chat, reactions and viewers over data-channels; signed webhooks for everything that happens.
Chat over LiveKit data-channels (with emojis). The backend can inject messages and it fires the chat_message callback.
Floating animated reactions on the reaction topic: real-time hearts and likes, with their reaction callback.
Real subscriber count per live stream, excluding publishers and hidden/QC participants. Exported to Prometheus.
StreamHub posts a signed JSON (HMAC-SHA256) for EVERY event: room, participants, ingress/egress, recording, VOD and HLS.
Public /play and /embed pages + iframe snippet. WebRTC and HLS players without auth, with a chat/reactions/viewers panel included.
Isolated apps, adaptive transcoding with optional GPU and Prometheus metrics for everything.
Rendition ladder via LiveKit simulcast + multi-layer ingress for an adaptive player per app, with no extra config.
Per-node GPU detection (NVIDIA nvidia-smi / VAAPI /dev/dri) and auto/gpu/cpu hwaccel per app. Never fails when there's no GPU.
Each app is a tenant: namespaced rooms/streams, its own S3, its own VODs/DB, its own tokens and callbacks — fully isolated.
Teams isolated per tenant, roles/permissions and quotas: apps, concurrent streams, recording minutes and GB of egress per month.
Prometheus metrics at /metrics (streams, viewers, VODs, S3 uploads, callbacks, GPU…), health probe, stats and queryable logs.
Global streamhub.db registry + one vods.db per app. No heavy database to operate: SQLite per tenant and media in S3 per app.
Everything over an API, everything self-hostable. Drop in the SDK and start publishing in a few lines.
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.
API under /api/v1 with an { data, error } envelope. Apps, ingress, recording, VODs, tokens, broadcast and HLS: all over HTTP.
Built-in auth (JWT signup/login, teams, superadmin) + sk_ API tokens for server-to-server. Bearer on every endpoint.
Audio-only rooms (Discord-style voice) + radio mode: one host publishes and listeners join hidden, subscribe-only, with a public listen-token.
Self-hostable end to end (Docker + LiveKit). In beta and free. Don't want to run it? We host it for you.
Real screenshots of the StreamHub backoffice. One place for your apps, streams, recordings, tokens and webhooks — running on LiveKit.






Core status at a glance: live CPU, memory and disk, active streams, registered apps and ingress/egress connections. With LiveKit health and uptime.
Light or dark, your call — the whole backoffice is themed.


From zero to on-air in three steps.
Each app is an isolated tenant: its own room prefix, S3 bucket, tokens and quotas. Ready in seconds from the backoffice.
Push your feed over RTMP, WHIP, RTSP or straight from the webcam. Copy the URL and stream key into OBS and go live.
Sub-second WebRTC or HLS, recording to your own S3 (VOD + snapshots) and embeddable players via public link or iframe.
We're in beta and it's free. Open source and self-hostable — or we host it for you.
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.
Per-feature deep-dives, from WebRTC to quotas.
Server-wide REST surface, guarded by sk_ tokens.
Everything scoped to a single tenant app.
Incoming LiveKit events and outbound signed POSTs.
The per-app config.yaml, secrets stripped.
Native and device clients, and the protocol each speaks.
One docker compose up, or systemd + nginx.
How it is built and where it is heading.
Authenticate with a sk_ token. Room names are namespaced under the app prefix (live + demo → live-demo).
# 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 // 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();
}); # Open source — self-host it, or let us host it for you.
git clone <vision-media-server> # repo: vision-media-server
cp .env.example .env # LiveKit keys, per-app S3, PUBLIC_BASE_URL
docker compose up -d # core + LiveKit + ingress/egress, HTTPS via Caddy
# 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 is a lightweight JavaScript SDK for publishing and subscribing over WebRTC, built on livekit-client. A tiny constructor, a handful of methods and simple string callbacks — put a camera on air or play a room in just a few lines.
import { StreamHubAdaptor } from "@streamhub/adaptor";
// A lightweight WebRTC client, built on livekit-client.
const adaptor = new StreamHubAdaptor({ /* config below */ }); 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 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> <!-- 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> initialized, publish_started, newStreamAvailable, roomInformation, data_received…
joinRoom, publish, play, sendData, switchVideoCameraCapture, enableStats…
swap camera or mic on the fly and tune bitrate per sender, live
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.
StreamHub is 100% open source and self-hostable — or, if you'd rather not run it, we host it for you. We're in beta, so you can try it free while we keep polishing.
Clone the repo and run your own media server on LiveKit. Recording to your S3, your apps, your rules. No lock-in.
Don't want to operate the infra? We hand you StreamHub managed, updated and monitored. You focus on streaming.
Tell us your use case — low-latency WebRTC, RTMP/WHIP/RTSP ingest, recording to your own S3, restreaming to YouTube/Twitch or multi-tenant. We'll help you get going, self-hosted or managed.
Start in minutes
Try the live app, nothing to install.
Your data, your S3
Recordings and VOD in your own bucket.
Open the app
app.streamhub.studio