ExoPlayer
1. Introduction
Integration information:
License URL:
Merchant Information:
2. Require
Prerequisite
Operating system: Android 16+
Install Exoplayer 2:
Add a repositories in the build.gradle file of your app module.
javarepositories{ maven { url "https://maven.sigmadrm.com" } } }
Params that using in this document
Props | Type | Description |
---|---|---|
LICENSE_URL | String | Url of the license server. You can find at here |
MERCHANT_ID | String | Identify of merchant. You can find at here |
APP_ID | String | Identify of application. You can find at here |
USER_ID | String | User Identify that provided by merchant system |
SESSION_ID | String | Session Identify that provided by merchant system |
Note: "If you use both Sigma MultiDRM and Sigma DRM, please add dependency as Sigma DRM"
3. Support Exoplayer version
4. Create MediaDrmCallback
Notes:
- Exoplayer before 2.12.x: Please replace EXCEPTION to IOException
- Exoplayer from 2.12.x: Please replace EXCEPTION to MediaDrmCallbackException
java
import android.annotation.TargetApi;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.sigma.packer.RequestInfo;
// FIXME: If you user license encrypt feature then please uncomment 2 lines below
// import com.sigma.packer.RequestInfo;
// import com.sigma.packer.SigmaDrmPacker;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances.
*/
@TargetApi(18)
public final class WidevineMediaDrmCallback implements MediaDrmCallback {
private final HttpDataSource.Factory dataSourceFactory;
private final String defaultLicenseUrl;
private final boolean forceDefaultLicenseUrl;
private final Map<String, String> keyRequestProperties;
/**
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
*/
public WidevineMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
this(defaultLicenseUrl, false, dataSourceFactory);
}
/**
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is
* set to true.
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
*/
public WidevineMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl,
HttpDataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
this.defaultLicenseUrl = defaultLicenseUrl;
this.forceDefaultLicenseUrl = forceDefaultLicenseUrl;
this.keyRequestProperties = new HashMap<>();
}
/**
* Sets a header for key requests made by the callback.
*
* @param name The name of the header field.
* @param value The value of the field.
*/
public void setKeyRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (keyRequestProperties) {
keyRequestProperties.put(name, value);
}
}
/**
* Clears a header for key requests made by the callback.
*
* @param name The name of the header field.
*/
public void clearKeyRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (keyRequestProperties) {
keyRequestProperties.remove(name);
}
}
/**
* Clears all headers for key requests made by the callback.
*/
public void clearAllKeyRequestProperties() {
synchronized (keyRequestProperties) {
keyRequestProperties.clear();
}
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws EXCEPTION {
String url =
request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData());
return executePost(dataSourceFactory, url, Util.EMPTY_BYTE_ARRAY, null);
}
@Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws EXCEPTION {
try {
String url = request.getLicenseServerUrl();
if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) {
url = defaultLicenseUrl;
}
Map<String, String> requestProperties = new HashMap<>();
// Add standard request properties for supported schemes.
String contentType = "application/octet-stream";
requestProperties.put("Content-Type", contentType);
requestProperties.put("custom-data", getCustomData(request));
// Add additional request properties.
synchronized (keyRequestProperties) {
requestProperties.putAll(keyRequestProperties);
}
byte[] bytes = executePost(dataSourceFactory, url, request.getData(), requestProperties);
JSONObject jsonObject = new JSONObject(new String(bytes));
String licenseEncrypted = jsonObject.getString("license");
return Base64.decode(licenseEncrypted, Base64.DEFAULT);
} catch (JSONException e) {
throw new RuntimeException("Error while parsing response", e);
} catch (Exception e) {
throw new RuntimeException("Error while parsing response", e);
}
}
private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url,
byte[] data, Map<String, String> requestProperties) throws IOException {
HttpDataSource dataSource = dataSourceFactory.createDataSource();
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
dataSource.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
}
}
while (true) {
DataSpec dataSpec =
new DataSpec(
Uri.parse(url),
data,
/* absoluteStreamPosition= */ 0,
/* position= */ 0,
/* length= */ C.LENGTH_UNSET,
/* key= */ null,
DataSpec.FLAG_ALLOW_GZIP);
DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
try {
return Util.toByteArray(inputStream);
} catch (Exception e) {
throw e;
} finally {
Util.closeQuietly(inputStream);
}
}
}
private String getCustomData(KeyRequest keyRequest) throws Exception {
JSONObject customData = new JSONObject();
customData.put("userId", USER_ID);
customData.put("sessionId", SESSION_ID);
customData.put("merchantId", MERCHANT_ID);
customData.put("appId", APP_ID);
// FIXME: If you user license encrypt feature then please uncomment 3 lines below
// RequestInfo requestInfo = SigmaDrmPacker.requestInfo(keyRequest.getData());
// customData.put("reqId", requestInfo.requestId);
// customData.put("deviceInfo", requestInfo.deviceInfo);
String customHeader = Base64.encodeToString(customData.toString().getBytes(), Base64.NO_WRAP);
Log.e("Custom Data: ", customHeader);
return customHeader;
}
}
5. Sample Code
Sample source code