Havik Player Android SDK API Reference (pre-release)
gg.oddinvideo.havik · Android player SDK for Oddin live streams (LL-HLS + Widevine).
Pre-release. This SDK is in active development and has no released artifact. This reference documents only what exists and runs against the dev environment today: the core client, attested-session auth, and the Widevine pipeline (Mode B building blocks). The managed player and live-pickup loop are in development and intentionally undocumented here. Vocabulary and contracts mirror the iOS SDK and web SDK.
Min SDK 26 (Android 8.0) · Media3/ExoPlayer · Kotlin coroutines/Flow.
Contents
- Overview
- Concepts
- HavikClient
- Playback with Media3 (Mode B)
- Live-state events (SSE)
- Errors
- Data types
- Attestation
- Device identity & DRM support
Overview
The SDK resolves Oddin live streams (/v1/playback) and plays them over LL-HLS with Widevine DRM via Media3/ExoPlayer. What runs today is the bring-your-own-player path: resolve a descriptor, build a DRM-wired MediaSource, hand it to your ExoPlayer.
val client = HavikClient.attested(
baseUrl = "https://feed-dev.oddin-video.gg",
apiKey = "pk_test_your_key",
appId = "your.application.id",
)
val descriptor = client.resolvePlayback("od:match:1234")
val source = HavikMediaSources.mediaSource(context, client, descriptor, apiKey, userId = null)
// exoPlayer.setMediaSource(source); prepare(); play()Concepts
- Attested sessions. Apps do not send the api-key on streams requests. The SDK exchanges a platform attestation for a short-lived session token (
/v1/attest/*) and authenticates catalog/playback/SSE with it, minting and refreshing transparently (a mid-match refresh is the normal case). The raw key rides only on the DRM license surface. - Signed license URLs. The descriptor's Widevine license URL is HMAC-signed with ~10 minutes of validity.
HavikMediaDrmCallbackre-resolves the descriptor when it ages out, so long sessions and license renewals keep working. - Same error taxonomy as iOS/web, plus
DEVICE_UNSUPPORTEDfor devices without a Widevine CDM.
HavikClient
Entry point for catalog, playback resolution, and live-state events.
| Factory | Use |
|---|---|
HavikClient.attested(baseUrl, apiKey, appId, attestation, eventsBaseUrl) | The sanctioned mode for apps. appId must be on the key's Android-app allow-list. attestation defaults to NoAttestationProvider (works everywhere against non-strict keys; platform providers slot in as they ship). |
HavikClient.apiKey(baseUrl, apiKey, eventsBaseUrl) | Plain-key mode (web-style keys, tests). |
| Member | Description |
|---|---|
suspend fetchCatalog(): Catalog | GET /v1/catalog: tournaments and their matches. |
suspend resolvePlayback(matchUrn): StreamDescriptor | GET /v1/playback/{urn}; throws a typed PlaybackException on non-2xx. |
liveStateEvents(matchUrn): Flow<LiveStateStreamEvent> | Pushed live-state over SSE, with automatic reconnect/backoff. |
baseUrl / eventsBaseUrl | The streams API and its SSE endpoint (events. host prefix by convention). |
Playback with Media3 (Mode B)
HavikMediaSources.mediaSource(context, client, descriptor, apiKey, userId) builds an HlsMediaSource for the descriptor, wiring a Widevine DrmSessionManager + HavikMediaDrmCallback when the stream is protected. Throws DEVICE_UNSUPPORTED (per match, at build time) when the stream is protected and the device has no Widevine CDM, so a GMS-less device still plays the clear catalog.
HavikMediaDrmCallback: Media3 MediaDrmCallback that drives license acquisition against the DRM service: raw CDM challenge as the POST body, x-api-key / X-Match-Urn / X-Device-Id (+ optional X-User-Id) headers, descriptor re-resolve on signed-URL expiry. Construct directly only if you are assembling your own DrmSessionManager; HavikMediaSources does it for you.
Live-state events (SSE)
client.liveStateEvents("od:match:1234").collect { event ->
when (event) {
is LiveStateStreamEvent.State -> if (event.event.state == "ended") { /* … */ }
else -> {}
}
}Emits the snapshot on connect, every transition (live · upcoming · ended · gone), liveness ticks, and non-fatal failures (the client reconnects with exponential backoff). Cancel the collection to unsubscribe.
Errors
Every failure is a PlaybackException carrying a PlaybackErrorCode:
INVALID_URN · NOT_FOUND · GONE · TOO_EARLY · UNAVAILABLE · UNAUTHORIZED · FORBIDDEN · RATE_LIMITED · INTERNAL · NETWORK · TIMEOUT · ABORTED with identical semantics to the web and iOS SDKs. Android adds native cases:
| Code | Meaning |
|---|---|
DEVICE_UNSUPPORTED | No Widevine CDM (or provisioning unreachable) and the stream is protected. |
ATTESTATION_UNAVAILABLE | The attestation provider cannot run on this device. |
ATTESTATION_FAILED | The server rejected the attestation or app binding. |
Only a license-POST 403 maps to FORBIDDEN (entitlement); other license failures map to INTERNAL.
Data types
StreamDescriptor:matchUrn,manifestUrl,drmEnabled,drm: DrmMaterial?(widevine: WidevineMaterial?withlicenseUrl), server time / kickoff hints.Catalog/CatalogTournament/CatalogMatch: the catalog tree;MatchLiveStatusisUPCOMING · LIVE · ENDED.Credential/CredentialSource: api-key plus optional session token, and the async provider the attested flow plugs in.HavikHeaders: the header-name constants shared across surfaces.
Attestation
| Type | Role |
|---|---|
AttestationProvider | Produce AttestationMaterial for a server challenge; one provider-driven retry on denial. |
NoAttestationProvider | App-binding only; works on any device or emulator against non-strict keys. |
AttestationMaterial | type (none today; android_keystore / play_integrity as they ship), payload, flags. |
Android attestation is stateless server-side: every mint verifies fresh.
Device identity & DRM support
HavikDeviceId.current(context): stable per-device UUID (persisted in app storage), sent asX-Device-Idon every license request;reset(context)clears it.WidevineSupport.isSupported(): whether this device exposes a Widevine CDM. Check before offering protected content, or rely on the per-matchDEVICE_UNSUPPORTEDfromHavikMediaSources.
Questions: havik-support@oddin.gg.