Skip to content

About

FastPix Android Data Core SDK serves as the foundational layer for integrating video playback analytics within Android applications

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

FastPix Android Data Core SDK

License Version Min SDK

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.

Key Features:

  • 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.

Requirements

  • Minimum Android SDK: 24 (Android 7.0)
  • Compile SDK: 36
  • Kotlin: 2.0.21+
  • Java: 11

Installation

Gradle (Kotlin DSL)

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")
}

Authentication

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_TOKEN

Note: Make sure to add local.properties to your .gitignore to keep credentials secure.

Quick Start

1. Initialize the SDK

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()
    }
}

2. Implement PlayerListener

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"
}

Player state (optional)

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.

Rendition (optional)

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.

3. Dispatch Events

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)

4. Clean Up

Release SDK resources when done:

override fun onDestroy() {
    super.onDestroy()
    fastPixSDK.release()
}

Configuration

SDKConfiguration

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)

VideoDataDetails

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)

PlayerDataDetails

Field Type Required Description
playerName String ❌ Player name (e.g., "ExoPlayer")
playerVersion String ❌ Player version
playerPoster String ❌ Poster image URL (plpo)

ApplicationDataDetails

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

AdDataDetails

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

ViewerDataDetails

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): on viewBegin, every pulse and viewCompleted, so a rotation shows within one pulse.
  • Connection speed (vicisf): wifi, or the cellular generation: 2g, 3g, LTE, 4g (LTE-Advanced, shown as 4G+) or 5g. Nothing is sent on Ethernet. LTE-Advanced and 5G come from Android's display info (Android 12+). The radio type needs READ_PHONE_STATE on older versions; without it the SDK uses the bandwidth estimate, which gives only 2g, 3g or LTE. The SDK never requests the permission itself.

CMCD

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.

  1. Turn CMCD on in Media3:
    val mediaSourceFactory = DefaultMediaSourceFactory(context)
        .setCmcdConfigurationFactory(CmcdConfiguration.Factory.DEFAULT)
    val player = ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build()
  2. In onLoadCompleted, onLoadError and onLoadCanceled, read the CMCD with the SDK helper, which handles both header and query mode:
    val cmcd = CmcdPayload.from(loadEventInfo.dataSpec.uri, loadEventInfo.dataSpec.httpRequestHeaders)
  3. Return it as BandwidthModel.cmcdPayload from getBandWidthData() 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

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.

ErrorModel

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 on viewCompleted. A pause still open when the view ends is counted; time between seeking and seeked never is.
  • vepyco: play count, sent on play and viewCompleted.
  • veevco: events in the view that the SDK accepted, sent on viewCompleted.
  • velarbstti: last rebuffer start time, sent on buffering and viewCompleted.
  • vemaphps: the furthest content position reached in the view, in ms, sent on pulse, pause, ended and viewCompleted. Seeking back doesn't lower it. Not sent for live.

CustomDataDetails

Supports up to 10 custom fields (customField1 through customField10) for additional metadata.

Event Types

The SDK tracks the following player events:

  • playerReady - Player is initialized and ready
  • viewBegin - Video view has started
  • play - Playback initiated
  • playing - Video is actively playing
  • pause - Playback paused
  • seeking - Seek operation started
  • seeked - Seek operation completed
  • buffering - Buffering started
  • buffered - Buffering completed
  • variantChanged - Quality/variant changed
  • ended - Playback ended
  • viewCompleted - View session completed
  • error - Playback error occurred
  • requestCanceled - Network request canceled
  • requestFailed - Network request failed
  • requestCompleted - 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.

Dependencies

The SDK uses the following libraries:

  • Kotlin Coroutines - Async operations
  • OkHttp - Network requests
  • Retrofit - API communication
  • Gson - JSON serialization
  • Kotlinx Serialization - Data serialization

Changelog

See CHANGELOG.md for release history and version updates.

Support

For questions, issues, or feature requests:

License

Copyright © 2025 FastPix. All rights reserved.

This SDK is proprietary software. Unauthorized copying, modification, distribution, or use of this software is strictly prohibited.

About

FastPix Android Data Core SDK serves as the foundational layer for integrating video playback analytics within Android applications

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages