Work around lack of LA_URL attributes in PlayReady key requests init data

PiperOrigin-RevId: 239066912
This commit is contained in:
aquilescanta 2019-03-18 21:53:29 +00:00 committed by Oliver Woodman
parent b5e4523b58
commit c81c14ae86
3 changed files with 71 additions and 0 deletions

View File

@ -6,6 +6,7 @@
* Add new `ExoPlaybackException` types for remote exceptions and out-of-memory
errors.
* HLS:
* Work around lack of LA_URL attribute in PlayReady key request init data.
* Prevent unnecessary reloads of initialization segments.
* Form an adaptive track group out of audio renditions with matching name.
* Support encrypted initialization segments

View File

@ -101,6 +101,9 @@ public final class C {
*/
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.
*/

View File

@ -29,8 +29,13 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
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.ParsableByteArray;
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.HashMap;
import java.util.List;
@ -44,6 +49,10 @@ import java.util.UUID;
public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto> {
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 MediaDrm mediaDrm;
@ -139,6 +148,9 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
byte[] requestData = adjustRequestData(uuid, request.getData());
String licenseServerUrl = request.getDefaultUrl();
if (MOCK_LA_URL_VALUE.equals(licenseServerUrl)) {
licenseServerUrl = "";
}
if (TextUtils.isEmpty(licenseServerUrl)
&& schemeData != null
&& !TextUtils.isEmpty(schemeData.licenseServerUrl)) {
@ -277,6 +289,18 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
}
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
// devices also required data to be extracted from the PSSH atom for PlayReady.
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() {
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();
}
}