Adding playback of PRM encrypted content
In Android Studio, add your Operator Vault to your application build project as a project resource.
Make sure
READ_PHONE_STATEpermission is declared in the application’sAndroidManifest.xml.XML<uses-permission android:name="android.permission.READ_PHONE_STATE" />Make sure the SDK is loaded in the application’s initial activity.
JAVAOTVSDK.load(this);Before initialising the PAK, the
READ_PHONE_STATEpermission must be requested at runtime when all of the following conditions are true:- The application will run on Android P devices.
 - The application 
targetSdkVersionis 28 (Android P) or higher. - The Operator Vault file 
EnforceSingleDeviceUniqueIDPerDeviceFlagis set totrue. 
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 anActivityoverride).JAVAif (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 } }Prepare PRM by instantiating
OTVPRMManagerand 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.
The application needs to create a callback class implementing
OTVPRMNonSilentCallback. Using theOTVSSPPRMNonSilentCallback, 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.JAVAprivate 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
OTVSSPPRMNonSilentCallbackimplementation ofOTVPRMNonSilentCallback, you also need to provide request properties, which are specific HTTP headers for initialisation and key requests.JAVAString 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);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
OTVPRMSessionEventListenerinterface, implementing the two event methods.JAVAprivate 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
OTVPRMManagerduring the initialisation.JAVAmPRMManager.addPRMSessionEventListener(mPrmSessionEventListener);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);Playback operations can now proceed the same way as with clear data.
JAVAOTVVideoView.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:
JAVAif (mPRMManager != null) { mPRMManager.releasePAK(false); }Setting the
releasePAK()parameter totruewill 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.