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:
- 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.
-
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 thesources
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 correctplaylist[].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"); }
-
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; }
-
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; }
-
Create a list of
mediaItems
with associated DRM configuration and assign the list to aDownloadTracker
andDownloadRequest
.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(); }
- A
Updated 7 months ago