diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
index d1b5ce600d..e19de76466 100644
--- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
+++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
@@ -68,7 +68,7 @@ public class AdtsReaderTest extends TestCase {
@Override
protected void setUp() throws Exception {
- FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(true);
+ FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
adtsOutput = fakeExtractorOutput.track(0);
id3Output = fakeExtractorOutput.track(1);
adtsReader = new AdtsReader(true);
diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
index 6bf65710fc..4120110afb 100644
--- a/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
+++ b/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
@@ -56,7 +56,7 @@ public interface Extractor {
boolean sniff(ExtractorInput input) throws IOException, InterruptedException;
/**
- * Initializes the extractor with an {@link ExtractorOutput}.
+ * Initializes the extractor with an {@link ExtractorOutput}. Called at most once.
*
* @param output An {@link ExtractorOutput} to receive extracted data.
*/
diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
index d138c7ce3a..a547f745ca 100644
--- a/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
+++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
@@ -21,18 +21,19 @@ package com.google.android.exoplayer2.extractor;
public interface ExtractorOutput {
/**
- * Called when the {@link Extractor} identifies the existence of a track in the stream.
+ * Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
*
- * Returns a {@link TrackOutput} that will receive track level data belonging to the track.
+ * The same {@link TrackOutput} is returned if multiple calls are made with the same
+ * {@code trackId}.
*
- * @param trackId A unique track identifier.
- * @return The {@link TrackOutput} that should receive track level data belonging to the track.
+ * @param trackId A track identifier.
+ * @return The {@link TrackOutput} for the given track identifier.
*/
TrackOutput track(int trackId);
/**
- * Called when all tracks have been identified, meaning that {@link #track(int)} will not be
- * called again.
+ * Called when all tracks have been identified, meaning no new {@code trackId} values will be
+ * passed to {@link #track(int)}.
*/
void endTracks();
diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
index f5a7d090e1..bac362d711 100644
--- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
@@ -54,7 +54,6 @@ public final class TsExtractor implements Extractor {
};
-
public static final int TS_STREAM_TYPE_MPA = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC = 0x0F;
@@ -92,6 +91,7 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread.
private ExtractorOutput output;
+ private boolean tracksEnded;
private ElementaryStreamReader id3Reader;
public TsExtractor() {
@@ -120,8 +120,8 @@ public final class TsExtractor implements Extractor {
tsScratch = new ParsableBitArray(new byte[3]);
trackIds = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>();
- tsPayloadReaders.put(TS_PAT_PID, new PatReader());
continuityCounters = new SparseIntArray();
+ resetPayloadReaders();
}
// Extractor implementation.
@@ -153,11 +153,10 @@ public final class TsExtractor implements Extractor {
@Override
public void seek(long position) {
timestampAdjuster.reset();
- for (int i = 0; i < tsPayloadReaders.size(); i++) {
- tsPayloadReaders.valueAt(i).seek();
- }
tsPacketBuffer.reset();
continuityCounters.clear();
+ // Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
+ resetPayloadReaders();
}
@Override
@@ -252,6 +251,13 @@ public final class TsExtractor implements Extractor {
// Internals.
+ private void resetPayloadReaders() {
+ trackIds.clear();
+ tsPayloadReaders.clear();
+ tsPayloadReaders.put(TS_PAT_PID, new PatReader());
+ id3Reader = null;
+ }
+
/**
* Parses TS packet payload data.
*/
@@ -345,7 +351,7 @@ public final class TsExtractor implements Extractor {
patScratch.skipBits(13); // network_PID (13)
} else {
int pid = patScratch.readBits(13);
- tsPayloadReaders.put(pid, new PmtReader());
+ tsPayloadReaders.put(pid, new PmtReader(pid));
}
}
}
@@ -365,14 +371,16 @@ public final class TsExtractor implements Extractor {
private final ParsableBitArray pmtScratch;
private final ParsableByteArray sectionData;
+ private final int pid;
private int sectionLength;
private int sectionBytesRead;
private int crc;
- public PmtReader() {
+ public PmtReader(int pid) {
pmtScratch = new ParsableBitArray(new byte[5]);
sectionData = new ParsableByteArray();
+ this.pid = pid;
}
@Override
@@ -466,8 +474,16 @@ public final class TsExtractor implements Extractor {
tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster));
}
}
-
- output.endTracks();
+ if (mapByType) {
+ if (!tracksEnded) {
+ output.endTracks();
+ }
+ } else {
+ tsPayloadReaders.remove(TS_PAT_PID);
+ tsPayloadReaders.remove(pid);
+ output.endTracks();
+ }
+ tracksEnded = true;
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java
index 49f2cffca5..27bd1f677f 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
import android.net.Uri;
import android.os.Handler;
+import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
@@ -41,7 +42,6 @@ import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
-import java.util.Arrays;
/**
* A {@link MediaPeriod} that extracts data using an {@link Extractor}.
@@ -68,6 +68,7 @@ import java.util.Arrays;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
+ private final SparseArray sampleQueues;
private Callback callback;
private SeekMap seekMap;
@@ -77,7 +78,6 @@ import java.util.Arrays;
private boolean seenFirstTrackSelection;
private boolean notifyReset;
private int enabledTrackCount;
- private DefaultTrackOutput[] sampleQueues;
private TrackGroupArray tracks;
private long durationUs;
private boolean[] trackEnabledStates;
@@ -131,7 +131,7 @@ import java.util.Arrays;
handler = new Handler();
pendingResetPositionUs = C.TIME_UNSET;
- sampleQueues = new DefaultTrackOutput[0];
+ sampleQueues = new SparseArray<>();
length = C.LENGTH_UNSET;
}
@@ -141,8 +141,9 @@ import java.util.Arrays;
@Override
public void run() {
extractorHolder.release();
- for (DefaultTrackOutput sampleQueue : sampleQueues) {
- sampleQueue.disable();
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
+ sampleQueues.valueAt(i).disable();
}
}
});
@@ -178,7 +179,7 @@ import java.util.Arrays;
Assertions.checkState(trackEnabledStates[track]);
enabledTrackCount--;
trackEnabledStates[track] = false;
- sampleQueues[track].disable();
+ sampleQueues.valueAt(track).disable();
streams[i] = null;
}
}
@@ -201,9 +202,10 @@ import java.util.Arrays;
if (!seenFirstTrackSelection) {
// At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required.
- for (int i = 0; i < sampleQueues.length; i++) {
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
if (!trackEnabledStates[i]) {
- sampleQueues[i].disable();
+ sampleQueues.valueAt(i).disable();
}
}
}
@@ -270,11 +272,12 @@ import java.util.Arrays;
// Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0;
lastSeekPositionUs = positionUs;
+ int trackCount = sampleQueues.size();
// If we're not pending a reset, see if we can seek within the sample queues.
boolean seekInsideBuffer = !isPendingReset();
- for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
+ for (int i = 0; seekInsideBuffer && i < trackCount; i++) {
if (trackEnabledStates[i]) {
- seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
+ seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs);
}
}
// If we failed to seek within the sample queues, we need to restart.
@@ -284,8 +287,8 @@ import java.util.Arrays;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
- for (int i = 0; i < sampleQueues.length; i++) {
- sampleQueues[i].reset(trackEnabledStates[i]);
+ for (int i = 0; i < trackCount; i++) {
+ sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
}
}
}
@@ -296,7 +299,7 @@ import java.util.Arrays;
// SampleStream methods.
/* package */ boolean isReady(int track) {
- return loadingFinished || (!isPendingReset() && !sampleQueues[track].isEmpty());
+ return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty());
}
/* package */ void maybeThrowError() throws IOException {
@@ -308,7 +311,8 @@ import java.util.Arrays;
return C.RESULT_NOTHING_READ;
}
- return sampleQueues[track].readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
+ return sampleQueues.valueAt(track).readData(formatHolder, buffer, loadingFinished,
+ lastSeekPositionUs);
}
// Loader.Callback implementation.
@@ -332,8 +336,9 @@ import java.util.Arrays;
long loadDurationMs, boolean released) {
copyLengthFromLoader(loadable);
if (!released && enabledTrackCount > 0) {
- for (int i = 0; i < sampleQueues.length; i++) {
- sampleQueues[i].reset(trackEnabledStates[i]);
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
+ sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
}
callback.onContinueLoadingRequested(this);
}
@@ -358,11 +363,13 @@ import java.util.Arrays;
@Override
public TrackOutput track(int id) {
- sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
- DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
- sampleQueue.setUpstreamFormatChangeListener(this);
- sampleQueues[sampleQueues.length - 1] = sampleQueue;
- return sampleQueue;
+ DefaultTrackOutput trackOutput = sampleQueues.get(id);
+ if (trackOutput == null) {
+ trackOutput = new DefaultTrackOutput(allocator);
+ trackOutput.setUpstreamFormatChangeListener(this);
+ sampleQueues.put(id, trackOutput);
+ }
+ return trackOutput;
}
@Override
@@ -390,18 +397,18 @@ import java.util.Arrays;
if (released || prepared || seekMap == null || !tracksBuilt) {
return;
}
- for (DefaultTrackOutput sampleQueue : sampleQueues) {
- if (sampleQueue.getUpstreamFormat() == null) {
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
+ if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
return;
}
}
loadCondition.close();
- int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount];
durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) {
- trackArray[i] = new TrackGroup(sampleQueues[i].getUpstreamFormat());
+ trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getUpstreamFormat());
}
tracks = new TrackGroupArray(trackArray);
prepared = true;
@@ -455,8 +462,9 @@ import java.util.Arrays;
// a new load.
lastSeekPositionUs = 0;
notifyReset = prepared;
- for (int i = 0; i < sampleQueues.length; i++) {
- sampleQueues[i].reset(!prepared || trackEnabledStates[i]);
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
+ sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]);
}
loadable.setLoadPosition(0);
}
@@ -464,17 +472,19 @@ import java.util.Arrays;
private int getExtractedSamplesCount() {
int extractedSamplesCount = 0;
- for (DefaultTrackOutput sampleQueue : sampleQueues) {
- extractedSamplesCount += sampleQueue.getWriteIndex();
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
+ extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex();
}
return extractedSamplesCount;
}
private long getLargestQueuedTimestampUs() {
long largestQueuedTimestampUs = Long.MIN_VALUE;
- for (DefaultTrackOutput sampleQueue : sampleQueues) {
+ int trackCount = sampleQueues.size();
+ for (int i = 0; i < trackCount; i++) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
- sampleQueue.getLargestQueuedTimestampUs());
+ sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
}
return largestQueuedTimestampUs;
}
@@ -523,7 +533,7 @@ import java.util.Arrays;
@Override
public void skipToKeyframeBefore(long timeUs) {
- sampleQueues[track].skipToKeyframeBefore(timeUs);
+ sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs);
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
index e316215160..b9aa098b9d 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
@@ -59,6 +59,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
// Accessed only on the loader thread.
private boolean seenTrack;
+ private int seenTrackId;
/**
* @param extractor The extractor to wrap.
@@ -116,8 +117,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public TrackOutput track(int id) {
- Assertions.checkState(!seenTrack);
+ Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true;
+ seenTrackId = id;
return this;
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
index b0ab90789c..3716c6d37f 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
@@ -23,7 +23,6 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import junit.framework.Assert;
-import junit.framework.TestCase;
/**
* A fake {@link ExtractorOutput}.
@@ -37,8 +36,6 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
*/
private static final boolean WRITE_DUMP = false;
- private final boolean allowDuplicateTrackIds;
-
public final SparseArray trackOutputs;
public int numberOfTracks;
@@ -46,11 +43,6 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
public SeekMap seekMap;
public FakeExtractorOutput() {
- this(false);
- }
-
- public FakeExtractorOutput(boolean allowDuplicateTrackIds) {
- this.allowDuplicateTrackIds = allowDuplicateTrackIds;
trackOutputs = new SparseArray<>();
}
@@ -58,11 +50,10 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
public FakeTrackOutput track(int trackId) {
FakeTrackOutput output = trackOutputs.get(trackId);
if (output == null) {
+ Assert.assertFalse(tracksEnded);
numberOfTracks++;
output = new FakeTrackOutput();
trackOutputs.put(trackId, output);
- } else {
- TestCase.assertTrue("Duplicate track id: " + trackId, allowDuplicateTrackIds);
}
return output;
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
index 7b88062718..6f4578b694 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
@@ -267,8 +267,8 @@ public class TestUtil {
*/
public static FakeExtractorOutput assertOutput(Extractor extractor, String sampleFile,
byte[] fileData, Instrumentation instrumentation, boolean simulateIOErrors,
- boolean simulateUnknownLength,
- boolean simulatePartialReads) throws IOException, InterruptedException {
+ boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException,
+ InterruptedException {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData)
.setSimulateIOErrors(simulateIOErrors)
.setSimulateUnknownLength(simulateUnknownLength)