Advanced Setup
Advanced Setup¶
This chapter explains some advanced topics related to player setup. Every entry is self-contained so you can skip directly to the topic of your interest.
Live Playback¶
This section elaborates on which advanced configuration objects and settings you can tweak in order to improve playback for live content.
Keep in mind that these settings are very use case-specific so depending on which platform, content and delivery method you’re using the default value may not be the optimal for your scenario.
Here is a short snippet showing how to tweak these params. Refer to the following sub-sections to get more info on them.
playerController.open(new PlayerConfig.Builder("http://url.com/manifest.mpd")
.liveConfiguration(new LiveConfiguration.Builder()
.liveEdgeLatencyMs(100)
.get())
.bufferConfiguration(new BufferConfiguration.Builder()
.minPlaybackStart(100, TimeUnit.MILLISECONDS)
.minRebufferStart(100, TimeUnit.MILLISECONDS)
.get())
.get());
LiveConfiguration¶
One of the configuration fields present in the PlayerConfig
, LiveConfiguration
,
is a set of live-related configuration options. Tweaking this configuration can
allow you to achieve the desired performance when playing live content.
There are some other live-affecting params which are not inside the LiveConfiguration
object,
since they’re not exclusive for live content, such as BufferConfiguration
.
Low latency¶
Our PRESTOplay SDK for Android is capable of playing CMAF Ultra-low latency streams. There is no special configuration for enabling this, if the stream is CMAF-LL compliant, it will be handled as such.
On the other hand, if you want to achieve the lowest possible latency, you should tweak
some settings on the LiveConfiguration
and the BufferConfiguration
objects.
The PRESTOplay SDK for Android also provides some default low-latency-friendly values for these configurations. These
can be set through the LowLatencyProfile
. This configuration object receives an already built
PlayerConfig
and sets some config fields with the aim of reducing the latency.
You can tweak the LowLatencyProfile
before applying it as well, through its Builder
.
This snippet shows how to apply the LowLatencyProfile
:
// Create player config, with url to play and other optional config
PlayerConfig cfg = new PlayerConfig.Builder("http://url.com/manifest.mpd").get();
// Apply the LowLatencyProfile to that config
cfg = new LowLatencyProfile.Builder().get().applyTo(cfg);
// Start playback
playerController.open(cfg);
Staying inside the Live Window¶
Live or streaming services usually make only a part of their Periods accessible at any specific point in time. The part of media which is accessible is defined by a segment list or by the buffer depth field. This is what is usually referred to as Live Window.
For this reason, if the player can’t keep up with the “speed” at which the Live Window moves ahead,
it’s possible that playback stops after the player raises a fatal BehindLiveWindowException
.
Causes for this usually are network congestion at any point of the delivery chain.
To minimize the chances of a fatal exception being thrown for this cause, it is recommended not to start playback close to the beginning of the Live Window.
If you are in control of the media generation or packaging, we encourage a Live Window no shorter than 1 minute for most use cases. Note that increasing the buffer depth does not have an effect on the minimum achievable latency, and it simply provides extra resilience.
In the following sections you can find some suggested configurations which will help to avoid falling behind the Live Window.
Live Configuration¶
The LiveConfiguration
allows the SDK user to set the starting
latency
of the playback. This latency is the
amount of time between the desired starting position and the end of the Live Window.
For instance, in a live stream with a buffer depth of 30 seconds, setting this parameter to 10 seconds would mean there are 20 seconds between the start of the live window and the initial playback position.
openBundle.putParcelable(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
.liveEdgeLatencyMs(10_000)
.get());
The LiveConfiguration
offers also some format-specific settings which may better suit specific
use cases.
Catchup configuration¶
The CatchupConfiguration
is part of the previously mentioned LiveConfiguration
. In
this context, such configuration may be useful to avoid falling behind the Live Window start.
This is one possible CatchupConfiguration which will automatically seek to a position 30 seconds before the media end (live edge) if the playback position gets further than 50 seconds from it. Assuming a Live Window duration of 1 minute, this would be 10 seconds away from the Window start.
openBundle.putParcelable(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
.liveEdgeLatencyMs(30_000)
.catchupConfiguration(new CatchupConfiguration.Builder(CatchupConfiguration.TimeReference.MEDIA_END)
.seek(50_000, 30_000)
.get())
.get());
The CatchupConfiguration
has some other settings and modes. Consult the class reference
for more information.
Fast playback or Trickplay¶
The PRESTOplay SDK for Android allows to play content at speeds other than 1, or normal forward playback. This is what we call “Trickplay” mode.
For the basic use case, the PlayerController.setSpeed(float)
method. It will take care
of setting the playback speed and using the more advanced API explained below.
Setup and usage¶
In order to perform Trickplay playback, you’ll need to
set a TrickplayConfiguration
and then, whenever you want to use it, call PlayerController.enableTrickplayMode(boolean)
.
You can call both methods during playback and changes should take effect immediately.
It is also possible to inform the TrickplayConfiguration through an Intent
param
when
opening
the PlayerController
, or also in the PlayerConfig
Builder
;
trickplayConfiguration(TrickplayConfiguration)
.
TrickplayConfiguration¶
The TrickplayConfiguration
groups all the Trickplay-related settings. In it you can configure
the playback speed, whether to keep audio enabled, and whether to use Trickplay-specific tracks,
among other settings.
It is specially relevant the TrickplayConfiguration.speedupMode
field. This field dictates
which strategy to use for Trickplay playback.
Decoder mode¶
This is the default, and recommended mode. With it, the player will just “normally” play the content at the indicated speed. If the playback speed is set to a very high value it is very likely that the player hits buffer underruns quite often and/or that the decoder cannot keep up and thus frames are be dropped.
This mode cannot be used for backwards playback.
Seek mode¶
This mode will internally pause the playback and continuously perform seek operations.
This mode is preferable for very high playback rates, as only a few segments must be downloaded, also with seeking mode is possible to perform backwards playback.
With Seek mode playback will continuously stutter, so hiding the buffering indicator on your app UI is encouraged.
Custom SSL key store¶
The PRESTOplay SDK for Android allows to use server’s SSL certificates that are not (yet) trusted by the device, for example
when the device is missing the CA certificate and therefore can not validate the server’s certificate.
The application shall create the custom SSL key store and set it to the PlayerSDK.SSL_KEY_STORE
, which
is used then to validate certificates ONLY in the case the system default key store fails to verify them. Thus,
the custom key store is NOT a replacement of the system key store!
The application shall also be responsible of maintaining the custom SSL key store and
keep it up-to-date. Create and set the custom key store in the following way:
KeyStore keyStore = null;
try {
// As an example we load the CA intermediate certificate from the assets into the key store
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
InputStream caInput = getApplicationContext().getAssets().open("DigiCertSHA2HighAssuranceServerCA.crt");
Certificate certificate;
try {
certificate = certificateFactory.generateCertificate(caInput);
System.out.println("certificate = " + ((X509Certificate) certificate).getSubjectDN());
} finally {
caInput.close();
}
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", certificate);
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException exception) {
Log.e(TAG, "Error setting custom SSL key store: " + exception.getMessage());
}
PlayerSDK.SSL_KEY_STORE = keyStore;
...
// Register other plugins
...
PlayerSDK.init(getApplicationContext());
Note: There is a limitation of using the custom SSL key store on Marshmallow and older devices, which results in performance degradation upon opening a network connection.
DASH adaptations grouping¶
When DASH manifest is being parsed, each video adaptation is translated into a separate VideoTrack
with
number of qualities or representations. ABR algorithm is then able to select any of the quality based on the current bitrate.
It is possible to retrieve the list of available tracks getVideoTracks
and
do the selection setVideoTrack
. PRESTOplay SDK for Android has two options to merge or group
adaptations set:
The first one is based on DASH supplemental property with id “urn:mpeg:dash:adaptation-set-switching:2016” signalled in the manifest and allowing all adaptations having the property to be grouped together. Whenever an adaptation has this property it will be grouped with other marked adaptations. This grouping can not be disabled by the application.
Another way of grouping is to provide custom implementation of
TrackTypeProvider
. This grouping method can be disabled either globally withMERGE_VIDEO_TRACKS
or per playback withINTENT_MERGE_VIDEO_TRACKS
(mergeVideoTracks
).PRESTOplay SDK for Android has its default implementation
DefaultTrackTypeProvider
which groups video adaptations based on a set of the following types: SD, HD and UHD. It finds out all possible combinations and creates video tracks each having all defined types with no type overlapping. Consider the examples:
¶ In adaptations
Out tracks
SD : 180, 360
HD : 720, 1080
UHD : 2160
SD-HD-UHD : 180, 360, 720, 1080, 2160
¶ In adaptations
Out tracks
SD-HD : 180, 360, 720
HD-UHD : 1080, 2160
SD-HD : 180, 360, 720
HD-UHD : 1080, 2160
Live Edge Catchup¶
The PRESTOplay SDK for Android has the option to perform automatic Catchup whenever the player position falls behind a configurable threshold. This mechanism allows the player maintain a set distance to either the end of the media or the end of the buffered content. It is useful to closely follow the live edge and/or to avoid falling behind the live window.
It is possible to change the CatchupConfiguration
at any time, even during playback. This may result
handy in case a user wants to seek back in the live window and playback should continue there without
being altered.
// Disable the CatchupConfiguration
LiveConfiguration currentLiveCfg = playerController.getLiveConfiguration();
playerController.setLiveConfiguration(new LiveConfiguration.Builder(currentLiveCfg)
.catchupConfiguration(null)
.get());
Live Edge Catchup Types¶
The Live Edge Catchup feature presents two strategies:
Speed
. The player will speed up its playing rate once a configurable threshold is reached, and return to normal speed once a lower bound is reached.Seek
. The player will perform a seek operation once the configurable upper threshold is reached.
Depending on the nature of the playing media one method may be preferred over the other one. If being as close to the live edge as possible is the most important in your use case, Seek mode may be preferred. For instance for a live sports broadcast. If, on the other hand, it’s crucial that the least amount of content is “lost” in the stream, Speed mode may be the best option. An example may be a live political debate.
Time reference¶
Additionally, the Live Edge Catchup behaviour can be configured to act relative to two time references:
Media end
. The specified thresholds in the CatchupConfiguration will be relative to the media end. This mode allows for precise control over how much behind the “live edge” the playback position lays.Buffer ahead
. The specified thresholds in the CatchupConfiguration will look at the amount of buffer remaining. A good use case of this mode would be with low or ultra-low latency streams. By using the buffer health, the player won’t try to catch up if there’s not enough media to play.
Thresholds behaviour¶
The CatchupConfiguration
defines a set of configurable thresholds. The thresholds are
always relative to either the media end
,
or the buffer ahead
end position. These specify exactly how the catchup mechanism works:
Upper threshold
. Defines when the catchup mechanism is triggered or started.Lower threshold
. Defines when the catchup speeds down to normal speed, or where the player seeks to in case the Seek type is configured.Fallback threshold
. Only applicable if the Catchup is configured with the Speed type. If this threshold is exceeded, the player will seek to it.
Configuration¶
CatchupConfiguration
is part of the LiveConfiguration
object, and can be
set before calling PlayerController.open(...)
. Here are two example configurations.
The following configuration example uses the Speed catchup type, with BUFFER_AHEAD
as time reference.
It will start speeding up as soon as the buffer is 3 seconds long, at a speed of 10% faster than normal.
And will speed down back to normal speed once there is only 1 second of content in the buffer left.
This is an example of an aggressive configuration with a ultra-low latency use case in mind.
bundle.putParcelable(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
//.liveEdgeLatencyMs(1_000)
//...
.catchupConfiguration(new CatchupConfiguration.Builder(CatchupConfiguration.TimeReference.BUFFER_AHEAD)
.speed(1_000, 3_000, 20_000, 1.1f)
.get())
.get());
// ...
playerController.open(bundle);
The next example uses the Seek catchup type, and also the MEDIA_END
time reference. As soon as the
playhead falls behind 50 seconds from the media end, it will seek to 10 seconds behind the media end.
This will prevent the media from falling behind the live window (assuming a live window of 1 minute)
and at the same time recovering to a point near the live edge through a seek operation. Note that
it’s very likely that the seek operation triggered by this configuration will trigger a Buffering event,
since there probably won’t be Buffered data at the seek location.
playbackBundle.putParcelable(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
//.liveEdgeLatencyMs(10_000)
//...
.catchupConfiguration(new CatchupConfiguration.Builder(CatchupConfiguration.TimeReference.MEDIA_END)
.seek(10_000, 50_000)
.get())
.get());
// ...
playerController.open(bundle);
Callback¶
The application can optionally register a CatchupListener
in the PlayerController
. This callback
will provide hook points whenever any relevant catchup operation takes place.
final CatchupListener catchupListener = new CatchupListener() {
@Override
public void onCatchupStarted(float speed) {
Log.i(TAG, "Catchup Speed mode started with speed: " + speed);
}
@Override
public void onCatchupEnded() {
Log.i(TAG, "Catchup Speed mode ended");
}
@Override
public void onCatchupSeek(CatchupConfiguration.Type type) {
if (type == CatchupConfiguration.Type.SPEED) {
Log.i(TAG, "Catchup seek due to fallback");
} else if (type == CatchupConfiguration.Type.SEEK) {
Log.i(TAG, "Catchup seek due to SEEK type");
}
}
};
playerController.addCatchupListener(catchupListener);
// ...
playerController.open(...);
Connected HDMI display¶
The PRESTOplay SDK for Android can handle HDMI status change on devices like STBs where HDMI display is the only one and also on a regular Android devices with connected secondary HDMI displays. All the devices are required to run Android API 21 or higher.
HDMI cable connection¶
During the playback, PlayerController is listening to HDMI connection change (ACTION_HDMI_AUDIO_PLUG event) and pauses the playback when HDMI is disconnected. The playback is able to continue afterwards when un-paused. Some devices also send ACTION_HDMI_AUDIO_PLUG event when HDMI display is turned on and off and the SDK will handle it in the same way. This logics can be disabled by unsetting PlayerSDK.PAUSE_ON_HDMI_DISCONNECTED option.
DRM license can additionally specify digital output protection by requiring a particular HDCP level. Trying to play a content with required HDCP level exceeding the level supported by device or display results in fatal CastLabsPlayerException with TYPE_VIDEO_DECRYPTION_ERROR.
Disconnecting HDMI cable changes HDCP level as well, but the change is detected by the SDK which saves the current playback and releases PlayerController with CastLabsPlayerException, TYPE_HDCP_CONNECTION_WARNING. When HDMI is connected back, the playback will be restored by the SDK automatically. This logics can be disabled by unsetting PlayerSDK.FORCE_HDCP_EXCEPTION option.
HDMI display on and off¶
There is no way for the SDK to know HDMI display state in order to handle it appropriately. It is recommended that HDMI CEC is enabled where possible to handle application’s playback activity lifecycle which in its turn handles the playback lifecycle. If there is no way to rely on HDMI CEC then the application is recommended to close the playback upon receiving CastlabsPlayerException, TYPE_HDCP_CONNECTION_WARNING.
HDCP exception timeout¶
Starting the playback and installing DRM keys may take less time than setting HDCP connection and level. To handle this case, PlayerSDK.HDCP_EXCEPTION_TIMEOUT_MS is defined, making the playback to wait for HDCP completing its setup. If HDCP level is neither ready nor sufficient after the timeout then fatal CastLabsPlayerException is raised with TYPE_VIDEO_DECRYPTION_ERROR.
Decoder selection and fallback¶
The PlayerConfig
permits to control to the selection of the video and audio decoder and
allows to configure an explicit decoder as well as control over the default selection process.
By default, the SDK’s decoder fallback mechanism is enabled. This can be controlled through
Builder.videoDecoderFallback(boolean)
for video and
Builder.audioDecoderFallback(boolean)
for audio. In addition,
Builder.unsecureDecoderFallback(boolean)
can also be used to further control
if “unsecure” decoders can be used as a fallback for video.
The selection process in the SDK prefers hardware over software decoders and secure over unsecure decoders for protected content. However, depending on the device, multiple decoder instances can sometimes not be instantiated. In order to still provide a playback capability, the SDK will fallback to alternative decoders by default.
For protected secure video playback, secure decoders are generally preferred by the selection, however, the same limitation as above might apply and if a secure decoder is required is ultimately based on the DRM license requirements. If the license does not require a secure video decoder, which is only required for the highest security level (EME level 5), a non-secure decoder can be used as a fallback for video. This helps especially in multi-player environments where multiple decoders are required.
An explicit decoder can be selected by name using Builder.videoCodec(java.lang.String)
for video decoders and Builder.audioCodec(java.lang.String)
for audio decoders.
The name of the decoder passed here is device-specific. Setting an explicit codec will disable any
automated fallback implementations.
HLS Clearkey caching¶
The PRESTOplay SDK for Android will cache Clearkey requests for HLS streams. This can be useful to minimize the number of outgoing key requests and provide slightly improved startup and seek times - if seeking to a Period using a different key.
The player will create and populate a cache instance by default. Such can be accessed through the
PlayerController.getHlsKeyCache()
method. Note that the returned cache will be null
before playback start.
A cache instance can be provided, before the open(...)
call, through
PlayerController.setHlsKeyCache(com.castlabs.android.player.HlsClearKeyCache)
.
The PRESTOplay SDK for Android provides a default implementation of the HlsClearKeyCache
interface, which will
be automatically created in case no cache instance is set before open(...)
. MemoryHlsClearKeyCache
,
as it name suggests will keep keys in memory. This default cache implementation will hold up to a
maximum of MemoryHlsClearKeyCache.DEFAULT_CACHE_SIZE
keys. Note that such is a static public
field and can be set to any other desired value.
It is also possible to provide a custom implementation of the HlsClearKeyCache
interface.
This way the storing and recovering of the keys is delegated to the user of the PRESTOplay SDK for Android.
Video quality blocklist upon frame drops (experimental)¶
When not in a tunneling mode, the player controls decoded video frames deciding when to render them and ensuring audio-video synchronization. It is possible that a device is capable of decoding high fps, bitrate and resolution video streams but has not enough power to render decoded frames in a real time. This could be either a permanent or temporary performance dip, for example, when some other background application is active. The PRESTOplay SDK for Android drops single video frames when they are late for more than 30ms and a series of frames up to the next key frame when late for more than 500ms.
In order to improve the UX, the PRESTOplay SDK for Android can be configured to measure the frame drops and disable (blocklist) the corresponding video quality either permanently or temporary. Video quality disabling is only possible when in ABR mode. To enable and configure the behaviour:
PlayerSDK.DROPPED_VIDEO_FRAME_DISABLE_DURATION_MS = 60_000L;
PlayerSDK.DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
PlayerSDK.DROPPED_VIDEO_FRAME_THRESHOLD_COUNT = 200;
PlayerSDK.DROPPED_VIDEO_FRAME_THRESHOLD_DURATION_MS = 10_000L;
According to the above configuration, when in ABR mode, the player will disable the currently played video quality for 60sec, when more than 200 video frames were dropped within the last 10sec of playback. The frame drops are analysed per bouquet having at least 50 frames in. The video buffer is also truncated not to contain downloaded segments of disabled quality. Note that if the segment is currently used by the decoder, then it will not be truncated and played out to its end.
Video selection reason SdkConsts.SELECTION_REASON_FRAME_DROP_EXCEED
is reported then to TrackSelectionListener
implementations. It is also possible that there is only
one available video quality left. In this case the quality is not disabled but the warning is created for the application to decide whether to stop the playback or not:
[WARNING] [TYPE_VIDEO_BLOCKLIST_FAILED] Video blocklist error: No more tracks to play Bundle[{code=41, severity=WARNING}]
SDK and PlayerController initialization¶
This chapter provides technical details for the PRESTOplay SDK for Android and PlayerController
initialization,
outlining how it handles unresponsive Widevine CDM instances and offering recommendations for
achieving uninterrupted playback using alternative DRM systems.
Initialization Behavior¶
The PRESTOplay SDK for Android enables uninterrupted initialization, even if the Widevine Content Decryption Module
(CDM) becomes unresponsive. When the first PlayerController
is opened after an SDK
initialization with a drmConfiguration, it will wait for the DRM capabilities information to be
retrieved for up to 10 seconds. During this time, the player will be in the “Preparing” state
without blocking the main thread.
It is important to note that this timeout is measured from the SDK initialization call until the first instance of opening a controller after such initialization. The results of this operation will be cached, so subsequent playback sessions will not need to wait for this process.
The initialization process is typically fast and does not block other operations, such as calling PlayerController.open. However, calling these operations in quick succession might lead to a longer than expected startup time for the first playback.
DRM capabilities detection¶
In order to determine which DRM systems are available, the PRESTOplay SDK for Android performs a system check. It has been observed that under unknown circumstances, CDMs sometimes become unresponsive.
Once the DRM capabilities information is successfully retrieved, different outcomes can occur depending on the usage of the SDK and the configuration:
Widevine is present: Playback proceeds as expected using the Widevine DRM system.
Widevine is not present: This can indicate that Widevine is either not supported or the CDM service is unresponsive.
Manual Force of
Widevine
inDrmConfiguration
: If Widevine has been manually forced in theDrmConfiguration
, a fatal error will be thrown upon calling controller.open since the Widevine DRM system is unavailable.BestAvailable
DRM inDrmConfiguration
: IfBestAvailable
has been specified in theDrmConfiguration
, and another DRM system is available on the device, playback will proceed using that alternative system.
Recommendations¶
To handle potential Widevine CDM unresponsiveness, we recommend the following actions:
Register our OMA plugin when initializing the SDK.
Specify
BestAvailable
as the DRM system in theDrmConfiguration
.
By adopting these measures, if the Widevine CDM is indeed unresponsive, playback can still be achieved using an alternative DRM system.
Please note that since OMA is a software DRM implementation, it may have similar licensing restrictions as Widevine L3. These restrictions could impact the available renditions, such as being limited to 720p or similar resolutions. The specific limitations ultimately depend on the licensing policy configuration set in the DRM backend.
Workarounds¶
The SDK provides workarounds for atypical/non-standard behavior by Codecs and Devices or for selective low-level use-case based improvement of behavior for customer devices. Some of the most recent ones used by customers are mentioned below. There are others to which references could be found in the Javadoc for Exoplayer or information can be obtained via the Android Support Portal.
#. MediaCodecRenderer#INPUT_BUFFER_INITIALIZATION_MODE
Default: BUFFER_REPLACEMENT_DISABLED
Decoder Input Buffers would be non-replaceable and an exception will be thrown if a buffer greater
that Buffer Capacity is requested. This can be set to BUFFER_REPLACEMENT_NORMAL
or
BUFFER_REPLACEMENT_DIRECT
for devices which are known to request more data than the
default capacity allocated or under-allocate Decoder Buffer sizes.
#. MediaCodecRenderer#DEVICE_NEEDS_SKIP_RESET_ON_DISABLED
Default: false
If set to true, we avoid codec release when transitioning from one stream to the next in
the playlist use-case.
#. MediaCodecVideoRenderer#CODEC_NEEDS_REINIT_ON_DRM_SESSION_CHANGE
Default: null
If codec value as string is set to variable then codec re-initialization is avoided
when DRM Session is changed.
#. MediaCodecVideoRenderer#FORCE_JOINING_ON_INIT_FOR_DEVICES_THAT_DONT_ACCURATELY_REPORT_BUFFER_FLAGS
Default: false
For devices that dont report, first frame rendered data accurately.