Fix spurious reset of PreparedState boolean flags

PiperOrigin-RevId: 300513930
This commit is contained in:
olly 2020-03-12 10:22:53 +00:00 committed by Oliver Woodman
parent c85e5137f0
commit 16e6ea6e40

View File

@ -57,6 +57,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link MediaPeriod} that extracts data using an {@link Extractor}. */ /** A {@link MediaPeriod} that extracts data using an {@link Extractor}. */
@ -111,23 +112,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Handler handler; private final Handler handler;
@Nullable private Callback callback; @Nullable private Callback callback;
@Nullable private SeekMap seekMap;
@Nullable private IcyHeaders icyHeaders; @Nullable private IcyHeaders icyHeaders;
private SampleQueue[] sampleQueues; private SampleQueue[] sampleQueues;
private TrackId[] sampleQueueTrackIds; private TrackId[] sampleQueueTrackIds;
private boolean sampleQueuesBuilt; private boolean sampleQueuesBuilt;
private @MonotonicNonNull PreparedState preparedState; private boolean prepared;
private boolean haveAudioVideoTracks; private boolean haveAudioVideoTracks;
private @MonotonicNonNull TrackState trackState;
private @MonotonicNonNull SeekMap seekMap;
private long durationUs;
private boolean isLive;
private int dataType; private int dataType;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
private boolean notifyDiscontinuity; private boolean notifyDiscontinuity;
private boolean notifiedReadingStarted; private boolean notifiedReadingStarted;
private int enabledTrackCount; private int enabledTrackCount;
private long durationUs;
private long length; private long length;
private boolean isLive;
private long lastSeekPositionUs; private long lastSeekPositionUs;
private long pendingResetPositionUs; private long pendingResetPositionUs;
@ -197,7 +199,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
public void release() { public void release() {
if (preparedState != null) { if (prepared) {
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise // Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
// sampleQueues may still be being modified by the loading thread. // sampleQueues may still be being modified by the loading thread.
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
@ -229,14 +231,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
maybeThrowError(); maybeThrowError();
if (loadingFinished && preparedState == null) { if (loadingFinished && !prepared) {
throw new ParserException("Loading finished before preparation is complete."); throw new ParserException("Loading finished before preparation is complete.");
} }
} }
@Override @Override
public TrackGroupArray getTrackGroups() { public TrackGroupArray getTrackGroups() {
return Assertions.checkNotNull(preparedState).tracks; assertPrepared();
return trackState.tracks;
} }
@Override @Override
@ -246,8 +249,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,
long positionUs) { long positionUs) {
TrackGroupArray tracks = Assertions.checkNotNull(preparedState).tracks; assertPrepared();
boolean[] trackEnabledStates = preparedState.trackEnabledStates; TrackGroupArray tracks = trackState.tracks;
boolean[] trackEnabledStates = trackState.trackEnabledStates;
int oldEnabledTrackCount = enabledTrackCount; int oldEnabledTrackCount = enabledTrackCount;
// Deselect old tracks. // Deselect old tracks.
for (int i = 0; i < selections.length; i++) { for (int i = 0; i < selections.length; i++) {
@ -316,10 +320,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void discardBuffer(long positionUs, boolean toKeyframe) { public void discardBuffer(long positionUs, boolean toKeyframe) {
assertPrepared();
if (isPendingReset()) { if (isPendingReset()) {
return; return;
} }
boolean[] trackEnabledStates = Assertions.checkNotNull(preparedState).trackEnabledStates; boolean[] trackEnabledStates = trackState.trackEnabledStates;
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
sampleQueues[i].discardTo(positionUs, toKeyframe, trackEnabledStates[i]); sampleQueues[i].discardTo(positionUs, toKeyframe, trackEnabledStates[i]);
@ -336,7 +341,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (loadingFinished if (loadingFinished
|| loader.hasFatalError() || loader.hasFatalError()
|| pendingDeferredRetry || pendingDeferredRetry
|| (preparedState != null && enabledTrackCount == 0)) { || (prepared && enabledTrackCount == 0)) {
return false; return false;
} }
boolean continuedLoading = loadCondition.open(); boolean continuedLoading = loadCondition.open();
@ -373,8 +378,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
boolean[] trackIsAudioVideoFlags = assertPrepared();
Assertions.checkNotNull(preparedState).trackIsAudioVideoFlags; boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
if (loadingFinished) { if (loadingFinished) {
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} else if (isPendingReset()) { } else if (isPendingReset()) {
@ -400,8 +405,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long seekToUs(long positionUs) { public long seekToUs(long positionUs) {
SeekMap seekMap = Assertions.checkNotNull(preparedState).seekMap; assertPrepared();
boolean[] trackIsAudioVideoFlags = preparedState.trackIsAudioVideoFlags; boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
// Treat all seeks into non-seekable media as being to t=0. // Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0; positionUs = seekMap.isSeekable() ? positionUs : 0;
@ -436,7 +441,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
SeekMap seekMap = Assertions.checkNotNull(preparedState).seekMap; assertPrepared();
if (!seekMap.isSeekable()) { if (!seekMap.isSeekable()) {
// Treat all seeks into non-seekable media as being to t=0. // Treat all seeks into non-seekable media as being to t=0.
return 0; return 0;
@ -498,10 +503,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void maybeNotifyDownstreamFormat(int track) { private void maybeNotifyDownstreamFormat(int track) {
boolean[] trackNotifiedDownstreamFormats = assertPrepared();
Assertions.checkNotNull(preparedState).trackNotifiedDownstreamFormats; boolean[] trackNotifiedDownstreamFormats = trackState.trackNotifiedDownstreamFormats;
if (!trackNotifiedDownstreamFormats[track]) { if (!trackNotifiedDownstreamFormats[track]) {
Format trackFormat = preparedState.tracks.get(track).getFormat(/* index= */ 0); Format trackFormat = trackState.tracks.get(track).getFormat(/* index= */ 0);
eventDispatcher.downstreamFormatChanged( eventDispatcher.downstreamFormatChanged(
MimeTypes.getTrackType(trackFormat.sampleMimeType), MimeTypes.getTrackType(trackFormat.sampleMimeType),
trackFormat, trackFormat,
@ -513,8 +518,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void maybeStartDeferredRetry(int track) { private void maybeStartDeferredRetry(int track) {
boolean[] trackIsAudioVideoFlags = assertPrepared();
Assertions.checkNotNull(preparedState).trackIsAudioVideoFlags; boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
if (!pendingDeferredRetry if (!pendingDeferredRetry
|| !trackIsAudioVideoFlags[track] || !trackIsAudioVideoFlags[track]
|| sampleQueues[track].isReady(/* loadingFinished= */ false)) { || sampleQueues[track].isReady(/* loadingFinished= */ false)) {
@ -688,12 +693,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void setSeekMap(SeekMap seekMap) { private void setSeekMap(SeekMap seekMap) {
this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs */ C.TIME_UNSET); this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs= */ C.TIME_UNSET);
if (preparedState == null) { if (!prepared) {
maybeFinishPrepare(); maybeFinishPrepare();
} else {
preparedState =
new PreparedState(seekMap, preparedState.tracks, preparedState.trackIsAudioVideoFlags);
} }
durationUs = seekMap.getDurationUs(); durationUs = seekMap.getDurationUs();
isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET; isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
@ -702,8 +704,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
SeekMap seekMap = this.seekMap; if (released || prepared || !sampleQueuesBuilt || seekMap == null) {
if (released || preparedState != null || !sampleQueuesBuilt || seekMap == null) {
return; return;
} }
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
@ -744,8 +745,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
trackArray[i] = new TrackGroup(trackFormat); trackArray[i] = new TrackGroup(trackFormat);
} }
preparedState = trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
new PreparedState(seekMap, new TrackGroupArray(trackArray), trackIsAudioVideoFlags); prepared = true;
Assertions.checkNotNull(callback).onPrepared(this); Assertions.checkNotNull(callback).onPrepared(this);
} }
@ -759,8 +760,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ExtractingLoadable loadable = ExtractingLoadable loadable =
new ExtractingLoadable( new ExtractingLoadable(
uri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition); uri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition);
if (preparedState != null) { if (prepared) {
SeekMap seekMap = preparedState.seekMap;
Assertions.checkState(isPendingReset()); Assertions.checkState(isPendingReset());
if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) { if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {
loadingFinished = true; loadingFinished = true;
@ -768,7 +768,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return; return;
} }
loadable.setLoadPosition( loadable.setLoadPosition(
seekMap.getSeekPoints(pendingResetPositionUs).first.position, pendingResetPositionUs); Assertions.checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
pendingResetPositionUs);
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
} }
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
@ -803,7 +804,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// request data starting from the point it left off. // request data starting from the point it left off.
extractedSamplesCountAtStartOfLoad = currentExtractedSampleCount; extractedSamplesCountAtStartOfLoad = currentExtractedSampleCount;
return true; return true;
} else if (preparedState != null && !suppressRead()) { } else if (prepared && !suppressRead()) {
// We're playing a stream of unknown length and duration. Assume it's live, and therefore that // We're playing a stream of unknown length and duration. Assume it's live, and therefore that
// the data at the uri is a continuously shifting window of the latest available media. For // the data at the uri is a continuously shifting window of the latest available media. For
// this case there's no way to continue loading from where a previous load finished, so it's // this case there's no way to continue loading from where a previous load finished, so it's
@ -820,7 +821,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// because there's no buffered data to be read. This case also covers an on-demand stream with // because there's no buffered data to be read. This case also covers an on-demand stream with
// unknown length that has yet to be prepared. This case cannot be disambiguated from the live // unknown length that has yet to be prepared. This case cannot be disambiguated from the live
// stream case, so we have no option but to load from the start. // stream case, so we have no option but to load from the start.
notifyDiscontinuity = preparedState != null; notifyDiscontinuity = prepared;
lastSeekPositionUs = 0; lastSeekPositionUs = 0;
extractedSamplesCountAtStartOfLoad = 0; extractedSamplesCountAtStartOfLoad = 0;
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
@ -875,6 +876,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return pendingResetPositionUs != C.TIME_UNSET; return pendingResetPositionUs != C.TIME_UNSET;
} }
@EnsuresNonNull({"trackState", "seekMap"})
private void assertPrepared() {
Assertions.checkState(prepared);
Assertions.checkNotNull(trackState);
Assertions.checkNotNull(seekMap);
}
private final class SampleStreamImpl implements SampleStream { private final class SampleStreamImpl implements SampleStream {
private final int track; private final int track;
@ -1042,18 +1050,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
/** Stores state that is initialized when preparation completes. */ /** Stores track state. */
private static final class PreparedState { private static final class TrackState {
public final SeekMap seekMap;
public final TrackGroupArray tracks; public final TrackGroupArray tracks;
public final boolean[] trackIsAudioVideoFlags; public final boolean[] trackIsAudioVideoFlags;
public final boolean[] trackEnabledStates; public final boolean[] trackEnabledStates;
public final boolean[] trackNotifiedDownstreamFormats; public final boolean[] trackNotifiedDownstreamFormats;
public PreparedState( public TrackState(TrackGroupArray tracks, boolean[] trackIsAudioVideoFlags) {
SeekMap seekMap, TrackGroupArray tracks, boolean[] trackIsAudioVideoFlags) {
this.seekMap = seekMap;
this.tracks = tracks; this.tracks = tracks;
this.trackIsAudioVideoFlags = trackIsAudioVideoFlags; this.trackIsAudioVideoFlags = trackIsAudioVideoFlags;
this.trackEnabledStates = new boolean[tracks.length]; this.trackEnabledStates = new boolean[tracks.length];