Mobile & native SDKs
StreamHub’s media plane is plain, self-hosted LiveKit, so native clients don’t talk to a
StreamHub-specific SDK for media — they use the official LiveKit client SDKs for their
platform, authenticated with a { token, wsUrl } pair minted by StreamHub’s REST API. The one
StreamHub-specific piece is streamhub-adaptor, a browser SDK that’s a drop-in shim over
livekit-client for apps migrating from the AntMedia WebRTCAdaptor API.
Every platform follows the same shape:
- Your backend calls
POST /apps/:app/tokenswith your StreamHub API token (server-side only — never embed the StreamHub Bearer token in a shipped app or device). - It returns
{ token, wsUrl }to the client. - The client feeds those two values straight to the platform’s LiveKit SDK:
room.connect(wsUrl, token).
Android — livekit-android (Kotlin)
Section titled “Android — livekit-android (Kotlin)”dependencies { implementation("io.livekit:livekit-android:2.11.0") // check for newer // Optional Jetpack Compose video renderer: implementation("io.livekit:livekit-android-compose-components:1.3.0")}<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />Request CAMERA/RECORD_AUDIO at runtime, then:
val room = LiveKit.create(applicationContext)val creds = fetchCredsFromYourBackend(room = "demo", identity = "phone-42") // { token, wsUrl }
room.connect(creds.wsUrl, creds.token)
val lp = room.localParticipantlp.setCameraEnabled(true)lp.setMicrophoneEnabled(true)
// Render remote video on RoomEvent.TrackSubscribed:room.events.collect { event -> if (event is RoomEvent.TrackSubscribed) { val track = event.track if (track is VideoTrack) { val renderer = SurfaceViewRenderer(context).apply { room.initVideoRenderer(this) } track.addRenderer(renderer) } }}Your backend’s token-minting call is a thin forward to StreamHub:
const r = await fetch("https://streamhub.example.com/api/v1/apps/live/tokens", { method: "POST", headers: { Authorization: `Bearer ${process.env.STREAMHUB_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ room, identity, canPublish: publish, canSubscribe: true, ttl: "1h" }),});const { data } = await r.json(); // { token, wsUrl, room, ... }Common variations:
- Subscribe-only (viewer): mint with
canPublish: false, skip thesetCameraEnabled/setMicrophoneEnabledcalls. - Audio-only / voice channel: mint with
audioOnly: true, or just enable the mic and never the camera. - Radio listener:
GET /apps/:app/radio/:room/listen-tokenreturns a subscribe-only audio token +wsUrldirectly. - Screen share:
lp.setScreenShareEnabled(true)(needs aMediaProjectionforeground service per Android platform rules). - Switch camera: the published
LocalVideoTrackexposesswitchCamera().
Checklist: add the SDK + manifest permissions → request runtime permissions → mint
{ token, wsUrl } server-side → room.connect() → enable camera/mic → render
TrackSubscribed → room.disconnect() on teardown.
iOS — LiveKitClient (Swift)
Section titled “iOS — LiveKitClient (Swift)”Add via Swift Package Manager:
https://github.com/livekit/client-sdk-swift.gitpinning a recent 2.x release. Info.plist needs usage descriptions or the app crashes on
capture:
<key>NSCameraUsageDescription</key><string>Used to publish your video.</string><key>NSMicrophoneUsageDescription</key><string>Used to publish your audio.</string>import LiveKit
let room = Room()let creds = try await fetchCredsFromYourBackend(room: "demo", identity: "iphone-7") // { token, wsUrl }
try await room.connect(url: creds.wsUrl, token: creds.token)try await room.localParticipant.setCamera(enabled: true)try await room.localParticipant.setMicrophone(enabled: true)Render remote video by implementing RoomDelegate.room(_:participant:didSubscribeTrack:) and
feeding the VideoTrack to the SDK’s VideoView (or SwiftUIVideoView in SwiftUI).
Common variations: same shape as Android — subscribe-only with canPublish: false;
audio-only/voice with setMicrophone(enabled: true) and no camera, or audioOnly: true;
radio listener via GET /apps/:app/radio/:room/listen-token with AVAudioSession configured
for background playback; switch camera via the CameraCapturer’s
switchCameraPosition().
Checklist: add client-sdk-swift via SPM + camera/mic usage strings → backend mints
{ token, wsUrl } → room.connect(url:token:) → setCamera/setMicrophone to publish →
implement RoomDelegate.didSubscribeTrack → room.disconnect() on teardown.
C++ / native
Section titled “C++ / native”Two realistic options:
| Option | Transport | When | Latency |
|---|---|---|---|
| (A) RTMP push to the ingress | RTMP/FLV | Recommended. Any C++ app with ffmpeg or GStreamer. Simple, robust, works today. | a few seconds |
(B) WebRTC FFI (livekit-ffi, the Rust core behind LiveKit’s other SDKs) |
WebRTC | Only if you truly need sub-second, two-way, or data channels in native C++. | sub-second |
For playback from C++, use HLS (/hls/<app>/<room>/index.m3u8) — trivially consumable by
libavformat, GStreamer, or any media widget.
Option A: push RTMP to the ingress
Section titled “Option A: push RTMP to the ingress”curl -s -X POST https://streamhub.example.com/api/v1/apps/live/ingress \ -H "Authorization: Bearer $STREAMHUB_TOKEN" -H "Content-Type: application/json" \ -d '{"inputType":"rtmp","room":"cpp","enableTranscoding":true}'# → { "data": { "ingressId": "...", "url": "rtmp://media.example.com:1935/live",# "streamKey": "sk-...", "roomName": "live-cpp" } }Your publish URL is url + / + streamKey. Easiest: shell out to ffmpeg, feeding it
raw frames on stdin:
std::string cmd = "ffmpeg -loglevel warning -y " "-f rawvideo -pix_fmt bgr24 -s 1280x720 -r 30 -i - " // raw frames on stdin "-c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p " "-g 60 -b:v 2500k -f flv \"" + rtmp_url + "\"";FILE* pipe = popen(cmd.c_str(), "w");// fwrite each frame's bytes to `pipe`; pclose(pipe) when done.For an in-process path without shelling out, link libavformat/libavcodec directly and mux
H.264 to the flv muxer against the same RTMP URL — the only StreamHub-specific detail is the
output URL. A GStreamer pipeline works the same way:
appsrc ! videoconvert ! x264enc tune=zerolatency bitrate=2500 key-int-max=60 ! flvmux streamable=true name=mux ! rtmpsink location="rtmp://media.example.com:1935/live/sk-... live=1"Verify with GET /apps/:app/streams, watch over HLS
(/hls/<app>/<room>/index.m3u8) or WebRTC via a subscribe token.
Option B: native WebRTC via livekit-ffi
Section titled “Option B: native WebRTC via livekit-ffi”LiveKit ships a Rust core with a C-ABI FFI (github.com/livekit/rust-sdks) that C++ can link
against to publish/subscribe real WebRTC tracks with sub-second latency, including data
channels. Auth is identical to Android/iOS — a { token, wsUrl } pair from
POST /apps/:app/tokens, fed into the FFI’s Connect request. There’s no first-class C++
wrapper (you drive the protobuf FFI directly) and the build is heavier (Rust toolchain +
WebRTC). For most native publishers, Option A is dramatically simpler and good enough —
only reach for the FFI if RTMP’s few seconds of latency is a hard blocker.
Checklist: POST /apps/:app/ingress {inputType:"rtmp"} → encode H.264/AAC and mux to FLV at
url/streamKey → confirm via GET /apps/:app/streams → watch via HLS.
Browser — streamhub-adaptor
Section titled “Browser — streamhub-adaptor”For web clients, streamhub-adaptor wraps livekit-client behind an API shaped like
AntMedia’s WebRTCAdaptor, so apps built against that API can point at StreamHub with minimal
changes. It’s built from streamhub-adaptor/ and served at /sdk/streamhub-adaptor.global.js
by core — see the adaptor’s own README for the publish/play API.