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.
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 Name | Type | Default Value | Description |
|---|---|---|---|
| preferredInitialBufferDurationMs | Int | 2500 | The default duration of media that must be buffered for playback to start or resume following a user action such as a seek, in milliseconds. |
| preferredMinBufferDurationMs | Int | 50000 | The default minimum duration of media that the player will attempt to ensure is buffered at all times, in milliseconds. |
| preferredMaxBufferDurationMs | Int | 50000 | The default maximum duration of media that the player will attempt to buffer, in milliseconds. |
| preferredBufferDurationAfterRebufferMs | Int | 5000 | The 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. |
| preferredBackBufferDurationMs | Int | 0 | The duration of media to retain in the buffer prior to the current playback position, for fast backward seeking. |
| preferredRetainBackBufferFromKeyframe | Boolean | false | Whether 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 Name | Type | Default Value | Description |
|---|---|---|---|
| preferredMinDurationForQualityIncreaseMs | Int | 10000 | The 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. |
| preferredMaxDurationForQualityDecreaseMs | Int | 25000 | The 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. |
| preferredMinDurationToRetainAfterDiscardMs | Int | 25000 | When 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. |
| preferredBandwidthFraction | Float | 0.7F | The 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 Name | Type | Default Value | Description |
|---|---|---|---|
| preferredInitialNetworkBandwidth | Long | Estimates based on the network type and country | Represents 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 type | Initial Network Bandwidth Estimate | Effective Network Bandwidth Estimate with default bandwidth fraction |
|---|---|---|
| Wifi / Ethernet | 3_100_000L (3.1 Mbps) | 2_170_000L (2.17 Mbps) |
| 5G_NSA | 1_800_000L (1.8 Mbps) | 1_440_000L (1.44 Mbps) |
| 5G_SA | 1_100_000L (1.1 Mbps) | 880_000L (0.88 Mbps) |
| 4G | 1_400_000L (1.4 Mbps) | 980_000L (0.98 Mbps) |
| 3G | 910_000L (0.91 Mbps) | 637_000L (0.63 Mbps) |
| 2G | 1_000_000L (1.0 Mbps) | 700_000L (0.7 Mbps) |
| Unknown | 1_000_000L (1.0 Mbps) | 800_000L (0.8 Mbps) |
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 Name | Type | Default Value | Description |
|---|---|---|---|
| preferredConnectTimeoutMs | Int | 8000 | The duration for which the player tries to connect to the network before throwing a SocketTimeoutException. |
| preferredReadTimeoutMs | Int | 8000 | The 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 Value | Protocols | APK size impact | Notes |
|---|---|---|---|
| NetworkStack.BUILT_IN (Default value) | HTTP (varies by device) | None | Implementation varies by device |
| NetworkStack.OK_HTTP | HTTP, HTTP/2 | Small (<1MB) | Requires Kotlin runtime |
| NetworkStack.CRONET_GPS | HTTP, HTTP/2,HTTP/3 over QUIC | Small(<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_HTTPnetwork 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_GPSnetwork 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 Name | Type | Description |
|---|---|---|
| preferredSeekForwardIncrementMs | Long | The seek forward increment, in milliseconds. |
| preferredSeekBackIncrementMs | Long | The 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 Name | Type | Description |
|---|---|---|
| preferredTargetLiveOffsetMs | Long | The 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. |
| preferredMinPlaybackSpeed | Float | The minimum playback speed the player can use to fall back when trying to reach the target live offset. |
| preferredMaxPlaybackSpeed | Float | The maximum playback speed, the player can use to catch up when trying to reach the target live offset. |
| preferredFallbackMinPlaybackSpeed | Float | Sets the minimum playback speed that should be used if no minimum playback speed is defined by the media. |
| preferredFallbackMaxPlaybackSpeed | Float | Sets the maximum playback speed that should be used if no maximum playback speed is defined by the media. |
| preferredTargetLiveOffsetIncrementOnRebufferMs | Long | Sets 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.
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 Action | Description |
|---|---|
| PlaybackStateCompat.ACTION_PLAY_PAUSE | Toggle play / pause |
| PlaybackStateCompat.ACTION_PLAY | Start the playback |
| PlaybackStateCompat.ACTION_PAUSE | Pause the playback |
| PlaybackStateCompat.ACTION_SEEK_TO | Seek forward or rewind to a particular point of time or by an amount of time |
| PlaybackStateCompat.ACTION_FAST_FORWARD | Seek forward 30 seconds |
| PlaybackStateCompat.ACTION_REWIND | Rewind 30 seconds |
| PlaybackStateCompat.ACTION_STOP | Stop the playback |
| PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED | Toggle 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 Checks | Description |
|---|---|
| Superuser file check | Disallows playback if a su binary file is found on the system. |
| Superuser command execution check | Disallows playback if a command with Superuser privileges can be executed. |
| Test Keys check | Checks 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 Checks | Type | Description |
|---|---|---|
| SecurityPolicy.CHECK_FOR_ROOT_APPS | Int | Checks for presence of any root management apps (apps that help in jail breaking devices). |
| SecurityPolicy.CHECK_FOR_DANGEROUS_APPS | Int | Checks for presence of any dangerous apps. |
| SecurityPolicy.CHECK_FOR_ROOT_CLOCK_APPS | Int | Checks for presence of any root clock apps (Apps that hide a device's jail broken info). |
| SecurityPolicy.CHECK_FOR_WRITABLE_PATHS | Int | Checks if any restricted path is writable. |
| SecurityPolicy.CHECK_FOR_MAGISK_BINARY | Int | Checks for presence of Magisk binary (one of the popular ways to jailbreak a device). |
| SecurityPolicy.CHECK_FOR_DANGEROUS_PROPS | Int | Checks for presence of dangerous system properties. |
| SecurityPolicy.CHECK_FOR_ROOT_NATIVE | Int | To 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)
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 Name | Type | Default Value | Description |
|---|---|---|---|
| liveStreamMonitorConfiguration | LiveStreamMonitorConfiguration | monitoringTimeIntervalMs = 10 liveEdgePositionThreshold = 20 currentWindowBufferedPositionThreshold = 40 | The live stream configuration to be employed. |
| monitorLiveStream | Boolean | false | Represents 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
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 Name | Type | Default Value | Description |
|---|---|---|---|
| vfpoMonitoringRange | IntRange | IntRange(15000, 40000) | The preferred minimum number of times to retry loading data prior to propagating the error. |
| monitorVFPO | Boolean | false | Enables/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
You can enable this feature only on devices with API versions >= 23. For devices with older API versions, this method is a no-op.