Media3 version 1.9.2
1. Giới thiệu
Thông tin tích hợp:
License URL:
Thông tin tích hợp của merchant:

2. Yêu cầu
Yêu cầu tiên quyết
Hệ điều hành: Android 16+
Hỗ trợ căn chỉnh trang 16 KB:
AGP 8.5.1 trở lên: Bắt buộc để các thư viện dùng chung (.so) không nén được căn chỉnh chính xác trên ranh giới 16 KB trong file APK.
AGP 8.5 trở xuống: Sử dụng thư viện nén bằng cách thêm cấu hình sau vào
app/build.gradle:groovyandroid { ... packagingOptions { jniLibs { useLegacyPackaging true } } }
Thêm một kho lưu trữ (repositories) trong file [root_directory]/build.gradle.
javarepositories{ google() mavenCentral() maven { url "https://maven.sigmadrm.com" } } }Thêm phụ thuộc thư viện vào app/build.gradle:
java
implementation 'androidx.media3:media3-exoplayer:1.9.2'
// FIXME: If you don't use feature license encrypt, please comment line below
implementation 'com.sigma.packer:media3-1.9.2:1.0.3'- Các thông tin được sử dụng trong tài liệu
| Trường | Kiểu | Mô tả |
|---|---|---|
| LICENSE_URL | String | Địa chỉ hệ thống máy chủ DRM |
| MERCHANT_ID | String | Định danh khách hàng |
| APP_ID | String | Định danh của ứng dụng |
| USER_ID | String | Định danh của người dùng trong hệ thống của khách hàng |
| SESSION_ID | String | Mã xác thực được cấp cho người dùng bởi hệ thống của khách hàng |
3. Tích hợp MediaDrmCallback
java
// WidevineMediaDrmCallback.java
import static androidx.media3.exoplayer.drm.DrmUtil.executePost;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import androidx.annotation.NonNull;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest;
import androidx.media3.exoplayer.drm.MediaDrmCallback;
import androidx.media3.exoplayer.drm.MediaDrmCallbackException;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
import com.google.common.primitives.Bytes;
// FIXME: If you user license encrypt feature then please uncomment 2 lines below
// import com.sigma.packer.RequestInfo;
// import com.sigma.packer.SigmaDrmPacker;
/**
* A {@link MediaDrmCallback} that makes requests using {@link DataSource}
* instances.
*/
@UnstableApi
public final class WidevineMediaDrmCallback implements MediaDrmCallback {
private final DataSource.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 DataSource}
* instances.
*/
public WidevineMediaDrmCallback(String defaultLicenseUrl, DataSource.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 DataSource} instances.
*/
public WidevineMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl,
DataSource.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();
}
}
// Wrapping into a RuntimeException is recommended by the JSONException docs:
// https://developer.android.com/reference/org/json/JSONException
@SuppressWarnings("ThrowSpecificExceptions")
@NonNull
@Override
public Response executeProvisionRequest(@NonNull UUID uuid, ProvisionRequest request)
throws MediaDrmCallbackException {
byte[] httpBody = Bytes.concat(
"{\"signedRequest\":\"".getBytes(UTF_8), request.getData(), "\"}".getBytes(UTF_8));
return executePost(
dataSourceFactory.createDataSource(),
request.getDefaultUrl(),
httpBody,
ImmutableMap.of(
HttpHeaders.CONTENT_TYPE,
MediaType.JSON_UTF_8.toString(),
HttpHeaders.CONTENT_LENGTH,
String.valueOf(httpBody.length)));
}
@NonNull
@Override
public Response executeKeyRequest(@NonNull UUID uuid, @NonNull KeyRequest request)
throws MediaDrmCallbackException {
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);
}
Response response = executePost(
dataSourceFactory.createDataSource(),
url,
request.getData(),
requestProperties);
JSONObject jsonObject = new JSONObject(new String(response.data));
String licenseEncrypted = jsonObject.getString("license");
return new Response(Base64.decode(licenseEncrypted, Base64.DEFAULT));
} catch (MediaDrmCallbackException e) {
throw e;
} catch (Exception e) {
throw new MediaDrmCallbackException(
new DataSpec.Builder().setUri(Uri.EMPTY).build(),
Uri.EMPTY,
/* responseHeaders= */ ImmutableMap.of(),
/* bytesLoaded= */ 0,
/* cause= */ new IllegalStateException("Error while parsing response", e));
}
}
private String getCustomData(KeyRequest keyRequest) throws Exception {
JSONObject customData = new JSONObject();
customData.put("merchantId", MERCHANT_ID);
customData.put("appId", APP_ID);
customData.put("userId", USER_ID);
customData.put("sessionId", SESSION_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("SIGMA", "Custom data " + customHeader);
return customHeader;
}
}4. Khởi tạo trình phát (Player)
java
// PlayerActivity.java
private void initializePlayer() {
DrmSessionManager drmSessionManager;
if (Util.SDK_INT >= 18) {
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid("widevine"));
MediaDrmCallback drmCallback = createMediaDrmCallback(drmLicenseUrl, null);
drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setMultiSession(true)
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, SigmaMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(videoPath));
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(getApplicationContext())
.setDrmSessionManagerProvider(mi -> drmSessionManager);
MediaSource mediaSource = mediaSourceFactory.createMediaSource(mediaItem);
trackSelector = new DefaultTrackSelector(/* context= */ this);
DefaultTrackSelector.Parameters trackSelectionParameters =
new DefaultTrackSelector.ParametersBuilder(/* context= */ this)
.setAllowVideoMixedMimeTypeAdaptiveness(true)
.setAllowVideoNonSeamlessAdaptiveness(true)
.build();
player = new ExoPlayer.Builder(getApplicationContext())
.setTrackSelector(trackSelector)
.build();
player.setTrackSelectionParameters(trackSelectionParameters);
player.setMediaSource(mediaSource);
player.prepare();
player.play();
playerView.setPlayer(player);
player.addAnalyticsListener(new EventLogger(trackSelector));
}
private WidevineMediaDrmCallback createMediaDrmCallback(String licenseUrl, String[] keyRequestPropertiesArray) {
HttpDataSource.Factory licenseDataSourceFactory =
((ExoplayerApplication) getApplication()).buildHttpDataSourceFactory();
WidevineMediaDrmCallback drmCallback =
new WidevineMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
return drmCallback;
}5. Cấu hình Proguard
Nếu bạn sử dụng tính năng Mã hóa license, hãy thêm các quy tắc Proguard sau:
java
-keep class com.sigma.packer.RequestInfo { *; }
-keep class com.sigma.packer.SigmaDrmPacker { *; }
-keep class com.sigma.packer.SigmaMediaDrm { *; }
-keepclasseswithmembers class com.sigma.packer.RequestInfo$* { *; }
-keepclasseswithmembers class com.sigma.packer.SigmaDrmPacker$* { *; }
-keepclasseswithmembers class com.sigma.packer.SigmaMediaDrm$* { *; }