StreamKit Player SDK

A cross-platform video player SDK. One shared Kotlin Multiplatform core drives playback, ad insertion and analytics; thin per-platform adapters wrap the native players — Media3/ExoPlayer on Android, AVPlayer on iOS, Shaka on the web.

Overview

The SDK separates orchestration (state, ad scheduling, analytics — written once in commonMain) from rendering (a small platform player that just loads, plays, seeks and reports raw signals). The same behaviour ships to Android, iOS and web, plus a Compose Multiplatform demo app and a standalone TypeScript web player.

:shared         Kotlin Multiplatform core  (StreamingPlayer, ad + analytics engines)
:android:player Android adapter            (Media3VideoPlayer over ExoPlayer)
:composeApp     Compose MP app             (Android / iOS / Wasm) + iosApp Swift host
web-demo/       TypeScript web player      (Shaka / hls.js + mux-embed)

Architecture

Three layers. Your UI talks to StreamingPlayer; it drives a PlatformVideoPlayer adapter, which wraps the native engine. Signals flow back up as PlayerEvents.

Your UI  ─▶  StreamingPlayer  ─▶  PlatformVideoPlayer  ─▶  ExoPlayer / AVPlayer / Shaka
   ▲                │  (orchestration)     (expect/actual adapter)      (native rendering)
   └──── PlayerEvent stream ────┘

Player API

sharedStreamingPlayer

The engine. Construct it with a platform player, an ad-strategy factory, an AnalyticsDispatcher, and a coroutine scope; then drive playback and subscribe to events.

MemberDescription
load(item: MediaItem)Prepare content; parses/schedules ad breaks.
play() / pause()Start / pause (a pending pre-roll plays first).
seekTo(positionMs)Seek content (blocked during ads).
release()Tear down player + analytics.
addListener((PlayerEvent)->Unit)Subscribe to the event stream.
sharedMediaItem

id, url, streamType (VOD/LIVE), container, metadata (title, content id, duration), and an optional ads: AdConfig.

Events & state

PlayerEvent is a sealed type — an exhaustive, platform-neutral stream your UI renders from.

EventWhen
StateChanged(from,to)Playback state transition (see below).
Progress(positionMs, bufferedMs, durationMs)~4×/sec content position.
AdBreaksScheduled(cuePointsMs)Scrubber marker positions for the item.
AdBreakStarted / AdStarted / AdProgressAd break + per-ad lifecycle + countdown.
AdCompleted / AdBreakCompleted / AdSkippedAd / break finished.
BitrateChanged, BufferingStarted/Ended, Error, EndedQoS + terminal events.

PlaybackState: IDLE, LOADING, BUFFERING, READY, PLAYING, PLAYING_AD, PAUSED, ENDED. Legal transitions are enforced by a shared state machine.

Platform players

sharedinterface PlatformVideoPlayer

The one seam each platform implements — deliberately "dumb": load, play, pause, seekTo, stop, release, playAd(url), currentPositionMs, durationMs, isPlaying, and a PlatformPlayerListener for raw signals (onReady, onPosition, onPlaybackStateNative, onVideoSizeChanged, onError, onAdEnded).

AndroidMedia3VideoPlayer

Wraps ExoPlayer (Media3). CSAI swaps in an ad MediaSource; the opt-in IMA path uses ImaAdsLoader + AdsConfiguration.

iOSAvPlayerVideoPlayer

Wraps AVPlayer/AVPlayerItem (Kotlin/Native). A periodic time observer polls state; CSAI ads swap the player item, then restore content (VOD seeks back; LIVE resumes at the edge).

WebShakaVideoPlayer

Wraps a DOM <video> via Shaka Player (HLS/DASH), with hls.js/native fallbacks; ad creatives play on a separate ad element.

Ad insertion

Three modes, driven from the shared core. Models: AdBreak(cuePoint, ads), AdInfo(id, durationMs, mediaUrl, title, adSystem, trackingEvents), AdCuePoint = PreRoll | MidRoll(offsetMs) | PostRoll, AdConfig(mode, vmapUrl, ssaiSessionUrl, useImaSdk).

CSAIAdMode.CSAI

Client-side: the core fetches + parses VMAP/VAST into cue points, plays each creative through the platform player, and fires impression/quartile/complete beacons. Supports ad pods.

SSAIAdMode.SSAI

Server-side: ads are stitched into one stream. The core loads a session timeline and fires tracking beacons as the unified playhead crosses each window — it never renders a separate creative.

IMAAdConfig.useImaSdk = true

Opt-in: the core skips its own scheduler and hands the VMAP tag to Google IMA (ExoPlayer IMA / iOS IMAAdsLoader), which plays the ads. The apps forward IMA's ad events into Mux and surface IMA's cue points on the shared scrubber.

Analytics & Mux

An AnalyticsDispatcher fans normalized events out to any number of AnalyticsCollectors; a failure in one is isolated from the others. Built-in adapters: MuxCollector, AmplitudeCollector, HttpSinkCollector, plus Conviva/GA4/NPAW/Adobe bridges. The vendor-agnostic verb→event mapping is unit-tested once in commonTest.

MuxCustom ad integration

Content and ad events reach Mux Data on every platform: Android via the data-media3 AdCollector, iOS via MUXSDKPlayerBinding.dispatchAdEvent, and web via mux.emit — so adbreakstart / adplay / adplaying / quartiles / adended / adbreakend all land on the content view.

Apps & Web SDK

ComposecomposeApp

Shared Compose UI over one per-platform seam: VideoEngine (expect/actual) + VideoSurface render the native video; PlayerController projects StreamingPlayer events into Compose state (scrubber markers, ad badge, time). Android, iOS (Kotlin/Native + Swift host) and Wasm targets.

Webweb-demo/

A standalone TypeScript player mirroring the core: StreamingPlayer + ContentEngine (Shaka), the same CSAI/SSAI ad orchestration, and mux-embed analytics.

StreamKit Player SDK · home · dashboard · catalog · server API in the source docstring & backend/