Fallback HDCP with the Native SDKs

In this topic, you will learn about using HDCP-protected content with the Brightcove Native SDKs to block streaming on unauthorized devices, and provide a standard definition fallback for devices that do not support HDCP.

Introduction

High-bandwidth Digital Content Protection (HDCP) is a form of digital copy protection used to protect high definition (HD) video and audio signals from being copied on unauthorized devices. The transmitting device first checks if the receiver is authorized to receive data. If yes, then the transmitter sends encrypted data to prevent eavesdropping.

The receiving setup must be HDCP compliant, including devices, cables, adaptors, and software drivers. If the receiver is not HDCP compliant, then video will play in standard definition (SD) only. In general, newer HDTVs and HDMI or DVI cables should be HDCP compliant.

For details, see the Fallback HDCP document.

Requirements

The following requirements are needed to support this feature:

Brightcove SDK version

  • Native SDK for Android 6.17.1 and newer
  • Native SDK for iOS 6.10.1 and newer

Android implementation

When playing a video with a mix of SD and HD renditions, you will want to do the following:

  • On devices that can support both HD and SD playback, you want to ensure that the player can switch to an appropriate rendition when needed, for example when network bandwidth changes. This rendition switch may require the player to select an SD rendition, where before it was playing HD.

  • On devices without HDCP support, for example older Android mobile devices with older OS levels, you want to ensure appropriate rendition switching within SD renditions, as well as guarding that the player will not attempt to load an HDCP-protected rendition, at which point the license request will fail, with an error message like this:

    Error message

    2021-11-01 19:01:36.943 30131-30131/com.brightcove.player.samples.exoplayer.basic E/VideoDisplayComponent: onPlayerError
      com.google.android.exoplayer2.ExoPlaybackException: MediaCodecVideoRenderer error, index=0, format=Format(bf310894-59b5-4f1b-9a37-e110a3d6121d, null, null, video/avc, avc1.4D401F, 1712000, null, [1280, 720, 30.0], [-1, -1]), format_supported=YES
          at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:555)
          at android.os.Handler.dispatchMessage(Handler.java:98)
          at android.os.Looper.loop(Looper.java:148)
          at android.os.HandlerThread.run(HandlerThread.java:61)
        Caused by: android.media.MediaCodec$CryptoException: Unknown Error
          at android.media.MediaCodec.native_queueSecureInputBuffer(Native Method)
          at android.media.MediaCodec.queueSecureInputBuffer(MediaCodec.java:2292)
          at com.google.android.exoplayer2.mediacodec.SynchronousMediaCodecAdapter.queueSecureInputBuffer(SynchronousMediaCodecAdapter.java:143)
          at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:1380)
          at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:845)
          at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:945)
          at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:478)
          at android.os.Handler.dispatchMessage(Handler.java:98) 
          at android.os.Looper.loop(Looper.java:148) 
          at android.os.HandlerThread.run(HandlerThread.java:61) 
    

A solution to this problem involves adding code to allow rendition switching across AdaptationSets in DASH, and to filter out renditions that are not expected to play on the current device.

In the following example, HD renditions are being protected by stricter HDCP requirements which the current device doesn't support. To achieve this, you can use an EventListener on the SET_SOURCE event, as you need to add these supports before creating the ExoPlayer instance.

An example like this would be added to your player’s Activity class, in the onCreate method:

eventEmitter.on(EventType.SET_SOURCE, event -> {
  try {
      // Get an instance of the MediaDrm from the device
      MediaDrm mediaDrm = new MediaDrm(Constants.WIDEVINE_UUID);

      // Create a new DefaultTrackSelector.ParamsBuilder object
      DefaultTrackSelector.ParametersBuilder builder = new DefaultTrackSelector.ParametersBuilder(this);

  // Call this method to enable rendition switching across DASH AdaptationSets
      builder.setAllowMultipleAdaptiveSelections(true);

      // Get the values for hdcpLevel and maxHdcpLevel
      String connectedHdcpLevel = mediaDrm.getPropertyString("hdcpLevel");
      String maxHdcpLevel = mediaDrm.getPropertyString("maxHdcpLevel");
      Log.v(TAG, "HDCP Level: " + connectedHdcpLevel + " Max HDCP Level: " + maxHdcpLevel);

      // If either level is null or an empty String or reads "Unprotected"
      if ((TextUtils.isEmpty(connectedHdcpLevel) || TextUtils.isEmpty(maxHdcpLevel)) ||
              ("Unprotected".equals(connectedHdcpLevel) || "Unprotected".equals(maxHdcpLevel))) {
          Log.v(TAG, "Restricting rendition selection to SD");

          // Set the max video size to SD
          builder.setMaxVideoSizeSd();
      }

  // Create a new DefaultTrackSelector object, and set the Parameters object created above
      DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(this);
      defaultTrackSelector.setParameters(builder.build());
      // Set this DefaultTrackSelector object on the ExoPlayerVideoDisplayComponent
      videoDisplayComponent.setTrackSelector(defaultTrackSelector);
  }
  catch (Exception exception) {
      if (exception instanceof UnsupportedSchemeException) {
          Log.e(TAG, "UnsupportedSchemeException: " + exception.getLocalizedMessage());
      }
      else {
          Log.e(TAG, "An unexpected error occurred: " + exception.getLocalizedMessage());
      }
  }
});             
    

iOS implementation

Fallback HDCP is supported with the Native SDK for iOS/tvOS, but is only enforced for content protected using FairPlay.

You can detect the non-compliance of the device, by using KVC to detect changes to the value isOutputObscuredDueToInsufficientExternalProtection on the AVPlayer.

AVPlayer.isOutputObscuredDueToInsufficientExternalProtection == true

The above property's value changes to true for the following reasons:

  • The current item requires external protection
  • The device does not meet the protection level
  • The user observes video loss

Since not all users have an HDCP compatable setup, Apple recommends including a variant (rendition) in the video manifest which does not require HDCP protection. Brightcove handles this for you when your account is enabled for Fallback HDCP.