Live stream
Broadcast from browser, OBS, or FFmpeg. Viewers watch via HLS (~5–12s) or WHEP (sub-2s).
On this page
1. Create a stream
From your backend:
POST https://kardocloud.com/v1/me/streams
Authorization: Bearer kl_live_xxx
Content-Type: application/json
{
"name": "Order #1234 walkthrough",
"mode": "LOW_LATENCY" // REALTIME | LOW_LATENCY | BROADCAST
}
→ {
"id": "stream_abc",
"name": "Order #1234 walkthrough",
"mode": "LOW_LATENCY",
"status": "IDLE",
"keys": [{ "id": "key_x", "key": "kl_a1b2c3...", ... }],
...
}
The keys[0].key is your stream key. Anyone with it can publish — guard it.
| Mode | Pipeline | Latency target | Use case |
|---|---|---|---|
REALTIME | WebRTC only, no HLS | < 300 ms | Interactive — calls, conferences |
LOW_LATENCY | WebRTC + LL-HLS | ~2 s (WHEP), ~3 s (LL-HLS) | Live shopping, sports, interactive broadcast |
BROADCAST | RTMP in + HLS out | 5–12 s | OBS streamers, large audience, archive |
2. Publish from OBS
In OBS → Settings → Stream:
- Service:
Custom... - Server:
rtmps://ingest.kardocloud.com:443/live(TLS) orrtmp://ingest.kardocloud.com:1935/live(plain) - Stream Key: the
keys[0].keyfrom above
Click Start Streaming. After ~5 seconds HLS is up at https://kardocloud.com/live/<key>.m3u8.
3. Publish from FFmpeg
Useful for automated test publishers or backend video processing pipelines:
ffmpeg -re \
-f lavfi -i "testsrc2=size=1280x720:rate=30,drawtext=text='%{localtime}':fontsize=32:fontcolor=white:x=20:y=20" \
-f lavfi -i "sine=frequency=1000" \
-c:v libx264 -preset veryfast -tune zerolatency \
-profile:v baseline -pix_fmt yuv420p \
-g 60 -keyint_min 60 -sc_threshold 0 \
-b:v 2500k -maxrate 2500k -bufsize 5000k \
-c:a aac -b:a 128k -ar 48000 -ac 2 \
-f flv rtmps://ingest.kardocloud.com:443/live/<your-stream-key>
4. Publish from browser (WHIP)
Use this when your users go live directly from the web — no OBS needed.
// 1) get a fresh stream key from your backend (skip if you already have one)
const { keys } = await fetch('/api/your-app/streams/' + streamId).then((r) => r.json());
const streamKey = keys[0].key;
// 2) acquire camera + mic
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720, frameRate: 30 },
audio: { echoCancellation: true, noiseSuppression: true },
});
// 3) PeerConnection — prefer H.264 so the SRS bridge to HLS works
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
stream.getTracks().forEach((t) => pc.addTrack(t, stream));
// IMPORTANT: prefer H.264 — without this, Chrome offers VP8 first and the
// HLS pipeline ends up audio-only (it can't transcode VP8 in real time).
for (const tx of pc.getTransceivers().filter((t) => t.sender.track?.kind === 'video')) {
const caps = RTCRtpSender.getCapabilities('video');
if (!caps) continue;
const h264 = caps.codecs.filter((c) => /H264/i.test(c.mimeType));
const others = caps.codecs.filter((c) => !/H264/i.test(c.mimeType));
if (h264.length) tx.setCodecPreferences([...h264, ...others]);
}
// 4) Create offer + WHIP POST
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
await new Promise((r) => {
if (pc.iceGatheringState === 'complete') return r();
setTimeout(r, 2000);
pc.addEventListener('icegatheringstatechange', () => pc.iceGatheringState === 'complete' && r());
});
const r = await fetch(
`https://kardocloud.com/rtc/v1/whip/?app=live&stream=${encodeURIComponent(streamKey)}`,
{ method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: pc.localDescription.sdp },
);
if (!r.ok) throw new Error('WHIP ' + r.status);
await pc.setRemoteDescription({ type: 'answer', sdp: await r.text() });
// pc is now publishing. Watch pc.iceConnectionState for 'connected'.
Kardolive ships a ready-made <WhipPublisher streamKey={...} /> React component in the customer-portal source — clone it into your app as-is.
5. Sharing with viewers
Three options ranked by effort:
Option A — public viewer page (no code)
https://kardocloud.com/watch/?key=<streamKey>&title=<optional+title>
Anyone with the URL can watch. No Kardolive account needed. The page shows a friendly "waiting for broadcaster" state when offline and auto-connects when you go live.
Option B — embed iframe
<iframe
src="https://kardocloud.com/embed/player.html?src=https://kardocloud.com/live/<key>.m3u8"
width="640" height="360"
allowfullscreen frameborder="0">
</iframe>
Option C — embed in your own React/JS code
Use the useLiveStream hook from React quickstart, or hls.js directly.
6. Embed in another website
Combined chat + live = use a scenario:
https://kardocloud.com/scenarios/shopping/?room=<streamKey>&hlsKey=<streamKey>
https://kardocloud.com/scenarios/webinar/?room=<streamKey>&hlsKey=<streamKey>
https://kardocloud.com/scenarios/karaoke/?room=<streamKey>&hlsKey=<streamKey>
Drop these in an iframe, or just give people the URL.
7. Watch via HLS
HLS works in any player — Safari natively, hls.js for other browsers, VLC, ffplay, OBS as a source, ExoPlayer on Android, AVPlayer on iOS.
https://kardocloud.com/live/<streamKey>.m3u8
~5–12 second delay. Universal compatibility. CDN-fronted (CloudFront).
8. Watch via WHEP (sub-2s)
For low-latency viewers (live shopping decisions, interactive Q&A):
// browser
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
pc.addTransceiver('audio', { direction: 'recvonly' });
pc.addTransceiver('video', { direction: 'recvonly' });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const r = await fetch(
`https://kardocloud.com/rtc/v1/whep/?app=live&stream=${encodeURIComponent(streamKey)}`,
{ method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: pc.localDescription.sdp },
);
const answerSdp = await r.text();
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
pc.ontrack = (e) => { videoEl.srcObject = e.streams[0]; };
Kardolive's customer-portal ships a <WhepPlayer streamKey={...} /> React component — copy that into your app.
9. Recording → VOD
Start a server-side recording while the stream is live:
POST https://kardocloud.com/v1/me/streams/<streamId>/recordings/start
Authorization: Bearer kl_live_xxx
→ { "recordingId": "rec_xxx", "status": "PROCESSING", "startedAt": "..." }
Stop it:
POST https://kardocloud.com/v1/me/streams/<streamId>/recordings/<rec>/stop
Once finalized, get the playback URL:
GET https://kardocloud.com/v1/me/recordings/<rec>/playback-url
→ { "ready": true, "url": "https://<signed>...", "format": "mp4", "expiresIn": 900 }
You can also subscribe to the recording.ready webhook to be notified async.
10. Adding live chat alongside the stream
Create a stream-type chat channel and use the stream ID as externalRef. See Chat → stream channels.
// after creating the stream:
POST /v1/me/channels
{ "type": "stream", "name": "Live Q&A", "externalRef": "<streamId>" }
The scenarios automatically connect chat when you pass ?room=<streamId> — they look up the matching channel by externalRef.