Skip to main content

Advanced Player Configuration

PlaybackProperties encapsulates most of the different configurations of the Player that you can leverage to customize the player behavior. It's an optional parameter that provides granular control over many aspects of playback.

Best practice

The Player already has default values for all the PlaybackProperties, which are reliably optimal for most scenarios. We recommend overriding the default behavior only to enforce any specific tried and tested behavior, as invalid or inexact values can adversely affect the playback experience.

We recommend making all of these properties remotely configurable to ensure you can tweak playback behavior remotely.

Initialize PlaybackProperties

You can use PlaybackProperties.Builder to create an instance of PlaybackProperties with all the preferred values. You can pass it to the player using PlayerBuilder, and you can't change it after the playback has started.

val playbackPropertiesBuilder = PlaybackProperties.Builder()
TODO("Tweak all the preferred properties")
val playbackProperties = playbackPropertiesBuilder.build()

val player = PlayerBuilder()
.mediaURL(contentURL)
.mediaType(mediaType)
.drmScheme(drmScheme)
.playbackProperties(playbackProperties)
.build()

Buffering Strategy

You can configure the various aspects of the default buffering strategy using the properties mentioned below. Changing these values can affect the video start time and re-buffering ratio of the player.

Property NameTypeDefault ValueDescription
preferredInitialBufferDurationMsInt2500The default duration of media that must be buffered for playback to start or resume following a user action such as a seek, in milliseconds.
preferredMinBufferDurationMsInt50000The default minimum duration of media that the player will attempt to ensure is buffered at all times, in milliseconds.
preferredMaxBufferDurationMsInt50000The default maximum duration of media that the player will attempt to buffer, in milliseconds.
preferredBufferDurationAfterRebufferMsInt5000The default duration of media that must be buffered for playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action.
preferredBackBufferDurationMsInt0The duration of media to retain in the buffer prior to the current playback position, for fast backward seeking.
preferredRetainBackBufferFromKeyframeBooleanfalseWhether the back buffer is retained from the previous keyframe. If it's false then seeking in the back-buffer will only be fast if the back-buffer contains a keyframe prior to the seek position.
playbackpropertiesBuilder.preferredInitialBufferDurationMs(2500) //use preferred value here
playbackpropertiesBuilder.preferredMinBufferDurationMs(50000) //use preferred value here
playbackpropertiesBuilder.preferredMaxBufferDurationMs(50000) //use preferred value here
playbackpropertiesBuilder.preferredBufferDurationAfterRebufferMs(5000) //use preferred value here
playbackpropertiesBuilder.preferredBackBufferDurationMs(0) //use preferred value here
playbackpropertiesBuilder.preferredRetainBackBufferFromKeyframe(false) //use preferred value here

Adaptive Track Selection Strategy

You can modify the properties mentioned below to dictate the strategy the player employs when selecting tracks (that is, switching up or down in quality after the playback starts).

Property NameTypeDefault ValueDescription
preferredMinDurationForQualityIncreaseMsInt10000The minimum duration of buffered data, in milliseconds, required for the selected track to switch to one of higher quality. In other words, it is the minimum buffer required by the player to upswitch.
preferredMaxDurationForQualityDecreaseMsInt25000The maximum duration of buffered data required for the selected track to switch to one of lower quality. In other words, it is the maximum buffer below which downswitching is allowed.
preferredMinDurationToRetainAfterDiscardMsInt25000When switching to a video track of higher quality, the selection may indicate that media already buffered at the lower quality can be discarded to speed up the switch. This is the minimum duration of media that must be retained at the lower quality. It must be at least minDurationForQualityIncreaseMs.
preferredBandwidthFractionFloat0.7FThe fraction of the available bandwidth that the selection should consider available for use. Setting to a value less than 1 is recommended to account for inaccuracies in the bandwidth estimator.
playbackpropertiesBuilder.preferredMinDurationForQualityIncreaseMs(10000) //use preferred value here
playbackpropertiesBuilder.preferredMaxDurationForQualityDecreaseMs(25000) //use preferred value here
playbackpropertiesBuilder.preferredMinDurationToRetainAfterDiscardMs(25000) //use preferred value here
playbackpropertiesBuilder.preferredBandwidthFraction(0.7F) //use preferred value here

Initial Track Selection

The Player estimates the available bandwidth before the start of the playback according to the type of network and the country. This estimate, along with PlaybackProperties.preferredBandwidthFraction, dictates the track that's selected when starting the playback. If you can provide a more accurate measure of the available network, you can pass it to PlaybackProperties to override the default estimate and start with a better suited track. This can also help optimize video start time.

Property NameTypeDefault ValueDescription
preferredInitialNetworkBandwidthLongEstimates based on the network type and countryRepresents initial network bandwidth, in bits per second, that the player will attempt to use on startup for the optimal track variant selection.
playbackpropertiesBuilder.preferredInitialNetworkBandwidth(<networkBandwidthInBitsPerSecond>) //use preferred value here

Estimates for India

Network typeInitial Network Bandwidth EstimateEffective Network Bandwidth Estimate with default bandwidth fraction
Wifi / Ethernet3_100_000L (3.1 Mbps)2_170_000L (2.17 Mbps)
5G_NSA1_800_000L (1.8 Mbps)1_440_000L (1.44 Mbps)
5G_SA1_100_000L (1.1 Mbps)880_000L (0.88 Mbps)
4G1_400_000L (1.4 Mbps)980_000L (0.98 Mbps)
3G910_000L (0.91 Mbps)637_000L (0.63 Mbps)
2G1_000_000L (1.0 Mbps)700_000L (0.7 Mbps)
Unknown1_000_000L (1.0 Mbps)800_000L (0.8 Mbps)
info

The default initial bandwidth estimates for each country and their corresponding network type can be looked up here.

Network Configurations

Network Timeouts

You can configure the amount of time the player waits before considering a network call as failed using the properties mentioned below.

Property NameTypeDefault ValueDescription
preferredConnectTimeoutMsInt8000The duration for which the player tries to connect to the network before throwing a SocketTimeoutException.
preferredReadTimeoutMsInt8000The duration for which the player tries to read from the network before throwing a SocketTimeoutException.
playbackpropertiesBuilder.preferredConnectTimeoutMs(8000) //use preferred value here
playbackpropertiesBuilder.preferredReadTimeoutMs(8000) //use preferred value here

Network Stack

You can provide a preferred choice of NetworkStack to the Player using PlayerBuilder.

Enum ValueProtocolsAPK size impactNotes
NetworkStack.BUILT_IN (Default value)HTTP (varies by device)NoneImplementation varies by device
NetworkStack.OK_HTTPHTTP, HTTP/2Small (<1MB)Requires Kotlin runtime
NetworkStack.CRONET_GPSHTTP, HTTP/2,HTTP/3 over QUICSmall(<100KB)Requires Google Play Services. Cronet version updated automatically

NetworkStack API usage:

val player = PlayerBuilder().networkStack(NetworkStack.OK_HTTP)
.build(appContext)
  • To use the NetworkStack.OK_HTTP network stack, add the dependency below. The 2.X.X version value in the dependency must match the version of the other media modules you're using.
implementation 'com.google.android.exoplayer:extension-okhttp:2.X.X'
  • The NetworkStack.CRONET_GPS network stack requires the dependency below. Here, the 2.X.X version value in the dependency must match the version of the other media modules you're using.
implementation 'com.google.android.exoplayer:extension-cronet:2.X.X'

Playback Controls

Forward and back seeking

Use the parameters below to configure forward and back seeking interval.

Property NameTypeDescription
preferredSeekForwardIncrementMsLongThe seek forward increment, in milliseconds.
preferredSeekBackIncrementMsLongThe seek back increment, in milliseconds.
playbackPropertiesBuilder.preferredSeekForwardIncrementMs(<preferredSeekForwardIncrementMs>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredSeekBackIncrementMs(<preferredSeekBackIncrementMs>)// use preferred value from remote playback config

Latency and start position in Live streams

The target Live offset is the position in the Live window that the player tries to consistently play at, behind the live edge. By default, the player calculates the target offset from the properties of the manifest such as suggestedPresentationDelay, minBufferTime, and similar properties.

Property NameTypeDescription
preferredTargetLiveOffsetMsLongThe target offset time from live edge in milliseconds. The player will attempt to get as close as possible to this live offset during playback. If this property is set, the value calculated from the manifest will be ignored.
preferredMinPlaybackSpeedFloatThe minimum playback speed the player can use to fall back when trying to reach the target live offset.
preferredMaxPlaybackSpeedFloatThe maximum playback speed, the player can use to catch up when trying to reach the target live offset.
preferredFallbackMinPlaybackSpeedFloatSets the minimum playback speed that should be used if no minimum playback speed is defined by the media.
preferredFallbackMaxPlaybackSpeedFloatSets the maximum playback speed that should be used if no maximum playback speed is defined by the media.
preferredTargetLiveOffsetIncrementOnRebufferMsLongSets the increment applied to the target live offset each time the player is rebuffering, in milliseconds. Set it to 0 to disable increment of TargetLiveOffset while rebuffering.
playbackPropertiesBuilder.preferredTargetLiveOffsetMs(<targetliveOffsetMs>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredMinPlaybackSpeed(<minPlaybackSpeed>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredMaxPlaybackSpeed(<maxPlaybackSpeed>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredFallbackMinPlaybackSpeed(<fallbackMinPlaybackSpeed>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredFallbackMaxPlaybackSpeed(<fallbackMaxPlaybackSpeed>)// use preferred value from remote playback config
playbackPropertiesBuilder.preferredTargetLiveOffsetIncrementOnRebufferMs(<targetLiveOffsetIncrement>)// use preferred value from remote playback config

Precision during seeking

SeekPreference holds all possible levels of precision that you can configure the Player to adhere to during seek operations. By default, the Player uses SeekPreference.EXACT (that is, it seeks to the exact position that you request). Use SeekPreference.CLOSEST_SYNC to seek to the closest sync position to the requested position.

To override the default precision level for the entire playback session, pass the desired value to PlayerBuilder.

val player = PlayerBuilder()
.mediaURL(contentURL)
.mediaType(mediaType)
.drmScheme(drmScheme)
.seekPreference(SeekPreference.EXACT) //pass preferred value here
.playbackProperties(playbackProperties)
.build()

To override the default precision level for one specific seek operation, pass the desired value to the Player.seek API.

val seekToPosition = 10000L
player.seek(seekToPosition, SeekPreference.EXACT) //pass preferred value here

Set custom surface or view for video rendering

The Quickplay Player creates its own SurfaceView by default for rendering the video.

Set a custom View

If you want to use your own View for video playback, you can do so by passing it to PlayerBuilder. Quickplay Player supports rendering with SurfaceView, TextureView, or SurfaceHolder.

val myVideoSurfaceView = SurfaceView(context)

val player = PlayerBuilder()
.mediaURL(contentURL)
.mediaType(mediaType)
.drmScheme(drmScheme)
.setRenderingView(myVideoSurfaceView) //pass your view here
.playbackProperties(playbackProperties)
.build()

Set a custom Surface

If you want to use your own Surface for video playback, you can do so by passing it to PlayerBuilder. You typically do this in conjunction with a SurfaceHolder.Callback that notifies you about the lifecycle of the underlying surface.

val myVideoSurface: Surface? = null

override fun surfaceCreated(holder: SurfaceHolder) {
val surface = holder.surface
if (surface.isValid) {
initPlayer(surface)
}
}

fun initPlayer(surface: Surface) {
val player = PlayerBuilder()
.mediaURL(contentURL)
.mediaType(mediaType)
.drmScheme(drmScheme)
.setRenderingView(myVideoSurfaceView) //pass your view here
.playbackProperties(playbackProperties)
.build()
}

You must ensure that the surface is valid before you pass it to the Player.

note

If you attach a custom Surface, you should do any resizing in your application itself, and Player.resizeMode becomes a no-op.

Set preferred audio language at the start of the playback

Specify the desired audio language for playback. When you set this, the player attempts to select the matching audio track automatically at the start of playback. To construct AudioLanguageInfo, see AudioLanguageInfo.

val audioLanguageInfo = AudioLanguageInfo(
languageCode = <desired language code "en" for English, "es" for Spanish>
roles = listOf(AudioTrackRole.DESCRIBES_VIDEO), //Optional
mimeType = <desired mime type to start the playback> // Optional
)

val player = PlayerBuilder()
.mediaURL(contentURL)
.mediaType(mediaType)
.drmScheme(drmScheme)
.preferredAudioLanguage(audioLanguageInfo)
.playbackProperties(playbackProperties)
.build()

Playback control with MediaSession

The Player uses Android's MediaSession API to support external playback control (that is, dispatching playback actions to the player via the MediaSession and not the Quickplay SDK). For example, you can control playback using Google Assistant's voice commands.

The player supports the playback actions mentioned below by default.

Supported Playback ActionDescription
PlaybackStateCompat.ACTION_PLAY_PAUSEToggle play / pause
PlaybackStateCompat.ACTION_PLAYStart the playback
PlaybackStateCompat.ACTION_PAUSEPause the playback
PlaybackStateCompat.ACTION_SEEK_TOSeek forward or rewind to a particular point of time or by an amount of time
PlaybackStateCompat.ACTION_FAST_FORWARDSeek forward 30 seconds
PlaybackStateCompat.ACTION_REWINDRewind 30 seconds
PlaybackStateCompat.ACTION_STOPStop the playback
PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLEDToggle captions during playback

You can override these default supported actions by passing an array of the preferred playback actions via PlaybackProperties. The player ignores any PlaybackStateCompat that isn't mentioned above.

val preferredPlaybackActions = arrayOf( 
//use preferred values here
PlaybackStateCompat.ACTION_PLAY_PAUSE,
PlaybackStateCompat.ACTION_SEEK_TO
)

playbackpropertiesBuilder.mediaSessionPlaybackActions(preferredPlaybackActions)

Device Security

Disallow playback on jail-broken devices

You can enable any number of the checks mentioned below to disallow playback on insecure (jail-broken) devices. Playback is allowed on insecure devices by default.

Default Security ChecksDescription
Superuser file checkDisallows playback if a su binary file is found on the system.
Superuser command execution checkDisallows playback if a command with Superuser privileges can be executed.
Test Keys checkChecks if the build was signed with a test key.

You can opt into all the above default security checks via PlayerBuilder.

playerBuilder.enableSecurityChecks()

The following is the list of opt-in checks that can be additionally enabled:

Additional Security ChecksTypeDescription
SecurityPolicy.CHECK_FOR_ROOT_APPSIntChecks for presence of any root management apps (apps that help in jail breaking devices).
SecurityPolicy.CHECK_FOR_DANGEROUS_APPSIntChecks for presence of any dangerous apps.
SecurityPolicy.CHECK_FOR_ROOT_CLOCK_APPSIntChecks for presence of any root clock apps (Apps that hide a device's jail broken info).
SecurityPolicy.CHECK_FOR_WRITABLE_PATHSIntChecks if any restricted path is writable.
SecurityPolicy.CHECK_FOR_MAGISK_BINARYIntChecks for presence of Magisk binary (one of the popular ways to jailbreak a device).
SecurityPolicy.CHECK_FOR_DANGEROUS_PROPSIntChecks for presence of dangerous system properties.
SecurityPolicy.CHECK_FOR_ROOT_NATIVEIntTo perform native checks.

You can pass a List of the preferred values to PlayerBuilder to provide any of the above additional checks that must be enabled in addition to the aforementioned default checks.

val securityChecks = listOf(SecurityPolicy.CHECK_FOR_ROOT_APPS, SecurityPolicy.CHECK_FOR_DANGEROUS_APPS) // use preferred values here
playerBuilder.enableSecurityChecks(securityChecks)
note

Pass SecurityPolicy.CHECK_FOR_ALL_ADDITIONAL_SECURITY_CHECKS to opt in for all default and additional checks.

Get device's security status

You can perform all the above checks on demand using PlatformClient.

// Provides info whether device is secure or not. Returns a Pair of Boolean along with either the checks that failed as comma separated strings, or null if device is secure.
PlatformClient.isSecure()

Handling errors

BehindLiveWindowException (Error: 40020d)

The Player throws a BehindLiveWindowException when the playback strays so far away from the live edge that it falls behind the available live window. The playback can fall behind because of continuous buffering or pausing of a live stream. You can prevent this exception by turning on the live monitor via PlaybackProperties and passing a LiveStreamMonitorConfiguration, if needed. Live stream monitoring is disabled by default.

Property NameTypeDefault ValueDescription
liveStreamMonitorConfigurationLiveStreamMonitorConfigurationmonitoringTimeIntervalMs = 10
liveEdgePositionThreshold = 20
currentWindowBufferedPositionThreshold = 40
The live stream configuration to be employed.
monitorLiveStreamBooleanfalseRepresents whether to monitor live stream position or not.
val liveStreamMonitorConfiguration = LiveStreamMonitorConfiguration(
monitoringTimeIntervalMs = 10, // Pass preferred value
liveEdgePositionThreshold = 20, // Pass preferred value
currentWindowBufferedPositionThreshold = 40 // Pass preferred value
)
playbackpropertiesBuilder.liveStreamMonitorConfiguration(liveStreamMonitorConfiguration) // Pass preferred value
playbackpropertiesBuilder.monitorLiveStream(false) //use preferred value here

Load errors

You can tweak preferredMinLoadableRetryCount to specify the number of load errors that the player can tolerate before throwing an error. Default value is 3.

playbackpropertiesBuilder.preferredMinLoadableRetryCount(3) //use preferred value here

Recoverable fatal errors

On encountering some fatal yet recoverable errors, the Player tries to reload in an attempt to recover from the error. You can configure the number of times the player can recover using preferredMaxPlaybackRecoveryRetryCount. This behavior is enabled by default with the default value 3. You can disable it by passing -1 to the property.

playbackpropertiesBuilder.preferredMaxPlaybackRecoveryRetryCount(3) //pass preferred value, or -1 to disable

Fixing specific issues

Handle AV sync issues for TV platforms

PlaybackProperties lets you turn on tunneling mode, which is a configuration that makes it possible to leverage hardware video decoding pipelines for playback on TV platforms. This can help with AV sync. If you enable this, the player checks if it can enforce tunneling mode (that is, if the audio and video renderers support it for the selected tracks). Tunneling mode is disabled by default.

playbackpropertiesBuilder.tunnelingMode(false) //use preferred value here
info

You should enable TunnelingMode exclusively for TV platforms. As it's known to have many device-specific issues and limitations, we strongly recommend manual testing to check that the media plays correctly when you enable this option.

Handling Frame Drops

VFPO (Video Frame Processing Offset Average) is a player metric to evaluate the rendering performance. VFPO measures how early the player processes a video frame compared to its presentation time, in microseconds. For example, if a video frame is processed by the player 30ms before the frame should be displayed on screen, the VFPO of this frame is 30000. Similarly, if a video frame is processed too late by 10ms (the player’s current position has progressed beyond the frame’s presentation time), the VFPO for this frame is -10000 (and the video frame is dropped). The smooth playback, without dropped frames or audio under-runs, is expected when the average VFPO is above 40000. Video Frame Processing Offset Average is computed by taking a sum of VFPOs captured over a playback time period and dividing the sum by the number of VFPOs captured over the same time period.

Thus, monitoring VFPO will select a lower quality track when needed and will help in recovering from frame drops and induce smoother playback at the cost of a lower quality.
VFPO Monitoring is disabled by default.

Property NameTypeDefault ValueDescription
vfpoMonitoringRangeIntRangeIntRange(15000, 40000)The preferred minimum number of times to retry loading data prior to propagating the error.
monitorVFPOBooleanfalseEnables/Disables the monitoring of VFPO.
playbackpropertiesBuilder.vfpoMonitoringRange(IntRange(15000, 40000)) //use preferred value here
playbackpropertiesBuilder.monitorVFPO(true) //use preferred value here

Improve rendering performance

You can enable asynchronous processing to improve the rendering performance of the player. This property force enables or disables the player's handling of Media Codec in asynchronous mode and also enables or disables submission of buffers to the MediaCodec on a separate thread from the player. Default value is null (that is, the Android system determines whether the playback needs or supports async codec processing).

playbackpropertiesBuilder.asyncCodecProcessing(null) //use preferred value here
info

You can enable this feature only on devices with API versions >= 23. For devices with older API versions, this method is a no-op.