Advanced Setup
==============

.. javaimport::
    com.castlabs.*
    com.castlabs.android.*
    com.castlabs.android.drm.*
    com.castlabs.android.player.*
    com.castlabs.android.player.models.*
    android.content.*
    android.view.*

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.

.. code-block:: java

    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 :javaref:`PlayerConfig`, :javaref:`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 :javaref:`BufferConfiguration`.

Low latency
+++++++++++

Our |SDK| 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. Packaging, CDN behavior, device performance, and network conditions still define
the practical latency range, so the |SDK| exposes several configuration points to tune
startup, rebuffering, and live-edge behavior.

For manifest and packaging requirements, refer to the relevant DASH and HLS low-latency
specifications. For deployment-specific recommendations, contact castLabs Professional
Services or integration support.

The simplest starting point is :javaref:`LowLatencyProfile`. It receives an already built
:javaref:`PlayerConfig` and applies low-latency-friendly values to the live, buffer, and
network retry configuration. You can also tune the ``LowLatencyProfile`` before applying it
through its :javaref:`Builder <LowLatencyProfile.Builder>`.

This snippet shows how to apply the profile:

.. code-block:: java

    // 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);

The most relevant configuration areas are:

``LowLatencyProfile``
    A convenience profile for live startup near the live edge. The profile sets a default
    UTC timing source, starts at the live edge, disables snapping to segment boundaries,
    lowers the minimum data required to start or resume playback, and applies a retry
    configuration suitable for short live segments.

``LiveConfiguration``
    Controls live-edge positioning and live manifest behavior. The most relevant fields are
    :javaref:`liveEdgeLatencyMs <LiveConfiguration#liveEdgeLatencyMs>`,
    :javaref:`customUtcTimingElement <LiveConfiguration#customUtcTimingElement>`,
    :javaref:`snapToSegmentStart <LiveConfiguration#snapToSegmentStart>`,
    :javaref:`minManifestUpdatePeriodMs <LiveConfiguration#minManifestUpdatePeriodMs>`,
    :javaref:`notifyManifestIntervalMs <LiveConfiguration#notifyManifestIntervalMs>`,
    :javaref:`availabilityStartTimeOffsetOverwriteMs <LiveConfiguration#availabilityStartTimeOffsetOverwriteMs>`,
    and :javaref:`timesyncSafetyMs <LiveConfiguration#timesyncSafetyMs>`. For HLS live streams,
    :javaref:`hlsLiveTailSegmentIndex <LiveConfiguration#hlsLiveTailSegmentIndex>` and
    :javaref:`hlsPlaylistUpdateTargetDurationCoefficient <LiveConfiguration#hlsPlaylistUpdateTargetDurationCoefficient>`
    can also influence the start position and playlist refresh cadence.

``BufferConfiguration``
    Controls how much data is required before playback starts or resumes, and how much data
    the player attempts to keep buffered. For low latency and fast tune-in, the most relevant
    fields are :javaref:`minPlaybackStartMs <BufferConfiguration#minPlaybackStartMs>`,
    :javaref:`minRebufferStartMs <BufferConfiguration#minRebufferStartMs>`,
    :javaref:`lowMediaTimeMs <BufferConfiguration#lowMediaTimeMs>`,
    :javaref:`highMediaTimeMs <BufferConfiguration#highMediaTimeMs>`, and
    :javaref:`bufferSizeBytes <BufferConfiguration#bufferSizeBytes>`.

``AbrConfiguration``
    Controls initial and adaptive quality selection. The initial quality can affect tune-in
    time before the bandwidth estimator has enough samples. Relevant fields include
    :javaref:`maxInitialBitrate <AbrConfiguration#maxInitialBitrate>`,
    :javaref:`initialTrackSelection <AbrConfiguration#initialTrackSelection>`,
    :javaref:`minDurationForQualityIncreaseUs <AbrConfiguration#minDurationForQualityIncreaseUs>`,
    :javaref:`maxDurationForQualityDecreaseUs <AbrConfiguration#maxDurationForQualityDecreaseUs>`,
    :javaref:`safeBufferSizeUs <AbrConfiguration#safeBufferSizeUs>`, and
    :javaref:`bandwidthFraction <AbrConfiguration#bandwidthFraction>`.

``CatchupConfiguration``
    Helps recover when playback drifts behind the desired live position. It can either seek
    or temporarily adjust playback speed. The time reference can be based on the
    :javaref:`media end <com.castlabs.android.player.CatchupConfiguration.TimeReference#MEDIA_END>`
    or :javaref:`buffer ahead <com.castlabs.android.player.CatchupConfiguration.TimeReference#BUFFER_AHEAD>`,
    which is useful for streams where the app wants to stay close to the live edge while preserving
    enough data for smooth playback.

Fast zapping between live streams
+++++++++++++++++++++++++++++++++

Fast zapping is the process of reducing the time between selecting a new live stream and
displaying the first frames. This is tuned with the same configuration areas used for low-latency
playback, but the target is different: favoring the lowest possible latency can conflict with the
fastest possible tune-in time.

* Use :javaref:`BufferConfiguration` to lower the amount of data required before playback starts
  or resumes after a user action.
* Use :javaref:`AbrConfiguration` to influence the initial quality selection when joining a new
  stream, before enough bandwidth samples are available.
* Use :javaref:`LiveConfiguration` to control the live start position and segment-boundary
  behavior. For ultra-low latency streams, starting in the middle of a segment can reduce latency;
  for fast zapping, starting on a segment boundary can be better for tune-in time.
* Use :javaref:`CatchupConfiguration` to recover latency after startup or after a temporary
  network slowdown.

These options are intentionally independent. The useful values depend on the stream segment
duration, CDN behavior, DRM setup, device class, target latency, and acceptable rebuffering risk.

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 :javaref:`LiveConfiguration` allows the SDK user to set the starting
:javaref:`latency <LiveConfiguration#liveEdgeLatencyMs>` 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.

.. code-block:: java

    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 :javaref:`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.

.. code-block:: java

    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 :javaref:`CatchupConfiguration` has some other settings and modes. Consult the class reference
for more information.

Fast playback or Trickplay
--------------------------

The |SDK| 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 :javaref:`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
:javaref:`set a TrickplayConfiguration <PlayerController#setTrickplayConfiguration(TrickplayConfiguration)>`
and then, whenever you want to use it, call :javaref:`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`` :javaref:`param <SdkConsts#INTENT_TRICKPLAY_CONFIGURATION>` when
:javaref:`opening <PlayerController#open(Bundle)>` the ``PlayerController``, or also in the ``PlayerConfig`` ``Builder``;
:javaref:`trickplayConfiguration(TrickplayConfiguration) <PlayerConfig.Builder#trickplayConfiguration(TrickplayConfiguration)>`.

TrickplayConfiguration
++++++++++++++++++++++

The :javaref:`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 :javaref:`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 |SDK| 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 :javaref:`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:

.. code-block:: java

        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 :javaref:`VideoTrack <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 :javaref:`getVideoTracks <PlayerController#getVideoTracks()>` and
do the selection :javaref:`setVideoTrack <PlayerController#setVideoTrack(VideoTrack)>`. |SDK| 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 :javaref:`TrackTypeProvider <TrackTypeProvider>`.
    This grouping method can be disabled either globally with :javaref:`MERGE_VIDEO_TRACKS <PlayerSDK#MERGE_VIDEO_TRACKS>` or per playback with
    :javaref:`INTENT_MERGE_VIDEO_TRACKS <SdkConsts#INTENT_MERGE_VIDEO_TRACKS>` (:javaref:`mergeVideoTracks <PlayerConfig#mergeVideoTracks>`).

    |SDK| has its default implementation :javaref:`DefaultTrackTypeProvider <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:

    .. list-table:: Simple grouping 3 to 1
       :widths: 30 30
       :header-rows: 1

       * - In adaptations
         - Out tracks
       * -
            * SD  : 180, 360
            * HD  : 720, 1080
            * UHD : 2160
         -
           * SD-HD-UHD : 180, 360, 720, 1080, 2160

    .. list-table:: No grouping due to type overlap
       :widths: 30 30
       :header-rows: 1

       * - 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 |SDK| 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 :javaref:`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.

.. code-block:: java

    // 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:

* :javaref:`Speed <com.castlabs.android.player.CatchupConfiguration.Type#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.

* :javaref:`Seek <com.castlabs.android.player.CatchupConfiguration.Type#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:

* :javaref:`Media end <com.castlabs.android.player.CatchupConfiguration.TimeReference#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.

* :javaref:`Buffer ahead <com.castlabs.android.player.CatchupConfiguration.TimeReference#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 :javaref:`CatchupConfiguration` defines a set of configurable thresholds. The thresholds are
always relative to either the :javaref:`media end <com.castlabs.android.player.CatchupConfiguration.TimeReference#MEDIA_END>`,
or the :javaref:`buffer ahead <com.castlabs.android.player.CatchupConfiguration.TimeReference#BUFFER_AHEAD>`
end position. These specify exactly how the catchup mechanism works:

* :javaref:`Upper threshold <com.castlabs.android.player.CatchupConfiguration#upperTimeThresholdMs>`.
  Defines when the catchup mechanism is triggered or started.
* :javaref:`Lower threshold <com.castlabs.android.player.CatchupConfiguration#lowerTimeThresholdMs>`.
  Defines when the catchup speeds down to normal speed, or where the player seeks to in case the
  Seek type is configured.
* :javaref:`Fallback threshold <com.castlabs.android.player.CatchupConfiguration#fallbackTimeThresholdMs>`.
  Only applicable if the Catchup is configured with the Speed type. If this threshold is exceeded,
  the player will seek to it.

Configuration
+++++++++++++

:javaref:`CatchupConfiguration` is part of the :javaref:`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.

.. code-block:: java

    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.

.. code-block:: java

    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 :javaref:`CatchupListener` in the ``PlayerController``. This callback
will provide hook points whenever any relevant catchup operation takes place.

.. code-block:: java

    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 |SDK| 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 :javaref:`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
:javaref:`PlayerConfig.Builder#videoDecoderFallback(boolean)` for video and
:javaref:`PlayerConfig.Builder#audioDecoderFallback(boolean)` for audio. In addition,
:javaref:`PlayerConfig.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 :javaref:`PlayerConfig.Builder#videoCodec(String)`
for video decoders and :javaref:`PlayerConfig.Builder#audioCodec(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 |SDK| 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
:javaref:`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
:javaref:`PlayerController#setHlsKeyCache(HlsClearKeyCache)`.

The |SDK| provides a default implementation of the :javaref:`HlsClearKeyCache` interface, which will
be automatically created in case no cache instance is set before ``open(...)``. :javaref:`MemoryHlsClearKeyCache`,
as it name suggests will keep keys in memory. This default cache implementation will hold up to a
maximum of :javaref:`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 :javaref:`HlsClearKeyCache` interface.
This way the storing and recovering of the keys is delegated to the user of the |SDK|.

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 |SDK| 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 |SDK| 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:

.. code-block:: java

    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:

.. code-block:: java

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

Common Media Client Data(CMCD)
++++++++++++++++++++++++++++++

The |SDK| clients can transmit valuable information to Content Delivery Networks (CDNs) with
each object request. Transmitting that data can improve QoS monitoring, adaptive traffic optimization,
and delivery performance, ultimately enhancing the consumer experience. Specification - CTA-5004

CMCD Support can only be enabled for adaptive streaming formats, DASH, HLS and SmoothStreaming.

CMCD Data Keys :
  1. CMCD-Request: keys whose values vary with each request.
  2. CMCD-Object: keys whose values vary with the object being requested.
  3. CMCD-Status: keys whose values don't vary with every request or object.
  4. CMCD-Session: keys whose values are expected to be invariant over the life of the session.

CMCD Data is transmitted as HTTP Request headers.

To Enable CMCD, an instance of :javaref:`CmcdConfigurationFactory` needs to be created and passed
to the :javaref:`PlayerController#setCmcdConfigurationFactory(CmcdConfigurationFactory)` method
which is used after building the player. You can either use the default ``CmcdConfigurationFactory``
or provide your own custom factory which is called each time an adaptive media source is created
for the given ``PlayerConfig``.

.. code-block:: java

    playerController.setCmcdConfigurationFactory(CmcdConfigurationFactory.DEFAULT);
    playerController.open(playerConfig);

.. code-block:: java

    CmcdConfigurationFactory cmcdConfigurationFactory = playerConfig -> {
        CmcdConfiguration.RequestConfig cmcdRequestConfig = new CmcdConfiguration.RequestConfig() {
            @Override
            public boolean isKeyAllowed(String key) {
                return key.equals("br") || key.equals("bl");
            }

            @Override
            public ImmutableMap<@CmcdConfiguration.HeaderKey String, String> getCustomData() {
                return ImmutableMap.of( CmcdConfiguration.KEY_CMCD_OBJECT, "key1=stringValue" );
            }

            @Override
            public int getRequestedMaximumThroughputKbps(int throughputKbps) {
                return 5 * throughputKbps;
            }
        };

        String sessionId = UUID.randomUUID().toString();
        String contentId = UUID.randomUUID().toString();
        return new CmcdConfiguration(sessionId, contentId, cmcdRequestConfig);
    };

    // Set your custom cmcdConfigurationFactory.
    playerView.getPlayerController().setCmcdConfigurationFactory(cmcdConfigurationFactory);

Recommendations
+++++++++++++++

To handle potential Widevine CDM unresponsiveness, we recommend specifying ``BestAvailable`` as
the DRM system in the ``DrmConfiguration``.

By adopting this measures, if the Widevine CDM is indeed unresponsive, playback can still be
achieved using an alternative DRM system, if available in the system.

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.
