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
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.
| Member | Description |
|---|---|
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. |
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.
| Event | When |
|---|---|
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 / AdProgress | Ad break + per-ad lifecycle + countdown. |
AdCompleted / AdBreakCompleted / AdSkipped | Ad / break finished. |
BitrateChanged, BufferingStarted/Ended, Error, Ended | QoS + terminal events. |
PlaybackState:
IDLE, LOADING, BUFFERING, READY, PLAYING,
PLAYING_AD, PAUSED, ENDED. Legal transitions are enforced by a
shared state machine.
Platform players
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).
Wraps ExoPlayer (Media3). CSAI swaps in an ad MediaSource; the opt-in IMA path uses
ImaAdsLoader + AdsConfiguration.
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).
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).
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.
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.
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.
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
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.
A standalone TypeScript player mirroring the core: StreamingPlayer + ContentEngine
(Shaka), the same CSAI/SSAI ad orchestration, and mux-embed analytics.