Apply Studio DRM with JW Platform (Android v4)

Learn a simplified approach to protecting your Android content with DRM.




JWP provides a simplified approach to protecting your content with industry-standard Digital Rights Management (DRM). By enabling DRM on a property from your JWP dashboard, the complex aspects of DRM management are managed by JWP on your behalf:

  • Several configured DRM Policies
  • DRM license generation and management for Widevine
  • License delivery services for content playback on any device

With JWP managing the technical aspects of DRM, you can focus on the design and implementation of engaging content experiences. For more information about the DRM workflow, please refer to the High-Level Workflow Overview.

📘

For the following use cases, use Studio DRM Standalone with your current streaming and hosting solution:

  • Choosing not to enable Studio DRM with JW Platform
  • Implementing live stream integrations


Compatibility

JWP supports industry-standard DRM. The following table shows the DRM technology that is supported with the Android SDK and which browsers and operating systems support this technology.

Browser | OS FairPlay PlayReady Widevine
Android 5+
(native)
Chrome (Android)
3 most recent stable versions


Requirements



Implementation

Use the following steps to set up DRM playback in your Android app:

  1. Generate a signed URL for DRM playback.
  2. Make a GET call with the signed URL. The signed content URL returns a JSON object of the media metadata.

🚧

Both the media URL and its associated LAURLs are valid for only 10 minutes from when they are requested.

curl -L -X GET 'https://cdn.jwplayer.com/v2/media/{media_id}/drm/{policy_id}?token={valid_JWT}' \
- H 'Authorization: Bearer {v2_api_secret}'
{
   "title":"Tears of Steel WAVE Test",
   "description":"",
   "kind":"Single Item",
   "playlist":[
      {
         "title":"Tears of Steel WAVE Test",
         "mediaid":"WlvsLi24",
         ...
         "images":[...],
         "duration":30,
         "pubdate":1603891888,
         "description":"",
         "sources":[
            {
               "drm":{
                  "widevine":{
                     "url":"{WIDEVINE_LICENSE_URL}"
                  }
               },
               "file":"{SIGNED_VIDEO_URL}",
               "type":"application/dash+xml"
            },
            ...         
         ],
         "tracks":[...],
         ...
   ],
   "feed_instance_id":"{FEED_INSTANCE_ID}"
}

  1. From the signed content URL response, extract the file title (title), file URL (playlist[].sources[].file) and the license URL (playlist[].sources[].drm.widevine.url).

🚧

The ordering of items within playlist[].sources[] is not static. Therefore, do not use a defined index (playlist[].sources[0]) as part of your extraction process.


  1. Use the extracted title, video URL, and license URL to set the title, file, and mediaDrmCallback().
PlaylistItem playlistItem = new PlaylistItem.Builder()
    .title("PARSED_TITLE")
    .file("PARSED_FILE_URL")
    .mediaDrmCallback(new WidevineMediaDrmCallback("PARSED_LICENSE_URL")
    .build();

List<PlaylistItem> playlist = new ArrayList<>();
playlist.add(playlistItem);
PlayerConfig config = new PlayerConfig.Builder()
    .playlist(playlist)
    .build();
mPlayer.setup(config);

📘

You may see a "Cannot resolve symbol" error on new WidevineMediaDrmCallback();. This error will be resolved once you have completed the following steps.


  1. Add a Util.java to your project. This utility is used by the MediaDrmCallback class to download data.
public class Util {

    public static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
            throws IOException {
        HttpURLConnection urlConnection = null;
        try {
            urlConnection = (HttpURLConnection) new URL(url).openConnection();
            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(data != null);
            urlConnection.setDoInput(true);
            if (requestProperties != null) {
                for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
                    urlConnection.setRequestProperty(requestProperty.getKey(),
                                                     requestProperty.getValue());
                }
            }
            // Write the request body, if there is one.
            if (data != null) {
                OutputStream out = urlConnection.getOutputStream();
                try {
                    out.write(data);
                } finally {
                    out.close();
                }
            }
            // Read and return the response body.
            InputStream inputStream = urlConnection.getInputStream();
            try {
                return toByteArray(inputStream);
            } finally {
                inputStream.close();
            }
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
    }

    public static boolean isValidURL(String url){
        return URLUtil.isValidUrl(url) && Patterns.WEB_URL.matcher(url).matches();
    }
}