Preview. The Kotlin module is in private development — the release AAR builds cleanly (~329 KB, 18/18 unit tests pass) — and a Maven Central publication is on the v0.3 roadmap. The API surface below is stable; only the distribution channel is in flight. Need source access during the preview window? Email [email protected].
Kotlin SDK for Android. Mirrors the @levelchat/web concepts — LevelChat, Room, tracks, events — adapted to idiomatic Kotlin Coroutines + Flow. Naming follows Kotlin idiom: events are a sealed class RoomEvent (RoomEvent.ParticipantJoined) collected from a SharedFlow, not Web's kebab-case event strings. See the SDK parity matrix for the full per-platform naming table.
MIT-licensed, ~329 KB release AAR. Real WebRTC via io.github.webrtc-sdk:android.
Install — preview
The standard hosted install (the shape below) lands with v0.3:
dependencies {
implementation("ai.levelchat:sdk-android:0.3.0")
}During the preview window the AAR is distributed under NDA — email
[email protected] with your use case and we'll send back
the private Maven mirror credentials + a build.gradle.kts snippet you can drop into your app
module.
Permissions + foreground service
Required AndroidManifest.xml entries:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- For screen-share via MediaProjection: -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />For screen-share you also need to declare a Service with android:foregroundServiceType="mediaProjection", started while the capture session is live. The SDK's ScreenShare module wraps MediaProjectionManager.createScreenCaptureIntent() and surfaces the result through a flow you forward into room.publishScreen().
The end-to-end path
Every LevelChat SDK follows the same five steps: install → token → join → publish → handle events → leave. Here it is for Kotlin.
1. Mint a token
In production the token comes from your backend — embedding an API key in an APK leaks it,
since APKs decompile trivially. The issueToken helper exists only for getting-started
tutorials. See the Quickstart for the POST /v1/auth/tokens/room request shape.
val token = fetchTokenFromYourBackend()2. Construct the client + join
LevelChat is constructed via the create factory (it needs a Context), not new. joinRoom
is a suspend function that returns the Room; joinLive wraps it with the role hint:
import ai.levelchat.sdk.LevelChat
import ai.levelchat.sdk.JoinRoomOptions
import ai.levelchat.sdk.JoinLiveOptions
import ai.levelchat.sdk.LiveRole
val client = LevelChat.create(context = applicationContext)
// Either join a room directly…
val room = client.joinRoom(JoinRoomOptions(token = token))
// …or join as a typed broadcaster / viewer:
val live = client.joinLive(JoinLiveOptions(token = token, role = LiveRole.BROADCASTER))
val room = live.roomJoinRoomOptions mirrors the web SDK: token (required), device, preferredCodec,
simulcast (default true), svc, e2eeKey, signalingUrl.
3. Handle events
room.events is a SharedFlow<RoomEvent> — collect it from your ViewModel scope. RoomEvent
is a sealed class, so a when covers every case:
viewModelScope.launch {
room.events.collect { event ->
when (event) {
is RoomEvent.ParticipantJoined ->
Log.d("LC", "${event.participant.identity} joined")
is RoomEvent.ParticipantLeft ->
Log.d("LC", "${event.participant.identity} left")
is RoomEvent.TrackSubscribed ->
// A remote track is ready — bind event.track to a SurfaceViewRenderer.
attachRenderer(event.track)
is RoomEvent.TrackUnsubscribed ->
detachRenderer(event.track)
is RoomEvent.ConnectionStateChanged ->
// event.state ∈ CONNECTING | CONNECTED | RECONNECTING | DISCONNECTED | FAILED
updateConnectionBanner(event.state)
is RoomEvent.ConnectionQuality ->
updateNetworkIndicator(event.participantId, event.quality)
is RoomEvent.ChatMessage ->
appendChatMessage(event.message)
is RoomEvent.Error ->
Log.e("LC", event.error.code)
else -> Unit
}
}
}4. Publish camera + mic
Both methods are suspend and return a TrackView whose nativeTrackId you bind to a
SurfaceViewRenderer for the local preview:
val camera = room.publishCamera(PublishCameraOptions(resolution = Resolution.P720))
val mic = room.publishMic()PublishCameraOptions / PublishMicOptions mirror the web SDK — deviceId for device
selection, encodings for simulcast layers, echoCancellation / noiseSuppression /
autoGainControl on the mic.
5. Chat + recording
In-call chat rides the room's data channel — sendChat to send, RoomEvent.ChatMessage events
to receive (wired in step 3):
room.sendChat(text = "hello room")Recording is host-only and server-side. startRecording() returns a RecordingView whose id
you can surface in your UI:
val recording = room.startRecording(
StartRecordingOptions(layout = StartRecordingOptions.Layout.SPEAKER),
)
// …
room.stopRecording()Recording is gated by the
recordcapability on the token — a UI-only "host" flag is not enough. On Web the canonical method isroom.record({ compose }); on Android thestartRecordingname +layoutoption is current. See the Recordings guide.
6. Leave
leave() tears down the peer connection and signaling:
room.leave()UI Kit
The SDK exports brand-pinned Jetpack Compose composables under the ai.levelchat.ui package, matching the React component kit's component set.
import ai.levelchat.ui.VideoTile
import ai.levelchat.ui.ControlBar
import ai.levelchat.ui.PreJoin
import ai.levelchat.ui.NetworkQuality
VideoTile(
name = "Mert",
quality = NetworkQuality.Good,
micOn = true,
isSpeaking = speaker == myId,
modifier = Modifier.size(240.dp, 180.dp),
)The Compose kit ships VideoTile, ControlBar, PreJoin, SpeakerStage, Chat, PollPanel, QAPanel, and Whiteboard — every component the React web kit exports has a Compose counterpart with the same name and brand tokens.
Public API
class LevelChat— entrypoint; construct withLevelChat.create(context, config).joinRoom/joinLive/issueToken(dev only).class Room—join/leave/publishCamera/publishMic/publishScreen/stopPublishing/subscribe/unsubscribe/sendChat/startRecording/stopRecording.room.events: SharedFlow<RoomEvent>— typed event surface (sealed class hierarchy:ParticipantJoined,TrackSubscribed,ConnectionStateChanged,ConnectionQuality,ChatMessage, …) youcollectin your ViewModel.
Modules: Errors, Logger, Events, Constants, Region, Quality, Encryption, Devices, Signaling, Peer, Track, Publication, Subscription, Participant, Room, AudioSession, Lifecycle, ScreenShare.
Audio session
Auto-configured for voice calls: AudioManager.MODE_IN_COMMUNICATION + AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE. The AudioSession.UNMANAGED escape hatch is available for Telecom/ConnectionService-integrated apps that already drive routing themselves.
Lifecycle
Built-in ProcessLifecycleOwner / DefaultLifecycleObserver integration via the
LifecycleObserver class — it emits Paused / Resumed events on a SharedFlow
and the Room pauses local video when the app is backgrounded. Drive it yourself
(observer.start() / observer.stop()) if you need custom routing.
License
The SDK itself is MIT-licensed — embed in any app, commercial or personal, no royalties. The LevelChat server is BUSL-licensed and requires a paid commercial license for production use beyond the trial threshold. See Licensing.