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:

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

  2. Another way of grouping is to provide custom implementation of TrackTypeProvider. This grouping method can be disabled either globally with MERGE_VIDEO_TRACKS or per playback with INTENT_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:

    Simple grouping 3 to 1

    In adaptations

    Out tracks

    • SD : 180, 360

    • HD : 720, 1080

    • UHD : 2160

    • SD-HD-UHD : 180, 360, 720, 1080, 2160

    No grouping due to type overlap

    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.

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 in DrmConfiguration: If Widevine has been manually forced in the DrmConfiguration, a fatal error will be thrown upon calling controller.open since the Widevine DRM system is unavailable.

    • BestAvailable DRM in DrmConfiguration: If BestAvailable has been specified in the DrmConfiguration, 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:

  1. Register our OMA plugin when initializing the SDK.

  2. Specify BestAvailable as the DRM system in the DrmConfiguration.

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.

Previous topic: Player Basics
Next topic: The Player Controller