From 2a8cf2f5efa97bd4bcdc2a25cd93fb17f0d2d922 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 23 Jul 2019 13:53:59 +0100 Subject: [PATCH] Plumb DrmSessionManager into HlsMediaSource PiperOrigin-RevId: 259520431 --- .../exoplayer2/demo/PlayerActivity.java | 4 +- .../exoplayer2/source/hls/HlsMediaPeriod.java | 8 +++ .../exoplayer2/source/hls/HlsMediaSource.java | 23 +++++++ .../source/hls/HlsSampleStream.java | 5 +- .../source/hls/HlsSampleStreamWrapper.java | 63 ++++++++++++++----- .../source/hls/HlsMediaPeriodTest.java | 2 + 6 files changed, 87 insertions(+), 18 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 249223bff5..1f60224b28 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -484,7 +484,9 @@ public class PlayerActivity extends AppCompatActivity .setDrmSessionManager(drmSessionManager) .createMediaSource(uri); case C.TYPE_HLS: - return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + return new HlsMediaSource.Factory(dataSourceFactory) + .setDrmSessionManager(drmSessionManager) + .createMediaSource(uri); case C.TYPE_OTHER: return new ProgressiveMediaSource.Factory(dataSourceFactory) .setDrmSessionManager(drmSessionManager) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 39b49da402..e21827557a 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -22,6 +22,8 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.SeekParameters; 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.extractor.Extractor; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; @@ -63,6 +65,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private final HlsPlaylistTracker playlistTracker; private final HlsDataSourceFactory dataSourceFactory; @Nullable private final TransferListener mediaTransferListener; + private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final EventDispatcher eventDispatcher; private final Allocator allocator; @@ -91,6 +94,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper * and keys. * @param mediaTransferListener The transfer listener to inform of any media data transfers. May * be null if no listener is available. + * @param drmSessionManager The {@link DrmSessionManager} to acquire {@link DrmSession + * DrmSessions} with. * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. * @param eventDispatcher A dispatcher to notify of events. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. @@ -104,6 +109,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, @Nullable TransferListener mediaTransferListener, + DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher, Allocator allocator, @@ -114,6 +120,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper this.playlistTracker = playlistTracker; this.dataSourceFactory = dataSourceFactory; this.mediaTransferListener = mediaTransferListener; + this.drmSessionManager = drmSessionManager; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.eventDispatcher = eventDispatcher; this.allocator = allocator; @@ -735,6 +742,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper allocator, positionUs, muxedAudioFormat, + drmSessionManager, loadErrorHandlingPolicy, eventDispatcher); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 12c6a8ee72..877b6d486e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -20,6 +20,8 @@ import android.os.Handler; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; +import com.google.android.exoplayer2.drm.DrmSession; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.BaseMediaSource; @@ -65,6 +67,7 @@ public final class HlsMediaSource extends BaseMediaSource @Nullable private List streamKeys; private HlsPlaylistTracker.Factory playlistTrackerFactory; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; + private DrmSessionManager drmSessionManager; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private boolean allowChunklessPreparation; private boolean useSessionKeys; @@ -93,6 +96,7 @@ public final class HlsMediaSource extends BaseMediaSource playlistParserFactory = new DefaultHlsPlaylistParserFactory(); playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY; extractorFactory = HlsExtractorFactory.DEFAULT; + drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } @@ -127,6 +131,20 @@ public final class HlsMediaSource extends BaseMediaSource return this; } + /** + * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The + * default value is {@link DrmSessionManager#DUMMY}. + * + * @param drmSessionManager The {@link DrmSessionManager}. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setDrmSessionManager(DrmSessionManager drmSessionManager) { + Assertions.checkState(!isCreateCalled); + this.drmSessionManager = drmSessionManager; + return this; + } + /** * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link * DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy()}. @@ -271,6 +289,7 @@ public final class HlsMediaSource extends BaseMediaSource hlsDataSourceFactory, extractorFactory, compositeSequenceableLoaderFactory, + drmSessionManager, loadErrorHandlingPolicy, playlistTrackerFactory.createTracker( hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory), @@ -297,6 +316,7 @@ public final class HlsMediaSource extends BaseMediaSource private final Uri manifestUri; private final HlsDataSourceFactory dataSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; + private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final boolean allowChunklessPreparation; private final boolean useSessionKeys; @@ -310,6 +330,7 @@ public final class HlsMediaSource extends BaseMediaSource HlsDataSourceFactory dataSourceFactory, HlsExtractorFactory extractorFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, HlsPlaylistTracker playlistTracker, boolean allowChunklessPreparation, @@ -319,6 +340,7 @@ public final class HlsMediaSource extends BaseMediaSource this.dataSourceFactory = dataSourceFactory; this.extractorFactory = extractorFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + this.drmSessionManager = drmSessionManager; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.playlistTracker = playlistTracker; this.allowChunklessPreparation = allowChunklessPreparation; @@ -352,6 +374,7 @@ public final class HlsMediaSource extends BaseMediaSource playlistTracker, dataSourceFactory, mediaTransferListener, + drmSessionManager, loadErrorHandlingPolicy, eventDispatcher, allocator, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java index cf879e91c6..c820038b80 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java @@ -62,8 +62,11 @@ import java.io.IOException; if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL) { throw new SampleQueueMappingException( sampleStreamWrapper.getTrackGroups().get(trackGroupIndex).getFormat(0).sampleMimeType); + } else if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING) { + sampleStreamWrapper.maybeThrowError(); + } else if (sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL) { + sampleStreamWrapper.maybeThrowError(sampleQueueIndex); } - sampleStreamWrapper.maybeThrowError(); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 360a7d6f72..c8c1b8f566 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -23,12 +23,15 @@ 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.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.PrivFrame; +import com.google.android.exoplayer2.source.DecryptableSampleQueueReader; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; @@ -94,6 +97,7 @@ import java.util.Set; private final HlsChunkSource chunkSource; private final Allocator allocator; private final Format muxedAudioFormat; + private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final Loader loader; private final EventDispatcher eventDispatcher; @@ -107,6 +111,7 @@ import java.util.Set; private final Map overridingDrmInitData; private SampleQueue[] sampleQueues; + private DecryptableSampleQueueReader[] sampleQueueReaders; private int[] sampleQueueTrackIds; private boolean audioSampleQueueMappingDone; private int audioSampleQueueIndex; @@ -154,6 +159,8 @@ import java.util.Set; * @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param positionUs The position from which to start loading media. * @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist. + * @param drmSessionManager The {@link DrmSessionManager} to acquire {@link DrmSession + * DrmSessions} with. * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. * @param eventDispatcher A dispatcher to notify of events. */ @@ -165,6 +172,7 @@ import java.util.Set; Allocator allocator, long positionUs, Format muxedAudioFormat, + DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher) { this.trackType = trackType; @@ -173,6 +181,7 @@ import java.util.Set; this.overridingDrmInitData = overridingDrmInitData; this.allocator = allocator; this.muxedAudioFormat = muxedAudioFormat; + this.drmSessionManager = drmSessionManager; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.eventDispatcher = eventDispatcher; loader = new Loader("Loader:HlsSampleStreamWrapper"); @@ -181,6 +190,7 @@ import java.util.Set; audioSampleQueueIndex = C.INDEX_UNSET; videoSampleQueueIndex = C.INDEX_UNSET; sampleQueues = new SampleQueue[0]; + sampleQueueReaders = new DecryptableSampleQueueReader[0]; sampleQueueIsAudioVideoFlags = new boolean[0]; sampleQueuesEnabledStates = new boolean[0]; mediaChunks = new ArrayList<>(); @@ -211,7 +221,7 @@ import java.util.Set; public void prepareWithMasterPlaylistInfo( TrackGroup[] trackGroups, int primaryTrackGroupIndex, int... optionalTrackGroupsIndices) { prepared = true; - this.trackGroups = new TrackGroupArray(trackGroups); + this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups); optionalTrackGroups = new HashSet<>(); for (int optionalTrackGroupIndex : optionalTrackGroupsIndices) { optionalTrackGroups.add(this.trackGroups.get(optionalTrackGroupIndex)); @@ -438,6 +448,9 @@ import java.util.Set; for (SampleQueue sampleQueue : sampleQueues) { sampleQueue.discardToEnd(); } + for (DecryptableSampleQueueReader reader : sampleQueueReaders) { + reader.release(); + } } loader.release(this); handler.removeCallbacksAndMessages(null); @@ -448,6 +461,9 @@ import java.util.Set; @Override public void onLoaderReleased() { resetSampleQueues(); + for (DecryptableSampleQueueReader reader : sampleQueueReaders) { + reader.release(); + } } public void setIsTimestampMaster(boolean isTimestampMaster) { @@ -461,7 +477,12 @@ import java.util.Set; // SampleStream implementation. public boolean isReady(int sampleQueueIndex) { - return loadingFinished || (!isPendingReset() && sampleQueues[sampleQueueIndex].hasNextSample()); + return !isPendingReset() && sampleQueueReaders[sampleQueueIndex].isReady(loadingFinished); + } + + public void maybeThrowError(int sampleQueueIndex) throws IOException { + maybeThrowError(); + sampleQueueReaders[sampleQueueIndex].maybeThrowError(); } public void maybeThrowError() throws IOException { @@ -494,13 +515,8 @@ import java.util.Set; } int result = - sampleQueues[sampleQueueIndex].read( - formatHolder, - buffer, - requireFormat, - /* allowOnlyClearBuffers= */ false, - loadingFinished, - lastSeekPositionUs); + sampleQueueReaders[sampleQueueIndex].read( + formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs); if (result == C.RESULT_FORMAT_READ) { Format format = formatHolder.format; if (sampleQueueIndex == primarySampleQueueIndex) { @@ -516,12 +532,6 @@ import java.util.Set; : upstreamTrackFormat; format = format.copyWithManifestFormatInfo(trackFormat); } - if (format.drmInitData != null) { - DrmInitData drmInitData = overridingDrmInitData.get(format.drmInitData.schemeType); - if (drmInitData != null) { - format = format.copyWithDrmInitData(drmInitData); - } - } formatHolder.format = format; } return result; @@ -836,6 +846,9 @@ import java.util.Set; sampleQueueTrackIds[trackCount] = id; sampleQueues = Arrays.copyOf(sampleQueues, trackCount + 1); sampleQueues[trackCount] = trackOutput; + sampleQueueReaders = Arrays.copyOf(sampleQueueReaders, trackCount + 1); + sampleQueueReaders[trackCount] = + new DecryptableSampleQueueReader(sampleQueues[trackCount], drmSessionManager); sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1); sampleQueueIsAudioVideoFlags[trackCount] = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO; @@ -1048,11 +1061,29 @@ import java.util.Set; trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat, false)); } } - this.trackGroups = new TrackGroupArray(trackGroups); + this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups); Assertions.checkState(optionalTrackGroups == null); optionalTrackGroups = Collections.emptySet(); } + private TrackGroupArray createTrackGroupArrayWithDrmInfo(TrackGroup[] trackGroups) { + for (int i = 0; i < trackGroups.length; i++) { + TrackGroup trackGroup = trackGroups[i]; + Format[] exposedFormats = new Format[trackGroup.length]; + for (int j = 0; j < trackGroup.length; j++) { + Format format = trackGroup.getFormat(j); + if (format.drmInitData != null) { + format = + format.copyWithExoMediaCryptoType( + drmSessionManager.getExoMediaCryptoType(format.drmInitData)); + } + exposedFormats[j] = format; + } + trackGroups[i] = new TrackGroup(exposedFormats); + } + return new TrackGroupArray(trackGroups); + } + private HlsMediaChunk getLastMediaChunk() { return mediaChunks.get(mediaChunks.size() - 1); } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java index f389944670..93b8be3346 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; @@ -81,6 +82,7 @@ public final class HlsMediaPeriodTest { mockPlaylistTracker, mockDataSourceFactory, mock(TransferListener.class), + mock(DrmSessionManager.class), mock(LoadErrorHandlingPolicy.class), new EventDispatcher() .withParameters(