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.
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 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. |
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 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 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 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
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 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
A preferred choice of NetworkStack can be provided 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)
- 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 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 / 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 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. |
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.
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 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 |
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 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. |
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 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. |
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)
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 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
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
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 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
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
This feature can be enabled only on devices with API versions >= 23. For devices with older API versions, this method is a no-op.