Commit 32035e1b by andrewlewis Committed by Oliver Woodman

Fix NPE in ExtractorMediaPeriod

Also turn on nullity checks for ExtractorMediaPeriod.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=208467644
parent 1c09af02
...@@ -49,6 +49,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -49,6 +49,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
* A {@link MediaPeriod} that extracts data using an {@link Extractor}. * A {@link MediaPeriod} that extracts data using an {@link Extractor}.
...@@ -84,7 +85,7 @@ import java.util.Arrays; ...@@ -84,7 +85,7 @@ import java.util.Arrays;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final Listener listener; private final Listener listener;
private final Allocator allocator; private final Allocator allocator;
@Nullable private final String customCacheKey; private final @Nullable String customCacheKey;
private final long continueLoadingCheckIntervalBytes; private final long continueLoadingCheckIntervalBytes;
private final Loader loader; private final Loader loader;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
...@@ -94,23 +95,21 @@ import java.util.Arrays; ...@@ -94,23 +95,21 @@ import java.util.Arrays;
private final Handler handler; private final Handler handler;
private @Nullable Callback callback; private @Nullable Callback callback;
private SeekMap seekMap; private @Nullable SeekMap seekMap;
private SampleQueue[] sampleQueues; private SampleQueue[] sampleQueues;
private int[] sampleQueueTrackIds; private int[] sampleQueueTrackIds;
private boolean sampleQueuesBuilt; private boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
private @Nullable PreparedState preparedState;
private boolean haveAudioVideoTracks;
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 TrackGroupArray tracks;
private long durationUs; private long durationUs;
private boolean[] trackEnabledStates;
private boolean[] trackIsAudioVideoFlags;
private boolean[] trackFormatNotificationSent;
private boolean haveAudioVideoTracks;
private long length; private long length;
private long lastSeekPositionUs; private long lastSeekPositionUs;
...@@ -134,6 +133,8 @@ import java.util.Arrays; ...@@ -134,6 +133,8 @@ import java.util.Arrays;
* @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each
* invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. * invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}.
*/ */
// maybeFinishPrepare is not posted to the handler until initialization completes.
@SuppressWarnings("nullness:methodref.receiver.bound.invalid")
public ExtractorMediaPeriod( public ExtractorMediaPeriod(
Uri uri, Uri uri,
DataSource dataSource, DataSource dataSource,
...@@ -153,11 +154,15 @@ import java.util.Arrays; ...@@ -153,11 +154,15 @@ import java.util.Arrays;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("Loader:ExtractorMediaPeriod"); loader = new Loader("Loader:ExtractorMediaPeriod");
extractorHolder = new ExtractorHolder(extractors, this); extractorHolder = new ExtractorHolder(extractors);
loadCondition = new ConditionVariable(); loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare; maybeFinishPrepareRunnable = this::maybeFinishPrepare;
onContinueLoadingRequestedRunnable = onContinueLoadingRequestedRunnable =
() -> callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); () -> {
if (!released) {
Assertions.checkNotNull(callback).onContinueLoadingRequested(ExtractorMediaPeriod.this);
}
};
handler = new Handler(); handler = new Handler();
sampleQueueTrackIds = new int[0]; sampleQueueTrackIds = new int[0];
sampleQueues = new SampleQueue[0]; sampleQueues = new SampleQueue[0];
...@@ -176,7 +181,7 @@ import java.util.Arrays; ...@@ -176,7 +181,7 @@ import java.util.Arrays;
sampleQueue.discardToEnd(); sampleQueue.discardToEnd();
} }
} }
loader.release(this); loader.release(/* callback= */ this);
handler.removeCallbacksAndMessages(null); handler.removeCallbacksAndMessages(null);
callback = null; callback = null;
released = true; released = true;
...@@ -205,13 +210,19 @@ import java.util.Arrays; ...@@ -205,13 +210,19 @@ import java.util.Arrays;
@Override @Override
public TrackGroupArray getTrackGroups() { public TrackGroupArray getTrackGroups() {
return tracks; return getPreparedState().tracks;
} }
@Override @Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, public long selectTracks(
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { TrackSelection[] selections,
Assertions.checkState(prepared); boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs) {
PreparedState preparedState = getPreparedState();
TrackGroupArray tracks = preparedState.tracks;
boolean[] trackEnabledStates = preparedState.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++) {
...@@ -280,6 +291,7 @@ import java.util.Arrays; ...@@ -280,6 +291,7 @@ import java.util.Arrays;
@Override @Override
public void discardBuffer(long positionUs, boolean toKeyframe) { public void discardBuffer(long positionUs, boolean toKeyframe) {
boolean[] trackEnabledStates = getPreparedState().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]);
...@@ -325,6 +337,7 @@ import java.util.Arrays; ...@@ -325,6 +337,7 @@ import java.util.Arrays;
@Override @Override
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
boolean[] trackIsAudioVideoFlags = getPreparedState().trackIsAudioVideoFlags;
if (loadingFinished) { if (loadingFinished) {
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} else if (isPendingReset()) { } else if (isPendingReset()) {
...@@ -350,12 +363,15 @@ import java.util.Arrays; ...@@ -350,12 +363,15 @@ import java.util.Arrays;
@Override @Override
public long seekToUs(long positionUs) { public long seekToUs(long positionUs) {
PreparedState preparedState = getPreparedState();
SeekMap seekMap = preparedState.seekMap;
boolean[] trackIsAudioVideoFlags = preparedState.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;
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
notifyDiscontinuity = false; notifyDiscontinuity = false;
// If we're not pending a reset, see if we can seek within the buffer. // If we're not pending a reset, see if we can seek within the buffer.
if (!isPendingReset() && seekInsideBufferUs(positionUs)) { if (!isPendingReset() && seekInsideBufferUs(trackIsAudioVideoFlags, positionUs)) {
return positionUs; return positionUs;
} }
// We were unable to seek within the buffer, so need to reset. // We were unable to seek within the buffer, so need to reset.
...@@ -374,6 +390,7 @@ import java.util.Arrays; ...@@ -374,6 +390,7 @@ import java.util.Arrays;
@Override @Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
SeekMap seekMap = getPreparedState().seekMap;
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;
...@@ -432,6 +449,9 @@ import java.util.Arrays; ...@@ -432,6 +449,9 @@ import java.util.Arrays;
} }
private void maybeNotifyTrackFormat(int track) { private void maybeNotifyTrackFormat(int track) {
PreparedState preparedState = getPreparedState();
boolean[] trackFormatNotificationSent = preparedState.trackFormatNotificationSent;
TrackGroupArray tracks = preparedState.tracks;
if (!trackFormatNotificationSent[track]) { if (!trackFormatNotificationSent[track]) {
Format trackFormat = tracks.get(track).getFormat(0); Format trackFormat = tracks.get(track).getFormat(0);
eventDispatcher.downstreamFormatChanged( eventDispatcher.downstreamFormatChanged(
...@@ -445,6 +465,7 @@ import java.util.Arrays; ...@@ -445,6 +465,7 @@ import java.util.Arrays;
} }
private void maybeStartDeferredRetry(int track) { private void maybeStartDeferredRetry(int track) {
boolean[] trackIsAudioVideoFlags = getPreparedState().trackIsAudioVideoFlags;
if (!pendingDeferredRetry if (!pendingDeferredRetry
|| !trackIsAudioVideoFlags[track] || !trackIsAudioVideoFlags[track]
|| sampleQueues[track].hasNextSample()) { || sampleQueues[track].hasNextSample()) {
...@@ -458,7 +479,7 @@ import java.util.Arrays; ...@@ -458,7 +479,7 @@ import java.util.Arrays;
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset(); sampleQueue.reset();
} }
callback.onContinueLoadingRequested(this); Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
} }
private boolean suppressRead() { private boolean suppressRead() {
...@@ -471,6 +492,7 @@ import java.util.Arrays; ...@@ -471,6 +492,7 @@ import java.util.Arrays;
public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs, public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs,
long loadDurationMs) { long loadDurationMs) {
if (durationUs == C.TIME_UNSET) { if (durationUs == C.TIME_UNSET) {
SeekMap seekMap = Assertions.checkNotNull(this.seekMap);
long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
...@@ -491,7 +513,7 @@ import java.util.Arrays; ...@@ -491,7 +513,7 @@ import java.util.Arrays;
loadable.dataSource.getBytesRead()); loadable.dataSource.getBytesRead());
copyLengthFromLoader(loadable); copyLengthFromLoader(loadable);
loadingFinished = true; loadingFinished = true;
callback.onContinueLoadingRequested(this); Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
} }
@Override @Override
...@@ -516,7 +538,7 @@ import java.util.Arrays; ...@@ -516,7 +538,7 @@ import java.util.Arrays;
sampleQueue.reset(); sampleQueue.reset();
} }
if (enabledTrackCount > 0) { if (enabledTrackCount > 0) {
callback.onContinueLoadingRequested(this); Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
} }
} }
} }
...@@ -575,8 +597,9 @@ import java.util.Arrays; ...@@ -575,8 +597,9 @@ import java.util.Arrays;
trackOutput.setUpstreamFormatChangeListener(this); trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id; sampleQueueTrackIds[trackCount] = id;
sampleQueues = Arrays.copyOf(sampleQueues, trackCount + 1); @NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
sampleQueues[trackCount] = trackOutput; sampleQueues[trackCount] = trackOutput;
this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
return trackOutput; return trackOutput;
} }
...@@ -602,7 +625,8 @@ import java.util.Arrays; ...@@ -602,7 +625,8 @@ import java.util.Arrays;
// Internal methods. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
if (released || prepared || seekMap == null || !sampleQueuesBuilt) { SeekMap seekMap = this.seekMap;
if (released || prepared || !sampleQueuesBuilt || seekMap == null) {
return; return;
} }
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
...@@ -613,9 +637,7 @@ import java.util.Arrays; ...@@ -613,9 +637,7 @@ import java.util.Arrays;
loadCondition.close(); loadCondition.close();
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount]; TrackGroup[] trackArray = new TrackGroup[trackCount];
trackIsAudioVideoFlags = new boolean[trackCount]; boolean[] trackIsAudioVideoFlags = new boolean[trackCount];
trackEnabledStates = new boolean[trackCount];
trackFormatNotificationSent = new boolean[trackCount];
durationUs = seekMap.getDurationUs(); durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
Format trackFormat = sampleQueues[i].getUpstreamFormat(); Format trackFormat = sampleQueues[i].getUpstreamFormat();
...@@ -625,14 +647,24 @@ import java.util.Arrays; ...@@ -625,14 +647,24 @@ import java.util.Arrays;
trackIsAudioVideoFlags[i] = isAudioVideo; trackIsAudioVideoFlags[i] = isAudioVideo;
haveAudioVideoTracks |= isAudioVideo; haveAudioVideoTracks |= isAudioVideo;
} }
tracks = new TrackGroupArray(trackArray);
dataType = dataType =
length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET
? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE
: C.DATA_TYPE_MEDIA; : C.DATA_TYPE_MEDIA;
preparedState =
new PreparedState(
new TrackGroupArray(trackArray),
/* trackEnabledStates= */ new boolean[trackCount],
trackIsAudioVideoFlags,
/* trackFormatNotificationSent= */ new boolean[trackCount],
seekMap);
prepared = true; prepared = true;
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable());
callback.onPrepared(this); Assertions.checkNotNull(callback).onPrepared(this);
}
private PreparedState getPreparedState() {
return Assertions.checkNotNull(preparedState);
} }
private void copyLengthFromLoader(ExtractingLoadable loadable) { private void copyLengthFromLoader(ExtractingLoadable loadable) {
...@@ -643,8 +675,10 @@ import java.util.Arrays; ...@@ -643,8 +675,10 @@ import java.util.Arrays;
private void startLoading() { private void startLoading() {
ExtractingLoadable loadable = ExtractingLoadable loadable =
new ExtractingLoadable(uri, dataSource, extractorHolder, loadCondition); new ExtractingLoadable(
uri, dataSource, extractorHolder, /* extractorOutput= */ this, loadCondition);
if (prepared) { if (prepared) {
SeekMap seekMap = getPreparedState().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;
...@@ -719,10 +753,11 @@ import java.util.Arrays; ...@@ -719,10 +753,11 @@ import java.util.Arrays;
/** /**
* Attempts to seek to the specified position within the sample queues. * Attempts to seek to the specified position within the sample queues.
* *
* @param trackIsAudioVideoFlags Whether each track is audio/video.
* @param positionUs The seek position in microseconds. * @param positionUs The seek position in microseconds.
* @return Whether the in-buffer seek was successful. * @return Whether the in-buffer seek was successful.
*/ */
private boolean seekInsideBufferUs(long positionUs) { private boolean seekInsideBufferUs(boolean[] trackIsAudioVideoFlags, long positionUs) {
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
SampleQueue sampleQueue = sampleQueues[i]; SampleQueue sampleQueue = sampleQueues[i];
...@@ -798,6 +833,7 @@ import java.util.Arrays; ...@@ -798,6 +833,7 @@ import java.util.Arrays;
private final Uri uri; private final Uri uri;
private final StatsDataSource dataSource; private final StatsDataSource dataSource;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
private final ExtractorOutput extractorOutput;
private final ConditionVariable loadCondition; private final ConditionVariable loadCondition;
private final PositionHolder positionHolder; private final PositionHolder positionHolder;
...@@ -812,10 +848,12 @@ import java.util.Arrays; ...@@ -812,10 +848,12 @@ import java.util.Arrays;
Uri uri, Uri uri,
DataSource dataSource, DataSource dataSource,
ExtractorHolder extractorHolder, ExtractorHolder extractorHolder,
ExtractorOutput extractorOutput,
ConditionVariable loadCondition) { ConditionVariable loadCondition) {
this.uri = Assertions.checkNotNull(uri); this.uri = uri;
this.dataSource = new StatsDataSource(dataSource); this.dataSource = new StatsDataSource(dataSource);
this.extractorHolder = Assertions.checkNotNull(extractorHolder); this.extractorHolder = extractorHolder;
this.extractorOutput = extractorOutput;
this.loadCondition = loadCondition; this.loadCondition = loadCondition;
this.positionHolder = new PositionHolder(); this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true; this.pendingExtractorSeek = true;
...@@ -842,8 +880,9 @@ import java.util.Arrays; ...@@ -842,8 +880,9 @@ import java.util.Arrays;
if (length != C.LENGTH_UNSET) { if (length != C.LENGTH_UNSET) {
length += position; length += position;
} }
Uri uri = Assertions.checkNotNull(dataSource.getUri());
input = new DefaultExtractorInput(dataSource, position, length); input = new DefaultExtractorInput(dataSource, position, length);
Extractor extractor = extractorHolder.selectExtractor(input, dataSource.getUri()); Extractor extractor = extractorHolder.selectExtractor(input, extractorOutput, uri);
if (pendingExtractorSeek) { if (pendingExtractorSeek) {
extractor.seek(position, seekTimeUs); extractor.seek(position, seekTimeUs);
pendingExtractorSeek = false; pendingExtractorSeek = false;
...@@ -877,24 +916,20 @@ import java.util.Arrays; ...@@ -877,24 +916,20 @@ import java.util.Arrays;
} }
} }
/** /** Stores a list of extractors and a selected extractor when the format has been detected. */
* Stores a list of extractors and a selected extractor when the format has been detected.
*/
private static final class ExtractorHolder { private static final class ExtractorHolder {
private final Extractor[] extractors; private final Extractor[] extractors;
private final ExtractorOutput extractorOutput;
private Extractor extractor; private @Nullable Extractor extractor;
/** /**
* Creates a holder that will select an extractor and initialize it using the specified output. * Creates a holder that will select an extractor and initialize it using the specified output.
* *
* @param extractors One or more extractors to choose from. * @param extractors One or more extractors to choose from.
* @param extractorOutput The output that will be used to initialize the selected extractor.
*/ */
public ExtractorHolder(Extractor[] extractors, ExtractorOutput extractorOutput) { public ExtractorHolder(Extractor[] extractors) {
this.extractors = extractors; this.extractors = extractors;
this.extractorOutput = extractorOutput;
} }
/** /**
...@@ -902,13 +937,15 @@ import java.util.Arrays; ...@@ -902,13 +937,15 @@ import java.util.Arrays;
* later calls. * later calls.
* *
* @param input The {@link ExtractorInput} from which data should be read. * @param input The {@link ExtractorInput} from which data should be read.
* @param output The {@link ExtractorOutput} that will be used to initialize the selected
* extractor.
* @param uri The {@link Uri} of the data. * @param uri The {@link Uri} of the data.
* @return An initialized extractor for reading {@code input}. * @return An initialized extractor for reading {@code input}.
* @throws UnrecognizedInputFormatException Thrown if the input format could not be detected. * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected.
* @throws IOException Thrown if the input could not be read. * @throws IOException Thrown if the input could not be read.
* @throws InterruptedException Thrown if the thread was interrupted. * @throws InterruptedException Thrown if the thread was interrupted.
*/ */
public Extractor selectExtractor(ExtractorInput input, Uri uri) public Extractor selectExtractor(ExtractorInput input, ExtractorOutput output, Uri uri)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (extractor != null) { if (extractor != null) {
return extractor; return extractor;
...@@ -929,7 +966,7 @@ import java.util.Arrays; ...@@ -929,7 +966,7 @@ import java.util.Arrays;
throw new UnrecognizedInputFormatException("None of the available extractors (" throw new UnrecognizedInputFormatException("None of the available extractors ("
+ Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.", uri); + Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.", uri);
} }
extractor.init(extractorOutput); extractor.init(output);
return extractor; return extractor;
} }
...@@ -940,4 +977,26 @@ import java.util.Arrays; ...@@ -940,4 +977,26 @@ import java.util.Arrays;
} }
} }
} }
/** Stores state that is initialized when preparation completes. */
private static final class PreparedState {
public final TrackGroupArray tracks;
public final boolean[] trackEnabledStates;
public final boolean[] trackIsAudioVideoFlags;
public final boolean[] trackFormatNotificationSent;
public final SeekMap seekMap;
public PreparedState(
TrackGroupArray tracks,
boolean[] trackEnabledStates,
boolean[] trackIsAudioVideoFlags,
boolean[] trackFormatNotificationSent,
SeekMap seekMap) {
this.tracks = tracks;
this.trackEnabledStates = trackEnabledStates;
this.trackIsAudioVideoFlags = trackIsAudioVideoFlags;
this.trackFormatNotificationSent = trackFormatNotificationSent;
this.seekMap = seekMap;
}
}
} }
...@@ -123,7 +123,7 @@ public final class MimeTypes { ...@@ -123,7 +123,7 @@ public final class MimeTypes {
* @param mimeType The mimeType to test. * @param mimeType The mimeType to test.
* @return Whether the top level type is audio. * @return Whether the top level type is audio.
*/ */
public static boolean isAudio(String mimeType) { public static boolean isAudio(@Nullable String mimeType) {
return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType)); return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType));
} }
...@@ -133,7 +133,7 @@ public final class MimeTypes { ...@@ -133,7 +133,7 @@ public final class MimeTypes {
* @param mimeType The mimeType to test. * @param mimeType The mimeType to test.
* @return Whether the top level type is video. * @return Whether the top level type is video.
*/ */
public static boolean isVideo(String mimeType) { public static boolean isVideo(@Nullable String mimeType) {
return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType)); return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType));
} }
...@@ -143,7 +143,7 @@ public final class MimeTypes { ...@@ -143,7 +143,7 @@ public final class MimeTypes {
* @param mimeType The mimeType to test. * @param mimeType The mimeType to test.
* @return Whether the top level type is text. * @return Whether the top level type is text.
*/ */
public static boolean isText(String mimeType) { public static boolean isText(@Nullable String mimeType) {
return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType)); return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType));
} }
...@@ -153,7 +153,7 @@ public final class MimeTypes { ...@@ -153,7 +153,7 @@ public final class MimeTypes {
* @param mimeType The mimeType to test. * @param mimeType The mimeType to test.
* @return Whether the top level type is application. * @return Whether the top level type is application.
*/ */
public static boolean isApplication(String mimeType) { public static boolean isApplication(@Nullable String mimeType) {
return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType)); return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType));
} }
......
...@@ -73,6 +73,7 @@ import java.util.regex.Pattern; ...@@ -73,6 +73,7 @@ import java.util.regex.Pattern;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.Inflater; import java.util.zip.Inflater;
import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.checker.nullness.qual.PolyNull;
...@@ -271,6 +272,13 @@ public final class Util { ...@@ -271,6 +272,13 @@ public final class Util {
return value; return value;
} }
/** Casts a nullable type array to a non-null type array without runtime null check. */
@SuppressWarnings({"contracts.postcondition.not.satisfied", "return.type.incompatible"})
@EnsuresNonNull("#1")
public static <T> T[] castNonNullTypeArray(@NullableType T[] value) {
return value;
}
/** /**
* Copies and optionally truncates an array. Prevents null array elements created by {@link * Copies and optionally truncates an array. Prevents null array elements created by {@link
* Arrays#copyOf(Object[], int)} by ensuring the new length does not exceed the current length. * Arrays#copyOf(Object[], int)} by ensuring the new length does not exceed the current length.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment