Skip to main content

Adding playback of PRM encrypted content

  1. In Android Studio, add your Operator Vault to your application build project as a project resource.

    Make sure READ_PHONE_STATE permission is declared in the application’s AndroidManifest.xml.

    XML
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    Make sure the SDK is loaded in the application’s initial activity.

    JAVA
    OTVSDK.load(this);
  2. Before initialising the PAK, the READ_PHONE_STATE permission must be requested at runtime when all of the following conditions are true:

    • The application will run on Android P devices.
    • The application targetSdkVersion is 28 (Android P) or higher.
    • The Operator Vault file EnforceSingleDeviceUniqueIDPerDeviceFlag is set to true.

    The following code will, if necessary, show a system dialogue box requesting approval from the user. If permission has not already been granted, the user’s approval comes back asynchronously in the onRequestPermissionsResult() callback (which is an Activity override).

    JAVA
          if (checkReadPhoneStatePermission()) {
            // Permission granted. Can continue.
          } else {
            // Permission not granted yet. Need to wait for user's approval.
          }
    
        public boolean checkReadPhoneStatePermission() {
          boolean hasPermission = (ActivityCompat.checkSelfPermission(this,
          Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED);
          if (!hasPermission) {
            requestReadPhoneStatePermission();
          }
          return hasPermission;
        }
    
        @TargetApi(23)
        public void requestReadPhoneStatePermission() {
          if (shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE)) {
            // Show our own request permission dialog before popping the system one.
            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
            dialogBuilder.setTitle("External permission required for Read Phone State");
            dialogBuilder.setPositiveButton("Ok", (dialog, which) ->
              requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE}, 1))
              .show();
          } else {
            // Show system dialog to request permission
            requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE}, 1);
          }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          // Dialogue results callback
          if (requestCode != 1) {
            // Not coming from our request, tagged as 1
            return;
          }
          if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            // Permission refused/not granted by user
          } else {
            // Permission granted by user
          }
        }
  3. Prepare PRM by instantiating OTVPRMManager and initialising the PAK library with the Operator Vault. Convert the resource into a byte array.

    JAVA
        // Read OpVault resource as byte array
        byte[] opvault = null;
        try (InputStream opvaultStream = getResources().openRawResource(R.raw.opvault)) {
          opvault = new byte[opvaultStream.available()];
          opvaultStream.read(opvault);
        } catch (IOException e) {
          OTVLog.e(TAG, "Failed to read opvault", e);
          return;
        }
    
        // Create an OTVPRMManager and prepare it with context and opvault
        OTVPRMManager prmManager = new OTVPRMManager();
        if (!prmManager.preparePAK(this, opvault)) {
          OTVLog.e(TAG, "Failed to prepare PAK");
          return;
        }

    As PRM preparation is typically a one-off exercise, this can also be done in the initial activity.

  4. The application needs to create a callback class implementing OTVPRMNonSilentCallback. Using the OTVSSPPRMNonSilentCallback, create the callback with the data specific to the licence server. Create a listener; in the example below, a semaphore is implemented for later use in a wait context.

    JAVA
        private Semaphore mPakResponseSemaphore;
        private boolean mIsPrmInitialised = false;
        private OTVPAKReadyListener mPAKReadyListener = new OTVPAKReadyListener() {
          @Override
          public void onReady() {
            OTVLog.d(TAG, "PRM successfully initialized");
            if (mPakResponseSemaphore != null) {
              mPakResponseSemaphore.release();
            }
          }
    
          @Override
          public void onError() {
            OTVLog.w(TAG, "Failed to initialize PRM");
            if (mPakResponseSemaphore != null) {
              mPakResponseSemaphore.release();
            }
          }
        };

    Initialise with server data. For the OTVSSPPRMNonSilentCallback implementation of OTVPRMNonSilentCallback, you also need to provide request properties, which are specific HTTP headers for initialisation and key requests.

    JAVA
        String PRM_SERVER_URL = "https://path/to/licence-server/";
        OTVSSPPRMNonSilentCallback prmCallback = new OTVSSPPRMNonSilentCallback(PRM_SERVER_URL);
        // Setting request properties specific for SSP
        prmCallback.setKeyRequestProperty("Accept", "application/json");
        prmCallback.setKeyRequestProperty("Content-Type", "application/json");
        prmCallback.setKeyRequestProperty("nv-portal-id", "SSP AuthZ");
        // Server private data is a secret between the PRM client and licence server
        String PRM_SERVER_PRIVATE_DATA = "private-data-specific-to-server";
        prmCallback.setInitializationClientProtectedPrivateData(PRM_SERVER_PRIVATE_DATA);
        mPRMManager.initializePrmWithCallbacks(prmCallback, mPAKReadyListener);
        // initializePrmWithCallbacks is asynchronous and the listener will get onReady()
        // when the initialisation is complete. However, if PRM was already initialised, onReady()
        // may not be called. Therefore we check below if initialisation has already taken place.
        mIsPrmInitialised = mPrmManager.isPrmInitialized();

    Once everything is set, wait for the initialisation to complete.

    JAVA
        // At this point, PRM may already be initialised, so no need to wait.
        if (!mIsPrmInitialised) {
          // Wait for onReady() or onError() from manager.
          mPakResponseSemaphore = new Semaphore(0);
          boolean waitResult = false;
          try {
            waitResult = mPakResponseSemaphore.tryAcquire(5, TimeUnit.SECONDS);
          } catch (InterruptedException e) {
            OTVLog.e(TAG, "PAK initialization thread interrupted", e);
            Thread.currentThread().interrupt();
          }
          if (!waitResult) {
            OTVLog.e(TAG, "Timed out waiting for PAK initialization response");
          }
        }
        mIsPrmInitialised = mPrmManager.isPrmInitialized();
        OTVLog.d(TAG, "mIsPrmInitialised = " + mIsPrmInitialised);
  5. To detect when and why PRM key retrieval fails, it is useful to listen to session events. Within the fragment/activity, create an internal class, implementing the OTVPRMSessionEventListener interface, implementing the two event methods.

    JAVA
        private OTVPRMSessionEventListener mPrmSessionEventListener = new OTVPRMSessionEventListener() {
          @Override
          public void onPrmKeysLoadedSuccess() {
            // Do something - Prm key loaded
          }
    
          @Override
          public void onPrmKeysLoadedFail(OTVPRMException error) {
            int erroCode = error.errorCode();
            switch (erroCode) {
              case OTVPRMException.ERROR_FETCH_LICENSE:
                // Do something - ERROR_FETCH_LICENSE
                break;
              case OTVPRMException.ERROR_LICENSE_EXPIRED:
                // Do something - ERROR_LICENSE_EXPIRED
                break;
              case OTVPRMException.ERROR_INVALID_ENTITLEMENT:
                // Do something - ERROR_INVALID_ENTITLEMENT
                break;
              default:
                // Shouldn't get here
                break;
            }
        };

    The above listener should be added to the OTVPRMManager during the initialisation.

    JAVA
     mPRMManager.addPRMSessionEventListener(mPrmSessionEventListener);
  6. Before starting playback, the stream token must be provided to the callback instance.

    JAVA
        // The stream token is information specific for fetching the licence for the stream.
        // Depending on the licence server and stream signalisation,
        // it may also contain the PRM Content ID.
        String STREAM_TOKEN = "private-data-specific-to-stream"
        prmCallback.setLicenseRequestClientProtectedPrivateData(STREAM_TOKEN);
  7. Playback operations can now proceed the same way as with clear data.

    JAVA
        OTVVideoView.setVideoPath();
        OTVVideoView.start();

    In most cases, the PAK can remain active throughout the application’s lifecycle. However, if PAK needs to be released, the following should be called:

    JAVA
        if (mPRMManager != null) {
          mPRMManager.releasePAK(false);
        }

    Setting the releasePAK() parameter to true  will also erase the PAK database, which includes licences for offline playback. Unless the licence server is changed, there is no need to clear the database.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.