Skip to content

Repository files navigation

compose-livekit

CI Maven Central

A Compose Multiplatform wrapper around the native LiveKit client SDKs — one shared Kotlin API (rooms, participants, tracks, events) plus a common @Composable VideoRenderer, for Android and iOS.

  • Android — backed by client-sdk-android 2.28.0 (a normal Maven dependency).
  • iOS — backed by client-sdk-swift 2.16.0 through a hand-written @objc Swift bridge (LiveKitKMPBridge), because the pure-Swift SDK isn't reachable from Kotlin/Native cinterop directly.

Status: Published and runnable on both platforms — io.github.akardas16:compose-livekit:0.1.0 on Maven Central (Android/KMP) and via SwiftPM (iOS). Android was tested live on two physical devices; iOS builds/links/launches and connected live to LiveKit Cloud from the simulator (iOS 26). Not yet done: a full-device iOS broadcast-extension screen share (iOS currently does in-app capture).

Installation

Latest version: 0.1.0

Android / Kotlin Multiplatform (Maven Central)

// build.gradle.kts
dependencies {
    implementation("io.github.akardas16:compose-livekit:0.1.0")
}

The transitive WebRTC dependency resolves via JitPack, so add it to your repositories:

// settings.gradle.kts → dependencyResolutionManagement.repositories
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }

iOS (Swift Package Manager)

In Xcode: File → Add Package Dependencies…https://github.com/akardas16/compose-livekit → add the ComposeLiveKit product. Or in Package.swift:

dependencies: [
    .package(url: "https://github.com/akardas16/compose-livekit", from: "0.1.0"),
],
targets: [
    .target(name: "YourApp", dependencies: [
        .product(name: "ComposeLiveKit", package: "compose-livekit"),
    ]),
]

ComposeLiveKit bundles the LiveKitKMP XCFramework + the LiveKitKMPBridge bridge (which pulls in LiveKit + WebRTC). Link your app target with -ObjC, and set the Info.plist keys under iOS setup.

Quick start

A minimal call screen in shared Compose code — connect, publish camera + mic, and render every participant's video:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.weight
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.blindid.livekit.VideoRenderer
import com.blindid.livekit.rememberLiveKitRoom

@Composable
fun CallScreen(url: String, token: String) {
    val room = rememberLiveKitRoom()                     // created now, closed when it leaves composition

    val state by room.state.collectAsState()
    val local by room.localParticipant.collectAsState()
    val remotes by room.remoteParticipants.collectAsState()

    LaunchedEffect(Unit) {
        room.connect(url, token)                         // suspend
        room.setCameraEnabled(true)
        room.setMicrophoneEnabled(true)
    }

    Column(Modifier.fillMaxSize()) {
        Text("State: $state")

        // Your own camera preview (mirrored for the front camera)
        VideoRenderer(
            track = local?.videoTracks?.firstOrNull()?.videoTrack,
            mirror = true,
            modifier = Modifier.fillMaxWidth().weight(1f),
        )

        // Every remote participant's video
        remotes.forEach { participant ->
            VideoRenderer(
                track = participant.videoTracks.firstOrNull()?.videoTrack,
                modifier = Modifier.fillMaxWidth().weight(1f),
            )
        }
    }
}

Observe events and data messages with room.events (a SharedFlow<RoomEvent>), and send data with room.publishData(bytes). On Android, request the CAMERA and RECORD_AUDIO runtime permissions before enabling capture; on iOS add the usage keys (see iOS setup). A full example is in sample-shared.

Features

Capability Android iOS Common API
Connect / disconnect / reconnect state connect, disconnect, close, state
Camera enable + flip (front/back) setCameraEnabled, switchCamera
Microphone enable setMicrophoneEnabled
Video rendering (front-camera-only mirror) @Composable VideoRenderer(track, mirror = …)
Participants (local + remote) & their data localParticipant, remoteParticipants, activeSpeakers
Room events (local + remote) events: SharedFlow<RoomEvent>
Data messages (send + receive) publishData, RoomEvent.DataReceived
Audio output selection (speaker / earpiece / wired / Bluetooth) ✅¹ audioDevices, selectedAudioDevice, selectAudioDevice
Screen share ✅² setScreenShareEnabled

¹ iOS routes via AVAudioSession (Speaker / receiver + connected accessories); best-effort. ² iOS uses in-app ReplayKit capture (shares the app's own content). Full-device capture needs a separate Broadcast Upload Extension (not included yet). Android uses MediaProjection + LiveKit's built-in foreground service.

Modules

Path What
livekit/ The publishable Compose Multiplatform library. commonMain = shared API; androidMain = real livekit-android impl; iosMain = real impl via cinterop against the bridge.
iosBridge/ LiveKitKMPBridge — a Swift Package exposing an @objc facade over the LiveKit Swift SDK (async→completion-handlers, structs/enums→ObjC-safe DTOs).
sample-shared/ Shared Compose UI (App(), iOS MainViewController(), expect/actual ScreenShareButton) used by both sample apps.
sample/ Android sample app (src/main) + the iOS Xcode app (iosApp/, generated with XcodeGen from project.yml). Both runnable.

The library targets androidTarget, iosArm64, and iosSimulatorArm64 (Intel-simulator iosX64 is intentionally omitted).

How the iOS bridge works

Kotlin/Native cinterop only reads C/Objective-C headers, and LiveKit's Swift SDK is pure Swift. So the library binds a hand-written clean Objective-C header (livekit/src/nativeInterop/cinterop/include/LiveKitKMPBridge.h) that mirrors the bridge's @objc surface, and the real symbols + WebRTC link in the consuming app via the LiveKitKMPBridge SPM package. The KMP framework itself needs no Xcode/LiveKit/WebRTC to compile. See iosBridge/README.md.

Public API (common)

val room = rememberLiveKitRoom()                 // Compose; or on Android: LiveKit.create(context)
room.connect(url, token)                         // suspend
room.setCameraEnabled(true); room.setMicrophoneEnabled(true)
room.switchCamera()
room.publishData("hi".encodeToByteArray(), topic = "chat")

// State & events (all Flows)
room.state              // StateFlow<ConnectionState>
room.events             // SharedFlow<RoomEvent>  (Connected, ParticipantConnected, TrackSubscribed,
                        //   TrackMuted, ActiveSpeakersChanged, ConnectionQualityChanged, DataReceived, …)
room.localParticipant   // StateFlow<LocalParticipant?>
room.remoteParticipants // StateFlow<List<RemoteParticipant>>
room.activeSpeakers     // StateFlow<List<Participant>>

// Participant data: identity, sid, name, metadata, isSpeaking, connectionQuality,
// videoTracks / audioTracks (TrackPublication: source, isMuted, isSubscribed, videoTrack/audioTrack)

// Audio output
room.audioDevices(); room.selectedAudioDevice(); room.selectAudioDevice(device)

// Video (common Composable; mirror is display-only)
VideoRenderer(track = participant.videoTracks.firstOrNull()?.videoTrack, mirror = false)

// Screen share (see platform notes below)
room.setScreenShareEnabled(true)

Requirements

  • Kotlin 2.4.10, Compose Multiplatform 1.11.1, AGP 9.1.0, Gradle 9.6.1, JDK 11+.
  • Android minSdk 24. iOS deployment target 14+. iOS builds need Xcode 16+ and XcodeGen.

Android setup

Add the JitPack repository — required, because LiveKit's WebRTC dependency resolves there:

// settings.gradle.kts → dependencyResolutionManagement.repositories
google(); mavenCentral()
maven { url = uri("https://jitpack.io") }

Manifest permissions: INTERNET, CAMERA, RECORD_AUDIO, MODIFY_AUDIO_SETTINGS (request CAMERA / RECORD_AUDIO at runtime). For screen share also add FOREGROUND_SERVICE + FOREGROUND_SERVICE_MEDIA_PROJECTION and declare LiveKit's built-in service:

<service
    android:name="io.livekit.android.room.track.screencapture.ScreenCaptureService"
    android:exported="false"
    android:foregroundServiceType="mediaProjection" />

Then request the MediaProjection permission, hand the result to the room, and enable:

// after the MediaProjectionManager.createScreenCaptureIntent() result:
room.setScreenCaptureResult(resultIntent)   // androidMain-only extension
room.setScreenShareEnabled(true)

See sample/src/main/AndroidManifest.xml and sample-shared/src/androidMain/.../ScreenShare.android.kt for the full flow.

iOS setup

A consuming iOS app adds two things:

  1. The compose-livekit KMP framework (produced by :sample-shared:embedAndSignAppleFrameworkForXcode, or an XCFramework of :livekit).
  2. The LiveKitKMPBridge Swift package (iosBridge/) — which transitively pulls LiveKit + WebRTC and provides the Objective-C symbols.

Link with -ObjC (so the bridge's ObjC classes aren't dead-stripped), exclude x86_64 for the simulator SDK, and set CADisableMinimumFrameDurationOnPhone = true. Info.plist needs NSCameraUsageDescription + NSMicrophoneUsageDescription; enable the Audio, AirPlay, and Picture in Picture background mode. The sample/iosApp/project.yml shows a complete working setup.

Running the samples

Android (device/emulator connected):

./gradlew :sample:installDebug     # or open in Android Studio and Run

iOS:

brew install xcodegen              # once
cd sample/iosApp && xcodegen generate && open iosApp.xcodeproj   # then Run in Xcode

Enter your LiveKit server URL (wss://…) and an access token, tap Connect. The sample exercises the full API: camera/mic, flip, screen share, data messages, an audio-output picker, a live event log, and a participants panel — plus a video grid with a speaking indicator.

Build & verify

./gradlew :sample:assembleDebug                        # Android app (compiles :livekit + :sample-shared)
./gradlew :livekit:compileKotlinIosSimulatorArm64      # iOS library
./gradlew :livekit:linkDebugFrameworkIosSimulatorArm64 # iOS framework links
cd iosBridge && xcodebuild -scheme LiveKitKMPBridge -destination 'generic/platform=iOS Simulator' build

Note: the library uses AGP 9's androidLibrary KMP DSL (single variant), so there is no :livekit:assembleDebug — verify the library by building the sample.

License

Apache License 2.0 — see LICENSE.

About

Compose Multiplatform wrapper around the LiveKit client SDKs for Android and iOS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages