Skip to content

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

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.

kotlin
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. HavikMediaDrmCallback re-resolves the descriptor when it ages out, so long sessions and license renewals keep working.
  • Same error taxonomy as iOS/web, plus DEVICE_UNSUPPORTED for devices without a Widevine CDM.

HavikClient

Entry point for catalog, playback resolution, and live-state events.

FactoryUse
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).
MemberDescription
suspend fetchCatalog(): CatalogGET /v1/catalog: tournaments and their matches.
suspend resolvePlayback(matchUrn): StreamDescriptorGET /v1/playback/{urn}; throws a typed PlaybackException on non-2xx.
liveStateEvents(matchUrn): Flow<LiveStateStreamEvent>Pushed live-state over SSE, with automatic reconnect/backoff.
baseUrl / eventsBaseUrlThe 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)

kotlin
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:

CodeMeaning
DEVICE_UNSUPPORTEDNo Widevine CDM (or provisioning unreachable) and the stream is protected.
ATTESTATION_UNAVAILABLEThe attestation provider cannot run on this device.
ATTESTATION_FAILEDThe 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? with licenseUrl), server time / kickoff hints.
  • Catalog / CatalogTournament / CatalogMatch: the catalog tree; MatchLiveStatus is UPCOMING · 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

TypeRole
AttestationProviderProduce AttestationMaterial for a server challenge; one provider-driven retry on denial.
NoAttestationProviderApp-binding only; works on any device or emulator against non-strict keys.
AttestationMaterialtype (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 as X-Device-Id on 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-match DEVICE_UNSUPPORTED from HavikMediaSources.

Questions: havik-support@oddin.gg.

ISC licensed. Bundles hls.js (Apache-2.0).