diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
index 3483ed68d5..400c03d769 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java
@@ -17,18 +17,18 @@ package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
-import com.google.android.exoplayer.extractor.DefaultTrackOutput;
+import com.google.android.exoplayer.extractor.RollingSampleBuffer;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
/**
* A base implementation of {@link MediaChunk}, for chunks that contain a single track.
*
- * Loaded samples are output to a {@link DefaultTrackOutput}.
+ * Loaded samples are output to a {@link RollingSampleBuffer}.
*/
public abstract class BaseMediaChunk extends MediaChunk {
- private DefaultTrackOutput trackOutput;
+ private RollingSampleBuffer trackOutput;
private int firstSampleIndex;
/**
@@ -46,19 +46,19 @@ public abstract class BaseMediaChunk extends MediaChunk {
}
/**
- * Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive
+ * Initializes the chunk for loading, setting the {@link RollingSampleBuffer} that will receive
* samples as they are loaded.
*
* @param trackOutput The output that will receive the loaded samples.
*/
- public void init(DefaultTrackOutput trackOutput) {
+ public void init(RollingSampleBuffer trackOutput) {
this.trackOutput = trackOutput;
this.firstSampleIndex = trackOutput.getWriteIndex();
}
/**
* Returns the index of the first sample in the output that was passed to
- * {@link #init(DefaultTrackOutput)} that will originate from this chunk.
+ * {@link #init(RollingSampleBuffer)} that will originate from this chunk.
*/
public final int getFirstSampleIndex() {
return firstSampleIndex;
@@ -72,9 +72,9 @@ public abstract class BaseMediaChunk extends MediaChunk {
public abstract DrmInitData getDrmInitData();
/**
- * Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}.
+ * Returns the track output most recently passed to {@link #init(RollingSampleBuffer)}.
*/
- protected final DefaultTrackOutput getTrackOutput() {
+ protected final RollingSampleBuffer getTrackOutput() {
return trackOutput;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
index d79d8cb302..d008d2da98 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
@@ -26,7 +26,7 @@ import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener.EventDispatcher;
-import com.google.android.exoplayer.extractor.DefaultTrackOutput;
+import com.google.android.exoplayer.extractor.RollingSampleBuffer;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions;
@@ -56,7 +56,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
private final ChunkHolder nextChunkHolder;
private final LinkedList mediaChunks;
private final List readOnlyMediaChunks;
- private final DefaultTrackOutput sampleQueue;
+ private final RollingSampleBuffer sampleQueue;
private final int bufferSizeContribution;
private final EventDispatcher eventDispatcher;
@@ -125,7 +125,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
nextChunkHolder = new ChunkHolder();
mediaChunks = new LinkedList<>();
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
- sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
+ sampleQueue = new RollingSampleBuffer(loadControl.getAllocator());
pendingResetPositionUs = C.UNSET_TIME_US;
}
@@ -222,9 +222,9 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
} else if (isPendingReset()) {
return pendingResetPositionUs;
} else {
- long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs();
- return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
- : largestParsedTimestampUs;
+ long largestQueuedTimestampUs = sampleQueue.getLargestQueuedTimestampUs();
+ return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
+ : largestQueuedTimestampUs;
}
}
@@ -316,7 +316,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
return NOTHING_READ;
}
- if (sampleQueue.getSample(buffer)) {
+ if (sampleQueue.readSample(buffer)) {
if (buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
index 67d8da8d5f..e6739c8a90 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
@@ -19,9 +19,9 @@ import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
-import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
+import com.google.android.exoplayer.extractor.RollingSampleBuffer;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
@@ -111,7 +111,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (bytesLoaded == 0) {
// Set the target to ourselves.
- DefaultTrackOutput trackOutput = getTrackOutput();
+ RollingSampleBuffer trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs);
extractorWrapper.init(this, trackOutput);
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
index 82a3dc8696..241d4c4f3e 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
@@ -19,8 +19,8 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
-import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.ExtractorInput;
+import com.google.android.exoplayer.extractor.RollingSampleBuffer;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Util;
@@ -91,7 +91,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
length += bytesLoaded;
}
ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length);
- DefaultTrackOutput trackOutput = getTrackOutput();
+ RollingSampleBuffer trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, 0);
// Load the sample data.
int result = 0;
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
index c3ef28c4d2..eefccccf38 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
@@ -36,9 +36,6 @@ public final class DefaultTrackOutput implements TrackOutput {
private long lastReadTimeUs;
private long spliceOutTimeUs;
- // Accessed by both the loading and consuming threads.
- private volatile long largestParsedTimestampUs;
-
/**
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
*/
@@ -48,7 +45,6 @@ public final class DefaultTrackOutput implements TrackOutput {
needKeyframe = true;
lastReadTimeUs = Long.MIN_VALUE;
spliceOutTimeUs = Long.MIN_VALUE;
- largestParsedTimestampUs = Long.MIN_VALUE;
}
// Called by the consuming thread, but only when there is no loading thread.
@@ -61,36 +57,10 @@ public final class DefaultTrackOutput implements TrackOutput {
needKeyframe = true;
lastReadTimeUs = Long.MIN_VALUE;
spliceOutTimeUs = Long.MIN_VALUE;
- largestParsedTimestampUs = Long.MIN_VALUE;
- }
-
- /**
- * Returns the current absolute write index.
- */
- public int getWriteIndex() {
- return rollingBuffer.getWriteIndex();
- }
-
- /**
- * Discards samples from the write side of the queue.
- *
- * @param discardFromIndex The absolute index of the first sample to be discarded.
- */
- public void discardUpstreamSamples(int discardFromIndex) {
- rollingBuffer.discardUpstreamSamples(discardFromIndex);
- largestParsedTimestampUs = rollingBuffer.peekSample(sampleBuffer) ? sampleBuffer.timeUs
- : Long.MIN_VALUE;
}
// Called by the consuming thread.
- /**
- * Returns the current absolute read index.
- */
- public int getReadIndex() {
- return rollingBuffer.getReadIndex();
- }
-
/**
* Returns the current upstream {@link Format}.
*/
@@ -98,19 +68,12 @@ public final class DefaultTrackOutput implements TrackOutput {
return rollingBuffer.getUpstreamFormat();
}
- /**
- * Returns the current downstream {@link Format}.
- */
- public Format getDownstreamFormat() {
- return rollingBuffer.getDownstreamFormat();
- }
-
/**
* The largest timestamp of any sample received by the output, or {@link Long#MIN_VALUE} if a
* sample has yet to be received.
*/
public long getLargestParsedTimestampUs() {
- return largestParsedTimestampUs;
+ return rollingBuffer.getLargestQueuedTimestampUs();
}
/**
@@ -148,16 +111,6 @@ public final class DefaultTrackOutput implements TrackOutput {
rollingBuffer.skipAllSamples();
}
- /**
- * Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
- *
- * @param timeUs The seek time.
- * @return True if the skip was successful. False otherwise.
- */
- public boolean skipToKeyframeBefore(long timeUs) {
- return rollingBuffer.skipToKeyframeBefore(timeUs);
- }
-
/**
* Attempts to configure a splice from this queue to the next.
*
@@ -216,19 +169,6 @@ public final class DefaultTrackOutput implements TrackOutput {
// Called by the loading thread.
- /**
- * Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
- * samples subsequently queued to the buffer. The offset is also used to adjust
- * {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently
- * passed to {@link #format(Format)}.
- *
- * @param format The format.
- * @param sampleOffsetUs The offset in microseconds.
- */
- public void formatWithOffset(Format format, long sampleOffsetUs) {
- rollingBuffer.formatWithOffset(format, sampleOffsetUs);
- }
-
@Override
public void format(Format format) {
rollingBuffer.format(format);
@@ -247,7 +187,6 @@ public final class DefaultTrackOutput implements TrackOutput {
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
- largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs);
rollingBuffer.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
}
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
index 378917e5de..03eeb37692 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
@@ -195,7 +195,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private final ExtractorHolder extractorHolder;
private final Allocator allocator;
private final int requestedBufferSize;
- private final SparseArray sampleQueues;
+ private final SparseArray sampleQueues;
private final int minLoadableRetryCount;
private final Uri uri;
private final DataSource dataSource;
@@ -425,13 +425,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
} else if (isPendingReset()) {
return pendingResetPositionUs;
} else {
- long largestParsedTimestampUs = Long.MIN_VALUE;
+ long largestQueuedTimestampUs = Long.MIN_VALUE;
for (int i = 0; i < sampleQueues.size(); i++) {
- largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
- sampleQueues.valueAt(i).getLargestParsedTimestampUs());
+ largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
+ sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
}
- return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
- : largestParsedTimestampUs;
+ return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
+ : largestQueuedTimestampUs;
}
}
@@ -474,7 +474,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return TrackStream.NOTHING_READ;
}
- DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track);
+ RollingSampleBuffer sampleQueue = sampleQueues.valueAt(track);
if (pendingMediaFormat[track]) {
formatHolder.format = sampleQueue.getUpstreamFormat();
formatHolder.drmInitData = drmInitData;
@@ -482,7 +482,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return TrackStream.FORMAT_READ;
}
- if (sampleQueue.getSample(buffer)) {
+ if (sampleQueue.readSample(buffer)) {
if (buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
@@ -538,9 +538,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override
public TrackOutput track(int id) {
- DefaultTrackOutput sampleQueue = sampleQueues.get(id);
+ RollingSampleBuffer sampleQueue = sampleQueues.get(id);
if (sampleQueue == null) {
- sampleQueue = new DefaultTrackOutput(allocator);
+ sampleQueue = new RollingSampleBuffer(allocator);
sampleQueues.put(id, sampleQueue);
}
return sampleQueue;
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
index 2ac38e66d6..372a9a442f 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
@@ -31,7 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque;
/**
* A rolling buffer of sample data and corresponding sample information.
*/
-/* package */ final class RollingSampleBuffer implements TrackOutput {
+public final class RollingSampleBuffer implements TrackOutput {
private static final int INITIAL_SCRATCH_SIZE = 32;
@@ -129,6 +129,13 @@ import java.util.concurrent.LinkedBlockingDeque;
// Called by the consuming thread.
+ /**
+ * Returns whether the buffer is empty.
+ */
+ public boolean isEmpty() {
+ return infoQueue.isEmpty();
+ }
+
/**
* Returns the current absolute read index.
*/
@@ -151,6 +158,20 @@ import java.util.concurrent.LinkedBlockingDeque;
return nextSampleFormat != null ? nextSampleFormat : upstreamFormat;
}
+ /**
+ * Returns the largest sample timestamp that has been queued since the last {@link #clear()}.
+ *
+ * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
+ * considered as having been queued. Samples that were dequeued from the front of the queue are
+ * considered as having been queued.
+ *
+ * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no
+ * samples have been queued.
+ */
+ public long getLargestQueuedTimestampUs() {
+ return infoQueue.getLargestQueuedTimestampUs();
+ }
+
/**
* Fills {@code buffer} with information about the current sample, but does not write its data.
*
@@ -470,6 +491,9 @@ import java.util.concurrent.LinkedBlockingDeque;
private int relativeReadIndex;
private int relativeWriteIndex;
+ private long largestDequeuedTimestampUs;
+ private long largestQueuedTimestampUs;
+
public InfoQueue() {
capacity = SAMPLE_CAPACITY_INCREMENT;
offsets = new long[capacity];
@@ -478,6 +502,8 @@ import java.util.concurrent.LinkedBlockingDeque;
sizes = new int[capacity];
encryptionKeys = new byte[capacity][];
formats = new Format[capacity];
+ largestDequeuedTimestampUs = Long.MIN_VALUE;
+ largestQueuedTimestampUs = Long.MIN_VALUE;
}
// Called by the consuming thread, but only when there is no loading thread.
@@ -490,6 +516,8 @@ import java.util.concurrent.LinkedBlockingDeque;
relativeReadIndex = 0;
relativeWriteIndex = 0;
queueSize = 0;
+ largestDequeuedTimestampUs = Long.MIN_VALUE;
+ largestQueuedTimestampUs = Long.MIN_VALUE;
}
/**
@@ -520,6 +548,16 @@ import java.util.concurrent.LinkedBlockingDeque;
queueSize -= discardCount;
relativeWriteIndex = (relativeWriteIndex + capacity - discardCount) % capacity;
+ // Update the largest queued timestamp, assuming that the timestamps prior to a keyframe are
+ // always less than the timestamp of the keyframe itself, and of subsequent frames.
+ largestQueuedTimestampUs = Long.MIN_VALUE;
+ for (int i = queueSize - 1; i >= 0; i--) {
+ int sampleIndex = (relativeReadIndex + i) % capacity;
+ largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timesUs[sampleIndex]);
+ if ((flags[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) {
+ break;
+ }
+ }
return offsets[relativeWriteIndex];
}
@@ -532,6 +570,10 @@ import java.util.concurrent.LinkedBlockingDeque;
return absoluteReadIndex;
}
+ public synchronized boolean isEmpty() {
+ return queueSize == 0;
+ }
+
/**
* Returns the {@link Format} of the next sample, or null of the queue is empty.
*/
@@ -542,6 +584,20 @@ import java.util.concurrent.LinkedBlockingDeque;
return formats[relativeReadIndex];
}
+ /**
+ * Returns the largest sample timestamp that has been queued since the last {@link #clear()}.
+ *
+ * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
+ * considered as having been queued. Samples that were dequeued from the front of the queue are
+ * considered as having been queued.
+ *
+ * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no
+ * samples have been queued.
+ */
+ public synchronized long getLargestQueuedTimestampUs() {
+ return Math.max(largestDequeuedTimestampUs, largestQueuedTimestampUs);
+ }
+
/**
* Fills {@code buffer} with information about the current sample, but does not write its data.
* The absolute position of the sample's data in the rolling buffer is stored in
@@ -575,6 +631,7 @@ import java.util.concurrent.LinkedBlockingDeque;
*/
public synchronized long moveToNextSample() {
queueSize--;
+ largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, timesUs[relativeReadIndex]);
int lastReadIndex = relativeReadIndex++;
absoluteReadIndex++;
if (relativeReadIndex == capacity) {
@@ -658,6 +715,7 @@ import java.util.concurrent.LinkedBlockingDeque;
flags[relativeWriteIndex] = sampleFlags;
encryptionKeys[relativeWriteIndex] = encryptionKey;
formats[relativeWriteIndex] = format;
+ largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
// Increment the write index.
queueSize++;
if (queueSize == capacity) {