Skip to content

Kotlin SDK walkthrough — one app from Android 5.0, with a CDN fallback

This is the step-by-step guide for aliran-kit, the native Kotlin SDK (sdk/android/ in the repo). You'll build an app that ships as one APK with minSdk 21. It behaves per device:

  • Android 10+ — the full P2P engine: login over the DHT, catalog, live playback, re-seeding. Same engine bundle and protocol as the official apps.
  • Android 5.0 – 9 — the engine physically cannot load, because its native runtime needs a libc feature added in Android 10. So the SDK stays silently inert and hands you two things instead: a detection hook, and a ready-made notice screen with an action button. This is the seam where you offer the viewer your own delivery, typically plain HLS from your CDN.

Every snippet below is the pattern of the repo's demo/MainActivity.kt. This was verified end-to-end on an Android 5.1 emulator (notice → fallback HLS playing) and a modern one (full P2P against a production panel).

0. Get the library building

aliran-kit is consumed from the repo for now — there is no Maven artifact yet. One-time prerequisites: the engine runtime is vendored from the React Native package's checkout:

git clone https://github.com/AbueloSimpson/aliran && cd aliran
cd client && npm install && cd ..
# the per-ABI addon set (populated by any client Android build, or directly):
cd client/node_modules/react-native-bare-kit/android && node link.mjs && cd ../../../..

cd sdk/android
./gradlew :aliran-kit:testDebugUnitTest   # sanity: JVM protocol tests
./gradlew :demo:assembleDebug             # the runnable reference APK

To use it from your own project, include the module — includeBuild/ include from your settings.gradle, or copy aliran-kit/ into your project — and depend on it:

dependencies { implementation(project(":aliran-kit")) }

Your app manifest needs INTERNET and, for the P2P path, cleartext permitted to loopback only (the engine serves HLS on 127.0.0.1). Copy demo/src/main/res/xml/network_security_config.xml.

1. The hook: detect an incompatible device

One call, at startup:

import aliran.kit.AliranBackend

if (AliranBackend.isSupported()) {
    startP2P()        // step 3
} else {
    showFallbackOffer()  // step 2 — Android 5.0-9 lands here
}

isSupported() is false on any Android below 10. In that state the whole backend is inert by contract: start() and every other method are safe no-ops, nothing throws, nothing queues, and no listener ever fires. So even code that forgets the check cannot crash the app. The engine's native library is never even class-loaded on these devices.

2. The notice + the CDN switch (your side)

EngineNotice is the ready-made screen for the unsupported branch: honest default copy, your branding, and an action button that is the switch. Wire onAction to mount your own delivery. The SDK deliberately provides the notice and the switch, never the content — what plays after the press is yours.

Here is the complete fallback, using ExoPlayer. aliran-kit already brings ExoPlayer in, and it plays plain HLS down to Android 5.0:

import aliran.kit.EngineNotice
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

private var fallbackPlayer: ExoPlayer? = null

private fun showFallbackOffer() {
    setContentView(EngineNotice(
        this,
        title = "Acme TV",                                   // your brand
        message = "This device can't run the P2P engine — " +
                  "Android 10 or newer is required.",        // optional override
        actionLabel = "Watch over the internet",             // the switch
        onAction = { startCdnPlayback() }
        // colors = EngineNotice defaults are dark; pass your palette to rebrand
    ))
}

private fun startCdnPlayback() {
    val view = PlayerView(this)
    setContentView(view)
    fallbackPlayer = ExoPlayer.Builder(this).build().also { p ->
        view.player = p
        // YOUR delivery — plain HLS from your CDN. This is entirely outside
        // the P2P system; entitlement/auth for this URL is your design.
        p.setMediaItem(MediaItem.fromUri("https://cdn.example.com/live/main.m3u8"))
        p.playWhenReady = true
        p.prepare()
    }
}

// release fallbackPlayer in onDestroy()

The action button is focusable with visible feedback, so the same code works on TV boxes with a D-pad. Omit actionLabel/onAction and the screen is a plain informational notice.

The one old-device trap: Android below 7.1.1 does not trust Let's Encrypt's root certificate. If your CDN's HTTPS chain is Let's-Encrypt-only, the fallback fails TLS on exactly the devices it exists for. Serve it from a host with a classic certificate chain.

3. The supported path (Android 10+): full P2P

import aliran.kit.*

private val backend = AliranBackend()

private fun startP2P() {
    backend.onMessage { m ->
        when (m) {
            is BackendMessage.Ready -> backend.login(username, password)
            is BackendMessage.LoginError ->
                // 'ready' fires before the panel link completes — retry the
                // transient case instead of surfacing it:
                if (m.message.contains("not connected")) retryLoginSoon()
                else showLoginError(m.message)
            is BackendMessage.Streams -> showChannelList(m.streams)
            else -> {}
        }
    }
    backend.start(this, StartOptions().apply {
        panelPubKey = "your-64-hex-panel-public-key"
        prewarm = 8   // warm the first channels' feeds for fast first zaps
    })
}

private fun play(stream: Stream) {
    val player = AliranPlayerView(this)
    setContentView(player)
    player.onTune = { e -> showTuningPill(e.phase != TunePhase.PLAYING) }
    player.onError = { msg -> showChannelError(msg) }
    player.attach(backend, stream.id)   // sends play() and renders the video
}

AliranPlayerView carries the official apps' playback contracts, so you don't reimplement them: the ~1 s zap buffer, the engine-driven tune lifecycle (drive your "tuning…" indicator from onTune, not raw player events), the frozen-live-edge self-heal ladder, feed-rotation rebuilds, and the vod seek transport (auto-enabled when the engine reports a vod title).

4. Run it

./gradlew :demo:assembleDebug
adb install demo/build/outputs/apk/debug/demo-debug.apk

On a modern device you get login → channel list → live P2P playback. On an Android 5–9 device (or emulator) you get the notice, and the button plays the fallback stream. The demo bakes its service descriptor from demo/src/main/assets/service.json. This file is gitignored — copy demo/service.example.json and fill in your panel key and a dev account.

Reference