Products       ExpressPlay       Developers       Tutorial: Android App (Using BB)

Developer Center

Android App Tutorial (Using BB Token)

This tutorial guides you through the process of streaming encrypted content on an Android device and creating a barebones Android ExpressPlay enabled media app to play the content using a Marlin BB token.

Recommended equipment:

  • A device that runs on Android 4.0.X or higher

STEP 1 – Install the Android SDK

Download the Android SDK; the latest release can be found here: http://developer.android.com/sdk/index.html


STEP 2 – Download the latest ExpressPlay SDK

The latest copy of the ExpressPlay SDK can be found here: https://admin.expressplay.com/sdk/

Be sure to remember where you saved your file.


STEP 3 – Import the ExpressPlayExampleBB Project from the ExpressPlay SDK

Please refer to http://www.expressplay.com/developer/tutorial-android-app for importing the project into either Android Studio or Eclipse.


The imports are essentially the same as for the basic tutorial:

import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import android.app.Activity; import android.app.Fragment; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.MediaController; import android.widget.VideoView; import com.intertrust.wasabi.ErrorCodeException; import com.intertrust.wasabi.Runtime; import com.intertrust.wasabi.media.PlaylistProxy; import com.intertrust.wasabi.media.PlaylistProxy.MediaSourceParams; import com.intertrust.wasabi.media.PlaylistProxy.MediaSourceType;


The Activity class defines some constants/enums and the fragment that implements personalization, marlin broadband license token acquisition, and streams the video through the VideoView.

/* * this enum simply maps the media types to the mimetypes required for the playlist proxy */ enum ContentTypes { DASH("application/dash+xml"), HLS("application/vnd.apple.mpegurl"), PDCF( "video/mp4"), M4F("video/mp4"), DCF("application/vnd.oma.drm.dcf"), BBTS( "video/mp2t"); String mediaSourceParamsContentType = null; private ContentTypes(String mediaSourceParamsContentType) { this.mediaSourceParamsContentType = mediaSourceParamsContentType; } public String getMediaSourceParamsContentType() { return mediaSourceParamsContentType; } } public class MarlinBroadbandExample extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_marlin_broadband_example); if (savedInstanceState == null) { getFragmentManager().beginTransaction() .add(R.id.container, new MBB_Playback_Fragment()).commit(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.marlin_broadband_example, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * A placeholder fragment containing a simple view. */ public static class MBB_Playback_Fragment extends Fragment { private PlaylistProxy playerProxy; static final String TAG = "SampleBBPlayer"; public MBB_Playback_Fragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate( R.layout.fragment_marlin_broadband_example, container, false); /* * Create a VideView for playback */ VideoView videoView = (VideoView) rootView .findViewById(R.id.videoView); MediaController mediaController = new MediaController( getActivity(), false); mediaController.setAnchorView(videoView); videoView.setMediaController(mediaController); try { /* * Initialize the Wasabi Runtime (necessary only once for each * instantiation of the application) * * ** Note: Set Runtime Properties as needed for your * environment */ Runtime.initialize(getActivity().getDir("wasabi", MODE_PRIVATE) .getAbsolutePath()); /* * Personalize the application (acquire DRM keys). This is only * necessary once each time the application is freshly installed * * ** Note: personalize() is a blocking call and may take long * enough to complete to trigger ANR (Application Not * Responding) errors. In a production application this should * be called in a background thread. */ if (!Runtime.isPersonalized()) Runtime.personalize(); } catch (NullPointerException e) { return rootView; } catch (ErrorCodeException e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "runtime initialization or personalization error: " + e.getLocalizedMessage()); return rootView; } /* * Acquire a Marlin Broadband License. The license is acquired using * a License Acquisition token. Such tokens for sample content can * be obtained from http://content.intertrust.com/express/ and in * this example are stored in the Android project /assets directory * using the filename "license-token.xml". * * For instance, you can download such a token from * http://content-access.intertrust-dev.com/EXPR005/bb, and save it * to the assets directory as license-token.xml" * * *** Note: processServiceToken() is a blocking call and may take * long enough to complete to trigger ANR (Application Not * Responding) errors. In a production application this should be * called in a background thread. */ String licenseAcquisitionToken = getActionTokenFromAssets(LICENSE_TOKEN_FILENAME); if (licenseAcquisitionToken == null) { Log.e(TAG, "Could not find action token in the assets directory - exiting"); return rootView; } long start = System.currentTimeMillis(); try { Runtime.processServiceToken(licenseAcquisitionToken); Log.i(TAG, "License successfully acquired in (ms): " + (System.currentTimeMillis() - start)); } catch (ErrorCodeException e1) { Log.e(TAG, "Could not acquire the license from the license acquisition token - exiting"); return rootView; } /* * create a playlist proxy and start it */ try { playerProxy = new PlaylistProxy(); playerProxy.start(); } catch (ErrorCodeException e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "playlist proxy error: " + e.getLocalizedMessage()); return rootView; } /* * Acquire a media stream URL encrypted with the key delivered in * the above license. Media Stream URLs can be obtained at * http://content.intertrust.com/express/. * * For instance, a DASH content stream protected with the license * token example above is * "http://content.intertrust.com/express/dash/mpd.xml" * * Note that the MediaSourceType must be adapted to the stream type * (DASH or HLS). Similarly, * the MediaSourceParams need to be set according to the media type * if MediaSourceType is SINGLE_FILE */ String dash_url = DASH_MPD_URL; ContentTypes contentType = ContentTypes.FIXTHIS; MediaSourceParams params = new MediaSourceParams(); params.sourceContentType = contentType .getMediaSourceParamsContentType(); /* * if the content has separate audio tracks (eg languages) you may * select one using MediaSourceParams, eg params.language="es"; */ /* * Create a PlaylistProxy URL and pass it to the VideView and start * playback */ String proxy_url = null; try { proxy_url = playerProxy.makeUrl(dash_url, MediaSourceType.DASH, params); videoView.setVideoURI(Uri.parse(proxy_url)); videoView.start(); } catch (Exception e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "playback error: " + e.getLocalizedMessage()); e.printStackTrace(); return rootView; } return rootView; } /************************************** * Helper methods to avoid cluttering * **************************************/ /* * Read an action token file from the assets directory */ protected String getActionTokenFromAssets(String tokenFileName) { String token = null; byte[] readBuffer = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = null; int bytesRead = 0; try { is = getActivity().getAssets() .open(tokenFileName, MODE_PRIVATE); while ((bytesRead = is.read(readBuffer)) != -1) { baos.write(readBuffer, 0, bytesRead); } baos.close(); is.close(); } catch (IOException e) { e.printStackTrace(); return null; } token = new String(baos.toByteArray()); return token; } } }

Now let’s take a look at what the code does.

The MBB_Playback_Fragment Fragment’s onCreateView method begins by setting the content to the xml file created with the VideoView inside it.

View rootView = inflater.inflate( R.layout.fragment_marlin_broadband_example, container, false);

Next, the VideoView is set up.

/* * Create a VideView for playback */ VideoView videoView = (VideoView) rootView .findViewById(R.id.videoView); MediaController mediaController = new MediaController( getActivity(), false); mediaController.setAnchorView(videoView); videoView.setMediaController(mediaController);

Then, personalization.

try { /* * Initialize the Wasabi Runtime (necessary only once for each * instantiation of the application) * * ** Note: Set Runtime Properties as needed for your * environment */ Runtime.initialize(getActivity().getDir("wasabi", MODE_PRIVATE) .getAbsolutePath()); /* * Personalize the application (acquire DRM keys). This is only * necessary once each time the application is freshly installed * * ** Note: personalize() is a blocking call and may take long * enough to complete to trigger ANR (Application Not * Responding) errors. In a production application this should * be called in a background thread. */ if (!Runtime.isPersonalized()) Runtime.personalize(); } catch (NullPointerException e) { return rootView; } catch (ErrorCodeException e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "runtime initialization or personalization error: " + e.getLocalizedMessage()); return rootView; }

Then, the Marlin Broadband License is acquired. Following the comments in the code, get a License Acquisition Token by clicking here
and saving the file in “license-token.xml”. Save this file in the assets folder of your Android Application Project.
Note: After some time, this license will expire. But a new one can be acquired following the same process.

/* * Acquire a Marlin Broadband License. The license is acquired using * a License Acquisition token. Such tokens for sample content can * be obtained from http://content.intertrust.com/express/ and in * this example are stored in the Android project /assets directory * using the filename "license-token.xml". * * For instance, you can download such a token from * http://content-access.intertrust-dev.com/EXPR005/bb, and save it * to the assets directory as license-token.xml" * * *** Note: processServiceToken() is a blocking call and may take * long enough to complete to trigger ANR (Application Not * Responding) errors. In a production application this should be * called in a background thread. */ String licenseAcquisitionToken = getActionTokenFromAssets(LICENSE_TOKEN_FILENAME); if (licenseAcquisitionToken == null) { Log.e(TAG, "Could not find action token in the assets directory - exiting"); return rootView; } long start = System.currentTimeMillis(); try { Runtime.processServiceToken(licenseAcquisitionToken); Log.i(TAG, "License successfully acquired in (ms): " + (System.currentTimeMillis() - start)); } catch (ErrorCodeException e1) { Log.e(TAG, "Could not acquire the license from the license acquisition token - exiting"); return rootView; }

Then, get the media stream URL. This URL is encrypted with the key found in the above license token.

/* * Acquire a Marlin Broadband License. The license is acquired using * a License Acquisition token. Such tokens for sample content can * be obtained from http://content.intertrust.com/express/ and in * this example are stored in the Android project /assets directory * using the filename "license-token.xml". * * For instance, you can download such a token from * http://content-access.intertrust-dev.com/EXPR005/bb, and save it * to the assets directory as license-token.xml" * * *** Note: processServiceToken() is a blocking call and may take * long enough to complete to trigger ANR (Application Not * Responding) errors. In a production application this should be * called in a background thread. */ String licenseAcquisitionToken = getActionTokenFromAssets(LICENSE_TOKEN_FILENAME); if (licenseAcquisitionToken == null) { Log.e(TAG, "Could not find action token in the assets directory - exiting"); return rootView; } long start = System.currentTimeMillis(); try { Runtime.processServiceToken(licenseAcquisitionToken); Log.i(TAG, "License successfully acquired in (ms): " + (System.currentTimeMillis() - start)); } catch (ErrorCodeException e1) { Log.e(TAG, "Could not acquire the license from the license acquisition token - exiting"); return rootView; }

Then, create the PlaylistProxy and start it. Acquire the protected media stream corresponding to the license, create a PlaylistProxy URL from that media url, and set the VideoView to play from the PlaylistProxy URL.

/* * create a playlist proxy and start it */ try { playerProxy = new PlaylistProxy(); playerProxy.start(); } catch (ErrorCodeException e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "playlist proxy error: " + e.getLocalizedMessage()); return rootView; } /* * Acquire a media stream URL encrypted with the key delivered in * the above license. Media Stream URLs can be obtained at * http://content.intertrust.com/express/. * * For instance, a DASH content stream protected with the license * token example above is * "http://content.intertrust.com/express/dash/mpd.xml" * * Note that the MediaSourceType must be adapted to the stream type * (DASH or HLS). Similarly, * the MediaSourceParams need to be set according to the media type * if MediaSourceType is SINGLE_FILE */ String dash_url = DASH_MPD_URL; ContentTypes contentType = ContentTypes.FIXTHIS; MediaSourceParams params = new MediaSourceParams(); params.sourceContentType = contentType .getMediaSourceParamsContentType(); /* * if the content has separate audio tracks (eg languages) you may * select one using MediaSourceParams, eg params.language="es"; */ /* * Create a PlaylistProxy URL and pass it to the VideView and start * playback */ String proxy_url = null; try { proxy_url = playerProxy.makeUrl(dash_url, MediaSourceType.DASH, params); videoView.setVideoURI(Uri.parse(proxy_url)); videoView.start(); } catch (Exception e) { // Consult WasabiErrors.txt for resolution of the error codes Log.e(TAG, "playback error: " + e.getLocalizedMessage()); e.printStackTrace(); return rootView; }

The following method acquires the token from the license-token.xml file located in the assets folder.

/************************************** * Helper methods to avoid cluttering * **************************************/ /* * Read an action token file from the assets directory */ protected String getActionTokenFromAssets(String tokenFileName) { String token = null; byte[] readBuffer = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = null; int bytesRead = 0; try { is = getActivity().getAssets() .open(tokenFileName, MODE_PRIVATE); while ((bytesRead = is.read(readBuffer)) != -1) { baos.write(readBuffer, 0, bytesRead); } baos.close(); is.close(); } catch (IOException e) { e.printStackTrace(); return null; } token = new String(baos.toByteArray()); return token; }


STEP 4 – Permission

Go to the package explorer and open ‘AndroidManifest.xml’. Go to the actual xml code.

To give the ExpressPlay SDK internet permission as well as permission to write to the external storage of the Android device, add the following code block in between android:targetSdkVersion="17"/> and <application as shown in the picture above.

<uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>

Now the code is ready to run.

Note: If you are using an android virtual machine, as opposed to an actual device, the video may not work, but the audio will.