Remove DRM->DASH dependency in prep for DASH module split
Also renamed releaseResources->release to be consistent with the rest of the library, and added some synchronization to ensure correct usage. Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150753414
This commit is contained in:
parent
065d3dc523
commit
b1a2ae1856
@ -23,17 +23,9 @@ import android.test.InstrumentationTestCase;
|
|||||||
import android.test.MoreAsserts;
|
import android.test.MoreAsserts;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
|
||||||
@ -50,89 +42,57 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
|
|||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
TestUtil.setUpMockito(this);
|
TestUtil.setUpMockito(this);
|
||||||
|
|
||||||
when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3});
|
when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3});
|
||||||
|
|
||||||
offlineLicenseHelper = new OfflineLicenseHelper<>(mediaDrm, mediaDrmCallback, null);
|
offlineLicenseHelper = new OfflineLicenseHelper<>(mediaDrm, mediaDrmCallback, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void tearDown() throws Exception {
|
protected void tearDown() throws Exception {
|
||||||
offlineLicenseHelper.releaseResources();
|
offlineLicenseHelper.release();
|
||||||
|
offlineLicenseHelper = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDownloadRenewReleaseKey() throws Exception {
|
public void testDownloadRenewReleaseKey() throws Exception {
|
||||||
DashManifest manifest = newDashManifestWithAllElements();
|
|
||||||
setStubLicenseAndPlaybackDurationValues(1000, 200);
|
setStubLicenseAndPlaybackDurationValues(1000, 200);
|
||||||
|
|
||||||
byte[] keySetId = {2, 5, 8};
|
byte[] keySetId = {2, 5, 8};
|
||||||
setStubKeySetId(keySetId);
|
setStubKeySetId(keySetId);
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
|
||||||
|
|
||||||
assertOfflineLicenseKeySetIdEqual(keySetId, offlineLicenseKeySetId);
|
assertOfflineLicenseKeySetIdEqual(keySetId, offlineLicenseKeySetId);
|
||||||
|
|
||||||
byte[] keySetId2 = {6, 7, 0, 1, 4};
|
byte[] keySetId2 = {6, 7, 0, 1, 4};
|
||||||
setStubKeySetId(keySetId2);
|
setStubKeySetId(keySetId2);
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renew(offlineLicenseKeySetId);
|
byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId);
|
||||||
|
|
||||||
assertOfflineLicenseKeySetIdEqual(keySetId2, offlineLicenseKeySetId2);
|
assertOfflineLicenseKeySetIdEqual(keySetId2, offlineLicenseKeySetId2);
|
||||||
|
|
||||||
offlineLicenseHelper.release(offlineLicenseKeySetId2);
|
offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDownloadFailsIfThereIsNoInitData() throws Exception {
|
public void testDownloadLicenseFailsIfNullInitData() throws Exception {
|
||||||
setDefaultStubValues();
|
try {
|
||||||
DashManifest manifest =
|
offlineLicenseHelper.downloadLicense(null);
|
||||||
newDashManifest(newPeriods(newAdaptationSets(newRepresentations(null /*no init data*/))));
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
// Expected.
|
||||||
|
}
|
||||||
assertNull(offlineLicenseKeySetId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDownloadFailsIfThereIsNoRepresentation() throws Exception {
|
public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception {
|
||||||
setDefaultStubValues();
|
|
||||||
DashManifest manifest = newDashManifest(newPeriods(newAdaptationSets(/*no representation*/)));
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
|
||||||
|
|
||||||
assertNull(offlineLicenseKeySetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDownloadFailsIfThereIsNoAdaptationSet() throws Exception {
|
|
||||||
setDefaultStubValues();
|
|
||||||
DashManifest manifest = newDashManifest(newPeriods(/*no adaptation set*/));
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
|
||||||
|
|
||||||
assertNull(offlineLicenseKeySetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDownloadFailsIfThereIsNoPeriod() throws Exception {
|
|
||||||
setDefaultStubValues();
|
|
||||||
DashManifest manifest = newDashManifest(/*no periods*/);
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
|
||||||
|
|
||||||
assertNull(offlineLicenseKeySetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDownloadFailsIfNoKeySetIdIsReturned() throws Exception {
|
|
||||||
setStubLicenseAndPlaybackDurationValues(1000, 200);
|
setStubLicenseAndPlaybackDurationValues(1000, 200);
|
||||||
DashManifest manifest = newDashManifestWithAllElements();
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
|
||||||
|
|
||||||
assertNull(offlineLicenseKeySetId);
|
assertNull(offlineLicenseKeySetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDownloadDoesNotFailIfDurationNotAvailable() throws Exception {
|
public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception {
|
||||||
setDefaultStubKeySetId();
|
setDefaultStubKeySetId();
|
||||||
DashManifest manifest = newDashManifestWithAllElements();
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
|
||||||
|
|
||||||
assertNotNull(offlineLicenseKeySetId);
|
assertNotNull(offlineLicenseKeySetId);
|
||||||
}
|
}
|
||||||
@ -142,9 +102,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
|
|||||||
int playbackDuration = 200;
|
int playbackDuration = 200;
|
||||||
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
|
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
|
||||||
setDefaultStubKeySetId();
|
setDefaultStubKeySetId();
|
||||||
DashManifest manifest = newDashManifestWithAllElements();
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
|
||||||
|
|
||||||
Pair<Long, Long> licenseDurationRemainingSec = offlineLicenseHelper
|
Pair<Long, Long> licenseDurationRemainingSec = offlineLicenseHelper
|
||||||
.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
||||||
@ -158,9 +117,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
|
|||||||
int playbackDuration = 0;
|
int playbackDuration = 0;
|
||||||
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
|
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
|
||||||
setDefaultStubKeySetId();
|
setDefaultStubKeySetId();
|
||||||
DashManifest manifest = newDashManifestWithAllElements();
|
|
||||||
|
|
||||||
byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest);
|
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
|
||||||
|
|
||||||
Pair<Long, Long> licenseDurationRemainingSec = offlineLicenseHelper
|
Pair<Long, Long> licenseDurationRemainingSec = offlineLicenseHelper
|
||||||
.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
||||||
@ -169,12 +127,6 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
|
|||||||
assertEquals(playbackDuration, (long) licenseDurationRemainingSec.second);
|
assertEquals(playbackDuration, (long) licenseDurationRemainingSec.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDefaultStubValues()
|
|
||||||
throws android.media.NotProvisionedException, android.media.DeniedByServerException {
|
|
||||||
setDefaultStubKeySetId();
|
|
||||||
setStubLicenseAndPlaybackDurationValues(1000, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDefaultStubKeySetId()
|
private void setDefaultStubKeySetId()
|
||||||
throws android.media.NotProvisionedException, android.media.DeniedByServerException {
|
throws android.media.NotProvisionedException, android.media.DeniedByServerException {
|
||||||
setStubKeySetId(new byte[] {2, 5, 8});
|
setStubKeySetId(new byte[] {2, 5, 8});
|
||||||
@ -201,31 +153,6 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
|
|||||||
when(mediaDrm.queryKeyStatus(any(byte[].class))).thenReturn(keyStatus);
|
when(mediaDrm.queryKeyStatus(any(byte[].class))).thenReturn(keyStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DashManifest newDashManifestWithAllElements() {
|
|
||||||
return newDashManifest(newPeriods(newAdaptationSets(newRepresentations(newDrmInitData()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DashManifest newDashManifest(Period... periods) {
|
|
||||||
return new DashManifest(0, 0, 0, false, 0, 0, 0, null, null, Arrays.asList(periods));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Period newPeriods(AdaptationSet... adaptationSets) {
|
|
||||||
return new Period("", 0, Arrays.asList(adaptationSets));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AdaptationSet newAdaptationSets(Representation... representations) {
|
|
||||||
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Representation newRepresentations(DrmInitData drmInitData) {
|
|
||||||
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
|
|
||||||
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
|
|
||||||
if (drmInitData != null) {
|
|
||||||
format = format.copyWithDrmInitData(drmInitData);
|
|
||||||
}
|
|
||||||
return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DrmInitData newDrmInitData() {
|
private static DrmInitData newDrmInitData() {
|
||||||
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType",
|
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType",
|
||||||
new byte[] {1, 4, 7, 0, 3, 6}));
|
new byte[] {1, 4, 7, 0, 3, 6}));
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.source.dash;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeDataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link DashUtil}.
|
||||||
|
*/
|
||||||
|
public final class DashUtilTest extends TestCase {
|
||||||
|
|
||||||
|
public void testLoadDrmInitDataFromManifest() throws Exception {
|
||||||
|
Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData())));
|
||||||
|
DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period);
|
||||||
|
assertEquals(newDrmInitData(), drmInitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLoadDrmInitDataMissing() throws Exception {
|
||||||
|
Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */)));
|
||||||
|
DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period);
|
||||||
|
assertNull(drmInitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLoadDrmInitDataNoRepresentations() throws Exception {
|
||||||
|
Period period = newPeriod(newAdaptationSets(/* no representation */));
|
||||||
|
DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period);
|
||||||
|
assertNull(drmInitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLoadDrmInitDataNoAdaptationSets() throws Exception {
|
||||||
|
Period period = newPeriod(/* no adaptation set */);
|
||||||
|
DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period);
|
||||||
|
assertNull(drmInitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Period newPeriod(AdaptationSet... adaptationSets) {
|
||||||
|
return new Period("", 0, Arrays.asList(adaptationSets));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdaptationSet newAdaptationSets(Representation... representations) {
|
||||||
|
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Representation newRepresentations(DrmInitData drmInitData) {
|
||||||
|
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
|
||||||
|
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
|
||||||
|
if (drmInitData != null) {
|
||||||
|
format = format.copyWithDrmInitData(drmInitData);
|
||||||
|
}
|
||||||
|
return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DrmInitData newDrmInitData() {
|
||||||
|
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType",
|
||||||
|
new byte[]{1, 4, 7, 0, 3, 6}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataSource newDataSource() {
|
||||||
|
// TODO: Use DummyDataSource when available.
|
||||||
|
FakeDataSource fakeDataSource = new FakeDataSource();
|
||||||
|
fakeDataSource.getDataSet().newDefaultData().appendReadError(new IOException("Unexpected"));
|
||||||
|
return fakeDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,15 +22,9 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.EventListener;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.EventListener;
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.source.dash.DashUtil;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
|
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -38,8 +32,7 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to download, renew and release offline licenses. It utilizes {@link
|
* Helper class to download, renew and release offline licenses.
|
||||||
* DefaultDrmSessionManager}.
|
|
||||||
*/
|
*/
|
||||||
public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
||||||
|
|
||||||
@ -48,8 +41,8 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
private final HandlerThread handlerThread;
|
private final HandlerThread handlerThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when
|
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
|
||||||
* you're done with the helper instance.
|
* is no longer required.
|
||||||
*
|
*
|
||||||
* @param licenseUrl The default license URL.
|
* @param licenseUrl The default license URL.
|
||||||
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||||
@ -64,8 +57,8 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when
|
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
|
||||||
* you're done with the helper instance.
|
* is no longer required.
|
||||||
*
|
*
|
||||||
* @param callback Performs key and provisioning requests.
|
* @param callback Performs key and provisioning requests.
|
||||||
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
|
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
|
||||||
@ -84,7 +77,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Call {@link #releaseResources()} when you're done with it.
|
* Constructs an instance. Call {@link #release()} when the instance is no longer required.
|
||||||
*
|
*
|
||||||
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
|
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
|
||||||
* @param callback Performs key and provisioning requests.
|
* @param callback Performs key and provisioning requests.
|
||||||
@ -97,7 +90,6 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
HashMap<String, String> optionalKeyRequestParameters) {
|
HashMap<String, String> optionalKeyRequestParameters) {
|
||||||
handlerThread = new HandlerThread("OfflineLicenseHelper");
|
handlerThread = new HandlerThread("OfflineLicenseHelper");
|
||||||
handlerThread.start();
|
handlerThread.start();
|
||||||
|
|
||||||
conditionVariable = new ConditionVariable();
|
conditionVariable = new ConditionVariable();
|
||||||
EventListener eventListener = new EventListener() {
|
EventListener eventListener = new EventListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -124,67 +116,23 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener);
|
optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Releases the used resources. */
|
/** Releases the helper. Should be called when the helper is no longer required. */
|
||||||
public void releaseResources() {
|
public void release() {
|
||||||
handlerThread.quit();
|
handlerThread.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads an offline license.
|
* Downloads an offline license.
|
||||||
*
|
*
|
||||||
* @param dataSource The {@link HttpDataSource} to be used for download.
|
* @param drmInitData The {@link DrmInitData} for the content whose license is to be downloaded.
|
||||||
* @param manifestUriString The URI of the manifest to be read.
|
* @return The key set id for the downloaded license.
|
||||||
* @return The downloaded offline license key set id.
|
|
||||||
* @throws IOException If an error occurs reading data from the stream.
|
* @throws IOException If an error occurs reading data from the stream.
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
* @throws DrmSessionException Thrown when there is an error during DRM session.
|
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
||||||
*/
|
*/
|
||||||
public byte[] download(HttpDataSource dataSource, String manifestUriString)
|
public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException,
|
||||||
throws IOException, InterruptedException, DrmSessionException {
|
InterruptedException, DrmSessionException {
|
||||||
return download(dataSource, DashUtil.loadManifest(dataSource, manifestUriString));
|
Assertions.checkArgument(drmInitData != null);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads an offline license.
|
|
||||||
*
|
|
||||||
* @param dataSource The {@link HttpDataSource} to be used for download.
|
|
||||||
* @param dashManifest The {@link DashManifest} of the DASH content.
|
|
||||||
* @return The downloaded offline license key set id.
|
|
||||||
* @throws IOException If an error occurs reading data from the stream.
|
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
|
||||||
* @throws DrmSessionException Thrown when there is an error during DRM session.
|
|
||||||
*/
|
|
||||||
public byte[] download(HttpDataSource dataSource, DashManifest dashManifest)
|
|
||||||
throws IOException, InterruptedException, DrmSessionException {
|
|
||||||
// Get DrmInitData
|
|
||||||
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
|
|
||||||
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
|
||||||
if (dashManifest.getPeriodCount() < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Period period = dashManifest.getPeriod(0);
|
|
||||||
int adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO);
|
|
||||||
if (adaptationSetIndex == C.INDEX_UNSET) {
|
|
||||||
adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_AUDIO);
|
|
||||||
if (adaptationSetIndex == C.INDEX_UNSET) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
|
|
||||||
if (adaptationSet.representations.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Representation representation = adaptationSet.representations.get(0);
|
|
||||||
DrmInitData drmInitData = representation.format.drmInitData;
|
|
||||||
if (drmInitData == null) {
|
|
||||||
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation);
|
|
||||||
if (sampleFormat != null) {
|
|
||||||
drmInitData = sampleFormat.drmInitData;
|
|
||||||
}
|
|
||||||
if (drmInitData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData);
|
blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData);
|
||||||
return drmSessionManager.getOfflineLicenseKeySetId();
|
return drmSessionManager.getOfflineLicenseKeySetId();
|
||||||
}
|
}
|
||||||
@ -193,10 +141,11 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
* Renews an offline license.
|
* Renews an offline license.
|
||||||
*
|
*
|
||||||
* @param offlineLicenseKeySetId The key set id of the license to be renewed.
|
* @param offlineLicenseKeySetId The key set id of the license to be renewed.
|
||||||
* @return Renewed offline license key set id.
|
* @return The renewed offline license key set id.
|
||||||
* @throws DrmSessionException Thrown when there is an error during DRM session.
|
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
||||||
*/
|
*/
|
||||||
public byte[] renew(byte[] offlineLicenseKeySetId) throws DrmSessionException {
|
public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId)
|
||||||
|
throws DrmSessionException {
|
||||||
Assertions.checkNotNull(offlineLicenseKeySetId);
|
Assertions.checkNotNull(offlineLicenseKeySetId);
|
||||||
blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null);
|
blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null);
|
||||||
return drmSessionManager.getOfflineLicenseKeySetId();
|
return drmSessionManager.getOfflineLicenseKeySetId();
|
||||||
@ -206,19 +155,22 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
|||||||
* Releases an offline license.
|
* Releases an offline license.
|
||||||
*
|
*
|
||||||
* @param offlineLicenseKeySetId The key set id of the license to be released.
|
* @param offlineLicenseKeySetId The key set id of the license to be released.
|
||||||
* @throws DrmSessionException Thrown when there is an error during DRM session.
|
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
||||||
*/
|
*/
|
||||||
public void release(byte[] offlineLicenseKeySetId) throws DrmSessionException {
|
public synchronized void releaseLicense(byte[] offlineLicenseKeySetId)
|
||||||
|
throws DrmSessionException {
|
||||||
Assertions.checkNotNull(offlineLicenseKeySetId);
|
Assertions.checkNotNull(offlineLicenseKeySetId);
|
||||||
blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null);
|
blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns license and playback durations remaining in seconds of the given offline license.
|
* Returns the remaining license and playback durations in seconds, for an offline license.
|
||||||
*
|
*
|
||||||
* @param offlineLicenseKeySetId The key set id of the license.
|
* @param offlineLicenseKeySetId The key set id of the license.
|
||||||
|
* @return The remaining license and playback durations, in seconds.
|
||||||
|
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
||||||
*/
|
*/
|
||||||
public Pair<Long, Long> getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId)
|
public synchronized Pair<Long, Long> getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId)
|
||||||
throws DrmSessionException {
|
throws DrmSessionException {
|
||||||
Assertions.checkNotNull(offlineLicenseKeySetId);
|
Assertions.checkNotNull(offlineLicenseKeySetId);
|
||||||
DrmSession<T> session = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY,
|
DrmSession<T> session = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY,
|
||||||
|
@ -38,7 +38,7 @@ public final class WidevineUtil {
|
|||||||
* @throws IllegalStateException If called when a session isn't opened.
|
* @throws IllegalStateException If called when a session isn't opened.
|
||||||
* @param drmSession
|
* @param drmSession
|
||||||
*/
|
*/
|
||||||
public static Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession drmSession) {
|
public static Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession<?> drmSession) {
|
||||||
Map<String, String> keyStatus = drmSession.queryKeyStatus();
|
Map<String, String> keyStatus = drmSession.queryKeyStatus();
|
||||||
return new Pair<>(
|
return new Pair<>(
|
||||||
getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING),
|
getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING),
|
||||||
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.dash;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||||
@ -26,6 +27,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
|
|||||||
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
|
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
|
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
@ -34,6 +36,7 @@ import com.google.android.exoplayer2.upstream.DataSpec;
|
|||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for DASH streams.
|
* Utility methods for DASH streams.
|
||||||
@ -46,8 +49,7 @@ public final class DashUtil {
|
|||||||
* @param dataSource The {@link HttpDataSource} from which the manifest should be read.
|
* @param dataSource The {@link HttpDataSource} from which the manifest should be read.
|
||||||
* @param manifestUriString The URI of the manifest to be read.
|
* @param manifestUriString The URI of the manifest to be read.
|
||||||
* @return An instance of {@link DashManifest}.
|
* @return An instance of {@link DashManifest}.
|
||||||
* @throws IOException If an error occurs reading data from the stream.
|
* @throws IOException Thrown when there is an error while loading.
|
||||||
* @see DashManifestParser
|
|
||||||
*/
|
*/
|
||||||
public static DashManifest loadManifest(DataSource dataSource, String manifestUriString)
|
public static DashManifest loadManifest(DataSource dataSource, String manifestUriString)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -63,8 +65,35 @@ public final class DashUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads initialization data for the {@code representation} and returns the sample {@link
|
* Loads {@link DrmInitData} for a given period in a DASH manifest.
|
||||||
* Format}.
|
*
|
||||||
|
* @param dataSource The {@link HttpDataSource} from which data should be loaded.
|
||||||
|
* @param period The {@link Period}.
|
||||||
|
* @return The loaded {@link DrmInitData}, or null if none is defined.
|
||||||
|
* @throws IOException Thrown when there is an error while loading.
|
||||||
|
* @throws InterruptedException Thrown if the thread was interrupted.
|
||||||
|
*/
|
||||||
|
public static DrmInitData loadDrmInitData(DataSource dataSource, Period period)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
Representation representation = getFirstRepresentation(period, C.TRACK_TYPE_VIDEO);
|
||||||
|
if (representation == null) {
|
||||||
|
representation = getFirstRepresentation(period, C.TRACK_TYPE_AUDIO);
|
||||||
|
if (representation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrmInitData drmInitData = representation.format.drmInitData;
|
||||||
|
if (drmInitData != null) {
|
||||||
|
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
|
||||||
|
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
||||||
|
return drmInitData;
|
||||||
|
}
|
||||||
|
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation);
|
||||||
|
return sampleFormat == null ? null : sampleFormat.drmInitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads initialization data for the {@code representation} and returns the sample {@link Format}.
|
||||||
*
|
*
|
||||||
* @param dataSource The source from which the data should be loaded.
|
* @param dataSource The source from which the data should be loaded.
|
||||||
* @param representation The representation which initialization chunk belongs to.
|
* @param representation The representation which initialization chunk belongs to.
|
||||||
@ -155,6 +184,15 @@ public final class DashUtil {
|
|||||||
return new ChunkExtractorWrapper(extractor, format);
|
return new ChunkExtractorWrapper(extractor, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Representation getFirstRepresentation(Period period, int type) {
|
||||||
|
int index = period.getAdaptationSetIndex(type);
|
||||||
|
if (index == C.INDEX_UNSET) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Representation> representations = period.adaptationSets.get(index).representations;
|
||||||
|
return representations.isEmpty() ? null : representations.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
private DashUtil() {}
|
private DashUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,15 @@ package com.google.android.exoplayer2.playbacktests.gts;
|
|||||||
import android.media.MediaDrm.MediaDrmStateException;
|
import android.media.MediaDrm.MediaDrmStateException;
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.drm.OfflineLicenseHelper;
|
import com.google.android.exoplayer2.drm.OfflineLicenseHelper;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.ActionSchedule;
|
import com.google.android.exoplayer2.playbacktests.util.ActionSchedule;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
|
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashUtil;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -72,7 +76,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa
|
|||||||
releaseLicense();
|
releaseLicense();
|
||||||
}
|
}
|
||||||
if (offlineLicenseHelper != null) {
|
if (offlineLicenseHelper != null) {
|
||||||
offlineLicenseHelper.releaseResources();
|
offlineLicenseHelper.release();
|
||||||
}
|
}
|
||||||
offlineLicenseHelper = null;
|
offlineLicenseHelper = null;
|
||||||
httpDataSourceFactory = null;
|
httpDataSourceFactory = null;
|
||||||
@ -89,7 +93,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa
|
|||||||
testRunner.run();
|
testRunner.run();
|
||||||
|
|
||||||
// Renew license after playback should still work
|
// Renew license after playback should still work
|
||||||
offlineLicenseKeySetId = offlineLicenseHelper.renew(offlineLicenseKeySetId);
|
offlineLicenseKeySetId = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId);
|
||||||
Assert.assertNotNull(offlineLicenseKeySetId);
|
Assert.assertNotNull(offlineLicenseKeySetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,15 +168,18 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void downloadLicense() throws InterruptedException, DrmSessionException, IOException {
|
private void downloadLicense() throws InterruptedException, DrmSessionException, IOException {
|
||||||
offlineLicenseKeySetId = offlineLicenseHelper.download(
|
DataSource dataSource = httpDataSourceFactory.createDataSource();
|
||||||
httpDataSourceFactory.createDataSource(), DashTestData.WIDEVINE_H264_MANIFEST);
|
DashManifest dashManifest = DashUtil.loadManifest(dataSource,
|
||||||
|
DashTestData.WIDEVINE_H264_MANIFEST);
|
||||||
|
DrmInitData drmInitData = DashUtil.loadDrmInitData(dataSource, dashManifest.getPeriod(0));
|
||||||
|
offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(drmInitData);
|
||||||
Assert.assertNotNull(offlineLicenseKeySetId);
|
Assert.assertNotNull(offlineLicenseKeySetId);
|
||||||
Assert.assertTrue(offlineLicenseKeySetId.length > 0);
|
Assert.assertTrue(offlineLicenseKeySetId.length > 0);
|
||||||
testRunner.setOfflineLicenseKeySetId(offlineLicenseKeySetId);
|
testRunner.setOfflineLicenseKeySetId(offlineLicenseKeySetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseLicense() throws DrmSessionException {
|
private void releaseLicense() throws DrmSessionException {
|
||||||
offlineLicenseHelper.release(offlineLicenseKeySetId);
|
offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId);
|
||||||
offlineLicenseKeySetId = null;
|
offlineLicenseKeySetId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user