LevelChatLevelChatDocs
Android

SDKs

Android SDK

Kotlin SDK on libwebrtc.

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:

Kotlinappbuild.gradle.kts (v0.3+, Maven Central)
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:

xml
<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.

Kotlin
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:

Kotlin
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.room

JoinRoomOptions 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:

Kotlin
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:

Kotlin
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):

Kotlin
room.sendChat(text = "hello room")

Recording is host-only and server-side. startRecording() returns a RecordingView whose id you can surface in your UI:

Kotlin
val recording = room.startRecording(
  StartRecordingOptions(layout = StartRecordingOptions.Layout.SPEAKER),
)
// …
room.stopRecording()

Recording is gated by the record capability on the token — a UI-only "host" flag is not enough. On Web the canonical method is room.record({ compose }); on Android the startRecording name + layout option is current. See the Recordings guide.

6. Leave

leave() tears down the peer connection and signaling:

Kotlin
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.

Kotlin
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 with LevelChat.create(context, config). joinRoom / joinLive / issueToken (dev only).
  • class Roomjoin / 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, …) you collect in 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.