mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
Work around lack of LA_URL attributes in PlayReady key requests init data
PiperOrigin-RevId: 239066912
This commit is contained in:
parent
b5e4523b58
commit
c81c14ae86
@ -6,6 +6,7 @@
|
|||||||
* Add new `ExoPlaybackException` types for remote exceptions and out-of-memory
|
* Add new `ExoPlaybackException` types for remote exceptions and out-of-memory
|
||||||
errors.
|
errors.
|
||||||
* HLS:
|
* HLS:
|
||||||
|
* Work around lack of LA_URL attribute in PlayReady key request init data.
|
||||||
* Prevent unnecessary reloads of initialization segments.
|
* Prevent unnecessary reloads of initialization segments.
|
||||||
* Form an adaptive track group out of audio renditions with matching name.
|
* Form an adaptive track group out of audio renditions with matching name.
|
||||||
* Support encrypted initialization segments
|
* Support encrypted initialization segments
|
||||||
|
@ -101,6 +101,9 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final String UTF16_NAME = "UTF-16";
|
public static final String UTF16_NAME = "UTF-16";
|
||||||
|
|
||||||
|
/** The name of the UTF-16 little-endian charset. */
|
||||||
|
public static final String UTF16LE_NAME = "UTF-16LE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the serif font family.
|
* The name of the serif font family.
|
||||||
*/
|
*/
|
||||||
|
@ -29,8 +29,13 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
|
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -44,6 +49,10 @@ import java.util.UUID;
|
|||||||
public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto> {
|
public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto> {
|
||||||
|
|
||||||
private static final String CENC_SCHEME_MIME_TYPE = "cenc";
|
private static final String CENC_SCHEME_MIME_TYPE = "cenc";
|
||||||
|
private static final String MOCK_LA_URL_VALUE = "https://x";
|
||||||
|
private static final String MOCK_LA_URL = "<LA_URL>" + MOCK_LA_URL_VALUE + "</LA_URL>";
|
||||||
|
private static final int UTF_16_BYTES_PER_CHARACTER = 2;
|
||||||
|
private static final String TAG = "FrameworkMediaDrm";
|
||||||
|
|
||||||
private final UUID uuid;
|
private final UUID uuid;
|
||||||
private final MediaDrm mediaDrm;
|
private final MediaDrm mediaDrm;
|
||||||
@ -139,6 +148,9 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
|
|||||||
byte[] requestData = adjustRequestData(uuid, request.getData());
|
byte[] requestData = adjustRequestData(uuid, request.getData());
|
||||||
|
|
||||||
String licenseServerUrl = request.getDefaultUrl();
|
String licenseServerUrl = request.getDefaultUrl();
|
||||||
|
if (MOCK_LA_URL_VALUE.equals(licenseServerUrl)) {
|
||||||
|
licenseServerUrl = "";
|
||||||
|
}
|
||||||
if (TextUtils.isEmpty(licenseServerUrl)
|
if (TextUtils.isEmpty(licenseServerUrl)
|
||||||
&& schemeData != null
|
&& schemeData != null
|
||||||
&& !TextUtils.isEmpty(schemeData.licenseServerUrl)) {
|
&& !TextUtils.isEmpty(schemeData.licenseServerUrl)) {
|
||||||
@ -277,6 +289,18 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] adjustRequestInitData(UUID uuid, byte[] initData) {
|
private static byte[] adjustRequestInitData(UUID uuid, byte[] initData) {
|
||||||
|
// TODO: Add API level check once [Internal ref: b/112142048] is fixed.
|
||||||
|
if (C.PLAYREADY_UUID.equals(uuid)) {
|
||||||
|
byte[] schemeSpecificData = PsshAtomUtil.parseSchemeSpecificData(initData, uuid);
|
||||||
|
if (schemeSpecificData == null) {
|
||||||
|
// The init data is not contained in a pssh box.
|
||||||
|
schemeSpecificData = initData;
|
||||||
|
}
|
||||||
|
initData =
|
||||||
|
PsshAtomUtil.buildPsshAtom(
|
||||||
|
C.PLAYREADY_UUID, addLaUrlAttributeIfMissing(schemeSpecificData));
|
||||||
|
}
|
||||||
|
|
||||||
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon
|
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon
|
||||||
// devices also required data to be extracted from the PSSH atom for PlayReady.
|
// devices also required data to be extracted from the PSSH atom for PlayReady.
|
||||||
if ((Util.SDK_INT < 21 && C.WIDEVINE_UUID.equals(uuid))
|
if ((Util.SDK_INT < 21 && C.WIDEVINE_UUID.equals(uuid))
|
||||||
@ -324,4 +348,47 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
|
|||||||
private static boolean needsForceWidevineL3Workaround() {
|
private static boolean needsForceWidevineL3Workaround() {
|
||||||
return "ASUS_Z00AD".equals(Util.MODEL);
|
return "ASUS_Z00AD".equals(Util.MODEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the LA_URL tag is missing, injects a mock LA_URL value to avoid causing the CDM to throw
|
||||||
|
* when creating the key request. The LA_URL attribute is optional but some Android PlayReady
|
||||||
|
* implementations are known to require it. Does nothing it the provided {@code data} already
|
||||||
|
* contains an LA_URL value.
|
||||||
|
*/
|
||||||
|
private static byte[] addLaUrlAttributeIfMissing(byte[] data) {
|
||||||
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
// See https://docs.microsoft.com/en-us/playready/specifications/specifications for more
|
||||||
|
// information about the init data format.
|
||||||
|
int length = byteArray.readLittleEndianInt();
|
||||||
|
int objectRecordCount = byteArray.readLittleEndianShort();
|
||||||
|
int recordType = byteArray.readLittleEndianShort();
|
||||||
|
if (objectRecordCount != 1 || recordType != 1) {
|
||||||
|
Log.i(TAG, "Unexpected record count or type. Skipping LA_URL workaround.");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
int recordLength = byteArray.readLittleEndianShort();
|
||||||
|
String xml = byteArray.readString(recordLength, Charset.forName(C.UTF16LE_NAME));
|
||||||
|
if (xml.contains("<LA_URL>")) {
|
||||||
|
// LA_URL already present. Do nothing.
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// This PlayReady object record does not include an LA_URL. We add a mock value for it.
|
||||||
|
int endOfDataTagIndex = xml.indexOf("</DATA>");
|
||||||
|
if (endOfDataTagIndex == -1) {
|
||||||
|
Log.w(TAG, "Could not find the </DATA> tag. Skipping LA_URL workaround.");
|
||||||
|
}
|
||||||
|
String xmlWithMockLaUrl =
|
||||||
|
xml.substring(/* beginIndex= */ 0, /* endIndex= */ endOfDataTagIndex)
|
||||||
|
+ MOCK_LA_URL
|
||||||
|
+ xml.substring(/* beginIndex= */ endOfDataTagIndex);
|
||||||
|
int extraBytes = MOCK_LA_URL.length() * UTF_16_BYTES_PER_CHARACTER;
|
||||||
|
ByteBuffer newData = ByteBuffer.allocate(length + extraBytes);
|
||||||
|
newData.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
newData.putInt(length + extraBytes);
|
||||||
|
newData.putShort((short) objectRecordCount);
|
||||||
|
newData.putShort((short) recordType);
|
||||||
|
newData.putShort((short) (xmlWithMockLaUrl.length() * UTF_16_BYTES_PER_CHARACTER));
|
||||||
|
newData.put(xmlWithMockLaUrl.getBytes(Charset.forName(C.UTF16LE_NAME)));
|
||||||
|
return newData.array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user