FastPix player for Android
Integrate FastPix Android Player SDK for Media3-based video streaming with security and analytics.
The FastPix Android Player SDK is a full-featured, Media3-based playback library for Android apps. Built on top of ExoPlayer, it supports live and on-demand video streaming, playback security, analytics, track switching, and resolution control, giving you everything you need to deliver reliable, branded, and insight-rich video playback inside your Android applications.
You can use the GitHub repository for FastPix Android Player.
Prerequisites
Before you start, make sure you meet the following development and FastPix setup requirements.
Android environment
- Android Studio: Arctic Fox or later
- Android SDK: Version 24+
- FastPix Player dependency for Player
- FastPix ExoPlayer SDK (Media3-compatible) as a dependency
- GitHub Personal Access Token (PAT) for private Maven access
FastPix requirements
Complete the following setup steps in the FastPix Dashboard before integrating the SDK:
- Login to FastPix Dashboard: Go to dashboard.fastpix.io and sign in.
- Create Media: Use the UI or API to create a media asset (via push or pull URL).
- Get Playback ID: Go to View Media, select your uploaded file, and copy the
playbackId. - Use the Playback ID: This will be required to generate the playback URL in your app.
Install and configure the SDK
Open your android studio project
Open the project where you want to integrate the SDK.
Add the FastPix player SDK dependency
Navigate to your app-level build.gradle file (or build.gradle.kts if using Kotlin DSL) and add the following under the dependencies section:
dependencies {
// Check for latest version
implementation("io.fastpix.player:android:1.0.5")
}Add the GitHub Maven repository
Navigate to your settings.gradle (or settings.gradle.kts) file. You will need a GitHub Personal Access Token (PAT) to access the private Maven package.
Recommended: Load credentials from
local.propertiesso you do not hardcode secrets.
Add the following lines inside the repositories section:
repositories {
maven {
url = uri("https://maven.pkg.github.com/FastPix/fastpix-android-player")
credentials {
gpr.user=<your_github_username>
gpr.key=<your_github_pat>
}
}
}Sync your project with gradle files
Click Sync Now in the notification bar to download and integrate the FastPix Player SDK. Once the dependency is added, you can use the FastPix Player SDK module in any part of your Android project.
Import the SDK
Required imports
import io.fastpix.media3.PlayerView
import io.fastpix.media3.PlaybackListener
import io.fastpix.media3.core.FastPixPlayer
import io.fastpix.media3.core.PlaybackResolution
import io.fastpix.media3.core.RenditionOrder
import io.fastpix.media3.core.StreamType
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackExceptionGlobal declarations
You can either:
- Use
PlayerViewonly (it auto-creates aFastPixPlayerwith default settings), or - Create a configured
FastPixPlayeryourself (loop, autoplay, seek preview, analytics, etc.) and attach it toPlayerView.
A global declaration ensures these objects can be accessed throughout the lifecycle of your Activity/Fragment.
class CustomizedPlayerActivity : AppCompatActivity() {
private lateinit var binding: ActivityCustomizedPlayerBinding
private val playerView get() = binding.playerView
private var player: FastPixPlayer? = null
private lateinit var playbackListener: PlaybackListener
}Activity setup with playback
The following example demonstrates a complete Activity setup including player initialization, listener registration, and resource cleanup:
import io.fastpix.media3.PlayerView
import io.fastpix.media3.PlaybackListener
import io.fastpix.media3.core.FastPixPlayer
import io.fastpix.media3.core.PlaybackResolution
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
class CustomizedPlayerActivity : AppCompatActivity() {
private lateinit var binding: ActivityCustomizedPlayerBinding
private val playerView get() = binding.playerView
private var player: FastPixPlayer? = null
private lateinit var playbackListener: PlaybackListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCustomizedPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create a configured player (optional; PlayerView can also auto-create one).
player = FastPixPlayer.Builder(this)
.setAutoplay(true)
.setLoop(false)
.build()
playerView.player = player
}
override fun onStart() {
super.onStart()
startPlayback()
}
private fun startPlayback() {
playbackListener = object : PlaybackListener {
override fun onPlay() {}
override fun onPause() {}
override fun onPlaybackStateChanged(isPlaying: Boolean) {}
override fun onError(error: PlaybackException) {}
}
player?.addPlaybackListener(playbackListener)
// Option A: Direct URL playback
// player?.setMediaItem(MediaItem.fromUri("https://example.com/video.m3u8"))
// Option B: FastPix playback via playbackId (recommended)
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID"
maxResolution = PlaybackResolution.FHD_1080
// playbackToken = "YOUR_PLAYBACK_TOKEN" // optional (secure playback)
}
}
override fun onDestroy() {
super.onDestroy()
player?.removePlaybackListener(playbackListener)
if (isFinishing) {
// Forces release even if config-change retention is enabled on the view.
playerView.release()
}
}
}Key points about this setup:
CustomizedPlayerActivityuses view binding (ActivityCustomizedPlayerBinding) to accessPlayerViewfrom the layout.- A
FastPixPlayerinstance is created once and attached toPlayerView. - A
PlaybackListenerobserves playback state and errors. setFastPixMediaItem { ... }generates the playback URL usingplaybackIdplus optional parameters.- Call
playerView.release()inonDestroy()when the Activity is finishing to release resources.
Playback setup and use cases
The SDK supports several playback configurations depending on your security, quality, and routing requirements. The following sections cover each major scenario.
Public media playback (with stream type)
private fun startPlayback() {
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // mandatory
// streamType = StreamType.onDemand // reserved for future use
}
}Stream type notes:
setFastPixMediaItem { playbackId = ... }resolves a FastPix HLS stream URL:https://stream.fastpix.io/{playbackId}.m3u8- If you already have a playback URL (VOD or live), use direct URL playback via
setMediaItem(MediaItem.fromUri(url)). StreamTypeis currently included in the builder for API compatibility but is not yet applied to the resolved URL in this SDK version.
Secure playback (token-based)
private fun startPlayback() {
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // mandatory
playbackToken = "SET_PLAYBACK_TOKEN" // optional
}
}Token benefits:
- Unique playback ID: Identifies each video asset individually for secure and trackable playback.
- Signed token authentication: Grants access only if a valid, cryptographically signed token is present in the playback URL.
- Time-limited access: Tokens can include expiration timestamps to allow playback only within a defined time window.
- User-specific control: Tokens can be generated per user or session, restricting access based on identity or device.
- Prevents unauthorized sharing: Signed tokens stop others from reusing the URL, protecting content from piracy and hotlinking.
- Customizable restrictions: Tokens can include rules like IP restrictions, usage limits, or geo-blocking for enhanced security.
Playback resolution control
private fun startPlayback() {
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // mandatory
minResolution = PlaybackResolution.LD_480
maxResolution = PlaybackResolution.FHD_1080
// Or force a fixed value:
// resolution = PlaybackResolution.HD_720
}
}Benefits:
- Min/max resolution: Set quality boundaries to manage bandwidth and device performance.
- Fixed resolution: Force playback at a specific resolution (for example, always 720p).
- Adaptive range: Let the player switch between defined min–max levels (for example, 480p to 1080p) based on network.
- Optimized playback: Achieve smoother streaming by balancing quality and performance.
- Full control: Tailor video quality to user settings or app-specific requirements.
Rendition order (quality preference)
Define which resolutions should be preferred during adaptive playback (for example, prefer HD over SD when available). You can align playback quality with user-selected settings (like "Data Saver," "Auto," or "High Quality"). The player intelligently switches resolutions based on real-time network and device conditions, while respecting the set priorities. This also helps avoid sudden quality drops or unnecessary resolution changes, maintaining smoother playback.
private fun startPlayback() {
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // mandatory
renditionOrder = RenditionOrder.Descending
}
}Modes:
- Ascending: 144p → 360p → 720p → 1080p (quick startup)
- Descending: 1080p → 720p → 360p → 144p (best-quality first)
Custom domain support
private fun startPlayback() {
player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // mandatory
customDomain = "stream.yoursite.com" // optional (defaults to stream.fastpix.io)
}
}Why use a custom domain:
- Stream videos directly from your own domain (for example,
stream.yoursite.com) instead of default CDN URLs. - Supports public and private content.
- You can use signed playback tokens for private videos to restrict access to authorized users only.
- Manage who can view your content with minimal setup using domain and token-based security.
- Hosting via your custom domain can improve performance and ensure better branding consistency.
- Seamless transition for white-labeled apps.
Full configuration example
The complete example below includes listener setup, URL generation, player configuration, seek preview, and analytics:
import io.fastpix.media3.PlayerView
import io.fastpix.media3.PlaybackListener
import io.fastpix.media3.analytics.AnalyticsConfig
import io.fastpix.media3.core.FastPixPlayer
import io.fastpix.media3.core.PlaybackResolution
import io.fastpix.media3.core.RenditionOrder
import io.fastpix.media3.seekpreview.models.PreviewFallbackMode
import io.fastpix.media3.seekpreview.models.SeekPreviewConfig
import io.fastpix.data.domain.model.VideoDataDetails
import androidx.media3.common.PlaybackException
class CustomizedPlayerActivity : AppCompatActivity() {
private lateinit var binding: ActivityCustomizedPlayerBinding
private val playerView get() = binding.playerView
private var player: FastPixPlayer? = null
private lateinit var playbackListener: PlaybackListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCustomizedPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
val seekPreviewConfig = SeekPreviewConfig.Builder()
.setEnabled(true)
.setFallbackMode(PreviewFallbackMode.TIMESTAMP)
.setEnablePreload(true)
.setPreloadRadius(1)
.setCacheEnabled(true)
.build()
val analyticsConfig = AnalyticsConfig.Builder(playerView, "YOUR_WORKSPACE_ID")
.setVideoDataDetails(VideoDataDetails(videoId = "videoId", videoTitle = "videoTitle"))
.setEnabled(true)
.build()
player = FastPixPlayer.Builder(this)
.setAutoplay(true)
.setLoop(false)
.setSeekPreviewConfig(seekPreviewConfig)
.setAnalyticsConfig(analyticsConfig)
.build()
playerView.player = player
}
override fun onStart() {
super.onStart()
startPlayback()
}
private fun startPlayback() {
playbackListener = object : PlaybackListener {
override fun onPlay() {}
override fun onPause() {}
override fun onPlaybackStateChanged(isPlaying: Boolean) {}
override fun onError(error: PlaybackException) {}
}
player?.addPlaybackListener(playbackListener)
val ok = player?.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID"
minResolution = PlaybackResolution.LD_480
maxResolution = PlaybackResolution.FHD_1080
renditionOrder = RenditionOrder.Descending
playbackToken = "SET_PLAYBACK_TOKEN"
customDomain = "SET_CUSTOM_DOMAIN"
} ?: false
if (!ok) return
}
override fun onStop() { super.onStop() }
override fun onPause() { super.onPause() }
override fun onDestroy() {
super.onDestroy()
player?.removePlaybackListener(playbackListener)
if (isFinishing) {
playerView.release()
}
}
}SDK API reference
This section documents the SDK's primary API surface and how to use it.
Player objects
io.fastpix.media3.PlayerView: UI component for XML layouts. It renders video and owns aFastPixPlayerinstance.io.fastpix.media3.core.FastPixPlayer: The core player (wrapping Media3 ExoPlayer). This is where most controls and callbacks live.
Typical pattern:
val player = FastPixPlayer.Builder(context)
.setAutoplay(true)
.setLoop(false)
.build()
playerView.player = playerMedia setup (FastPix streams vs. direct URL)
FastPix playback (Recommended)
Use this when you have a playbackId from the FastPix dashboard.
val ok = player.setFastPixMediaItem {
playbackId = "YOUR_PLAYBACK_ID" // required
// Optional playback security:
playbackToken = "YOUR_PLAYBACK_TOKEN"
// Optional custom domain:
customDomain = "stream.yoursite.com" // default: stream.fastpix.io
// Optional quality controls:
minResolution = PlaybackResolution.LD_480
maxResolution = PlaybackResolution.FHD_1080
// resolution = PlaybackResolution.HD_720 // fixed quality
renditionOrder = RenditionOrder.Descending
// StreamType is currently reserved for future use:
// streamType = StreamType.onDemand
}
if (!ok) {
// Validation failed (for example, empty playbackId). Details arrive via PlaybackListener.onError().
}Direct URL playback
Use this when you already have a URL (HLS .m3u8, DASH, progressive MP4, etc.).
player.setMediaItem(MediaItem.fromUri("https://example.com/video.m3u8"))Playback controls (Play / Pause / Seek / State)
These methods are on FastPixPlayer and are safe to call from the main thread (recommended). They delegate to Media3 internally.
player.play() // start/resume
player.pause() // pause
player.togglePlayPause() // toggle
player.seekTo(positionMs = 5_000) // seek to 5s
val isAutoPlay = player.autoplay // whether playWhenReady is set when ready
player.setPlayWhenReady(true) // start automatically when ready
val willPlayWhenReady = player.getPlayWhenReady()
val pos = player.getCurrentPosition()
val duration = player.getDuration()
val state = player.getPlaybackState() // Media3: STATE_IDLE/BUFFERING/READY/ENDEDBehavior details:
play(): Starts playback if prepared; if buffering/ready, it resumes. CallsetMediaItem(...)orsetFastPixMediaItem { ... }first.pause(): Pauses if playing.togglePlayPause(): Convenience for tap gestures or a single button.seekTo(ms): Seeks within the current item. Seek events are surfaced viaPlaybackListener.onSeekStartandonSeekEnd.getDuration(): Returns0Lwhen unknown.getPlaybackState(): Media3 playback state. Use with buffering callbacks to show spinners.setPlayWhenReady(true)/autoplay: Use either, depending on whether you want a one-off "start when ready" vs a persistent player-level policy.
Volume and mute / unmute
player.setVolume(0.75f) // 0.0..1.0
val vol = player.getVolume()
player.mute()
player.unmute()Behavior details:
setVolume(x): Clamps to0.0 ≤ x ≤ 1.0.mute()/unmute():mute()stores the previous non-zero volume andunmute()restores it (or defaults to 1.0 if nothing was saved).- Device volume buttons: The SDK monitors system volume while you have at least one playback listener attached and fires
PlaybackListener.onVolumeChanged(volumeLevel)andPlaybackListener.onMuteStateChanged(isMuted).
Playback speed (rate control)
The SDK supports speeds from 0.25x to 2.0x.
player.setPlaybackSpeed(1.5f) // picks closest supported speed if needed
val speed = player.getPlaybackSpeed()
val all = player.getAvailablePlaybackSpeeds() // FloatArray
player.fast() // next speed (wraps)
player.slow() // previous speed (wraps)
player.normalize() // back to 1.0xListen for speed changes with PlaybackListener.onPlaybackRateChanged(rate).
Playback callbacks (PlaybackListener)
PlaybackListener)Attach a listener to receive events. All callbacks are invoked on the main thread.
val listener = object : PlaybackListener {
override fun onPlay() {
// Playback started/resumed
}
override fun onPause() {
// Playback paused (not called for ended/idle transitions)
}
override fun onPlaybackStateChanged(isPlaying: Boolean) {
// Unified play/pause state changes
}
override fun onPlayerReady(durationMs: Long) {
// Called once per media item when player becomes READY the first time
}
override fun onTimeUpdate(currentPositionMs: Long, durationMs: Long, bufferedPositionMs: Long) {
// Periodic time updates during active playback (default ~500ms)
}
override fun onSeekStart(currentPositionMs: Long) {
// Fired when a seek begins (user scrub or programmatic seekTo)
}
override fun onSeekEnd(fromPositionMs: Long, toPositionMs: Long, durationMs: Long) {
// Fired when seek completes (READY again or discontinuity completes)
}
override fun onBufferingStart() {
// Transition to buffering
}
override fun onBufferingEnd() {
// Transition from buffering to ready
}
override fun onVolumeChanged(volumeLevel: Float) {
// Device volume changed (0.0..1.0)
}
override fun onMuteStateChanged(isMuted: Boolean) {
// Convenience mute state
}
override fun onPlaybackRateChanged(rate: Float) {
// Playback speed updated
}
override fun onCompleted() {
// Reached end of media (still called even if loop=true, before repeating)
}
override fun onError(error: PlaybackException) {
// Playback or validation error
}
}
player.addPlaybackListener(listener)Callback semantics (practical notes):
onPlayerReady(durationMs): Fires once per media item when the player reaches READY the first time (not on every rebuffer/seek).onTimeUpdate(...): Fires periodically during active playback (default ~500ms) and stops automatically when paused/ended or when no listeners remain.onSeekStart/onSeekEnd: Triggered for both user scrubs and programmaticseekTocalls.onBufferingStart/onBufferingEnd: Emitted on buffering transitions; ideal for showing/hiding a spinner.onCompleted(): Called when reaching the end; ifloop = true, still fired before the video repeats.
Remove the listener when you're done:
player.removePlaybackListener(listener)Seek preview (spritesheet thumbnails)
Seek preview shows thumbnail previews while the user scrubs. When enabled (via FastPixPlayer.Builder.setSeekPreviewConfig(...)), you can wire seek preview into your scrubbing UI:
- Call
sdk.showPreview()when the user starts dragging. - Call
sdk.loadPreview(positionMs)while dragging (safe to call frequently). - Call
sdk.hidePreview()when the user stops dragging.
How spritesheets are resolved (FastPix streams)
- Stream URL:
https://stream.fastpix.io/{playbackId}.m3u8 - Spritesheet metadata:
https://images.fastpix.io/{playbackId}/spritesheet.json
If the spritesheet does not exist, or the current media is not a FastPix stream, the SDK follows PreviewFallbackMode:
TIMESTAMP: Shows a time label (for example"02:30");SpritesheetMetadata.bitmapmay be null.NONE: Shows nothing.
Enable seek preview during player creation
val seekPreviewConfig = SeekPreviewConfig.Builder()
.setEnabled(true)
.setFallbackMode(PreviewFallbackMode.TIMESTAMP) // or NONE
.setEnablePreload(true)
.setPreloadRadius(1)
.setCacheEnabled(true)
.build()
val player = FastPixPlayer.Builder(context)
.setSeekPreviewConfig(seekPreviewConfig)
.build()Listen for preview lifecycle and frames
player.setSeekPreviewListener(object : SeekPreviewListenerAdapter() {
override fun onSpritesheetInitialized() {
// Spritesheet metadata is available and preview can be used
}
override fun onSpritesheetFailed(error: Throwable) {
// Spritesheet unavailable; fallback may still work depending on config
}
override fun onPreviewShow() {
// Show your preview container
}
override fun onPreviewHide() {
// Hide your preview container
}
override fun onSpritesheetLoaded(metadata: SpritesheetMetadata) {
// metadata.bitmap: bitmap for current position (can be null in timestamp fallback)
// metadata.timestampMs: current seek timestamp (ms)
// Additional fields: rows/columns/frameWidth/frameHeight/frameCount
// durationMs/intervalMs (distance between frames)
}
})Hook to SeekBar scrubbing
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onStartTrackingTouch(seekBar: SeekBar) {
player.showPreview()
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
player.loadPreview(progress.toLong()) // recommended: progress in ms
}
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
player.hidePreview()
player.seekTo(seekBar.progress.toLong())
}
})Optional: Request a preview bitmap directly
If you want to render previews yourself (for example, custom UI), you can fetch bitmaps:
// Call from a coroutine
val bmp = player.getPreviewBitmap(timeMs = 30_000)
val label = player.formatTimestamp(timeMs = 30_000)Audio tracks (discovery and switching)
FastPixPlayer can discover and switch audio tracks (commonly on HLS streams with multiple audio renditions).
What an AudioTrack contains (useful for UI):
id: Pass this tosetAudioTrack(id).languageCode/languageName/label: Show in your menu.isSelected,isPlayable,isDefault: For UI state.- Optional diagnostics:
role,channels,codec,bitrate,groupId.
Listen for audio track updates
import io.fastpix.media3.tracks.AudioTrackListener
import io.fastpix.media3.tracks.AudioTrackUpdateReason
player.addAudioTrackListener(object : AudioTrackListener {
override fun onAudioTracksLoaded(tracks: List<AudioTrack>, reason: AudioTrackUpdateReason) {
// Populate your UI (language menu, etc.)
// Each AudioTrack has an id; use it to switch.
}
override fun onAudioTracksChange(selectedTrack: AudioTrack) {
// Update selected UI state
}
override fun onAudioTrackSwitching(isSwitching: Boolean) {
// Optional: show/hide a "switching" indicator
}
override fun onAudioTracksLoadedFailed(error: AudioTrackError) {
// Handle invalid id, player not ready, selection failure, etc.
}
})Switch audio track by ID
NOTE:
If a seek is in progress, the SDK defers switching until the seek completes.
val tracks = player.getAudioTracks()
val target = tracks.firstOrNull() ?: return
player.setAudioTrack(target.id)Example: Build a simple language menu
val tracks = player.getAudioTracks()
val items = tracks.filter { it.isPlayable }.map { t ->
val title = t.languageName ?: t.label ?: t.languageCode ?: "Unknown"
title to t.id
}
// Present items in a dialog; on click: player.setAudioTrack(id)Subtitles (Discovery, Switching, and Cues)
What a SubtitleTrack contains:
id: Pass tosetSubtitleTrack(id).languageCode/languageName/labelisSelected,isPlayable,isDefault,isForced- Optional:
role,codec,groupId
Listen for subtitle track updates and cue changes
import io.fastpix.media3.tracks.SubtitleTrackListener
player.addSubtitleTrackListener(object : SubtitleTrackListener {
override fun onSubtitlesLoaded(tracks: List<SubtitleTrack>) {
// Populate subtitle selection UI
}
override fun onSubtitleChange(track: SubtitleTrack?) {
// track == null means subtitles disabled
}
override fun onSubtitlesLoadedFailed(error: SubtitleTrackError) {
// Track not found, not playable, player not ready, etc.
}
override fun onSubtitleCueChange(info: SubtitleRenderInfo) {
// Optional: observe cues (text + timing) if building a custom subtitle renderer
}
})Switch subtitle track by ID, or disable subtitles
val subs = player.getSubtitleTracks()
val target = subs.firstOrNull()
if (target != null) {
player.setSubtitleTrack(target.id)
} else {
player.disableSubtitles()
}Preferred / Default language (auto selection)
These preferences apply automatically when tracks become available, and never override a manual selection:
player.setDefaultAudioTrack(languageName = "English")
player.setDefaultSubtitleTrack(languageName = "Spanish")NOTE:
Use BCP-47 / ISO language names like"English","Spanish","Hindi","French". If the preferred language does not exist in the stream, the player keeps its normal selection.
PlayerView details (XML integration tips)
If you are using the XML io.fastpix.media3.PlayerView, the following properties are important:
- Configuration-change survival: By default
PlayerView.retainPlayerOnConfigChange = true. For best results, assign anandroid:idin XML; otherwise the view cannot retain/recover the stored player instance across rotation. - Tap gesture:
playerView.isTapGestureEnabled = trueenables single-tap toggle play/pause usingtogglePlayPause(). - Release: Call
playerView.release()when the Activity is finishing (onDestroywithisFinishing == true) to force a real release even if retention is enabled.
Updated 14 days ago