ExoPlayer (JW Platform)

Learn how to integrate your DRM-protected content in Android.

💡

As an alternative, you can use the JWP Android SDK to manage the technical aspects of DRM.


Studio DRM and ExoPlayer provide a comprehensive approach to protecting your content with industry-standard Digital Rights Management (DRM). After enabling DRM on a property from your JWP dashboard and integrating with ExoPlayer, DRM decryption of the content will be managed by ExoPlayer and the OS.

For more information about the DRM workflow, 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


Prerequisites

Item Notes
DRM entitlement Contact your JWP representative for more information.
DRM-enabled property See: Enable a property


Implementation

💡

We strongly recommend referring to and starting with our Studio DRM with JW Platform and ExoPlayer demo for Android and Android TV. This .zip file allows you to see both Google's recommended full working implementation and an example of how to manage the licensing of online and offline multiple assets.


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

  1. Generate a signed URL for DRM playback.

    ⚠️

    We strongly recommend using a proxy service to generate the JSON web token (JWT). If you generate the JWT within a client-side native app, you risk exposing your API secret.


  1. Make a GET call with the signed URL.

    From the signed content URL response, the code sample extracts the file title (title), file URL (playlist[].sources[].file) and the license URL (playlist[].sources[].drm.widevine.url) from the sources array and populates the specific stream configuration with them.

    🚧

    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. The following code sample demonstrates how to locate the correct playlist[].sources index.

    Also, both the media URL and its associated license URL are valid for only 10 minutes from when they are requested.

    private static void ParseJson(String responseBody) throws JSONException {
        JSONObject json = new JSONObject(responseBody);
    
        JSONArray playlist = json.getJSONArray("playlist");
        JSONObject playlist_result = playlist.getJSONObject(0);
        JSONArray sources = playlist_result.getJSONArray("sources");
        JSONObject sources_result = sources.getJSONObject(0);
        JSONObject drm = sources_result.getJSONObject("drm");
        JSONObject widevine = drm.getJSONObject("widevine");
    
        title = playlist_result.getString("title");
        licenseUrl = widevine.getString("url");
        streamUrl = sources_result.getString("file");
    }
    

  1. Create a list of mediaItems with the associated DRM configuration.

    private List<MediaItem> createMediaItems(Intent intent) {
        String action = intent.getAction();
        boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
        if (!actionIsListView && !IntentUtil.ACTION_VIEW.equals(action)) {
            showToast(getString(R.string.unexpected_intent_action, action));
            finish();
            return Collections.emptyList();
        }
    
        List<MediaItem> mediaItems =
                createMediaItems(intent, DemoUtil.getDownloadTracker(/* context= */ this));
        for (int i = 0; i < mediaItems.size(); i++) {
            MediaItem mediaItem = mediaItems.get(i);
    
            if (!Util.checkCleartextTrafficPermitted(mediaItem)) {
                showToast(R.string.error_cleartext_not_permitted);
                finish();
                return Collections.emptyList();
            }
            if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) {
                // The player will be reinitialized if the permission is granted.
                return Collections.emptyList();
            }
    
            MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
            if (drmConfiguration != null) {
                if (Build.VERSION.SDK_INT < 18) {
                    showToast(R.string.error_drm_unsupported_before_api_18);
                    finish();
                    return Collections.emptyList();
                } else if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) {
                    showToast(R.string.error_drm_unsupported_scheme);
                    finish();
                    return Collections.emptyList();
                }
            }
        }
        return mediaItems;
    }
    

  1. Initialize an ExoPlayer player using the list of mediaItems.

    protected boolean initializePlayer() {
        if (player == null) {
            Intent intent = getIntent();
    
            mediaItems = createMediaItems(intent);
            if (mediaItems.isEmpty()) {
                return false;
            }
    
            lastSeenTracks = Tracks.EMPTY;
            ExoPlayer.Builder playerBuilder =
                    new ExoPlayer.Builder(/* context= */ this)
                            .setMediaSourceFactory(createMediaSourceFactory());
            setRenderersFactory(
                    playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));
            player = playerBuilder.build();
            player.setTrackSelectionParameters(trackSelectionParameters);
            player.addListener(new PlayerEventListener());
            player.addAnalyticsListener(new EventLogger());
            player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
            player.setPlayWhenReady(startAutoPlay);
            playerView.setPlayer(player);
            configurePlayerWithServerSideAdsLoader();
            debugViewHelper = new DebugTextViewHelper(player, debugTextView);
            debugViewHelper.start();
        }
        boolean haveStartPosition = startItemIndex != C.INDEX_UNSET;
        if (haveStartPosition) {
            player.seekTo(startItemIndex, startPosition);
        }
        player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
        player.prepare();
        updateButtonVisibility();
        return true;
    }
    

  1. Create a list of mediaItems with associated DRM configuration and assign the list to a DownloadTracker and DownloadRequest.

    📘

    Offline playback requires the following:

    • A DownloadTracker that tracks media that has been downloaded
    • A service for downloading media, such as a DownloadService

    Full examples of these classes and how to implement them are available in the Studio DRM with JW Platform and ExoPlayer demo.

    private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
        List<MediaItem> mediaItems = new ArrayList<>();
        for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
            mediaItems.add(
                    maybeSetDownloadProperties(
                            item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
        }
        return mediaItems;
    }
    
    private static MediaItem maybeSetDownloadProperties(
            MediaItem item, @Nullable DownloadRequest downloadRequest) {
        if (downloadRequest == null) {
            return item;
        }
        MediaItem.Builder builder = item.buildUpon();
        builder
                .setMediaId(downloadRequest.id)
                .setUri(downloadRequest.uri)
                .setCustomCacheKey(downloadRequest.customCacheKey)
                .setMimeType(downloadRequest.mimeType)
                .setStreamKeys(downloadRequest.streamKeys);
        @Nullable
        MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
        if (drmConfiguration != null) {
            builder.setDrmConfiguration(
                    drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
        }
        return builder.build();
    }