ExoPlayer module that provides the core ExoPlayer implementation for local media playback on Android, supporting various media formats and streaming protocols
—
ExoPlayer provides comprehensive Digital Rights Management (DRM) support for protected content playback. The DRM system handles key acquisition, session management, and secure decoding for various DRM schemes including Widevine, PlayReady, and ClearKey.
The core interface for managing DRM sessions and cryptographic operations.
public interface DrmSessionManager {
/**
* Prepares the DRM session manager.
*/
void prepare();
/**
* Returns the crypto type for the given format.
*
* @param format The format
* @return The crypto type, or C.CRYPTO_TYPE_NONE if not encrypted
*/
@C.CryptoType int getCryptoType(Format format);
/**
* Acquires a DRM session for the given DRM init data.
*
* @param playbackLooper The playback looper
* @param drmInitData The DRM initialization data
* @return The DRM session
*/
@Nullable DrmSession acquireSession(@Nullable Looper playbackLooper, @Nullable DrmInitData drmInitData);
/**
* Releases a DRM session.
*
* @param drmSession The session to release
*/
void releaseSession(@Nullable DrmSession drmSession);
/**
* Sets the DRM mode and offline license key set ID.
*
* @param mode The DRM mode
* @param offlineLicenseKeySetId The offline license key set ID, or null
*/
void setMode(@C.CryptoMode int mode, @Nullable byte[] offlineLicenseKeySetId);
/**
* Sets the player using this DRM session manager.
*
* @param playbackLooper The playback looper
* @param playerId The player ID
*/
void setPlayer(@Nullable Looper playbackLooper, @Nullable PlayerId playerId);
}The default implementation of DrmSessionManager supporting common DRM schemes.
public final class DefaultDrmSessionManager implements DrmSessionManager {
/**
* Builder for DefaultDrmSessionManager.
*/
public static final class Builder {
/**
* Creates a builder.
*/
public Builder();
/**
* Sets the UUID of the DRM scheme.
*
* @param uuid The DRM scheme UUID
* @return This builder
*/
public Builder setUuidAndExoMediaDrmProvider(UUID uuid, ExoMediaDrmProvider exoMediaDrmProvider);
/**
* Sets whether to use multiple sessions.
*
* @param multiSession Whether to use multiple sessions
* @return This builder
*/
public Builder setMultiSession(boolean multiSession);
/**
* Sets whether to play clear samples without keys.
*
* @param playClearSamplesWithoutKeys Whether to play clear samples without keys
* @return This builder
*/
public Builder setPlayClearSamplesWithoutKeys(boolean playClearSamplesWithoutKeys);
/**
* Sets the session keepalive timeout.
*
* @param useDrmSessionsForClearContentTrackTypes The track types
* @return This builder
*/
public Builder setUseDrmSessionsForClearContent(int... useDrmSessionsForClearContentTrackTypes);
/**
* Sets the key request parameters.
*
* @param keyRequestParameters The key request parameters
* @return This builder
*/
public Builder setKeyRequestParameters(@Nullable Map<String, String> keyRequestParameters);
/**
* Sets the load error handling policy.
*
* @param loadErrorHandlingPolicy The load error handling policy
* @return This builder
*/
public Builder setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy);
/**
* Builds the DefaultDrmSessionManager.
*
* @param httpDataSource The HTTP data source for license requests
* @return The built DefaultDrmSessionManager
*/
public DefaultDrmSessionManager build(HttpDataSource.Factory httpDataSource);
}
/**
* Sets the mode and offline license key set ID.
*
* @param mode The DRM mode
* @param offlineLicenseKeySetId The offline license key set ID
*/
@Override
public void setMode(@C.CryptoMode int mode, @Nullable byte[] offlineLicenseKeySetId);
/**
* Sets the player.
*
* @param playbackLooper The playback looper
* @param playerId The player ID
*/
@Override
public void setPlayer(@Nullable Looper playbackLooper, @Nullable PlayerId playerId);
/**
* Prepares the DRM session manager.
*/
@Override
public void prepare();
/**
* Releases the DRM session manager.
*/
public void release();
}Represents a DRM session that provides cryptographic keys for decryption.
public interface DrmSession {
/**
* DRM session states.
*/
@IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
@interface State {}
int STATE_RELEASED = 0;
int STATE_ERROR = 1;
int STATE_OPENING = 2;
int STATE_OPENED = 3;
int STATE_OPENED_WITH_KEYS = 4;
/**
* Returns the current state of the session.
*
* @return The session state
*/
@State int getState();
/**
* Returns whether clear samples can be played.
*
* @return Whether clear samples can be played
*/
boolean playClearSamples();
/**
* Returns the session error, if any.
*
* @return The error, or null if none
*/
@Nullable DrmSessionException getError();
/**
* Returns the crypto configuration.
*
* @return The crypto configuration, or null if not available
*/
@Nullable CryptoConfig getCryptoConfig();
/**
* Returns whether secure decoding is required.
*
* @param mimeType The MIME type
* @return Whether secure decoding is required
*/
boolean requiresSecureDecoder(String mimeType);
/**
* Acquires a reference to this session.
*
* @param eventDispatcher The event dispatcher
* @return This session
*/
DrmSession acquire(@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher);
/**
* Releases a reference to this session.
*
* @param eventDispatcher The event dispatcher
*/
void release(@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher);
}Listener for DRM session events.
public interface DrmSessionEventListener {
/**
* Called when a DRM session manager error occurs.
*
* @param error The error
*/
default void onDrmSessionManagerError(Exception error) {}
/**
* Called when DRM keys are loaded.
*/
default void onDrmKeysLoaded() {}
/**
* Called when a DRM session is acquired.
*/
default void onDrmSessionAcquired() {}
/**
* Called when DRM keys are removed.
*/
default void onDrmKeysRemoved() {}
/**
* Called when DRM keys are restored.
*/
default void onDrmKeysRestored() {}
/**
* Called when a DRM session is released.
*/
default void onDrmSessionReleased() {}
/**
* Event dispatcher for DRM session events.
*/
final class EventDispatcher {
/**
* Creates an event dispatcher.
*
* @param eventHandler The event handler
* @param eventListener The event listener
*/
public EventDispatcher(@Nullable Handler eventHandler,
@Nullable DrmSessionEventListener eventListener);
/**
* Dispatches a DRM session manager error event.
*
* @param error The error
*/
public void drmSessionManagerError(Exception error);
/**
* Dispatches a DRM keys loaded event.
*/
public void drmKeysLoaded();
/**
* Dispatches a DRM session acquired event.
*/
public void drmSessionAcquired();
/**
* Dispatches a DRM keys removed event.
*/
public void drmKeysRemoved();
/**
* Dispatches a DRM keys restored event.
*/
public void drmKeysRestored();
/**
* Dispatches a DRM session released event.
*/
public void drmSessionReleased();
}
}Interfaces for interacting with the Android MediaDrm system.
public interface ExoMediaDrm {
/**
* Exception thrown by ExoMediaDrm operations.
*/
final class KeyRequest {
public final byte[] data;
public final String licenseServerUrl;
@KeyRequestType public final int requestType;
public KeyRequest(byte[] data, String licenseServerUrl, int requestType);
}
/**
* Exception thrown by ExoMediaDrm operations.
*/
final class ProvisionRequest {
public final byte[] data;
public final String defaultUrl;
public ProvisionRequest(byte[] data, String defaultUrl);
}
/**
* Sets the property value for the given property name.
*
* @param propertyName The property name
* @param value The property value
*/
void setPropertyString(String propertyName, String value);
/**
* Sets the property value for the given property name.
*
* @param propertyName The property name
* @param value The property value
*/
void setPropertyByteArray(String propertyName, byte[] value);
/**
* Gets the property value for the given property name.
*
* @param propertyName The property name
* @return The property value
*/
String getPropertyString(String propertyName);
/**
* Gets the property value for the given property name.
*
* @param propertyName The property name
* @return The property value
*/
byte[] getPropertyByteArray(String propertyName);
/**
* Opens a new DRM session.
*
* @return The session ID
* @throws MediaDrmException If opening the session fails
*/
byte[] openSession() throws MediaDrmException;
/**
* Closes a DRM session.
*
* @param sessionId The session ID
*/
void closeSession(byte[] sessionId);
/**
* Generates a key request.
*
* @param scope The scope (session ID or key set ID)
* @param init The initialization data
* @param mimeType The MIME type
* @param keyType The key type
* @param optionalParameters Optional parameters
* @return The key request
* @throws MediaDrmException If generating the request fails
*/
KeyRequest generateKeyRequest(byte[] scope, @Nullable byte[] init, @Nullable String mimeType,
@KeyType int keyType, @Nullable HashMap<String, String> optionalParameters)
throws MediaDrmException;
/**
* Provides a key response to the DRM session.
*
* @param scope The scope (session ID or key set ID)
* @param response The key response
* @return The key set ID for offline keys, or null for streaming keys
* @throws MediaDrmException If providing the response fails
*/
@Nullable byte[] provideKeyResponse(byte[] scope, byte[] response) throws MediaDrmException;
}import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
import androidx.media3.exoplayer.drm.HttpMediaDrmCallback;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.common.C;
import java.util.UUID;
// Widevine UUID
UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
// Create HTTP data source for license requests
DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory()
.setUserAgent("MyApp/1.0");
// Create DRM callback for license server communication
String licenseUrl = "https://your-license-server.com/license";
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);
// Create DRM session manager
DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(WIDEVINE_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
// Use with media source
MediaSource mediaSource = new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(manifestUri));
player.setMediaSource(mediaSource);// Create DRM callback with custom headers
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);
// Add custom headers
Map<String, String> licenseHeaders = new HashMap<>();
licenseHeaders.put("Authorization", "Bearer " + authToken);
licenseHeaders.put("Content-Type", "application/octet-stream");
drmCallback.setKeyRequestHeaders(licenseHeaders);
// Create DRM session manager with custom parameters
Map<String, String> keyRequestParameters = new HashMap<>();
keyRequestParameters.put("userId", "user123");
keyRequestParameters.put("deviceId", "device456");
DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(WIDEVINE_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.setKeyRequestParameters(keyRequestParameters)
.build(drmCallback);// Enable multi-session for different DRM keys per track
DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(WIDEVINE_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.setMultiSession(true) // Enable multiple sessions
.build(drmCallback);public class OfflineLicenseHelper {
private final DefaultDrmSessionManager drmSessionManager;
private final MediaDrmCallback mediaDrmCallback;
public OfflineLicenseHelper(UUID drmSchemeUuid, ExoMediaDrm.Provider mediaDrmProvider,
MediaDrmCallback mediaDrmCallback) {
this.mediaDrmCallback = mediaDrmCallback;
this.drmSessionManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, mediaDrmProvider)
.build(mediaDrmCallback);
}
/**
* Downloads an offline license.
*
* @param drmInitData The DRM initialization data
* @return The offline license key set ID
*/
public byte[] downloadLicense(DrmInitData drmInitData) throws DrmSessionException {
drmSessionManager.setMode(C.CRYPTO_MODE_AES_CTR, null);
drmSessionManager.prepare();
try {
DrmSession drmSession = drmSessionManager.acquireSession(Looper.myLooper(), drmInitData);
// Wait for keys to be loaded
while (drmSession.getState() == DrmSession.STATE_OPENING) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new DrmSessionException(e);
}
}
if (drmSession.getState() == DrmSession.STATE_ERROR) {
throw drmSession.getError();
}
// Get offline license key set ID
return getOfflineLicenseKeySetId(drmSession);
} finally {
drmSessionManager.release();
}
}
/**
* Renews an offline license.
*
* @param offlineLicenseKeySetId The offline license key set ID
* @return The renewed key set ID
*/
public byte[] renewLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException {
drmSessionManager.setMode(C.CRYPTO_MODE_AES_CTR, offlineLicenseKeySetId);
drmSessionManager.prepare();
try {
// Renewal logic similar to download
return performLicenseRenewal(offlineLicenseKeySetId);
} finally {
drmSessionManager.release();
}
}
/**
* Releases an offline license.
*
* @param offlineLicenseKeySetId The offline license key set ID
*/
public void releaseLicense(byte[] offlineLicenseKeySetId) {
// Implementation to release offline license
}
private byte[] getOfflineLicenseKeySetId(DrmSession drmSession) {
// Extract key set ID from DRM session
return null; // Implementation specific
}
private byte[] performLicenseRenewal(byte[] keySetId) throws DrmSessionException {
// Implementation for license renewal
return keySetId;
}
}public class DrmEventHandler implements DrmSessionEventListener {
private static final String TAG = "DrmEventHandler";
@Override
public void onDrmSessionAcquired() {
Log.d(TAG, "DRM session acquired");
// Update UI to show DRM is ready
showDrmStatus("Protected content ready");
}
@Override
public void onDrmKeysLoaded() {
Log.d(TAG, "DRM keys loaded successfully");
// Start playback now that keys are available
startPlayback();
}
@Override
public void onDrmSessionManagerError(Exception error) {
Log.e(TAG, "DRM session manager error", error);
// Handle specific DRM errors
if (error instanceof UnsupportedDrmException) {
showError("DRM scheme not supported on this device");
} else if (error instanceof DrmSessionException) {
DrmSessionException drmError = (DrmSessionException) error;
handleDrmSessionError(drmError);
} else {
showError("DRM error: " + error.getMessage());
}
}
@Override
public void onDrmKeysRemoved() {
Log.d(TAG, "DRM keys removed");
// Handle key removal (e.g., license expired)
showError("License expired. Please renew.");
}
@Override
public void onDrmKeysRestored() {
Log.d(TAG, "DRM keys restored");
// Resume playback after key restoration
resumePlayback();
}
@Override
public void onDrmSessionReleased() {
Log.d(TAG, "DRM session released");
// Clean up DRM-related resources
cleanupDrmResources();
}
private void handleDrmSessionError(DrmSessionException error) {
// Handle specific DRM session errors
switch (error.reason) {
case DrmSessionException.ERROR_PROVISIONING_FAILED:
showError("Device provisioning failed");
break;
case DrmSessionException.ERROR_LICENSE_ACQUISITION_FAILED:
showError("Failed to acquire license");
break;
case DrmSessionException.ERROR_PROVISIONING_CONFIG_CHANGED:
showError("Provisioning configuration changed");
break;
default:
showError("DRM session error: " + error.getMessage());
}
}
private void showDrmStatus(String message) {
// Update UI with DRM status
}
private void showError(String message) {
// Display error to user
}
private void startPlaybook() {
// Start media playback
}
private void resumePlayback() {
// Resume media playback
}
private void cleanupDrmResources() {
// Clean up DRM-related resources
}
}
// Use the event handler
DrmEventHandler drmEventHandler = new DrmEventHandler();
DrmSessionEventListener.EventDispatcher eventDispatcher =
new DrmSessionEventListener.EventDispatcher(handler, drmEventHandler);// ClearKey UUID (for testing purposes)
UUID CLEARKEY_UUID = new UUID(0x1077EFECC0B24D02L, 0xACE33C1E52E2FB4BL);
// Create ClearKey DRM session manager (no license server needed)
DefaultDrmSessionManager clearKeyDrmManager = new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(CLEARKEY_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(new LocalMediaDrmCallback("{\"keys\":[{\"kty\":\"oct\",\"k\":\"base64-key\",\"kid\":\"base64-key-id\"}]}".getBytes()));
// Use for testing DRM functionality without a license serverpublic class CustomDrmConfiguration {
public static DefaultDrmSessionManager createCustomDrmManager(Context context, String licenseUrl) {
// Create custom HTTP data source with timeout and retry configuration
DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory()
.setUserAgent("MyApp/1.0")
.setConnectTimeoutMs(10000) // 10 second connect timeout
.setReadTimeoutMs(30000); // 30 second read timeout
// Create DRM callback with custom configuration
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory);
// Set custom request properties
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Custom-Header", "CustomValue");
drmCallback.setKeyRequestHeaders(requestProperties);
// Create load error handling policy for DRM requests
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() {
@Override
public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
// Custom retry delay for DRM errors
return 2000; // 2 second delay between retries
}
@Override
public int getMinimumLoadableRetryCount(int dataType) {
// More retries for DRM requests
return dataType == C.DATA_TYPE_DRM ? 5 : super.getMinimumLoadableRetryCount(dataType);
}
};
return new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(WIDEVINE_UUID, FrameworkMediaDrm.DEFAULT_PROVIDER)
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
.setPlayClearSamplesWithoutKeys(false) // Require keys for all samples
.setMultiSession(true) // Support multiple DRM sessions
.build(drmCallback);
}
}Install with Tessl CLI
npx tessl i tessl/maven-androidx-media3--media3-exoplayer