@aliran/react-native
v0.1.5
Published
Aliran React Native binding — Bare worklet host + drop-in <AliranVideo> on react-native-video
Maintainers
Readme
@aliran/react-native
Drop-in React Native binding for the Aliran P2P player: hosts the engine in a Bare
worklet (react-native-bare-kit) and renders live HLS with react-native-video —
P2P channels and redirect channels (catalog entries that play an operator's
CDN/HLS URL directly) both flow through the same <AliranVideo> surface.
import { AliranBackend, AliranVideo } from '@aliran/react-native'
import bundleBase64 from './backend/app.bundle.js' // your bare-pack'd engine bundle
const backend = new AliranBackend()
backend.start(bundleBase64, {
panelPubKey: SERVICE.panelPubKey,
prewarm: 12, // warm the first N channels' feeds at login (fast first zap)
zapPrefetch: false // keep CH+/CH- neighbors' newest segment warm while playing —
// OFF by default: costs standing bandwidth (see sdk/README.md)
// swarm: { maxPeers: 256 } — seed-node hosts only; viewers keep the default (64)
// uploadPolicy: 'client-only' — never announce on feed topics: ~zero
// viewer-to-viewer upload (default 'reseed' serves blocks back)
})
// "Smooth zapping" is a runtime, user-facing choice — wire it to a Settings switch
// and feed the network profile so the engine can suspend it on metered connections:
backend.setZapPrefetch(true) // ON mid-play (echoed as {type:'zap-prefetch'})
backend.setNetworkProfile(expensive) // from NetInfo state.details.isConnectionExpensive
// The engine also auto-suspends while the ACTIVE stream stalls or the pipe shows no
// headroom, and resumes by itself — listen for {type:'zap-prefetch', state, reason}.
// after backend.login(user, pass) resolves entitlements ('streams' message):
<AliranVideo
backend={backend}
streamId="news"
onPeers={(n) => setPeers(n)}
onTune={(e) => setTuning(e.phase === 'playing' ? null : e)} // drive your tuning UI from this
onError={setError}
/>AliranBackend— boots the worklet from a bare-pack bundle (base64 or bytes) and speaks the engine's IPC protocol:login(),play(),playRaw(),reconnect()(tear down the active feed's swarm connections and dial fresh — the wedged-transport escalation),onMessage(), withstreams/port/url/sourcecached for screens that mount after the one-shot replies. Runtime service descriptor (keyless generic apps): omitpanelPubKeyfromstart()to boot the worklet without connecting, read the persisted prefs, thenconnect(panelPubKey)— with the engine already on a different panel this swaps it wholesale (teardown + fresh engine), so wait for the new{type:'ready'}before logging in.saveService()/clearService()persist the viewer-entered service beside the saved credentials (mirrored onbackend.service).<AliranVideo>— chrome-free video surface: plays the ACTIVE source URL (a localhost P2P URL, or a redirect channel's remote URL passed through verbatim), auto-retries while the P2P live edge replicates, and remounts onfeed-changed(the broadcaster rotated the watched channel's feed — reload to flush the stale playlist). (onFallback/onSourceChangedexist for the engine's internal hybrid test mode — production never configures it.) It also self-heals a frozen live edge: live HLS windows are short, so a network blip longer than the window slides it past the playhead with no error event — once the playhead sits still forstallTimeoutMs(default 12 s) while playing, the component remounts onto a fresh playlist load at the live edge and firesonStall. If a resync mount then fails to play within another window, the ladder escalates tobackend.reconnect()— the network flap left the engine's peer connection transport-alive but replication-dead, and only a fresh dial (not a remount) recovers that. Drive your tuning indicator fromonTune, not from raw player events: ONE localhost URL serves every P2P channel, so after a zap the previous channel keeps playing (and emittingonProgress/onBuffer) under the same URL until the engine flips the served feed.onTunereports each switch as a tune (monotonicid):start(arm/reset the indicator),retune/reconnect(the engine is self-healing — say "reconnecting", don't freeze a fake percentage),playing(the FIRST real playback of this tune — dismiss; edge-proof against mid-tune remounts and the old channel's events). The friendly tune-timeout arrives viaonErrorand ends the tune. Zap-tuned start buffer: ExoPlayer begins playback at ~1 s buffered (vs its ~2.5 s default) — override via thebufferConfigprop (merged over the defaults) if your feeds need more headroom. Overlays (badges, peer counts, spinners) belong to the host app via the callbacks — seeclient/src/screens/LiveScreen.tsxfor a complete example (the Aliran app dogfoods this package).
Requirements: peers react-native-bare-kit (min SDK 29) and react-native-video;
Android release builds need cleartext-to-loopback permitted for the local media
server (see the client build guide).
Older Android (below 10 / API 29): the engine's native runtime cannot load
there — that floor is a libc symbol dependency, not a pin. The SDK itself still
works below it, and one APK can cover Android 7 → current: apply the
bare-kit lazy-load patch (ships in the reference app,
client/patches/react-native-bare-kit+0.13.3.patch — turns the link-time
libbare-kit.so dependency into an API-29-gated dlopen), set minSdk 24,
and gate on AliranBackend.isSupported(): true on Android 10+ (full P2P),
false below — where the backend stays silently inactive (start() and
every method are safe no-ops; no message ever fires). Below Android 10 no P2P
data is reachable at all, so in that branch your app offers its own method —
and the SDK ships the screen for it, <EngineNotice>:
import React, { useState } from 'react'
import { AliranBackend, EngineNotice } from '@aliran/react-native'
import Video from 'react-native-video'
export default function App () {
const [fallback, setFallback] = useState(false)
if (!AliranBackend.isSupported()) {
// Engine can't run here (Android 7-9). Offer your own delivery instead —
// e.g. plain HLS from your CDN via ExoPlayer; the SDK never provides content.
if (fallback) {
return <Video source={{ uri: 'https://cdn.example.com/live/main.m3u8' }} style={{ flex: 1 }} />
}
return (
<EngineNotice
title="Acme TV"
colors={{ background: '#0B1220', accent: '#0EA5E9' }} // your brand
actionLabel="Watch over the internet" // omit both to
onAction={() => setFallback(true)} // hide the button
/>
)
}
return <YourNormalP2PApp /> // backend.start(...) etc — full engine (Android 10+)
}The action button is D-pad focusable for TV. This exact pattern — notice →
button → plain-HLS fallback — is verified on a Fire OS 7 stick (Android 9).
Recipe + details in the
SDK guide.
Ships TypeScript source (Metro consumes it
directly); if the package lives outside your app root (monorepo / file: dep), add
its path to Metro watchFolders and map its peers in tsconfig paths — see
client/metro.config.js + client/tsconfig.json.
