Skip to main content

Advanced Player Configuration

PlaybackProperties encapsulates most of the different configurations of the Player that can be leveraged to customize the player behaviour. It is 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. It is recommended to override the default behaviour only to enforce any specific tried and tested behaviour, as invalid or inexact values can adversely affect the playback experience.

It is recommended to make all of these properties remotely configurable to ensure the ability to tweak playback behaviour remotely.

Initialize PlaybackProperties

PlaybackProperties.Builder can be used to create an instance of PlaybackProperties with all the preferred values. It can be passed to the player using PlayerBuilder and cannot be changed 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

The various aspects of the default buffering strategy can be configured using the below-mentioned properties. 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.
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

Adaptive Track Selection Strategy

The below-mentioned properties can be modified to dictate the strategy of the player to be employed when selecting tracks i.e. switching up / 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 is to be selected when starting the playback. If a more accurate measure of the available network can be provided, it can be passed to PlaybackProperties to override the default estimate and start with a better suited track. This can also help in optimizing 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

The amount of time the player is supposed to wait before considering a network call as failed can be configured using the below-mentioned properties.

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

A preferred choice of NetworkStack can be provided 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)
  • For using NetworkStack.OK_HTTP network stack, please add the below dependency. And 2.X.X version value in the dependency must match the version of the other media modules being used.
implementation 'com.google.android.exoplayer:extension-okhttp:2.X.X'
  • NetworkStack.CRONET_GPS network stack requires the below dependency and here 2.X.X version value in the dependency must match the version of the other media modules being used.
implementation 'com.google.android.exoplayer:extension-cronet:2.X.X'

Playback Controls

forward and back seeking

Below parameters 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 / Start position in Live streams

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 target offset is calculated from the properties of the manifest such as suggestedPresentationDelay, minBufferTime, etc.

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

Precision during seeking

SeekPreference holds all possible levels of precision that the Player can be configured to adhere during seek operations.
By default, the Player uses SeekPreference.EXACT i.e. it seeks to the exact position that is requested by the application. 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 the for one specific seek operation, pass the desired value to Player.seek API.

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

Set custom surface / view for video rendering

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

Setting a custom View

If the application wants to use their own View for video playback, they can do so by passing the same 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()

Setting a custom Surface

If the application wants to use their own Surface for video playback, they can do so by passing the same to PlayerBuilder.
This is typically done in conjunction with a SurfaceHolder.Callback which notifies 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()
}

The application must ensure that the surface is valid before it is passed to the Player.

note

If the application attaches a custom Surface, any resizing should be done in the application itself and Player.resizeMode becomes a no-op.

Playback control with MediaSession

The Player uses Android's MediaSession API to support external playback control i.e. dispatching playback actions to the player via the MediaSession and not the Quickplay SDK.
Ex: Controlling playback using Google Assistant's voice commands.

The player supports the below-mentioned playback actions 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

This default supported actions can be overridden by passing an array of the preferred playback actions via PlaybackProperties. Any PlaybackStateCompat that is not mentioned above will be ignored.

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

Any number of the below-mentioned checks can be enabled 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.

All the above default security checks can be opted into 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.

A List of the preferred values can be passed to PlayerBuilder to provide any of the above additional checks that have to 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

All the above checks can be performed 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 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. This exception can be prevented 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

preferredMinLoadableRetryCount can be tweaked to specify the number of load errors that can be tolerated by the player 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 will try to reload in an attempt to recover from the error. The number of times the player is allowed to recover can be configured using preferredMaxPlaybackRecoveryRetryCount.
This behaviour is enabled by default with the default value 3. It can be disabled by passing -1 to the property.

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

Fixing specific issues

Handling AV sync issues for TV platforms

PlaybackProperties allows turning on tunneling mode - a configuration that makes it possible to leverage hardware video decoding pipelines for playback on TV platforms which can help in AV sync. If enabled, the player will check if tunneling mode can be enforced i.e. if it's supported by the audio and video renderers for the selected tracks.
Tunneling mode is disabled by default.

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

TunnelingMode should be enabled exclusively for TV platforms. As it is known to have many device specific issues and limitations, manual testing is strongly recommended to check that the media plays correctly when this option is enabled.

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

Asynchronous processing can be enabled that can help in improving the rendering performance of the player. This property force enables / disables the player handling of Media Codec in asynchronous mode and also enables / disables submission of buffers to the MediaCodec on a separate thread from the player.
Default value is null i.e. the Android system determines whether the playback needs / supports async codec processing or not.

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

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