The FastPix Android Data Core SDK serves as the foundational layer for integrating video playback analytics within Android applications. As a core SDK, it does not function as a standalone player but instead enhances any video player by capturing essential playback metrics such as playback events, buffering patterns, and user engagement data. This data is automatically collected and made available on the FastPix dashboard for real-time monitoring and analysis. Designed for efficiency, the SDK ensures minimal impact on playback performance. While currently optimized for Java-based Android projects, future updates will introduce extended support for Kotlin and enhanced customization options for analytics tracking.
- Automatic analytics tracking – Seamlessly captures playback metrics without additional configuration.
- Real-time insights – Provides instant access to video performance data on the FastPix dashboard.
- Optimized for performance – Ensures minimal impact on playback and system resources.
- Future enhancements – Planned support for Kotlin and expanded analytics customization options.
- Minimum Android SDK: 24 (Android 7.0)
- Compile SDK: 36
- Kotlin: 2.0.21+
- Java: 11
Add the GitHub Packages repository to your project's settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/FastPix/android-core-data-sdk")
credentials {
username = project.findProperty("lpr.user") as String? ?: "github-username"
password =
project.findProperty("lpr.token") as String? ?: "github-personal-access-token"
}
}
}
}Add the dependency to your app's build.gradle.kts:
dependencies {
implementation("io.fastpix.data:core:1.3.2")
}Create a local.properties file in your project root and add your GitHub credentials:
lpr.user=YOUR_GITHUB_USERNAME
lpr.key=YOUR_GITHUB_PERSONAL_ACCESS_TOKENNote: Make sure to add
local.propertiesto your.gitignoreto keep credentials secure.
import io.fastpix.data.FastPixDataSDK
import io.fastpix.data.domain.SDKConfiguration
import io.fastpix.data.domain.model.VideoDataDetails
import io.fastpix.data.domain.model.PlayerDataDetails
import io.fastpix.data.domain.model.CustomDataDetails
import io.fastpix.data.domain.listeners.PlayerListener
class MainActivity : AppCompatActivity() {
private val fastPixSDK = FastPixDataSDK()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create configuration
val config = SDKConfiguration(
workspaceId = "your-workspace-id",
beaconUrl = "custom.beacon.url", // Optional
// Optional
videoData = VideoDataDetails(
videoId = "video-123",
videoTitle = "My Awesome Video",
videoDuration = "300000", // in milliseconds
videoThumbnail = "https://example.com/thumbnail.jpg",
fpPlaybackId = "playback-id" // Optional FastPix playback ID
//.. etc
),
// Optional
playerData = PlayerDataDetails(
playerName = "ExoPlayer",
playerVersion = "2.19.0",
playerPoster = "https://example.com/poster.jpg" // Optional
),
playerListener = MyPlayerListener(),
enableLogging = true, // Enable for debugging
// Optional
customData = CustomDataDetails(
customField1 = "custom-value-1",
customField2 = "custom-value-2"
//.. etc
)
)
// Initialize SDK
fastPixSDK.initialize(config, applicationContext)
}
override fun onDestroy() {
super.onDestroy()
// Clear the SDK instance
fastPixSDK.release()
}
}The PlayerListener interface provides the SDK with real-time player state information:
import io.fastpix.data.domain.listeners.PlayerListener
import io.fastpix.data.domain.model.BandwidthModel
import io.fastpix.data.domain.model.ErrorModel
class MyPlayerListener : PlayerListener {
override fun playerHeight(): Int? = player.videoSize.height
override fun playerWidth(): Int? = player.videoSize.width
override fun videoSourceWidth(): Int? = player.videoFormat?.width
override fun videoSourceHeight(): Int? = player.videoFormat?.height
override fun playHeadTime(): Int? = player.currentPosition.toInt()
override fun mimeType(): String? = player.videoFormat?.sampleMimeType
override fun sourceFps(): String? = player.videoFormat?.frameRate?.toString()
override fun sourceAdvertisedBitrate(): String? = player.videoFormat?.bitrate?.toString()
override fun sourceAdvertiseFrameRate(): Int? = player.videoFormat?.frameRate
// Null until the duration is known: C.TIME_UNSET must not be sent
override fun sourceDuration(): Int? =
player.duration.takeIf { it != C.TIME_UNSET && it > 0 }?.toInt()
override fun isPause(): Boolean? = !player.isPlaying
override fun isAutoPlay(): Boolean? = player.isAutoPlay()
override fun preLoad(): Boolean? = player.preLoad
override fun isBuffering(): Boolean? = player.playbackState == Player.STATE_BUFFERING
override fun playerCodec(): String? = player.videoFormat?.codecs
override fun sourceHostName(): String? =
Uri.parse(player.currentMediaItem?.localConfiguration?.uri.toString()).host
override fun isLive(): Boolean? = player.isCurrentMediaItemLive
override fun sourceUrl(): String? = player.currentMediaItem?.localConfiguration?.uri.toString()
override fun isFullScreen(): Boolean? = isPlayerFullScreen
override fun getBandWidthData(): BandwidthModel {
return BandwidthModel(
bandwidth = player.currentBandwidthEstimate.toString(),
// Add other bandwidth metrics
)
}
override fun getPlayerError(): ErrorModel {
return ErrorModel(
errorCode = player.playerError?.errorCode?.toString(),
errorMessage = player.playerError?.message,
errorContext = player.playerError?.cause?.toString() // Optional
)
}
override fun getVideoCodec(): String? = player.videoFormat?.codecs
override fun getSoftwareName(): String? = "software-name"
override fun getSoftwareVersion(): String? = "software-version"
}These methods have default implementations that return null, so existing integrations keep compiling. Override the ones your player can answer, and return null for anything you don't know: an unknown value is left out, never guessed. The SDK samples them on every event: viewBegin carries all of them, every other event carries only the values that changed since the last event the SDK accepted, and viewCompleted carries none.
| Method | Wire key | Example with ExoPlayer |
|---|---|---|
isMuted() |
plmu |
Your player's mute state. Not inferred from volume |
volumeLevel() |
plvole |
player.volume.toDouble() |
playbackRate() |
plpbre |
player.playbackParameters.speed.toDouble() |
isLooping() |
pllpon |
player.repeatMode != Player.REPEAT_MODE_OFF |
areControlsEnabled() |
plcvon |
playerView.useController; with a custom UI, your own value or null |
isPictureInPicture() |
plispx |
activity.isInPictureInPictureMode |
isSubtitlesOn() |
plsbon |
A selected text track that isn't forced-only (SELECTION_FLAG_FORCED) |
subtitlesLanguageCode() |
plsblncd |
That track's format.language; the SDK normalizes it to BCP-47 |
audioLanguageCode() |
plaolncd |
Selected audio track's format.language |
bufferLength() |
vdsobflg |
player.bufferedPosition - player.currentPosition |
programDateTime() |
plpmdati |
window.windowStartTimeMs + player.currentPosition, only when windowStartTimeMs != C.TIME_UNSET |
The SDK derives the rest itself: pldfmuon and pldfpbre (muted and rate at viewBegin), vdsosmpc (HLS or DASH from the MIME type or URL, PROGRESSIVE for an .mp4, .webm, .ogv, .mov or .m4v URL), and vdsopnti (content playing time at the outgoing rendition, ads excluded, on variantChanged and viewCompleted). Volume and rate are sent as reported; the SDK rounds them only to decide whether they changed. A buffer length of 0 is left out.
These are sent on variantChanged, which happens only on adaptive streams. audioBitrate() is also sent on requestCompleted, as on Web, so it is reported even when the rendition never switches.
| Method | Wire key | Example with ExoPlayer |
|---|---|---|
audioCodec() |
vdsoaocc |
Selected audio track's format.codecs |
audioBitrate() |
vdsoaobi |
Selected audio track's format.bitrate |
dynamicRangeType() |
vdsodyrgty |
HDR when videoFormat.colorInfo.colorTransfer is PQ or HLG, else SDR |
renditionName() |
vdsornna |
The manifest label, else RenditionName.from(width, height, dynamicRange): 1080p FHD, 2160p 4K PQ, as Web |
The SDK derives the rest itself. The view_initial_* values (veiabi, veiafs, veiasoht, veiasowt, veiasocc, veiaaocc, veiadyrgty) are captured once and sent on viewBegin and viewCompleted: on adaptive streams at the first rendition with a bitrate above 0, on progressive files at the first known video format. FPS is the declared frame rate (Format.frameRate). Codecs are mapped to h264, h265, av1, aac, eac3 or ac3; anything else is left out. Dispatch variantChanged only when the player switches between renditions, not for the first rendition. On each switch the SDK sends the signed bitrate shift (vdsobish), the upshift and downshift counts (vernufco, verndsco) and the switch time (velarnchti). A variantChanged with an unchanged bitrate is not counted. The counts and time are also sent on viewCompleted.
Track player events throughout the video lifecycle:
import io.fastpix.data.domain.enums.PlayerEventType
// When player is ready
fastPixSDK.dispatchEvent(PlayerEventType.playerReady)
// When video begins
fastPixSDK.dispatchEvent(PlayerEventType.viewBegin)
// When playback starts
fastPixSDK.dispatchEvent(PlayerEventType.play)
fastPixSDK.dispatchEvent(PlayerEventType.playing)
// When user pauses
fastPixSDK.dispatchEvent(PlayerEventType.pause)
// When seeking
// Don't dispatch pause for a seek: the time between seeking and seeked is never paused time
fastPixSDK.dispatchEvent(PlayerEventType.seeking)
fastPixSDK.dispatchEvent(PlayerEventType.seeked)
// When buffering
fastPixSDK.dispatchEvent(PlayerEventType.buffering)
fastPixSDK.dispatchEvent(PlayerEventType.buffered)
// When quality changes
fastPixSDK.dispatchEvent(PlayerEventType.variantChanged)
// When video ends
fastPixSDK.dispatchEvent(PlayerEventType.ended)
fastPixSDK.dispatchEvent(PlayerEventType.viewCompleted)
// On errors
fastPixSDK.dispatchEvent(PlayerEventType.error)Release SDK resources when done:
override fun onDestroy() {
super.onDestroy()
fastPixSDK.release()
}| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId |
String | ✅ | Your FastPix workspace identifier |
playerListener |
PlayerListener | ✅ | Interface implementation for player state |
videoData |
VideoDataDetails | ❌ | Video metadata (see below) |
beaconUrl |
String | ❌ | Custom beacon URL (default: anlytix.io) |
playerData |
PlayerDataDetails | ❌ | Player information |
enableLogging |
Boolean | ❌ | Enable debug logs (default: true) |
customData |
CustomDataDetails | ❌ | Custom metadata fields |
applicationData |
ApplicationDataDetails | ❌ | Application context (see below) |
viewerData |
ViewerDataDetails | ❌ | Viewer subscription details (see below) |
adData |
AdDataDetails | ❌ | Ad details from your ad server (see below) |
| Field | Type | Required | Description |
|---|---|---|---|
videoId |
String | ✅ | Unique video identifier |
videoTitle |
String | ✅ | Video title |
videoDuration |
String | ❌ | Duration in milliseconds |
videoThumbnail |
String | ❌ | Thumbnail URL |
videoSeries |
String | ❌ | Series name |
videoProducer |
String | ❌ | Producer name |
videoDrmType |
String | ❌ | Producer name |
videoContentType |
String | ❌ | Content type |
videoVariant |
String | ❌ | Variant information |
videoLanguage |
String | ❌ | Language code |
fpPlaybackId |
String | ❌ | FastPix playback ID |
foMediaId |
String | ❌ | Media ID |
fpLiveStreamId |
String | ❌ | Live stream ID |
videoBrand |
String | ❌ | Brand (vdbd) |
videoAffiliate |
String | ❌ | Affiliate (vdaf) |
videoCreatorId |
String | ❌ | Creator ID (vdcwid) |
videoDescription |
String | ❌ | Description (vddc) |
videoMediaTags |
String | ❌ | Comma-separated tags (vdmets) |
videoCodec |
String | ❌ | Declared video codec (vdcc) |
videoAudioCodec |
String | ❌ | Declared audio codec (vdaocc) |
videoDynamicRangeType |
String | ❌ | Declared SDR or HDR (vddyrgty) |
videoDrmLevel |
String | ❌ | DRM security level, e.g. L1 (vedmle) |
videoCdnEdgePop |
String | ❌ | CDN edge PoP (vecnegpf) |
| Field | Type | Required | Description |
|---|---|---|---|
playerName |
String | ❌ | Player name (e.g., "ExoPlayer") |
playerVersion |
String | ❌ | Player version |
playerPoster |
String | ❌ | Poster image URL (plpo) |
Pass-through values supplied by your app, sent on viewBegin and viewCompleted. The SDK never computes or defaults them. Leave a field null when unknown; blank values are not sent.
| Field | Wire key | Description |
|---|---|---|
applicationName |
apna |
Name of your application |
applicationVersion |
apvn |
Version of your application |
pageType |
paty |
Type of screen hosting the player (e.g. feed) |
contentId |
ctid |
Your own content identifier |
deviceId |
deid |
Your own device identifier |
The view's ad details, passed in SDKConfiguration.adData from your ad server or VAST, and sent on viewBegin and viewCompleted. The SDK never computes them. HLS interstitials and server-side ads carry no VAST, so only your app can supply these. If you leave a field null and an ad SDK such as Google IMA reports it during the view (through PlayerListener.currentAd()), that value is sent on viewCompleted.
| Field | Wire key | Description |
|---|---|---|
adId |
veadid |
Industry Ad ID (VAST <Ad id>) |
creativeId |
veadcrid |
Creative ID |
advertiserName |
veaddvna |
Advertiser |
partner |
veadrt |
Ad partner |
dealId |
veaddlid |
Deal ID |
clickUrl |
veadlkur |
Click-through URL |
skipTimeOffset |
veadkptiof |
Seconds until the ad can be skipped |
wrapperAdIds |
veadwrid |
Wrapper chain ad IDs, sent comma-separated |
wrapperCreativeIds |
veadwrcrdi |
Wrapper chain creative IDs, comma-separated |
wrapperSystems |
veadwrym |
Wrapper chain ad systems, comma-separated |
Pass-through viewer details, sent on viewBegin and viewCompleted. Leave a field null when unknown.
| Field | Wire key | Description |
|---|---|---|
viewerPlan |
vipk |
Plan name, free text |
viewerPlanCategory |
vipkcg |
Expected: free, paid, trial |
viewerPlanStatus |
vipkss |
Expected: active, expired, cancelled |
Web-only fields are not sent on Android: page title, referrer and site domain (there is no page), Do Not Track, browser width and height, cookies enabled, and viewability (player_viewable_on, player_viewable_percentage). view_cdn_origin is not sent either: nothing the device receives names the origin, so the server fills it.
The SDK also measures the viewer's environment on its own:
- Timezone (
vitzna). - Orientation (
videor): onviewBegin, everypulseandviewCompleted, so a rotation shows within one pulse. - Connection speed (
vicisf):wifi, or the cellular generation:2g,3g,LTE,4g(LTE-Advanced, shown as 4G+) or5g. Nothing is sent on Ethernet. LTE-Advanced and 5G come from Android's display info (Android 12+). The radio type needsREAD_PHONE_STATEon older versions; without it the SDK uses the bandwidth estimate, which gives only2g,3gorLTE. The SDK never requests the permission itself.
The SDK sends the 18 request_cmcd_* fields on requestCompleted, requestFailed and requestCanceled, parsed from the CMCD your player attached to that request. They are sent in full on every request event and are absent when the player sends no CMCD.
- Turn CMCD on in Media3:
val mediaSourceFactory = DefaultMediaSourceFactory(context) .setCmcdConfigurationFactory(CmcdConfiguration.Factory.DEFAULT) val player = ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build()
- In
onLoadCompleted,onLoadErrorandonLoadCanceled, read the CMCD with the SDK helper, which handles both header and query mode:val cmcd = CmcdPayload.from(loadEventInfo.dataSpec.uri, loadEventInfo.dataSpec.httpRequestHeaders)
- Return it as
BandwidthModel.cmcdPayloadfromgetBandWidthData()while that request's event is dispatched.
CmcdMedia3ProofTest plays HLS and DASH on Media3 1.8.0 against a local server in both modes and checks that this is exactly the CMCD the server received.
Both header and query modes work, with any CDN.
Ads are measured the same way as the Web SDK. Dispatch the ad events below, and return the ad they are about from PlayerListener.currentAd() while each one is dispatched. The SDK keeps the ad state and sends the ad parameters on the regular events: all of them on viewBegin and viewCompleted, otherwise each one on the next event after it changes. The ad events themselves are not sent yet, until the collector accepts them.
| Event | When |
|---|---|
prerollScheduled |
The schedule contains a preroll (before it is requested) |
adRequest / adResponse |
An ad tag, VMAP or asset list was requested / answered (AdModel.responseCount) |
prerollRequested |
The preroll was requested; only the first per view counts |
adBreakStart / adBreakEnd |
A break opened / content resumed |
adPlay |
An ad was selected: the attempt |
adPlaying, adImpression |
The ad rendered, if the content player raised no playing for it |
adEnded |
The ad played to its end (not skipped) |
adError |
An ad failed (errorCode, errorMessage, errorSource, errorFatal) |
While a break is open, keep dispatching play, playing, pause and buffering. They carry view_is_ad_playing = true, and the SDK counts their time as ad time rather than content time. Seeks inside a break are dropped.
With Media3, read breaks and ads from the player (isPlayingAd, currentAdGroupIndex, currentAdIndexInAdGroup and the period's AdPlaybackState), so IMA and HLS interstitials work the same way. The sample's FastPixBaseMedia3Player does this. It also takes VAST metadata from IMA (onImaAdEvent, onImaAdError) and asset-list requests from HlsInterstitialsAdsLoader (hlsInterstitialsListener). For HLS interstitials, use HlsInterstitialsAdsLoader.AdsMediaSourceFactory: DefaultMediaSourceFactory prepares the content lazily, and the interstitials never load.
Returned from PlayerListener.getPlayerError(). Only errorCode and errorMessage are needed; severity and isBusinessException let you classify errors, and errorContext adds detail.
| Field | Wire key | Description |
|---|---|---|
errorCode |
— | Player error code |
errorMessage |
— | Player error message |
severity |
plersy |
ErrorSeverity.FATAL, WARNING or INFO |
isBusinessException |
plerbuxc |
true for a business rule (geo-block, entitlement), false for a technical fault, null when unclassified |
errorContext |
plercx |
Free-text detail, such as the cause chain or failing URL. Sent on error; blank is left out |
From these the SDK sets, on error and viewCompleted:
vepbfa: any error after playback started, whatever its classification.vespbuxc/vepbbuxc: your classification of errors before or after playback started. Left out when you didn't classify an error.
The SDK also tracks, without any input from you:
vepudu/vectpudu: total paused time in ms, sent onviewCompleted. A pause still open when the view ends is counted; time betweenseekingandseekednever is.vepyco: play count, sent onplayandviewCompleted.veevco: events in the view that the SDK accepted, sent onviewCompleted.velarbstti: last rebuffer start time, sent onbufferingandviewCompleted.vemaphps: the furthest content position reached in the view, in ms, sent onpulse,pause,endedandviewCompleted. Seeking back doesn't lower it. Not sent for live.
Supports up to 10 custom fields (customField1 through customField10) for additional metadata.
The SDK tracks the following player events:
playerReady- Player is initialized and readyviewBegin- Video view has startedplay- Playback initiatedplaying- Video is actively playingpause- Playback pausedseeking- Seek operation startedseeked- Seek operation completedbuffering- Buffering startedbuffered- Buffering completedvariantChanged- Quality/variant changedended- Playback endedviewCompleted- View session completederror- Playback error occurredrequestCanceled- Network request canceledrequestFailed- Network request failedrequestCompleted- Network request completed
Ad events (see Ads): prerollScheduled, adRequest, adResponse, prerollRequested, adBreakStart, adPlay, adPlaying, adImpression, adPause, adEnded, adError, adBreakEnd.
See PARAMETERS.md for every parameter the SDK sends and the events it is sent on.
The SDK uses the following libraries:
- Kotlin Coroutines - Async operations
- OkHttp - Network requests
- Retrofit - API communication
- Gson - JSON serialization
- Kotlinx Serialization - Data serialization
See CHANGELOG.md for release history and version updates.
For questions, issues, or feature requests:
- Email: support@fastpix.com
- Documentation: FastPix Documentation
- GitHub Issues: Report an issue
Copyright © 2025 FastPix. All rights reserved.
This SDK is proprietary software. Unauthorized copying, modification, distribution, or use of this software is strictly prohibited.