Enable DRM with JW Stream

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


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

Β Β β€’ Several configured DRM Policies
Β Β β€’ DRM license generation and management for Widevine
Β Β β€’ License delivery services for content playback on any device

With JW Player managing the technical aspects of DRM, you can focus on the design and implementation of engaging content experiences.

πŸ“˜

You can also Play DRM-protected content if you have not enabled DRM with JW Stream.



Requirements



Implementation

  1. Create a signed content URL. Follow steps 1-4 in the Implementation section of Enable DRM with JW Stream. The signed content URL returns a JSON object of the content metadata similar to the following example:
{
   "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}"
                  },
                  "playready":{
                     "url":"{PLAYREADY_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 of this tutorial.


  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();
           }
       }
   }
}

  1. Add the following DRM callback snippet to your project. You can modify the class for your needs.
@TargetApi(18)
public class WidevineMediaDrmCallback implements MediaDrmCallback {

   private static final String WIDEVINE_GTS_DEFAULT_BASE_URI =
           "https://proxy.uat.widevine.com/proxy";

   private final String defaultUri;

   public WidevineMediaDrmCallback(String contentId, String provider) {
       String params = "?video_id=" + contentId + "&provider=" + provider;
       defaultUri = WIDEVINE_GTS_DEFAULT_BASE_URI + params;
   }

   protected WidevineMediaDrmCallback(Parcel in) {
       defaultUri = in.readString();
   }

   public static final Creator<WidevineMediaDrmCallback> CREATOR = new Creator<WidevineMediaDrmCallback>() {
       @Override
       public WidevineMediaDrmCallback createFromParcel(Parcel in) {
           return new WidevineMediaDrmCallback(in);
       }

       @Override
       public WidevineMediaDrmCallback[] newArray(int size) {
           return new WidevineMediaDrmCallback[size];
       }
   };

   @Override
   public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) throws IOException {
       String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
       return Util.executePost(url, null, null);
   }

   @Override
   public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) throws IOException {
       String url = request.getLicenseServerUrl();
       if (TextUtils.isEmpty(url)) {
           url = defaultUri;
       }
       return Util.executePost(url, request.getData(), null);
   }

   @Override
   public int describeContents() {
       return 0;
   }

   @Override
   public void writeToParcel(Parcel dest, int flags) {
       dest.writeString(defaultUri);
   }
}