Skip to content

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:

  1. Your backend calls POST /apps/:app/tokens with your StreamHub API token (server-side only — never embed the StreamHub Bearer token in a shipped app or device).
  2. It returns { token, wsUrl } to the client.
  3. The client feeds those two values straight to the platform’s LiveKit SDK: room.connect(wsUrl, token).
app/build.gradle.kts
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")
}
AndroidManifest.xml
<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.localParticipant
lp.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 the setCameraEnabled/setMicrophoneEnabled calls.
  • 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-token returns a subscribe-only audio token + wsUrl directly.
  • Screen share: lp.setScreenShareEnabled(true) (needs a MediaProjection foreground service per Android platform rules).
  • Switch camera: the published LocalVideoTrack exposes switchCamera().

Checklist: add the SDK + manifest permissions → request runtime permissions → mint { token, wsUrl } server-side → room.connect() → enable camera/mic → render TrackSubscribedroom.disconnect() on teardown.

Add via Swift Package Manager:

https://github.com/livekit/client-sdk-swift.git

pinning 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.didSubscribeTrackroom.disconnect() on teardown.

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.

Terminal window
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.

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.

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.