Preacquire DRM sessions from the loading side of SampleQueue
Issue: #4133 PiperOrigin-RevId: 362478801
This commit is contained in:
parent
f8fb9dd606
commit
795ddfee40
@ -47,6 +47,8 @@
|
|||||||
video tracks (previously separate acquire and release events were
|
video tracks (previously separate acquire and release events were
|
||||||
dispatched for each track in each period).
|
dispatched for each track in each period).
|
||||||
* Include the session state in DRM session-acquired listener methods.
|
* Include the session state in DRM session-acquired listener methods.
|
||||||
|
* Prepare DRM sessions (and fetch keys) ahead of the playback position
|
||||||
|
([#4133](https://github.com/google/ExoPlayer/issues/4133)).
|
||||||
* Text
|
* Text
|
||||||
* Parse SSA/ASS bold & italic info in `Style:` lines
|
* Parse SSA/ASS bold & italic info in `Style:` lines
|
||||||
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
|
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
|
||||||
@ -65,8 +67,8 @@
|
|||||||
media item and so that it is not triggered after a timeline change.
|
media item and so that it is not triggered after a timeline change.
|
||||||
* Trigger `onMediaItemTransition` event for all reasons except
|
* Trigger `onMediaItemTransition` event for all reasons except
|
||||||
`MEDIA_ITEM_TRANSITION_REASON_REPEAT`.
|
`MEDIA_ITEM_TRANSITION_REASON_REPEAT`.
|
||||||
* Allow the use of platform extractors through [MediaParser]
|
* Allow the use of platform extractors through
|
||||||
(https://developer.android.com/reference/android/media/MediaParser).
|
[MediaParser](https://developer.android.com/reference/android/media/MediaParser).
|
||||||
Only supported on API 30+.
|
Only supported on API 30+.
|
||||||
* You can use it for progressive media by passing a
|
* You can use it for progressive media by passing a
|
||||||
`MediaParserExtractorAdapter.FACTORY` when creating the
|
`MediaParserExtractorAdapter.FACTORY` when creating the
|
||||||
|
@ -34,6 +34,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
|
|||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager.DrmSessionReference;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataReader;
|
import com.google.android.exoplayer2.upstream.DataReader;
|
||||||
@ -63,7 +64,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
|
|
||||||
private final SampleDataQueue sampleDataQueue;
|
private final SampleDataQueue sampleDataQueue;
|
||||||
private final SampleExtrasHolder extrasHolder;
|
private final SampleExtrasHolder extrasHolder;
|
||||||
private final SpannedData<Format> formatSpans;
|
private final SpannedData<SharedSampleMetadata> sharedSampleMetadata;
|
||||||
@Nullable private final DrmSessionManager drmSessionManager;
|
@Nullable private final DrmSessionManager drmSessionManager;
|
||||||
@Nullable private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
|
@Nullable private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
|
||||||
@Nullable private final Looper playbackLooper;
|
@Nullable private final Looper playbackLooper;
|
||||||
@ -156,7 +157,8 @@ public class SampleQueue implements TrackOutput {
|
|||||||
flags = new int[capacity];
|
flags = new int[capacity];
|
||||||
sizes = new int[capacity];
|
sizes = new int[capacity];
|
||||||
cryptoDatas = new CryptoData[capacity];
|
cryptoDatas = new CryptoData[capacity];
|
||||||
formatSpans = new SpannedData<>();
|
sharedSampleMetadata =
|
||||||
|
new SpannedData<>(/* removeCallback= */ metadata -> metadata.drmSessionReference.release());
|
||||||
startTimeUs = Long.MIN_VALUE;
|
startTimeUs = Long.MIN_VALUE;
|
||||||
largestDiscardedTimestampUs = Long.MIN_VALUE;
|
largestDiscardedTimestampUs = Long.MIN_VALUE;
|
||||||
largestQueuedTimestampUs = Long.MIN_VALUE;
|
largestQueuedTimestampUs = Long.MIN_VALUE;
|
||||||
@ -198,7 +200,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
largestDiscardedTimestampUs = Long.MIN_VALUE;
|
largestDiscardedTimestampUs = Long.MIN_VALUE;
|
||||||
largestQueuedTimestampUs = Long.MIN_VALUE;
|
largestQueuedTimestampUs = Long.MIN_VALUE;
|
||||||
isLastSampleQueued = false;
|
isLastSampleQueued = false;
|
||||||
formatSpans.clear();
|
sharedSampleMetadata.clear();
|
||||||
if (resetUpstreamFormat) {
|
if (resetUpstreamFormat) {
|
||||||
unadjustedUpstreamFormat = null;
|
unadjustedUpstreamFormat = null;
|
||||||
upstreamFormat = null;
|
upstreamFormat = null;
|
||||||
@ -371,7 +373,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
|| isLastSampleQueued
|
|| isLastSampleQueued
|
||||||
|| (upstreamFormat != null && upstreamFormat != downstreamFormat);
|
|| (upstreamFormat != null && upstreamFormat != downstreamFormat);
|
||||||
}
|
}
|
||||||
if (formatSpans.get(getReadIndex()) != downstreamFormat) {
|
if (sharedSampleMetadata.get(getReadIndex()).format != downstreamFormat) {
|
||||||
// A format can be read.
|
// A format can be read.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -690,7 +692,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Format format = formatSpans.get(getReadIndex());
|
Format format = sharedSampleMetadata.get(getReadIndex()).format;
|
||||||
if (formatRequired || format != downstreamFormat) {
|
if (formatRequired || format != downstreamFormat) {
|
||||||
onFormatResult(format, formatHolder);
|
onFormatResult(format, formatHolder);
|
||||||
return C.RESULT_FORMAT_READ;
|
return C.RESULT_FORMAT_READ;
|
||||||
@ -723,7 +725,10 @@ public class SampleQueue implements TrackOutput {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable Format upstreamCommittedFormat = formatSpans.getEndValue();
|
@Nullable SharedSampleMetadata upstreamCommittedMetadata = sharedSampleMetadata.getEndValue();
|
||||||
|
@Nullable
|
||||||
|
Format upstreamCommittedFormat =
|
||||||
|
upstreamCommittedMetadata != null ? upstreamCommittedMetadata.format : null;
|
||||||
if (Util.areEqual(format, upstreamCommittedFormat)) {
|
if (Util.areEqual(format, upstreamCommittedFormat)) {
|
||||||
// The format has changed back to the format of the last committed sample. If they are
|
// The format has changed back to the format of the last committed sample. If they are
|
||||||
// different objects, we revert back to using upstreamCommittedFormat as the upstreamFormat
|
// different objects, we revert back to using upstreamCommittedFormat as the upstreamFormat
|
||||||
@ -799,8 +804,18 @@ public class SampleQueue implements TrackOutput {
|
|||||||
cryptoDatas[relativeEndIndex] = cryptoData;
|
cryptoDatas[relativeEndIndex] = cryptoData;
|
||||||
sourceIds[relativeEndIndex] = upstreamSourceId;
|
sourceIds[relativeEndIndex] = upstreamSourceId;
|
||||||
|
|
||||||
if (!Util.areEqual(upstreamFormat, formatSpans.getEndValue())) {
|
@Nullable SharedSampleMetadata upstreamCommittedMetadata = sharedSampleMetadata.getEndValue();
|
||||||
formatSpans.appendSpan(getWriteIndex(), checkNotNull(upstreamFormat));
|
if (upstreamCommittedMetadata == null
|
||||||
|
|| !upstreamCommittedMetadata.format.equals(upstreamFormat)) {
|
||||||
|
DrmSessionReference drmSessionReference =
|
||||||
|
drmSessionManager != null
|
||||||
|
? drmSessionManager.preacquireSession(
|
||||||
|
checkNotNull(playbackLooper), drmEventDispatcher, upstreamFormat)
|
||||||
|
: DrmSessionReference.EMPTY;
|
||||||
|
|
||||||
|
sharedSampleMetadata.appendSpan(
|
||||||
|
getWriteIndex(),
|
||||||
|
new SharedSampleMetadata(checkNotNull(upstreamFormat), drmSessionReference));
|
||||||
}
|
}
|
||||||
|
|
||||||
length++;
|
length++;
|
||||||
@ -863,7 +878,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
length -= discardCount;
|
length -= discardCount;
|
||||||
largestQueuedTimestampUs = max(largestDiscardedTimestampUs, getLargestTimestamp(length));
|
largestQueuedTimestampUs = max(largestDiscardedTimestampUs, getLargestTimestamp(length));
|
||||||
isLastSampleQueued = discardCount == 0 && isLastSampleQueued;
|
isLastSampleQueued = discardCount == 0 && isLastSampleQueued;
|
||||||
formatSpans.discardFrom(discardFromIndex);
|
sharedSampleMetadata.discardFrom(discardFromIndex);
|
||||||
if (length != 0) {
|
if (length != 0) {
|
||||||
int relativeLastWriteIndex = getRelativeIndex(length - 1);
|
int relativeLastWriteIndex = getRelativeIndex(length - 1);
|
||||||
return offsets[relativeLastWriteIndex] + sizes[relativeLastWriteIndex];
|
return offsets[relativeLastWriteIndex] + sizes[relativeLastWriteIndex];
|
||||||
@ -1003,7 +1018,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
if (readPosition < 0) {
|
if (readPosition < 0) {
|
||||||
readPosition = 0;
|
readPosition = 0;
|
||||||
}
|
}
|
||||||
formatSpans.discardTo(absoluteFirstIndex);
|
sharedSampleMetadata.discardTo(absoluteFirstIndex);
|
||||||
|
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
int relativeLastDiscardIndex = (relativeFirstIndex == 0 ? capacity : relativeFirstIndex) - 1;
|
int relativeLastDiscardIndex = (relativeFirstIndex == 0 ? capacity : relativeFirstIndex) - 1;
|
||||||
@ -1057,4 +1072,15 @@ public class SampleQueue implements TrackOutput {
|
|||||||
public long offset;
|
public long offset;
|
||||||
@Nullable public CryptoData cryptoData;
|
@Nullable public CryptoData cryptoData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A holder for metadata that applies to a span of contiguous samples. */
|
||||||
|
private static final class SharedSampleMetadata {
|
||||||
|
public final Format format;
|
||||||
|
public final DrmSessionReference drmSessionReference;
|
||||||
|
|
||||||
|
private SharedSampleMetadata(Format format, DrmSessionReference drmSessionReference) {
|
||||||
|
this.format = format;
|
||||||
|
this.drmSessionReference = drmSessionReference;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import static java.lang.Math.min;
|
|||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.util.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores value objects associated with spans of integer keys.
|
* Stores value objects associated with spans of integer keys.
|
||||||
@ -39,10 +40,21 @@ import com.google.android.exoplayer2.C;
|
|||||||
private int memoizedReadIndex;
|
private int memoizedReadIndex;
|
||||||
|
|
||||||
private final SparseArray<V> spans;
|
private final SparseArray<V> spans;
|
||||||
|
private final Consumer<V> removeCallback;
|
||||||
|
|
||||||
/** Constructs an empty instance. */
|
/** Constructs an empty instance. */
|
||||||
public SpannedData() {
|
public SpannedData() {
|
||||||
|
this(/* removeCallback= */ value -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an empty instance that invokes {@code removeCallback} on each value that is removed
|
||||||
|
* from the collection.
|
||||||
|
*/
|
||||||
|
public SpannedData(Consumer<V> removeCallback) {
|
||||||
spans = new SparseArray<>();
|
spans = new SparseArray<>();
|
||||||
|
this.removeCallback = removeCallback;
|
||||||
|
memoizedReadIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +83,8 @@ import com.google.android.exoplayer2.C;
|
|||||||
* Adds a new span to the end starting at {@code startKey} and containing {@code value}.
|
* Adds a new span to the end starting at {@code startKey} and containing {@code value}.
|
||||||
*
|
*
|
||||||
* <p>{@code startKey} must be greater than or equal to the start key of the previous span. If
|
* <p>{@code startKey} must be greater than or equal to the start key of the previous span. If
|
||||||
* they're equal, the previous span is overwritten.
|
* they're equal, the previous span is overwritten and it's passed to {@code removeCallback} (if
|
||||||
|
* set).
|
||||||
*/
|
*/
|
||||||
public void appendSpan(int startKey, V value) {
|
public void appendSpan(int startKey, V value) {
|
||||||
if (memoizedReadIndex == C.INDEX_UNSET) {
|
if (memoizedReadIndex == C.INDEX_UNSET) {
|
||||||
@ -79,7 +92,13 @@ import com.google.android.exoplayer2.C;
|
|||||||
memoizedReadIndex = 0;
|
memoizedReadIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkArgument(spans.size() == 0 || startKey >= spans.keyAt(spans.size() - 1));
|
if (spans.size() > 0) {
|
||||||
|
int lastStartKey = spans.keyAt(spans.size() - 1);
|
||||||
|
checkArgument(startKey >= lastStartKey);
|
||||||
|
if (lastStartKey == startKey) {
|
||||||
|
removeCallback.accept(spans.valueAt(spans.size() - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
spans.append(startKey, value);
|
spans.append(startKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +121,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
*/
|
*/
|
||||||
public void discardTo(int discardToKey) {
|
public void discardTo(int discardToKey) {
|
||||||
for (int i = 0; i < spans.size() - 1 && discardToKey >= spans.keyAt(i + 1); i++) {
|
for (int i = 0; i < spans.size() - 1 && discardToKey >= spans.keyAt(i + 1); i++) {
|
||||||
|
removeCallback.accept(spans.valueAt(i));
|
||||||
spans.removeAt(i);
|
spans.removeAt(i);
|
||||||
if (memoizedReadIndex > 0) {
|
if (memoizedReadIndex > 0) {
|
||||||
memoizedReadIndex--;
|
memoizedReadIndex--;
|
||||||
@ -116,6 +136,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
*/
|
*/
|
||||||
public void discardFrom(int discardFromKey) {
|
public void discardFrom(int discardFromKey) {
|
||||||
for (int i = spans.size() - 1; i >= 0 && discardFromKey < spans.keyAt(i); i--) {
|
for (int i = spans.size() - 1; i >= 0 && discardFromKey < spans.keyAt(i); i--) {
|
||||||
|
removeCallback.accept(spans.valueAt(i));
|
||||||
spans.removeAt(i);
|
spans.removeAt(i);
|
||||||
}
|
}
|
||||||
memoizedReadIndex = spans.size() > 0 ? min(memoizedReadIndex, spans.size() - 1) : C.INDEX_UNSET;
|
memoizedReadIndex = spans.size() > 0 ? min(memoizedReadIndex, spans.size() - 1) : C.INDEX_UNSET;
|
||||||
@ -123,6 +144,9 @@ import com.google.android.exoplayer2.C;
|
|||||||
|
|
||||||
/** Remove all spans. */
|
/** Remove all spans. */
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
for (int i = 0; i < spans.size(); i++) {
|
||||||
|
removeCallback.accept(spans.valueAt(i));
|
||||||
|
}
|
||||||
memoizedReadIndex = C.INDEX_UNSET;
|
memoizedReadIndex = C.INDEX_UNSET;
|
||||||
spans.clear();
|
spans.clear();
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm;
|
|||||||
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.MediaDrmCallbackException;
|
import com.google.android.exoplayer2.drm.MediaDrmCallbackException;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
|
import com.google.android.exoplayer2.robolectric.RobolectricUtil;
|
||||||
import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper;
|
import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper;
|
||||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||||
@ -108,6 +109,7 @@ import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder;
|
|||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.util.Clock;
|
import com.google.android.exoplayer2.util.Clock;
|
||||||
|
import com.google.android.exoplayer2.util.ConditionVariable;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -1436,19 +1438,43 @@ public final class AnalyticsCollectorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void drmEvents_periodWithSameDrmData_keysReused() throws Exception {
|
public void drmEvents_periodsWithSameDrmData_keysReusedButLoadEventReportedTwice()
|
||||||
|
throws Exception {
|
||||||
|
BlockingDrmCallback mediaDrmCallback = BlockingDrmCallback.returnsEmpty();
|
||||||
|
DrmSessionManager blockingDrmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setMultiSession(true)
|
||||||
|
.build(mediaDrmCallback);
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new ConcatenatingMediaSource(
|
new ConcatenatingMediaSource(
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1),
|
new FakeMediaSource(
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1));
|
SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1),
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
new FakeMediaSource(
|
||||||
|
SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1));
|
||||||
|
TestAnalyticsListener listener =
|
||||||
|
runAnalyticsTest(
|
||||||
|
mediaSource,
|
||||||
|
// Wait for the media to be fully buffered before unblocking the DRM key request. This
|
||||||
|
// ensures both periods report the same load event (because period1's DRM session is
|
||||||
|
// already preacquired by the time the key load completes).
|
||||||
|
new ActionSchedule.Builder(TAG)
|
||||||
|
.waitForIsLoading(false)
|
||||||
|
.waitForIsLoading(true)
|
||||||
|
.waitForIsLoading(false)
|
||||||
|
.executeRunnable(mediaDrmCallback.keyCondition::open)
|
||||||
|
.build());
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED))
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_ACQUIRED))
|
||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_DRM_KEYS_LOADED)).containsExactly(period0);
|
// This includes both period0 and period1 because period1's DrmSession was preacquired before
|
||||||
|
// the key load completed. There's only one key load (a second would block forever). We can't
|
||||||
|
// assume the order these events will arrive in because it depends on the iteration order of a
|
||||||
|
// HashSet of EventDispatchers inside DefaultDrmSession.
|
||||||
|
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
|
// The period1 release event is lost because it's posted to "ExoPlayerTest thread" after that
|
||||||
// thread has been quit during clean-up.
|
// thread has been quit during clean-up.
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_RELEASED)).containsExactly(period0);
|
||||||
@ -1480,11 +1506,21 @@ public final class AnalyticsCollectorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void drmEvents_errorHandling() throws Exception {
|
public void drmEvents_errorHandling() throws Exception {
|
||||||
|
BlockingDrmCallback mediaDrmCallback = BlockingDrmCallback.alwaysFailing();
|
||||||
DrmSessionManager failingDrmSessionManager =
|
DrmSessionManager failingDrmSessionManager =
|
||||||
new DefaultDrmSessionManager.Builder().build(new FailingDrmCallback());
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setMultiSession(true)
|
||||||
|
.build(mediaDrmCallback);
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1);
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
TestAnalyticsListener listener =
|
||||||
|
runAnalyticsTest(
|
||||||
|
mediaSource,
|
||||||
|
new ActionSchedule.Builder(TAG)
|
||||||
|
.waitForIsLoading(false)
|
||||||
|
.executeRunnable(mediaDrmCallback.keyCondition::open)
|
||||||
|
.build());
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0);
|
||||||
@ -2341,21 +2377,56 @@ public final class AnalyticsCollectorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaDrmCallback} that throws exceptions for both {@link
|
* A {@link MediaDrmCallback} that blocks each provision and key request until the associated
|
||||||
* #executeProvisionRequest(UUID, ExoMediaDrm.ProvisionRequest)} and {@link
|
* {@link ConditionVariable} field is opened, and then returns an empty byte array. The {@link
|
||||||
* #executeKeyRequest(UUID, ExoMediaDrm.KeyRequest)}.
|
* ConditionVariable} must be explicitly opened for each request.
|
||||||
*/
|
*/
|
||||||
private static final class FailingDrmCallback implements MediaDrmCallback {
|
private static final class BlockingDrmCallback implements MediaDrmCallback {
|
||||||
|
|
||||||
|
public final ConditionVariable provisionCondition;
|
||||||
|
public final ConditionVariable keyCondition;
|
||||||
|
|
||||||
|
private final boolean alwaysFail;
|
||||||
|
|
||||||
|
private BlockingDrmCallback(boolean alwaysFail) {
|
||||||
|
this.provisionCondition = RobolectricUtil.createRobolectricConditionVariable();
|
||||||
|
this.keyCondition = RobolectricUtil.createRobolectricConditionVariable();
|
||||||
|
|
||||||
|
this.alwaysFail = alwaysFail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a callback that always returns an empty byte array from its execute methods. */
|
||||||
|
public static BlockingDrmCallback returnsEmpty() {
|
||||||
|
return new BlockingDrmCallback(/* alwaysFail= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a callback that always throws an exception from its execute methods. */
|
||||||
|
public static BlockingDrmCallback alwaysFailing() {
|
||||||
|
return new BlockingDrmCallback(/* alwaysFail= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request)
|
public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request)
|
||||||
throws MediaDrmCallbackException {
|
throws MediaDrmCallbackException {
|
||||||
throw new RuntimeException("executeProvision failed");
|
provisionCondition.blockUninterruptible();
|
||||||
|
provisionCondition.close();
|
||||||
|
if (alwaysFail) {
|
||||||
|
throw new RuntimeException("executeProvisionRequest failed");
|
||||||
|
} else {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request)
|
public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request)
|
||||||
throws MediaDrmCallbackException {
|
throws MediaDrmCallbackException {
|
||||||
throw new RuntimeException("executeKey failed");
|
keyCondition.blockUninterruptible();
|
||||||
|
keyCondition.close();
|
||||||
|
if (alwaysFail) {
|
||||||
|
throw new RuntimeException("executeKeyRequest failed");
|
||||||
|
} else {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,129 +16,161 @@
|
|||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager.DrmSessionReference;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
/** Tests for {@link SpannedData}. */
|
/** Tests for {@link SpannedData}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class SpannedDataTest {
|
public final class SpannedDataTest {
|
||||||
|
|
||||||
private static final String VALUE_1 = "value 1";
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
private static final String VALUE_2 = "value 2";
|
|
||||||
private static final String VALUE_3 = "value 3";
|
@Mock private DrmSessionReference value1;
|
||||||
|
@Mock private DrmSessionReference value2;
|
||||||
|
@Mock private DrmSessionReference value3;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void appendMultipleSpansThenRead() {
|
public void appendMultipleSpansThenRead() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData =
|
||||||
|
new SpannedData<>(/* removeCallback= */ DrmSessionReference::release);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
spannedData.appendSpan(/* startKey= */ 4, VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 4, value3);
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_1);
|
assertThat(spannedData.get(0)).isEqualTo(value1);
|
||||||
assertThat(spannedData.get(1)).isEqualTo(VALUE_1);
|
assertThat(spannedData.get(1)).isEqualTo(value1);
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_2);
|
assertThat(spannedData.get(2)).isEqualTo(value2);
|
||||||
assertThat(spannedData.get(3)).isEqualTo(VALUE_2);
|
assertThat(spannedData.get(3)).isEqualTo(value2);
|
||||||
assertThat(spannedData.get(4)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(4)).isEqualTo(value3);
|
||||||
assertThat(spannedData.get(5)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(5)).isEqualTo(value3);
|
||||||
|
|
||||||
|
verify(value1, never()).release();
|
||||||
|
verify(value2, never()).release();
|
||||||
|
verify(value3, never()).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void append_emptySpansDiscarded() {
|
public void append_emptySpansDiscarded() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData = new SpannedData<>();
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 2, value3);
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_1);
|
assertThat(spannedData.get(0)).isEqualTo(value1);
|
||||||
assertThat(spannedData.get(1)).isEqualTo(VALUE_1);
|
assertThat(spannedData.get(1)).isEqualTo(value1);
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(2)).isEqualTo(value3);
|
||||||
assertThat(spannedData.get(3)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(3)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void discardTo() {
|
public void discardTo() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData =
|
||||||
|
new SpannedData<>(/* removeCallback= */ DrmSessionReference::release);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
spannedData.appendSpan(/* startKey= */ 4, VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 4, value3);
|
||||||
|
|
||||||
spannedData.discardTo(2);
|
spannedData.discardTo(2);
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_2);
|
verify(value1).release();
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_2);
|
verify(value2, never()).release();
|
||||||
|
assertThat(spannedData.get(0)).isEqualTo(value2);
|
||||||
|
assertThat(spannedData.get(2)).isEqualTo(value2);
|
||||||
|
|
||||||
spannedData.discardTo(4);
|
spannedData.discardTo(4);
|
||||||
|
|
||||||
assertThat(spannedData.get(3)).isEqualTo(VALUE_3);
|
verify(value2).release();
|
||||||
assertThat(spannedData.get(4)).isEqualTo(VALUE_3);
|
verify(value3, never()).release();
|
||||||
|
assertThat(spannedData.get(3)).isEqualTo(value3);
|
||||||
|
assertThat(spannedData.get(4)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void discardTo_prunesEmptySpans() {
|
public void discardTo_prunesEmptySpans() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData = new SpannedData<>();
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 2, value3);
|
||||||
|
|
||||||
spannedData.discardTo(2);
|
spannedData.discardTo(2);
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(0)).isEqualTo(value3);
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_3);
|
assertThat(spannedData.get(2)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void discardFromThenAppend_keepsValueIfSpanEndsUpNonEmpty() {
|
public void discardFromThenAppend_keepsValueIfSpanEndsUpNonEmpty() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData =
|
||||||
|
new SpannedData<>(/* removeCallback= */ DrmSessionReference::release);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
spannedData.appendSpan(/* startKey= */ 4, VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 4, value3);
|
||||||
|
|
||||||
spannedData.discardFrom(2);
|
spannedData.discardFrom(2);
|
||||||
assertThat(spannedData.getEndValue()).isEqualTo(VALUE_2);
|
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 3, VALUE_3);
|
verify(value3).release();
|
||||||
|
assertThat(spannedData.getEndValue()).isEqualTo(value2);
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 3, value3);
|
||||||
assertThat(spannedData.get(1)).isEqualTo(VALUE_1);
|
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_2);
|
verify(value1, never()).release();
|
||||||
assertThat(spannedData.get(3)).isEqualTo(VALUE_3);
|
verify(value2, never()).release();
|
||||||
|
assertThat(spannedData.get(0)).isEqualTo(value1);
|
||||||
|
assertThat(spannedData.get(1)).isEqualTo(value1);
|
||||||
|
assertThat(spannedData.get(2)).isEqualTo(value2);
|
||||||
|
assertThat(spannedData.get(3)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void discardFromThenAppend_prunesEmptySpan() {
|
public void discardFromThenAppend_prunesEmptySpan() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData =
|
||||||
|
new SpannedData<>(/* removeCallback= */ DrmSessionReference::release);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
|
|
||||||
spannedData.discardFrom(2);
|
spannedData.discardFrom(2);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_3);
|
verify(value2, never()).release();
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 2, value3);
|
||||||
assertThat(spannedData.get(1)).isEqualTo(VALUE_1);
|
|
||||||
assertThat(spannedData.get(2)).isEqualTo(VALUE_3);
|
verify(value2).release();
|
||||||
|
assertThat(spannedData.get(0)).isEqualTo(value1);
|
||||||
|
assertThat(spannedData.get(1)).isEqualTo(value1);
|
||||||
|
assertThat(spannedData.get(2)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clear() {
|
public void clear() {
|
||||||
SpannedData<String> spannedData = new SpannedData<>();
|
SpannedData<DrmSessionReference> spannedData =
|
||||||
|
new SpannedData<>(/* removeCallback= */ DrmSessionReference::release);
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 0, VALUE_1);
|
spannedData.appendSpan(/* startKey= */ 0, value1);
|
||||||
spannedData.appendSpan(/* startKey= */ 2, VALUE_2);
|
spannedData.appendSpan(/* startKey= */ 2, value2);
|
||||||
|
|
||||||
spannedData.clear();
|
spannedData.clear();
|
||||||
|
|
||||||
spannedData.appendSpan(/* startKey= */ 1, VALUE_3);
|
verify(value1).release();
|
||||||
|
verify(value2).release();
|
||||||
|
|
||||||
assertThat(spannedData.get(0)).isEqualTo(VALUE_3);
|
spannedData.appendSpan(/* startKey= */ 1, value3);
|
||||||
assertThat(spannedData.get(1)).isEqualTo(VALUE_3);
|
|
||||||
|
assertThat(spannedData.get(0)).isEqualTo(value3);
|
||||||
|
assertThat(spannedData.get(1)).isEqualTo(value3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user