From 9c8cd4b5757513f8115cd56e2dda715c1b1b2e71 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 18 May 2020 15:06:27 +0100 Subject: [PATCH] Add DRM data to AnalyticsCollectorTest This requires lots of new DRM plumbing in FakeMedia{Period,Source} and FakeSampleStream. Part of issue:#6765 PiperOrigin-RevId: 312072332 --- .../android/exoplayer2/ExoPlayerTest.java | 29 ++- .../analytics/AnalyticsCollectorTest.java | 136 +++++++++++ .../audio/MediaCodecAudioRendererTest.java | 2 + .../metadata/MetadataRendererTest.java | 2 + .../source/ClippingMediaSourceTest.java | 9 +- .../video/DecoderVideoRendererTest.java | 8 + .../video/MediaCodecVideoRendererTest.java | 13 ++ .../testutil/FakeAdaptiveMediaPeriod.java | 23 +- .../testutil/FakeAdaptiveMediaSource.java | 4 +- .../exoplayer2/testutil/FakeExoMediaDrm.java | 219 ++++++++++++++++++ .../exoplayer2/testutil/FakeMediaPeriod.java | 53 ++++- .../exoplayer2/testutil/FakeMediaSource.java | 31 ++- .../exoplayer2/testutil/FakeRenderer.java | 16 +- .../exoplayer2/testutil/FakeSampleStream.java | 112 +++++++-- 14 files changed, 611 insertions(+), 46 deletions(-) create mode 100644 testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 770416bb4c..ad95fb11c7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Player.EventListener; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; @@ -600,6 +601,7 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher); @@ -635,6 +637,7 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher); @@ -661,6 +664,7 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher); @@ -904,11 +908,16 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { // Defer completing preparation of the period until playback parameters have been set. fakeMediaPeriodHolder[0] = - new FakeMediaPeriod(trackGroupArray, eventDispatcher, /* deferOnPrepared= */ true); + new FakeMediaPeriod( + trackGroupArray, + drmSessionManager, + eventDispatcher, + /* deferOnPrepared= */ true); createPeriodCalledCountDownLatch.countDown(); return fakeMediaPeriodHolder[0]; } @@ -950,11 +959,16 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { // Defer completing preparation of the period until seek has been sent. fakeMediaPeriodHolder[0] = - new FakeMediaPeriod(trackGroupArray, eventDispatcher, /* deferOnPrepared= */ true); + new FakeMediaPeriod( + trackGroupArray, + drmSessionManager, + eventDispatcher, + /* deferOnPrepared= */ true); createPeriodCalledCountDownLatch.countDown(); return fakeMediaPeriodHolder[0]; } @@ -3666,6 +3680,7 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { return new FakeMediaPeriod(trackGroupArray, eventDispatcher) { @@ -6367,6 +6382,7 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { return new FakeMediaPeriod(trackGroupArray, eventDispatcher) { @@ -6442,9 +6458,10 @@ public final class ExoPlayerTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { - return new FakeMediaPeriod(trackGroupArray, eventDispatcher) { + return new FakeMediaPeriod(trackGroupArray, drmSessionManager, eventDispatcher) { private Loader loader = new Loader("oomLoader"); @Override @@ -6456,11 +6473,15 @@ public final class ExoPlayerTest { @Override protected SampleStream createSampleStream( - long positionUs, TrackSelection selection, EventDispatcher eventDispatcher) { + long positionUs, + TrackSelection selection, + DrmSessionManager drmSessionManager, + EventDispatcher eventDispatcher) { // Create 3 samples without end of stream signal to test that all 3 samples are // still played before the exception is thrown. return new FakeSampleStream( selection.getSelectedFormat(), + drmSessionManager, eventDispatcher, positionUs, /* timeUsIncrement= */ 0, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index 4d4d25e486..891d7f28bb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -31,6 +31,12 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.ExoMediaDrm; +import com.google.android.exoplayer2.drm.MediaDrmCallback; +import com.google.android.exoplayer2.drm.MediaDrmCallbackException; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.LoadEventInfo; @@ -43,16 +49,19 @@ import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.FakeAudioRenderer; +import com.google.android.exoplayer2.testutil.FakeExoMediaDrm; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.FakeVideoRenderer; +import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; @@ -107,6 +116,24 @@ public final class AnalyticsCollectorTest { private static final int EVENT_DRM_SESSION_RELEASED = 38; private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 39; + private static final UUID DRM_SCHEME_UUID = + UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9)); + + public static final DrmInitData DRM_DATA_1 = + new DrmInitData( + new DrmInitData.SchemeData( + DRM_SCHEME_UUID, + ExoPlayerTestRunner.VIDEO_FORMAT.sampleMimeType, + /* data= */ TestUtil.createByteArray(1, 2, 3))); + public static final DrmInitData DRM_DATA_2 = + new DrmInitData( + new DrmInitData.SchemeData( + DRM_SCHEME_UUID, + ExoPlayerTestRunner.VIDEO_FORMAT.sampleMimeType, + /* data= */ TestUtil.createByteArray(4, 5, 6))); + private static final Format VIDEO_FORMAT_DRM_1 = + ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setDrmInitData(DRM_DATA_1).build(); + private static final int TIMEOUT_MS = 10000; private static final Timeline SINGLE_PERIOD_TIMELINE = new FakeTimeline(/* windowCount= */ 1); private static final EventWindowAndPeriodId WINDOW_0 = @@ -114,6 +141,12 @@ public final class AnalyticsCollectorTest { private static final EventWindowAndPeriodId WINDOW_1 = new EventWindowAndPeriodId(/* windowIndex= */ 1, /* mediaPeriodId= */ null); + private final DrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm()) + .setMultiSession(true) + .build(new EmptyDrmCallback()); + private EventWindowAndPeriodId period0; private EventWindowAndPeriodId period1; private EventWindowAndPeriodId period0Seq0; @@ -1158,6 +1191,71 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0); } + @Test + public void drmEvents_singlePeriod() throws Exception { + MediaSource mediaSource = + new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1); + TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + + populateEventIds(listener.lastReportedTimeline); + assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty(); + assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0); + // The release event is lost because it's posted to "ExoPlayerTest thread" after that thread + // has been quit during clean-up. + assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).isEmpty(); + } + + @Test + public void drmEvents_periodWithSameDrmData_keysReused() throws Exception { + MediaSource mediaSource = + new ConcatenatingMediaSource( + new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1), + new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1)); + TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + + populateEventIds(listener.lastReportedTimeline); + assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty(); + assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)).containsExactly(period0, period1); + assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0); + // The period1 release event is lost because it's posted to "ExoPlayerTest thread" after that + // thread has been quit during clean-up. + assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0); + } + + @Test + public void drmEvents_periodWithDifferentDrmData_keysLoadedAgain() throws Exception { + MediaSource mediaSource = + new ConcatenatingMediaSource( + new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1), + new FakeMediaSource( + SINGLE_PERIOD_TIMELINE, + drmSessionManager, + VIDEO_FORMAT_DRM_1.buildUpon().setDrmInitData(DRM_DATA_2).build())); + TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + + populateEventIds(listener.lastReportedTimeline); + assertThat(listener.getEvents(EVENT_DRM_ERROR)).isEmpty(); + assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED)).containsExactly(period0, period1); + assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0, period1); + // The period1 release event is lost because it's posted to "ExoPlayerTest thread" after that + // thread has been quit during clean-up. + assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0); + } + + @Test + public void drmEvents_errorHandling() throws Exception { + DrmSessionManager failingDrmSessionManager = + new DefaultDrmSessionManager.Builder().build(new FailingDrmCallback()); + MediaSource mediaSource = + new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1); + TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + + populateEventIds(listener.lastReportedTimeline); + assertThat(listener.getEvents(EVENT_DRM_ERROR)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0); + } + private void populateEventIds(Timeline timeline) { period0 = new EventWindowAndPeriodId( @@ -1544,4 +1642,42 @@ public final class AnalyticsCollectorTest { } } } + + /** + * A {@link MediaDrmCallback} that returns empty byte arrays for both {@link + * #executeProvisionRequest(UUID, ExoMediaDrm.ProvisionRequest)} and {@link + * #executeKeyRequest(UUID, ExoMediaDrm.KeyRequest)}. + */ + private static final class EmptyDrmCallback implements MediaDrmCallback { + @Override + public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) + throws MediaDrmCallbackException { + return new byte[0]; + } + + @Override + public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) + throws MediaDrmCallbackException { + return new byte[0]; + } + } + + /** + * A {@link MediaDrmCallback} that throws exceptions for both {@link + * #executeProvisionRequest(UUID, ExoMediaDrm.ProvisionRequest)} and {@link + * #executeKeyRequest(UUID, ExoMediaDrm.KeyRequest)}. + */ + private static final class FailingDrmCallback implements MediaDrmCallback { + @Override + public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) + throws MediaDrmCallbackException { + throw new RuntimeException("executeProvision failed"); + } + + @Override + public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) + throws MediaDrmCallbackException { + throw new RuntimeException("executeKey failed"); + } + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java index 48fbdf5564..9741200dfb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; @@ -111,6 +112,7 @@ public class MediaCodecAudioRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ AUDIO_AAC, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java index 4d1b4f601b..cf57f15f2f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java @@ -22,6 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; @@ -144,6 +145,7 @@ public class MetadataRendererTest { new Format[] {EMSG_FORMAT}, new FakeSampleStream( EMSG_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 0, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index a2ac9fd54c..c371ec0451 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.source.MaskingMediaSource.DummyTimeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; @@ -566,6 +567,7 @@ public final class ClippingMediaSourceTest { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { eventDispatcher.downstreamFormatChanged( @@ -578,7 +580,12 @@ public final class ClippingMediaSourceTest { C.usToMs(eventStartUs), C.usToMs(eventEndUs))); return super.createFakeMediaPeriod( - id, trackGroupArray, allocator, eventDispatcher, transferListener); + id, + trackGroupArray, + allocator, + drmSessionManager, + eventDispatcher, + transferListener); } }; final ClippingMediaSource clippingMediaSource = diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java index 17ba57698f..0c1c2787a6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.SimpleDecoder; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.testutil.FakeSampleStream; import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; @@ -185,6 +186,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -213,6 +215,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -240,6 +243,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -270,6 +274,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -278,6 +283,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream2 = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -314,6 +320,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -322,6 +329,7 @@ public final class DecoderVideoRendererTest { FakeSampleStream fakeSampleStream2 = new FakeSampleStream( /* format= */ H264_FORMAT, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index 4ec7bd8043..85b7604e42 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; @@ -126,6 +127,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50_000, @@ -162,6 +164,7 @@ public class MediaCodecVideoRendererTest { new Format[] {VIDEO_H264}, new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 0, @@ -199,6 +202,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ pAsp1, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 5000, @@ -248,6 +252,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -282,6 +287,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -308,6 +314,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -333,6 +340,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -359,6 +367,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -367,6 +376,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream2 = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -401,6 +411,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -409,6 +420,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream2 = new FakeSampleStream( /* format= */ VIDEO_H264, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, @@ -450,6 +462,7 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( /* format= */ mp4Uhd, + DrmSessionManager.DUMMY, /* eventDispatcher= */ null, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 50, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index 7d74cc2e66..67b08cbd58 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -67,14 +67,6 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod this.sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); } - @Override - public void release() { - for (ChunkSampleStream sampleStream : sampleStreams) { - sampleStream.release(); - } - super.release(); - } - @Override public synchronized void prepare(Callback callback, long positionUs) { super.prepare(callback, positionUs); @@ -141,8 +133,11 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod } @Override - protected SampleStream createSampleStream( - long positionUs, TrackSelection trackSelection, EventDispatcher eventDispatcher) { + protected final SampleStream createSampleStream( + long positionUs, + TrackSelection trackSelection, + DrmSessionManager drmSessionManager, + EventDispatcher eventDispatcher) { FakeChunkSource chunkSource = chunkSourceFactory.createChunkSource(trackSelection, durationUs, transferListener); return new ChunkSampleStream<>( @@ -159,11 +154,19 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod } @Override + // sampleStream is created by createSampleStream() above. @SuppressWarnings("unchecked") protected void seekSampleStream(SampleStream sampleStream, long positionUs) { ((ChunkSampleStream) sampleStream).seekToUs(positionUs); } + @Override + // sampleStream is created by createSampleStream() above. + @SuppressWarnings("unchecked") + protected void releaseSampleStream(SampleStream sampleStream) { + ((ChunkSampleStream) sampleStream).release(); + } + @Override public void onContinueLoadingRequested(ChunkSampleStream source) { Assertions.checkStateNotNull(callback).onContinueLoadingRequested(this); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java index 0f171dd009..451293746d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -37,7 +38,7 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { Timeline timeline, TrackGroupArray trackGroupArray, FakeChunkSource.Factory chunkSourceFactory) { - super(timeline, trackGroupArray); + super(timeline, DrmSessionManager.DUMMY, trackGroupArray); this.chunkSourceFactory = chunkSourceFactory; } @@ -46,6 +47,7 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { Period period = Util.castNonNull(getTimeline()).getPeriodByUid(id.periodUid, new Period()); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java new file mode 100644 index 0000000000..6e4b4f2437 --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2020 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.testutil; + +import android.media.DeniedByServerException; +import android.media.MediaCryptoException; +import android.media.MediaDrmException; +import android.media.NotProvisionedException; +import android.os.PersistableBundle; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.ExoMediaCrypto; +import com.google.android.exoplayer2.drm.ExoMediaDrm; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** A fake implementation of {@link ExoMediaDrm} for use in tests. */ +@RequiresApi(18) +public class FakeExoMediaDrm implements ExoMediaDrm { + + private static final KeyRequest DUMMY_KEY_REQUEST = + new KeyRequest(TestUtil.createByteArray(4, 5, 6), "foo.test"); + + private static final ProvisionRequest DUMMY_PROVISION_REQUEST = + new ProvisionRequest(TestUtil.createByteArray(7, 8, 9), "bar.test"); + + private final Map byteProperties; + private final Map stringProperties; + private final Set> openSessionIds; + private final AtomicInteger sessionIdGenerator; + + private int referenceCount; + + /** + * Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls + * to {@link #openSession()}. + */ + public FakeExoMediaDrm() { + byteProperties = new HashMap<>(); + stringProperties = new HashMap<>(); + openSessionIds = new HashSet<>(); + sessionIdGenerator = new AtomicInteger(); + + referenceCount = 1; + } + + @Override + public void setOnEventListener(@Nullable OnEventListener listener) { + // Do nothing. + } + + @Override + public void setOnKeyStatusChangeListener(@Nullable OnKeyStatusChangeListener listener) { + // Do nothing. + } + + @Override + public void setOnExpirationUpdateListener(@Nullable OnExpirationUpdateListener listener) { + // Do nothing. + } + + @Override + public byte[] openSession() throws MediaDrmException { + Assertions.checkState(referenceCount > 0); + byte[] sessionId = + TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet()); + if (!openSessionIds.add(toByteList(sessionId))) { + throw new MediaDrmException( + Util.formatInvariant( + "Generated sessionId[%s] clashes with already-open session", + sessionIdGenerator.get())); + } + return sessionId; + } + + @Override + public void closeSession(byte[] sessionId) { + Assertions.checkState(referenceCount > 0); + Assertions.checkState(openSessionIds.remove(toByteList(sessionId))); + } + + @Override + public KeyRequest getKeyRequest( + byte[] scope, + @Nullable List schemeDatas, + int keyType, + @Nullable HashMap optionalParameters) + throws NotProvisionedException { + Assertions.checkState(referenceCount > 0); + return DUMMY_KEY_REQUEST; + } + + @Nullable + @Override + public byte[] provideKeyResponse(byte[] scope, byte[] response) + throws NotProvisionedException, DeniedByServerException { + Assertions.checkState(referenceCount > 0); + return null; + } + + @Override + public ProvisionRequest getProvisionRequest() { + Assertions.checkState(referenceCount > 0); + return DUMMY_PROVISION_REQUEST; + } + + @Override + public void provideProvisionResponse(byte[] response) throws DeniedByServerException { + Assertions.checkState(referenceCount > 0); + } + + @Override + public Map queryKeyStatus(byte[] sessionId) { + Assertions.checkState(referenceCount > 0); + return Collections.emptyMap(); + } + + @Override + public void acquire() { + Assertions.checkState(referenceCount > 0); + referenceCount++; + } + + @Override + public void release() { + referenceCount--; + } + + @Override + public void restoreKeys(byte[] sessionId, byte[] keySetId) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public PersistableBundle getMetrics() { + Assertions.checkState(referenceCount > 0); + + return null; + } + + @Override + public String getPropertyString(String propertyName) { + Assertions.checkState(referenceCount > 0); + @Nullable String value = stringProperties.get(propertyName); + if (value == null) { + throw new IllegalArgumentException("Unrecognized propertyName: " + propertyName); + } + return value; + } + + @Override + public byte[] getPropertyByteArray(String propertyName) { + Assertions.checkState(referenceCount > 0); + @Nullable byte[] value = byteProperties.get(propertyName); + if (value == null) { + throw new IllegalArgumentException("Unrecognized propertyName: " + propertyName); + } + return value; + } + + @Override + public void setPropertyString(String propertyName, String value) { + Assertions.checkState(referenceCount > 0); + stringProperties.put(propertyName, value); + } + + @Override + public void setPropertyByteArray(String propertyName, byte[] value) { + Assertions.checkState(referenceCount > 0); + byteProperties.put(propertyName, value); + } + + @Override + public ExoMediaCrypto createMediaCrypto(byte[] sessionId) throws MediaCryptoException { + Assertions.checkState(referenceCount > 0); + Assertions.checkState(openSessionIds.contains(toByteList(sessionId))); + return new FakeExoMediaCrypto(); + } + + @Nullable + @Override + public Class getExoMediaCryptoType() { + return FakeExoMediaCrypto.class; + } + + private static List toByteList(byte[] byteArray) { + List result = new ArrayList<>(byteArray.length); + for (byte b : byteArray) { + result.add(b); + } + return result; + } + + private static class FakeExoMediaCrypto implements ExoMediaCrypto {} +} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java index 89e5451e80..35fd2d7f0e 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.SeekParameters; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; @@ -49,6 +50,7 @@ public class FakeMediaPeriod implements MediaPeriod { private final TrackGroupArray trackGroupArray; private final List sampleStreams; + private final DrmSessionManager drmSessionManager; private final EventDispatcher eventDispatcher; private final long fakePreparationLoadTaskId; @@ -62,23 +64,46 @@ public class FakeMediaPeriod implements MediaPeriod { private long discontinuityPositionUs; /** + * Constructs a FakeMediaPeriod. + * * @param trackGroupArray The track group array. * @param eventDispatcher A dispatcher for media source events. */ public FakeMediaPeriod(TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher) { - this(trackGroupArray, eventDispatcher, /* deferOnPrepared */ false); + this(trackGroupArray, DrmSessionManager.DUMMY, eventDispatcher, /* deferOnPrepared */ false); } /** + * Constructs a FakeMediaPeriod. + * * @param trackGroupArray The track group array. + * @param drmSessionManager The {@link DrmSessionManager} used for DRM interactions. + * @param eventDispatcher A dispatcher for media source events. + */ + public FakeMediaPeriod( + TrackGroupArray trackGroupArray, + DrmSessionManager drmSessionManager, + EventDispatcher eventDispatcher) { + this(trackGroupArray, drmSessionManager, eventDispatcher, /* deferOnPrepared */ false); + } + + /** + * Constructs a FakeMediaPeriod. + * + * @param trackGroupArray The track group array. + * @param drmSessionManager The DrmSessionManager used for DRM interactions. * @param eventDispatcher A dispatcher for media source events. * @param deferOnPrepared Whether {@link MediaPeriod.Callback#onPrepared(MediaPeriod)} should be * called only after {@link #setPreparationComplete()} has been called. If {@code false} * preparation completes immediately. */ public FakeMediaPeriod( - TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher, boolean deferOnPrepared) { + TrackGroupArray trackGroupArray, + DrmSessionManager drmSessionManager, + EventDispatcher eventDispatcher, + boolean deferOnPrepared) { this.trackGroupArray = trackGroupArray; + this.drmSessionManager = drmSessionManager; this.eventDispatcher = eventDispatcher; this.deferOnPrepared = deferOnPrepared; discontinuityPositionUs = C.TIME_UNSET; @@ -118,6 +143,9 @@ public class FakeMediaPeriod implements MediaPeriod { public void release() { prepared = false; + for (int i = 0; i < sampleStreams.size(); i++) { + releaseSampleStream(sampleStreams.get(i)); + } eventDispatcher.mediaPeriodReleased(); } @@ -173,7 +201,7 @@ public class FakeMediaPeriod implements MediaPeriod { int indexInTrackGroup = selection.getIndexInTrackGroup(selection.getSelectedIndex()); assertThat(indexInTrackGroup).isAtLeast(0); assertThat(indexInTrackGroup).isLessThan(trackGroup.length); - streams[i] = createSampleStream(positionUs, selection, eventDispatcher); + streams[i] = createSampleStream(positionUs, selection, drmSessionManager, eventDispatcher); sampleStreams.add(streams[i]); streamResetFlags[i] = true; } @@ -245,13 +273,18 @@ public class FakeMediaPeriod implements MediaPeriod { * * @param positionUs The position at which the tracks were selected, in microseconds. * @param selection A selection of tracks. + * @param drmSessionManager The DRM session manager. * @param eventDispatcher A dispatcher for events that should be used by the sample stream. * @return A {@link SampleStream} for this selection. */ protected SampleStream createSampleStream( - long positionUs, TrackSelection selection, EventDispatcher eventDispatcher) { + long positionUs, + TrackSelection selection, + DrmSessionManager drmSessionManager, + EventDispatcher eventDispatcher) { return new FakeSampleStream( selection.getSelectedFormat(), + drmSessionManager, eventDispatcher, positionUs, /* timeUsIncrement= */ 0, @@ -262,7 +295,7 @@ public class FakeMediaPeriod implements MediaPeriod { * Seeks inside the given sample stream. * * @param sampleStream A sample stream that was created by a call to {@link - * #createSampleStream(long, TrackSelection, EventDispatcher)}. + * #createSampleStream(long, TrackSelection, DrmSessionManager, EventDispatcher)}. * @param positionUs The position to seek to, in microseconds. */ protected void seekSampleStream(SampleStream sampleStream, long positionUs) { @@ -271,6 +304,16 @@ public class FakeMediaPeriod implements MediaPeriod { .resetSampleStreamItems(positionUs, FakeSampleStream.SINGLE_SAMPLE_THEN_END_OF_STREAM); } + /** + * Releases the given sample stream. + * + * @param sampleStream A sample stream that was created by a call to {@link + * #createSampleStream(long, TrackSelection, DrmSessionManager, EventDispatcher)}. + */ + protected void releaseSampleStream(SampleStream sampleStream) { + ((FakeSampleStream) sampleStream).release(); + } + private void finishPreparation() { prepared = true; Util.castNonNull(prepareCallback).onPrepared(this); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 1ff8f48c22..cb55127c22 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.BaseMediaSource; import com.google.android.exoplayer2.source.ForwardingTimeline; import com.google.android.exoplayer2.source.LoadEventInfo; @@ -73,6 +74,7 @@ public class FakeMediaSource extends BaseMediaSource { private final TrackGroupArray trackGroupArray; private final ArrayList activeMediaPeriods; private final ArrayList createdMediaPeriods; + private final DrmSessionManager drmSessionManager; private @MonotonicNonNull Timeline timeline; private boolean preparedSource; @@ -87,7 +89,19 @@ public class FakeMediaSource extends BaseMediaSource { * can be manually set later using {@link #setNewSourceInfo(Timeline)}. */ public FakeMediaSource(@Nullable Timeline timeline, Format... formats) { - this(timeline, buildTrackGroupArray(formats)); + this(timeline, DrmSessionManager.DUMMY, formats); + } + + /** + * Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with a + * {@link TrackGroupArray} using the given {@link Format}s. It passes {@code drmSessionManager} + * into the created periods. The provided {@link Timeline} may be null to prevent an immediate + * source info refresh message when preparing the media source. It can be manually set later using + * {@link #setNewSourceInfo(Timeline)}. + */ + public FakeMediaSource( + @Nullable Timeline timeline, DrmSessionManager drmSessionManager, Format... formats) { + this(timeline, drmSessionManager, buildTrackGroupArray(formats)); } /** @@ -96,13 +110,17 @@ public class FakeMediaSource extends BaseMediaSource { * immediate source info refresh message when preparing the media source. It can be manually set * later using {@link #setNewSourceInfo(Timeline)}. */ - public FakeMediaSource(@Nullable Timeline timeline, TrackGroupArray trackGroupArray) { + public FakeMediaSource( + @Nullable Timeline timeline, + DrmSessionManager drmSessionManager, + TrackGroupArray trackGroupArray) { if (timeline != null) { this.timeline = timeline; } this.trackGroupArray = trackGroupArray; this.activeMediaPeriods = new ArrayList<>(); this.createdMediaPeriods = new ArrayList<>(); + this.drmSessionManager = drmSessionManager; } @Nullable @@ -136,6 +154,7 @@ public class FakeMediaSource extends BaseMediaSource { public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { assertThat(preparedSource).isFalse(); transferListener = mediaTransferListener; + drmSessionManager.prepare(); preparedSource = true; releasedSource = false; sourceInfoRefreshHandler = Util.createHandler(); @@ -159,7 +178,8 @@ public class FakeMediaSource extends BaseMediaSource { EventDispatcher eventDispatcher = createEventDispatcher(period.windowIndex, id, period.getPositionInWindowMs()); FakeMediaPeriod mediaPeriod = - createFakeMediaPeriod(id, trackGroupArray, allocator, eventDispatcher, transferListener); + createFakeMediaPeriod( + id, trackGroupArray, allocator, drmSessionManager, eventDispatcher, transferListener); activeMediaPeriods.add(mediaPeriod); createdMediaPeriods.add(id); return mediaPeriod; @@ -179,6 +199,7 @@ public class FakeMediaSource extends BaseMediaSource { assertThat(preparedSource).isTrue(); assertThat(releasedSource).isFalse(); assertThat(activeMediaPeriods.isEmpty()).isTrue(); + drmSessionManager.release(); releasedSource = true; preparedSource = false; Util.castNonNull(sourceInfoRefreshHandler).removeCallbacksAndMessages(null); @@ -242,9 +263,11 @@ public class FakeMediaSource extends BaseMediaSource { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, + DrmSessionManager drmSessionManager, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { - return new FakeMediaPeriod(trackGroupArray, eventDispatcher); + return new FakeMediaPeriod( + trackGroupArray, drmSessionManager, eventDispatcher, /* deferOnPrepared= */ false); } private void finishSourcePreparation() { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java index b79e211f65..e2e6e2e27a 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer2.testutil; - +import androidx.annotation.Nullable; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -49,6 +50,8 @@ public class FakeRenderer extends BaseRenderer { private final DecoderInputBuffer buffer; + @Nullable private DrmSession currentDrmSession; + private long playbackPositionUs; private long lastSamplePositionUs; private boolean hasPendingBuffer; @@ -91,7 +94,10 @@ public class FakeRenderer extends BaseRenderer { buffer.clear(); @SampleStream.ReadDataResult int result = readSource(formatHolder, buffer, /* formatRequired= */ false); + if (result == C.RESULT_FORMAT_READ) { + DrmSession.replaceSession(currentDrmSession, formatHolder.drmSession); + currentDrmSession = formatHolder.drmSession; Format format = Assertions.checkNotNull(formatHolder.format); if (MimeTypes.getTrackType(format.sampleMimeType) != getTrackType()) { throw ExoPlaybackException.createForRenderer( @@ -147,6 +153,14 @@ public class FakeRenderer extends BaseRenderer { : RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE); } + @Override + protected void onReset() { + if (currentDrmSession != null) { + currentDrmSession.release(/* eventDispatcher= */ null); + currentDrmSession = null; + } + } + /** Called when the renderer reads a new format. */ protected void onFormatChanged(Format format) {} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java index 692cf6e399..0d84bcb48c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java @@ -15,16 +15,24 @@ */ package com.google.android.exoplayer2.testutil; +import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.DrmSession; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayDeque; import java.util.Arrays; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Fake {@link SampleStream} that outputs a given {@link Format}, any amount of {@link @@ -85,15 +93,16 @@ public class FakeSampleStream implements SampleStream { new FakeSampleStreamItem(new byte[] {0}), FakeSampleStreamItem.END_OF_STREAM_ITEM }; + private final Format initialFormat; private final ArrayDeque fakeSampleStreamItems; private final int timeUsIncrement; - + private final DrmSessionManager drmSessionManager; @Nullable private final EventDispatcher eventDispatcher; - private Format format; + private @MonotonicNonNull Format downstreamFormat; private long timeUs; - private boolean readFormat; private boolean readEOSBuffer; + @Nullable private DrmSession currentDrmSession; /** * Creates fake sample stream which outputs the given {@link Format}, optionally one sample with @@ -107,6 +116,7 @@ public class FakeSampleStream implements SampleStream { Format format, @Nullable EventDispatcher eventDispatcher, boolean shouldOutputSample) { this( format, + DrmSessionManager.DUMMY, eventDispatcher, /* firstSampleTimeUs= */ 0, /* timeUsIncrement= */ 0, @@ -120,6 +130,7 @@ public class FakeSampleStream implements SampleStream { * FakeSampleStreamItem items}, then end of stream. * * @param format The {@link Format} to output. + * @param drmSessionManager A {@link DrmSessionManager} for DRM interactions. * @param eventDispatcher An {@link EventDispatcher} to notify of read events. * @param firstSampleTimeUs The time at which samples will start being output, in microseconds. * @param timeUsIncrement The time each sample should increase by, in microseconds. @@ -129,11 +140,13 @@ public class FakeSampleStream implements SampleStream { */ public FakeSampleStream( Format format, + DrmSessionManager drmSessionManager, @Nullable EventDispatcher eventDispatcher, long firstSampleTimeUs, int timeUsIncrement, FakeSampleStreamItem... fakeSampleStreamItems) { - this.format = format; + this.initialFormat = format; + this.drmSessionManager = drmSessionManager; this.eventDispatcher = eventDispatcher; this.fakeSampleStreamItems = new ArrayDeque<>(Arrays.asList(fakeSampleStreamItems)); this.timeUs = firstSampleTimeUs; @@ -164,16 +177,21 @@ public class FakeSampleStream implements SampleStream { @Override public boolean isReady() { - return !readFormat || readEOSBuffer || !fakeSampleStreamItems.isEmpty(); + if (fakeSampleStreamItems.isEmpty()) { + return readEOSBuffer || downstreamFormat == null; + } + if (fakeSampleStreamItems.peek().format != null) { + // A format can be read. + return true; + } + return mayReadSample(); } @Override public int readData( FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - if (!readFormat || formatRequired) { - readFormat = true; - formatHolder.format = format; - notifyEventDispatcher(formatHolder); + if (downstreamFormat == null || formatRequired) { + onFormatResult(downstreamFormat == null ? initialFormat : downstreamFormat, formatHolder); return C.RESULT_FORMAT_READ; } // Once an EOS buffer has been read, send EOS every time. @@ -181,33 +199,79 @@ public class FakeSampleStream implements SampleStream { buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } + if (!fakeSampleStreamItems.isEmpty()) { FakeSampleStreamItem fakeSampleStreamItem = fakeSampleStreamItems.remove(); if (fakeSampleStreamItem.format != null) { - format = fakeSampleStreamItem.format; - formatHolder.format = format; - notifyEventDispatcher(formatHolder); + onFormatResult(fakeSampleStreamItem.format, formatHolder); return C.RESULT_FORMAT_READ; - } - if (fakeSampleStreamItem.sampleData != null) { - byte[] sampleData = fakeSampleStreamItem.sampleData; + } else { + byte[] sampleData = Assertions.checkNotNull(fakeSampleStreamItem.sampleData); + if (fakeSampleStreamItem.flags != 0) { + buffer.setFlags(fakeSampleStreamItem.flags); + if (buffer.isEndOfStream()) { + readEOSBuffer = true; + return C.RESULT_BUFFER_READ; + } + } + if (!mayReadSample()) { + // Put the item back so we can consume it next time. + fakeSampleStreamItems.addFirst(fakeSampleStreamItem); + return C.RESULT_NOTHING_READ; + } buffer.timeUs = timeUs; timeUs += timeUsIncrement; buffer.ensureSpaceForWrite(sampleData.length); buffer.data.put(sampleData); - if (fakeSampleStreamItem.flags != 0) { - buffer.setFlags(fakeSampleStreamItem.flags); - readEOSBuffer = buffer.isEndOfStream(); - } return C.RESULT_BUFFER_READ; } } return C.RESULT_NOTHING_READ; } + private void onFormatResult(Format newFormat, FormatHolder outputFormatHolder) { + outputFormatHolder.format = newFormat; + @Nullable + DrmInitData oldDrmInitData = downstreamFormat == null ? null : downstreamFormat.drmInitData; + boolean isFirstFormat = downstreamFormat == null; + downstreamFormat = newFormat; + @Nullable DrmInitData newDrmInitData = newFormat.drmInitData; + outputFormatHolder.drmSession = currentDrmSession; + notifyEventDispatcher(outputFormatHolder); + if (!isFirstFormat && Util.areEqual(oldDrmInitData, newDrmInitData)) { + // Nothing to do. + return; + } + // Ensure we acquire the new session before releasing the previous one in case the same session + // is being used for both DrmInitData. + @Nullable DrmSession previousSession = currentDrmSession; + Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper()); + currentDrmSession = + newDrmInitData != null + ? drmSessionManager.acquireSession(playbackLooper, eventDispatcher, newDrmInitData) + : drmSessionManager.acquirePlaceholderSession( + playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType)); + outputFormatHolder.drmSession = currentDrmSession; + + if (previousSession != null) { + previousSession.release(eventDispatcher); + } + } + + private boolean mayReadSample() { + @Nullable DrmSession drmSession = this.currentDrmSession; + return drmSession == null + || drmSession.getState() == DrmSession.STATE_OPENED_WITH_KEYS + || (!fakeSampleStreamItems.isEmpty() + && (fakeSampleStreamItems.peek().flags & C.BUFFER_FLAG_ENCRYPTED) == 0 + && drmSession.playClearSamplesWithoutKeys()); + } + @Override public void maybeThrowError() throws IOException { - // Do nothing. + if (currentDrmSession != null && currentDrmSession.getState() == DrmSession.STATE_ERROR) { + throw Assertions.checkNotNull(currentDrmSession.getError()); + } } @Override @@ -215,6 +279,14 @@ public class FakeSampleStream implements SampleStream { return 0; } + /** Release this SampleStream and all underlying resources. */ + public void release() { + if (currentDrmSession != null) { + currentDrmSession.release(eventDispatcher); + currentDrmSession = null; + } + } + private void notifyEventDispatcher(FormatHolder formatHolder) { if (eventDispatcher != null) { eventDispatcher.downstreamFormatChanged(