mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Merge pull request #10260 from sr1990:clearkey_parse_licenseurl
PiperOrigin-RevId: 459215225
This commit is contained in:
commit
f00f93a96e
@ -22,6 +22,9 @@
|
|||||||
`MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory,
|
`MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory,
|
||||||
boolean)` to specify whether the renderer will output metadata early or
|
boolean)` to specify whether the renderer will output metadata early or
|
||||||
in sync with the player position.
|
in sync with the player position.
|
||||||
|
* DASH:
|
||||||
|
* Parse ClearKey license URL from manifests
|
||||||
|
([#10246](https://github.com/google/ExoPlayer/issues/10246)).
|
||||||
* UI:
|
* UI:
|
||||||
* Ensure TalkBack announces the currently active speed option in the
|
* Ensure TalkBack announces the currently active speed option in the
|
||||||
playback controls menu
|
playback controls menu
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.common;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.DrmInitData.SchemeData;
|
import androidx.media3.common.DrmInitData.SchemeData;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
@ -157,6 +158,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||||||
* @param schemeType A protection scheme type. May be null.
|
* @param schemeType A protection scheme type. May be null.
|
||||||
* @return A copy with the specified protection scheme type.
|
* @return A copy with the specified protection scheme type.
|
||||||
*/
|
*/
|
||||||
|
@CheckResult
|
||||||
public DrmInitData copyWithSchemeType(@Nullable String schemeType) {
|
public DrmInitData copyWithSchemeType(@Nullable String schemeType) {
|
||||||
if (Util.areEqual(this.schemeType, schemeType)) {
|
if (Util.areEqual(this.schemeType, schemeType)) {
|
||||||
return this;
|
return this;
|
||||||
@ -333,6 +335,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||||||
* @param data The data to include in the copy.
|
* @param data The data to include in the copy.
|
||||||
* @return The new instance.
|
* @return The new instance.
|
||||||
*/
|
*/
|
||||||
|
@CheckResult
|
||||||
public SchemeData copyWithData(@Nullable byte[] data) {
|
public SchemeData copyWithData(@Nullable byte[] data) {
|
||||||
return new SchemeData(uuid, licenseServerUrl, mimeType, data);
|
return new SchemeData(uuid, licenseServerUrl, mimeType, data);
|
||||||
}
|
}
|
||||||
|
@ -599,6 +599,9 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
|
case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
|
||||||
uuid = C.WIDEVINE_UUID;
|
uuid = C.WIDEVINE_UUID;
|
||||||
break;
|
break;
|
||||||
|
case "urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e":
|
||||||
|
uuid = C.CLEARKEY_UUID;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -606,7 +609,9 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "clearkey:Laurl") && xpp.next() == XmlPullParser.TEXT) {
|
||||||
|
licenseServerUrl = xpp.getText();
|
||||||
|
} else if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) {
|
||||||
licenseServerUrl = xpp.getAttributeValue(null, "licenseUrl");
|
licenseServerUrl = xpp.getAttributeValue(null, "licenseUrl");
|
||||||
} else if (data == null
|
} else if (data == null
|
||||||
&& XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
|
&& XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
|
||||||
@ -853,6 +858,7 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
||||||
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
||||||
if (!drmSchemeDatas.isEmpty()) {
|
if (!drmSchemeDatas.isEmpty()) {
|
||||||
|
fillInClearKeyInformation(drmSchemeDatas);
|
||||||
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
|
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
|
||||||
formatBuilder.setDrmInitData(new DrmInitData(drmSchemeType, drmSchemeDatas));
|
formatBuilder.setDrmInitData(new DrmInitData(drmSchemeType, drmSchemeDatas));
|
||||||
}
|
}
|
||||||
@ -1660,6 +1666,32 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void fillInClearKeyInformation(ArrayList<SchemeData> schemeDatas) {
|
||||||
|
// Find and remove ClearKey information.
|
||||||
|
@Nullable String clearKeyLicenseServerUrl = null;
|
||||||
|
for (int i = 0; i < schemeDatas.size(); i++) {
|
||||||
|
SchemeData schemeData = schemeDatas.get(i);
|
||||||
|
if (C.CLEARKEY_UUID.equals(schemeData.uuid) && schemeData.licenseServerUrl != null) {
|
||||||
|
clearKeyLicenseServerUrl = schemeData.licenseServerUrl;
|
||||||
|
schemeDatas.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clearKeyLicenseServerUrl == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fill in the ClearKey information into the existing PSSH schema data if applicable.
|
||||||
|
for (int i = 0; i < schemeDatas.size(); i++) {
|
||||||
|
SchemeData schemeData = schemeDatas.get(i);
|
||||||
|
if (C.COMMON_PSSH_UUID.equals(schemeData.uuid) && schemeData.licenseServerUrl == null) {
|
||||||
|
schemeDatas.set(
|
||||||
|
i,
|
||||||
|
new SchemeData(
|
||||||
|
C.CLEARKEY_UUID, clearKeyLicenseServerUrl, schemeData.mimeType, schemeData.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a sample mimeType from a container mimeType and codecs attribute.
|
* Derives a sample mimeType from a container mimeType and codecs attribute.
|
||||||
*
|
*
|
||||||
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.DrmInitData;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
@ -79,6 +80,8 @@ public class DashManifestParserTest {
|
|||||||
"media/mpd/sample_mpd_service_description_low_latency_only_playback_rates";
|
"media/mpd/sample_mpd_service_description_low_latency_only_playback_rates";
|
||||||
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_ONLY_TARGET_LATENCY =
|
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_ONLY_TARGET_LATENCY =
|
||||||
"media/mpd/sample_mpd_service_description_low_latency_only_target_latency";
|
"media/mpd/sample_mpd_service_description_low_latency_only_target_latency";
|
||||||
|
private static final String SAMPLE_MPD_CLEAR_KEY_LICENSE_URL =
|
||||||
|
"media/mpd/sample_mpd_clear_key_license_url";
|
||||||
|
|
||||||
private static final String NEXT_TAG_NAME = "Next";
|
private static final String NEXT_TAG_NAME = "Next";
|
||||||
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
|
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
|
||||||
@ -880,6 +883,37 @@ public class DashManifestParserTest {
|
|||||||
assertThat(manifest.serviceDescription).isNull();
|
assertThat(manifest.serviceDescription).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void contentProtections_withClearKeyLicenseUrl() throws IOException {
|
||||||
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
|
||||||
|
DashManifest manifest =
|
||||||
|
parser.parse(
|
||||||
|
Uri.parse("https://example.com/test.mpd"),
|
||||||
|
TestUtil.getInputStream(
|
||||||
|
ApplicationProvider.getApplicationContext(), SAMPLE_MPD_CLEAR_KEY_LICENSE_URL));
|
||||||
|
|
||||||
|
assertThat(manifest.getPeriodCount()).isEqualTo(1);
|
||||||
|
Period period = manifest.getPeriod(0);
|
||||||
|
assertThat(period.adaptationSets).hasSize(2);
|
||||||
|
AdaptationSet adaptationSet0 = period.adaptationSets.get(0);
|
||||||
|
AdaptationSet adaptationSet1 = period.adaptationSets.get(1);
|
||||||
|
assertThat(adaptationSet0.representations).hasSize(1);
|
||||||
|
assertThat(adaptationSet1.representations).hasSize(1);
|
||||||
|
Representation representation0 = adaptationSet0.representations.get(0);
|
||||||
|
Representation representation1 = adaptationSet1.representations.get(0);
|
||||||
|
assertThat(representation0.format.drmInitData.schemeType).isEqualTo("cenc");
|
||||||
|
assertThat(representation1.format.drmInitData.schemeType).isEqualTo("cenc");
|
||||||
|
assertThat(representation0.format.drmInitData.schemeDataCount).isEqualTo(1);
|
||||||
|
assertThat(representation1.format.drmInitData.schemeDataCount).isEqualTo(1);
|
||||||
|
DrmInitData.SchemeData schemeData0 = representation0.format.drmInitData.get(0);
|
||||||
|
DrmInitData.SchemeData schemeData1 = representation1.format.drmInitData.get(0);
|
||||||
|
assertThat(schemeData0.uuid).isEqualTo(C.CLEARKEY_UUID);
|
||||||
|
assertThat(schemeData1.uuid).isEqualTo(C.CLEARKEY_UUID);
|
||||||
|
assertThat(schemeData0.licenseServerUrl).isEqualTo("https://testserver1.test/AcquireLicense");
|
||||||
|
assertThat(schemeData1.licenseServerUrl).isEqualTo("https://testserver2.test/AcquireLicense");
|
||||||
|
}
|
||||||
|
|
||||||
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
|
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
|
||||||
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
|
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Includes ContentProtection elements with additional ClearKey license URLs.
|
||||||
|
Covers all possible locations (in AdaptationSet and Representation) and possible orders of these
|
||||||
|
ContentProtection elements (CENC first or ClearKey first).
|
||||||
|
-->
|
||||||
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="static" availabilityStartTime="2016-10-14T17:00:17" xmlns:cenc="urn:mpeg:cenc:2013" xmlns:clearkey="http://dashif.org/guidelines/clearKey">
|
||||||
|
<Period start="PT0.000S" duration="PT0H5M50S">
|
||||||
|
<SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
|
||||||
|
<SegmentTimeline>
|
||||||
|
<S d="2002" t="6009" r="2"/>
|
||||||
|
</SegmentTimeline>
|
||||||
|
</SegmentTemplate>
|
||||||
|
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
|
||||||
|
<Representation id="140" codecs="mp4a.40.2" audioSamplingRate="48000" startWithSAP="1" bandwidth="144000">
|
||||||
|
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="9eb4050d-e44b-4802-932e-27d75083e266" />
|
||||||
|
<ContentProtection value="ClearKey1.0" schemeIdUri="urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e">
|
||||||
|
<clearkey:Laurl Lic_type="EME-1.0">https://testserver1.test/AcquireLicense</clearkey:Laurl>
|
||||||
|
</ContentProtection>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
|
||||||
|
<ContentProtection value="ClearKey1.0" schemeIdUri="urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e">
|
||||||
|
<clearkey:Laurl Lic_type="EME-1.0">https://testserver2.test/AcquireLicense</clearkey:Laurl>
|
||||||
|
</ContentProtection>
|
||||||
|
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="9eb4050d-e44b-4802-932e-27d75083e266" />
|
||||||
|
<Representation id="133" codecs="avc1.4d4015" width="426" height="240" startWithSAP="1" bandwidth="258000" frameRate="30" />
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
Loading…
x
Reference in New Issue
Block a user