diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index 985e93404a..afd690762b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -23,17 +23,9 @@ import android.test.InstrumentationTestCase; import android.test.MoreAsserts; import android.util.Pair; 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.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.upstream.HttpDataSource; -import com.google.android.exoplayer2.util.MimeTypes; -import java.util.Arrays; import java.util.HashMap; import org.mockito.Mock; @@ -50,89 +42,57 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { @Override protected void setUp() throws Exception { TestUtil.setUpMockito(this); - when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3}); - offlineLicenseHelper = new OfflineLicenseHelper<>(mediaDrm, mediaDrmCallback, null); } @Override protected void tearDown() throws Exception { - offlineLicenseHelper.releaseResources(); + offlineLicenseHelper.release(); + offlineLicenseHelper = null; } public void testDownloadRenewReleaseKey() throws Exception { - DashManifest manifest = newDashManifestWithAllElements(); setStubLicenseAndPlaybackDurationValues(1000, 200); byte[] keySetId = {2, 5, 8}; setStubKeySetId(keySetId); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertOfflineLicenseKeySetIdEqual(keySetId, offlineLicenseKeySetId); byte[] keySetId2 = {6, 7, 0, 1, 4}; setStubKeySetId(keySetId2); - byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renew(offlineLicenseKeySetId); + byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId); assertOfflineLicenseKeySetIdEqual(keySetId2, offlineLicenseKeySetId2); - offlineLicenseHelper.release(offlineLicenseKeySetId2); + offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId2); } - public void testDownloadFailsIfThereIsNoInitData() throws Exception { - setDefaultStubValues(); - DashManifest manifest = - newDashManifest(newPeriods(newAdaptationSets(newRepresentations(null /*no init data*/)))); - - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); - - assertNull(offlineLicenseKeySetId); + public void testDownloadLicenseFailsIfNullInitData() throws Exception { + try { + offlineLicenseHelper.downloadLicense(null); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } } - public void testDownloadFailsIfThereIsNoRepresentation() 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 { + public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertNull(offlineLicenseKeySetId); } - public void testDownloadDoesNotFailIfDurationNotAvailable() throws Exception { + public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception { setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertNotNull(offlineLicenseKeySetId); } @@ -142,9 +102,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { int playbackDuration = 200; setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration); setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); Pair licenseDurationRemainingSec = offlineLicenseHelper .getLicenseDurationRemainingSec(offlineLicenseKeySetId); @@ -158,9 +117,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { int playbackDuration = 0; setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration); setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); Pair licenseDurationRemainingSec = offlineLicenseHelper .getLicenseDurationRemainingSec(offlineLicenseKeySetId); @@ -169,12 +127,6 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { assertEquals(playbackDuration, (long) licenseDurationRemainingSec.second); } - private void setDefaultStubValues() - throws android.media.NotProvisionedException, android.media.DeniedByServerException { - setDefaultStubKeySetId(); - setStubLicenseAndPlaybackDurationValues(1000, 200); - } - private void setDefaultStubKeySetId() throws android.media.NotProvisionedException, android.media.DeniedByServerException { setStubKeySetId(new byte[] {2, 5, 8}); @@ -201,34 +153,9 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { 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() { 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})); } } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java new file mode 100644 index 0000000000..3ee76f43bb --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -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; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java index ad44574af9..93f3b396c8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java @@ -22,15 +22,9 @@ import android.os.Handler; import android.os.HandlerThread; import android.util.Pair; 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.Mode; 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.Factory; import com.google.android.exoplayer2.util.Assertions; @@ -38,8 +32,7 @@ import java.io.IOException; import java.util.HashMap; /** - * Helper class to download, renew and release offline licenses. It utilizes {@link - * DefaultDrmSessionManager}. + * Helper class to download, renew and release offline licenses. */ public final class OfflineLicenseHelper { @@ -48,8 +41,8 @@ public final class OfflineLicenseHelper { private final HandlerThread handlerThread; /** - * Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when - * you're done with the helper instance. + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. * * @param licenseUrl The default license URL. * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. @@ -64,8 +57,8 @@ public final class OfflineLicenseHelper { } /** - * Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when - * you're done with the helper instance. + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. * * @param callback Performs key and provisioning requests. * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument @@ -84,7 +77,7 @@ public final class OfflineLicenseHelper { } /** - * 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 callback Performs key and provisioning requests. @@ -97,7 +90,6 @@ public final class OfflineLicenseHelper { HashMap optionalKeyRequestParameters) { handlerThread = new HandlerThread("OfflineLicenseHelper"); handlerThread.start(); - conditionVariable = new ConditionVariable(); EventListener eventListener = new EventListener() { @Override @@ -124,67 +116,23 @@ public final class OfflineLicenseHelper { optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener); } - /** Releases the used resources. */ - public void releaseResources() { + /** Releases the helper. Should be called when the helper is no longer required. */ + public void release() { handlerThread.quit(); } /** * Downloads an offline license. * - * @param dataSource The {@link HttpDataSource} to be used for download. - * @param manifestUriString The URI of the manifest to be read. - * @return The downloaded offline license key set id. + * @param drmInitData The {@link DrmInitData} for the content whose license is to be downloaded. + * @return The key set id for the downloaded license. * @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. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public byte[] download(HttpDataSource dataSource, String manifestUriString) - throws IOException, InterruptedException, DrmSessionException { - return download(dataSource, DashUtil.loadManifest(dataSource, manifestUriString)); - } - - /** - * 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; - } - } + public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException, + InterruptedException, DrmSessionException { + Assertions.checkArgument(drmInitData != null); blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); return drmSessionManager.getOfflineLicenseKeySetId(); } @@ -193,10 +141,11 @@ public final class OfflineLicenseHelper { * Renews an offline license. * * @param offlineLicenseKeySetId The key set id of the license to be renewed. - * @return Renewed offline license key set id. - * @throws DrmSessionException Thrown when there is an error during DRM session. + * @return The renewed offline license key set id. + * @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); blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); return drmSessionManager.getOfflineLicenseKeySetId(); @@ -206,19 +155,22 @@ public final class OfflineLicenseHelper { * Releases an offline license. * * @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); 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. + * @return The remaining license and playback durations, in seconds. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) + public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); DrmSession session = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java index fc80cfb6fb..e5d014f102 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java @@ -38,7 +38,7 @@ public final class WidevineUtil { * @throws IllegalStateException If called when a session isn't opened. * @param drmSession */ - public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { + public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { Map keyStatus = drmSession.queryKeyStatus(); return new Pair<>( getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 8fca21b2e0..0b8750e20a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.dash; import android.net.Uri; import com.google.android.exoplayer2.C; 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.Extractor; 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.dash.manifest.DashManifest; 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.Representation; 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.util.MimeTypes; import java.io.IOException; +import java.util.List; /** * 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 manifestUriString The URI of the manifest to be read. * @return An instance of {@link DashManifest}. - * @throws IOException If an error occurs reading data from the stream. - * @see DashManifestParser + * @throws IOException Thrown when there is an error while loading. */ public static DashManifest loadManifest(DataSource dataSource, String manifestUriString) throws IOException { @@ -63,8 +65,35 @@ public final class DashUtil { } /** - * Loads initialization data for the {@code representation} and returns the sample {@link - * Format}. + * Loads {@link DrmInitData} for a given period in a DASH manifest. + * + * @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 representation The representation which initialization chunk belongs to. @@ -155,6 +184,15 @@ public final class DashUtil { 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 representations = period.adaptationSets.get(index).representations; + return representations.isEmpty() ? null : representations.get(0); + } + private DashUtil() {} } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java index 99a6f3bef5..44f25b49d9 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java @@ -18,11 +18,15 @@ package com.google.android.exoplayer2.playbacktests.gts; import android.media.MediaDrm.MediaDrmStateException; import android.test.ActivityInstrumentationTestCase2; 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.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.OfflineLicenseHelper; import com.google.android.exoplayer2.playbacktests.util.ActionSchedule; 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.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -72,7 +76,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa releaseLicense(); } if (offlineLicenseHelper != null) { - offlineLicenseHelper.releaseResources(); + offlineLicenseHelper.release(); } offlineLicenseHelper = null; httpDataSourceFactory = null; @@ -89,7 +93,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa testRunner.run(); // Renew license after playback should still work - offlineLicenseKeySetId = offlineLicenseHelper.renew(offlineLicenseKeySetId); + offlineLicenseKeySetId = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId); Assert.assertNotNull(offlineLicenseKeySetId); } @@ -164,15 +168,18 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa } private void downloadLicense() throws InterruptedException, DrmSessionException, IOException { - offlineLicenseKeySetId = offlineLicenseHelper.download( - httpDataSourceFactory.createDataSource(), DashTestData.WIDEVINE_H264_MANIFEST); + DataSource dataSource = httpDataSourceFactory.createDataSource(); + 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.assertTrue(offlineLicenseKeySetId.length > 0); testRunner.setOfflineLicenseKeySetId(offlineLicenseKeySetId); } private void releaseLicense() throws DrmSessionException { - offlineLicenseHelper.release(offlineLicenseKeySetId); + offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId); offlineLicenseKeySetId = null; }