Player SDK
Everything the Aliran Android app knows about playing P2P television lives in two reusable packages. The app itself is a consumer of them. Build your own viewer — set-top UI, kiosk, desktop app, seed node — on the same engine the shipped app dogfoods.
| Package | What it is | Runs in |
|---|---|---|
@aliran/player-sdk |
Headless player engine: DHT connect, OPRF login, catalog replication, entitled-feed serving on a localhost HLS URL | Node ≥ 20 and the Bare runtime |
@aliran/react-native |
Drop-in <AliranVideo> + AliranBackend worklet host on react-native-video / react-native-bare-kit, plus <EngineNotice> for the unsupported-device branch |
React Native — phone + TV, Android 7+ (P2P engine active on Android 10+; silent below, with a ready-made notice and fallback seam) |
aliran-kit |
Native Kotlin twin of the RN binding — AliranBackend on BareKit's plain-Java API, AliranPlayerView (Media3/ExoPlayer, same playback contracts), EngineNotice |
Any Android app, no React Native — Android 5.0+ (engine active on 10+; silent below) |
@aliran/core |
The shared crypto both sit on (OPRF, Argon2id verifiers, key sealing, tokens) | Node + Bare |
All three are MIT, ship from the monorepo, and are packaged for the npm
registry under the @aliran scope. TypeScript definitions are included:
player-sdk ships index.d.ts, and the RN binding ships TypeScript
source.
Building on it? Start with Build a player — the complete walkthrough (panel key → login → channel list → playing video, with runnable code). The installation & configuration guide is the complete manual — every install path, option, event, and troubleshooting. Operator APIs & the SDK maps the operator control plane to what your app observes.
Minimum requirements
The floors below come from the native P2P stack. The engine loads prebuilt native modules (libsodium, the UDP transport, …), and those prebuilds exist for exactly these targets. They are hard requirements, not recommendations: on anything older, the engine does not degrade — it simply cannot load. (The SDK's JavaScript is a separate story: on Android it degrades silently below the engine floor; see the last row.)
| Surface | Minimum | Why / notes |
|---|---|---|
Node host (@aliran/player-sdk) |
Node ≥ 20 (ESM-only package) on Linux x64/arm64 · Windows 10+ x64/arm64 · macOS 13+ (Apple silicon + Intel) | npm install places prebuilt natives. No compiler and no build step are needed. If your platform/arch isn't listed, there is no prebuild for it. |
React Native app (@aliran/react-native), P2P engine |
Android 10 (API level 29) or newer, 64-bit device; peers: react ≥ 18, react-native-bare-kit ≥ 0.13.3, react-native-video v6. Tested against react-native-tvos 0.83 (New Architecture) |
The engine worklet cannot load on Android 9 or older — a libc ELF-TLS dependency, not a pin. Stock react-native-bare-kit sets minSdkVersion 29. With the lazy-load patch, a single minSdk 24 APK ships the engine anyway and activates it only here (last row). |
| Android TV / Fire TV | Same Android 10 / API 29 engine floor | Android TV 10+ and Fire OS 8 devices get full P2P. Fire OS 7 sticks (Android 9 base) cannot run the engine — the single APK still installs and runs on them, with the engine silent (verified on a 4K Max 1st gen). |
Desktop player (desktop/) |
Windows 10 or newer (x64) · macOS 13 Ventura or newer (Apple silicon + Intel) | Electron 37 platform floors. HEVC channels additionally need platform hardware decode (codecs) |
| Bare / custom runtimes | The Bare runtime + the addon set react-native-bare-kit 0.13.x links |
See the Bare section of the install guide |
| React Native app, single APK (runtime engine gate) | Android 7 (API 24) — React Native 0.76+'s own hard floor (its prebuilds are built for 24; the build rejects lower) | With the bare-kit lazy-load patch, one APK installs from Android 7 and carries the engine. On Android 10+ the engine loads and runs in full. Below that, the SDK is silently inactive (AliranBackend.isSupported() → false, every call a safe no-op), and the app provides its own content path. No P2P data is reachable below Android 10, and Android 6 can't run a current-RN app at all. Recipe |
Native Kotlin app (aliran-kit), single APK |
Android 5.0 (API 21) — the Media3/AndroidX floor; no RN floor applies | The Kotlin SDK hosts the same engine via BareKit's plain-Java API: full P2P on Android 10+, silently inert below. It uses the same isSupported() contract, and no native patch is needed — the gate is Java class-loading. It covers fleets even RN can't reach, such as Android 5/6 boxes and Fire OS 5 sticks. Walkthrough |
(Android "SDK level"/"API level" mapping, since device spec sheets use both: API 29 = Android 10, 30 = 11, 31/32 = 12, 33 = 13, 34 = 14, 35 = 15.)
Headless quickstart (Node)
import { createPlayer } from '@aliran/player-sdk'
const player = createPlayer({ panelPubKey, storeDir: './aliran-store' })
player.on('peers', (n) => console.log(n, 'peers'))
await player.connect() // join the panel topic over the DHT
const streams = await player.login(user, pass) // OPRF login → entitled display list
const { url, source } = await player.resolve(streams[0].id)
// source 'p2p' → url is a localhost HLS playlist served from the replicating feed
// source 'cdn' → a redirect channel: play the operator's remote URL directly
Point ffplay, VLC, hls.js, or ExoPlayer — anything that plays HLS — at
the URL. A complete runnable version is
examples/headless-player.mjs.
Login never sends a plaintext password anywhere (see the security model). Stream keys stay inside the engine — hosts only ever see catalog metadata and localhost URLs. The store directory is a disposable replica cache: corruption is detected, purged, and re-replicated from peers automatically.
Pairing codes
If your app asks a viewer which service to use, ask for the 12-character service pairing code rather than the 64-character panel key:
import { resolvePairingCode } from '@aliran/player-sdk'
const service = await resolvePairingCode('A3K7-9QF2-M4XR')
// → { panelPubKey, name, branding, code }
const player = createPlayer({ panelPubKey: service.panelPubKey, storeDir: './aliran-store' })
The call finds the service over the DHT, then calculates the code
again from the panel key it received. It resolves only on a match, so
panelPubKey is always a key that owns the code. It rejects with a
PairingError whose code is:
code |
What happened |
|---|---|
malformed |
The input is not a pairing code. Nothing left the device. |
timeout |
No service answered within 30 s (override with timeoutMs). |
unverified |
A peer answered with a key that does not own the code. Treat this as a wrong service, never as a retry. |
In React Native, call backend.resolvePairing(code) instead — the search
runs in the worklet and resolves with { ok, panelPubKey, name, error }.
React Native
import { AliranBackend, AliranVideo } from '@aliran/react-native'
const backend = new AliranBackend()
backend.start(bundleBase64, { panelPubKey }) // your bare-pack'd engine bundle
// after backend.login(user, pass):
<AliranVideo backend={backend} streamId="news" onPeers={setPeers} onTune={setTune} />
<AliranVideo> self-heals frozen live edges, follows broadcaster feed
rotations, and reports tune progress via onTune. The worklet bundle is
produced by the client build. The binding has no
native code of its own.
What the engine handles for you
- Live catalog. The panel's signed DB replicates to the viewer. Title,
art, and isLive edits push to the
streamsevent without polling or re-login. - Feed rotation. A broadcaster restart publishes a new feed key. The
engine re-resolves and swaps the served feed behind the same localhost
URL (
feed-changedtells the host to reload the player). - Redirect channels. Catalog entries that play an operator's CDN URL
instead of a P2P feed (content management).
resolve()returns the URL verbatim withsource: 'cdn'. - Tune self-heal. Timeouts escalate from cache eviction to peer-connection teardown before the engine surfaces a friendly error.
- Zap latency. Progressive serving, playlist read-ahead, optional
prewarm, and the adaptive, runtime-switchablezapPrefetch("Smooth zapping"). - Viewer bandwidth.
uploadPolicy: 'client-only', orsetUploadPolicy()live, gives near-zero viewer-to-viewer upload for metered networks (measured numbers). - Seed nodes.
swarm: { maxPeers }raises the connection budget for repeater-style hosts (scaling).
The full option/event reference is the
package README and its
index.d.ts.
Partial adoption
You can keep your own catalog and metadata UI, and use only login() and
resolve() for the video URL. Video travels P2P; metadata stays yours.
At the other extreme, serveFeed(feedKey, encKey) plays a feed from raw
keys with no login at all (dev/direct-play).
Publishing status
Live on the npm registry. The first release was 0.1.0
(2026-07-22 UTC), for all three packages:
npm install @aliran/player-sdk # or @aliran/react-native for RN apps
A cold install resolves the whole dependency chain from the registry. Inside the
monorepo, the npm workspace (sdk/) still links the local copies for development,
and the Android app's worklet keeps consuming them via file:.