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 deleted file mode 100644 index eefccccf38..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.extractor; - -import com.google.android.exoplayer.DecoderInputBuffer; -import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.upstream.Allocator; -import com.google.android.exoplayer.util.ParsableByteArray; - -import java.io.IOException; - -/** - * A {@link TrackOutput} that buffers extracted samples in a queue, and allows for consumption from - * that queue. - */ -public final class DefaultTrackOutput implements TrackOutput { - - private final RollingSampleBuffer rollingBuffer; - private final DecoderInputBuffer sampleBuffer; - - // Accessed only by the consuming thread. - private boolean needKeyframe; - private long lastReadTimeUs; - private long spliceOutTimeUs; - - /** - * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. - */ - public DefaultTrackOutput(Allocator allocator) { - rollingBuffer = new RollingSampleBuffer(allocator); - sampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); - needKeyframe = true; - lastReadTimeUs = Long.MIN_VALUE; - spliceOutTimeUs = Long.MIN_VALUE; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Clears the queue, returning all allocations to the allocator. - */ - public void clear() { - rollingBuffer.clear(); - needKeyframe = true; - lastReadTimeUs = Long.MIN_VALUE; - spliceOutTimeUs = Long.MIN_VALUE; - } - - // Called by the consuming thread. - - /** - * Returns the current upstream {@link Format}. - */ - public Format getUpstreamFormat() { - return rollingBuffer.getUpstreamFormat(); - } - - /** - * 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 rollingBuffer.getLargestQueuedTimestampUs(); - } - - /** - * True if at least one sample can be read from the queue. False otherwise. - */ - public boolean isEmpty() { - return !advanceToEligibleSample(); - } - - /** - * Removes the next sample from the head of the queue, writing it into the provided buffer. - *
- * The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples - * queued prior to the first keyframe are discarded. - * - * @param buffer A {@link DecoderInputBuffer} into which the sample should be read. - * @return True if a sample was read. False otherwise. - */ - public boolean getSample(DecoderInputBuffer buffer) { - boolean foundEligibleSample = advanceToEligibleSample(); - if (!foundEligibleSample) { - return false; - } - // Write the sample into the buffer. - rollingBuffer.readSample(buffer); - needKeyframe = false; - lastReadTimeUs = buffer.timeUs; - return true; - } - - /** - * Skips all currently buffered samples. - */ - public void skipAllSamples() { - rollingBuffer.skipAllSamples(); - } - - /** - * Attempts to configure a splice from this queue to the next. - * - * @param nextQueue The queue being spliced to. - * @return Whether the splice was configured successfully. - */ - public boolean configureSpliceTo(DefaultTrackOutput nextQueue) { - if (spliceOutTimeUs != Long.MIN_VALUE) { - // We've already configured the splice. - return true; - } - long firstPossibleSpliceTime; - if (rollingBuffer.peekSample(sampleBuffer)) { - firstPossibleSpliceTime = sampleBuffer.timeUs; - } else { - firstPossibleSpliceTime = lastReadTimeUs + 1; - } - RollingSampleBuffer nextRollingBuffer = nextQueue.rollingBuffer; - while (nextRollingBuffer.peekSample(sampleBuffer) - && (sampleBuffer.timeUs < firstPossibleSpliceTime || !sampleBuffer.isKeyFrame())) { - // Discard samples from the next queue for as long as they are before the earliest possible - // splice time, or not keyframes. - nextRollingBuffer.skipSample(); - } - if (nextRollingBuffer.peekSample(sampleBuffer)) { - // We've found a keyframe in the next queue that can serve as the splice point. Set the - // splice point now. - spliceOutTimeUs = sampleBuffer.timeUs; - return true; - } - return false; - } - - /** - * Advances the underlying buffer to the next sample that is eligible to be returned. - * - * @return True if an eligible sample was found. False otherwise, in which case the underlying - * buffer has been emptied. - */ - private boolean advanceToEligibleSample() { - boolean haveNext = rollingBuffer.peekSample(sampleBuffer); - if (needKeyframe) { - while (haveNext && !sampleBuffer.isKeyFrame()) { - rollingBuffer.skipSample(); - haveNext = rollingBuffer.peekSample(sampleBuffer); - } - } - if (!haveNext) { - return false; - } - if (spliceOutTimeUs != Long.MIN_VALUE && sampleBuffer.timeUs >= spliceOutTimeUs) { - return false; - } - return true; - } - - // Called by the loading thread. - - @Override - public void format(Format format) { - rollingBuffer.format(format); - } - - @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return rollingBuffer.sampleData(input, length, allowEndOfInput); - } - - @Override - public void sampleData(ParsableByteArray buffer, int length) { - rollingBuffer.sampleData(buffer, length); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - rollingBuffer.sampleMetadata(timeUs, flags, size, offset, encryptionKey); - } - -} 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 372a9a442f..76bc58c60d 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 @@ -46,11 +46,13 @@ public final class RollingSampleBuffer implements TrackOutput { // Accessed only by the consuming thread. private long totalBytesDropped; - // Accessed only by the loading thread. + // Accessed only by the loading thread (or the consuming thread when there is no loading thread). private long sampleOffsetUs; private long totalBytesWritten; private Allocation lastAllocation; private int lastAllocationOffset; + private boolean needKeyframe; + private boolean pendingSplice; // Accessed by both the loading and consuming threads. private volatile Format upstreamFormat; @@ -66,6 +68,7 @@ public final class RollingSampleBuffer implements TrackOutput { extrasHolder = new BufferExtrasHolder(); scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); lastAllocationOffset = allocationLength; + needKeyframe = true; } // Called by the consuming thread, but only when there is no loading thread. @@ -82,6 +85,15 @@ public final class RollingSampleBuffer implements TrackOutput { totalBytesWritten = 0; lastAllocation = null; lastAllocationOffset = allocationLength; + needKeyframe = true; + } + + /** + * Indicates that samples subsequently queued to the buffer should be spliced into those already + * queued. + */ + public void splice() { + pendingSplice = true; } /** @@ -172,27 +184,6 @@ public final class RollingSampleBuffer implements TrackOutput { return infoQueue.getLargestQueuedTimestampUs(); } - /** - * Fills {@code buffer} with information about the current sample, but does not write its data. - *
- * Populates {@link DecoderInputBuffer#size}, {@link DecoderInputBuffer#timeUs} and the buffer - * flags. - * - * @param buffer The buffer into which the current sample information should be written. - * @return True if the buffer was filled. False if there is no current sample. - */ - public boolean peekSample(DecoderInputBuffer buffer) { - return infoQueue.peekSample(buffer, extrasHolder); - } - - /** - * Skips the current sample. - */ - public void skipSample() { - long nextOffset = infoQueue.moveToNextSample(); - dropDownstreamTo(nextOffset); - } - /** * Skips all currently buffered samples. */ @@ -227,7 +218,7 @@ public final class RollingSampleBuffer implements TrackOutput { */ public boolean readSample(DecoderInputBuffer buffer) { // Write the sample information into the buffer and extrasHolder. - boolean haveSample = infoQueue.peekSample(buffer, extrasHolder); + boolean haveSample = infoQueue.readSample(buffer, extrasHolder); if (!haveSample) { return false; } @@ -240,8 +231,7 @@ public final class RollingSampleBuffer implements TrackOutput { buffer.ensureSpaceForWrite(buffer.size); readData(extrasHolder.offset, buffer.data, buffer.size); // Advance the read head. - long nextOffset = infoQueue.moveToNextSample(); - dropDownstreamTo(nextOffset); + dropDownstreamTo(extrasHolder.nextOffset); return true; } @@ -396,7 +386,7 @@ public final class RollingSampleBuffer implements TrackOutput { */ public void formatWithOffset(Format format, long sampleOffsetUs) { this.sampleOffsetUs = sampleOffsetUs; - upstreamFormat = getAdjustedSampleFormat(format, sampleOffsetUs); + format(format); } @Override @@ -435,6 +425,22 @@ public final class RollingSampleBuffer implements TrackOutput { @Override public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + if (pendingSplice) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) { + return; + } + // TODO - We should be able to actually remove the data from the rolling buffer after a splice + // succeeds, but doing so is a little bit tricky; it requires moving data written after the + // last committed sample. + pendingSplice = false; + } + if (needKeyframe) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) { + // TODO - As above, although this case is probably less worthwhile. + return; + } + needKeyframe = false; + } timeUs += sampleOffsetUs; long absoluteOffset = totalBytesWritten - size - offset; infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey, upstreamFormat); @@ -601,7 +607,8 @@ public final class RollingSampleBuffer implements TrackOutput { /** * 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 - * {@code extrasHolder}. + * {@code extrasHolder}, along with an encryption id if present, and the absolute position of + * the first byte that may still be required after the current sample has been read. *
* Populates {@link DecoderInputBuffer#size}, {@link DecoderInputBuffer#timeUs}, the buffer
* flags and {@code extrasHolder}.
@@ -610,7 +617,7 @@ public final class RollingSampleBuffer implements TrackOutput {
* @param extrasHolder The holder into which extra sample information should be written.
* @return True if the buffer and extras were filled. False if there is no current sample.
*/
- public synchronized boolean peekSample(DecoderInputBuffer buffer,
+ public synchronized boolean readSample(DecoderInputBuffer buffer,
BufferExtrasHolder extrasHolder) {
if (queueSize == 0) {
return false;
@@ -620,26 +627,19 @@ public final class RollingSampleBuffer implements TrackOutput {
buffer.setFlags(flags[relativeReadIndex]);
extrasHolder.offset = offsets[relativeReadIndex];
extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex];
- return true;
- }
- /**
- * Advances the read index to the next sample.
- *
- * @return The absolute position of the first byte in the rolling buffer that may still be
- * required after advancing the index. Data prior to this position can be dropped.
- */
- public synchronized long moveToNextSample() {
+ largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, buffer.timeUs);
queueSize--;
- largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, timesUs[relativeReadIndex]);
- int lastReadIndex = relativeReadIndex++;
+ relativeReadIndex++;
absoluteReadIndex++;
if (relativeReadIndex == capacity) {
// Wrap around.
relativeReadIndex = 0;
}
- return queueSize > 0 ? offsets[relativeReadIndex]
- : (sizes[lastReadIndex] + offsets[lastReadIndex]);
+
+ extrasHolder.nextOffset = queueSize > 0 ? offsets[relativeReadIndex]
+ : extrasHolder.offset + buffer.size;
+ return true;
}
/**
@@ -760,6 +760,26 @@ public final class RollingSampleBuffer implements TrackOutput {
}
}
+ /**
+ * Attempts to discard samples from the tail of the queue to allow samples starting from the
+ * specified timestamp to be spliced in.
+ *
+ * @param timeUs The timestamp at which the splice occurs.
+ * @return Whether the splice was successful.
+ */
+ public synchronized boolean attemptSplice(long timeUs) {
+ if (largestDequeuedTimestampUs >= timeUs) {
+ return false;
+ }
+ int retainCount = queueSize;
+ while (retainCount > 0
+ && timesUs[(relativeReadIndex + retainCount - 1) % capacity] >= timeUs) {
+ retainCount--;
+ }
+ discardUpstreamSamples(absoluteReadIndex + retainCount);
+ return true;
+ }
+
}
/**
@@ -768,6 +788,7 @@ public final class RollingSampleBuffer implements TrackOutput {
private static final class BufferExtrasHolder {
public long offset;
+ public long nextOffset;
public byte[] encryptionKeyId;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
index 27d5b0d1ba..924c21d513 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
@@ -399,19 +399,16 @@ public class HlsChunkSource {
Format format = variants[variantIndex].format;
// Configure the extractor that will read the chunk.
- HlsExtractorWrapper extractorWrapper;
+ Extractor extractor;
+ boolean extractorNeedsInit = true;
String lastPathSegment = chunkUri.getLastPathSegment();
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
// TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
// case below.
- Extractor extractor = new AdtsExtractor(startTimeUs);
- extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
- switchingVariant);
+ extractor = new AdtsExtractor(startTimeUs);
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
- Extractor extractor = new Mp3Extractor(startTimeUs);
- extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
- switchingVariant);
+ extractor = new Mp3Extractor(startTimeUs);
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
@@ -422,9 +419,7 @@ public class HlsChunkSource {
// a discontinuity sequence greater than the one that this source is trying to start at.
return;
}
- Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
- extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
- switchingVariant);
+ extractor = new WebvttExtractor(format.language, timestampAdjuster);
} else if (previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.format) {
@@ -448,17 +443,16 @@ public class HlsChunkSource {
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
}
}
- Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
- extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
- switchingVariant);
+ extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
- extractorWrapper = previous.extractorWrapper;
+ extractor = previous.extractor;
+ extractorNeedsInit = false;
}
out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
- chunkMediaSequence, segment.discontinuitySequenceNumber, extractorWrapper, encryptionKey,
- encryptionIv);
+ chunkMediaSequence, segment.discontinuitySequenceNumber, extractor, extractorNeedsInit,
+ switchingVariant, encryptionKey, encryptionIv);
}
/**
@@ -584,15 +578,15 @@ public class HlsChunkSource {
private int getNextVariantIndex(TsChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants();
- long bufferedDurationUs;
- if (previous != null) {
- // Use start time of the previous chunk rather than its end time because switching format will
- // require downloading overlapping segments.
- bufferedDurationUs = Math.max(0, previous.startTimeUs - playbackPositionUs);
- } else {
- bufferedDurationUs = 0;
- }
if (enabledVariants.length > 1) {
+ long bufferedDurationUs;
+ if (previous != null) {
+ // Use start time of the previous chunk rather than its end time because switching format
+ // will require downloading overlapping segments.
+ bufferedDurationUs = Math.max(0, previous.startTimeUs - playbackPositionUs);
+ } else {
+ bufferedDurationUs = 0;
+ }
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, enabledVariantBlacklistFlags,
evaluation);
} else {
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
deleted file mode 100644
index cdcca2771f..0000000000
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.hls;
-
-import com.google.android.exoplayer.DecoderInputBuffer;
-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.Extractor;
-import com.google.android.exoplayer.extractor.ExtractorInput;
-import com.google.android.exoplayer.extractor.ExtractorOutput;
-import com.google.android.exoplayer.extractor.SeekMap;
-import com.google.android.exoplayer.extractor.TrackOutput;
-import com.google.android.exoplayer.upstream.Allocator;
-import com.google.android.exoplayer.util.Assertions;
-
-import android.util.SparseArray;
-
-import java.io.IOException;
-
-/**
- * Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples.
- */
-public final class HlsExtractorWrapper implements ExtractorOutput {
-
- public final int trigger;
- public final Format format;
- public final long startTimeUs;
-
- private final Extractor extractor;
- private final SparseArray
- * The splice is performed such that for each track the samples read from the next extractor
- * start with a keyframe, and continue from where the samples read from this extractor finish.
- * A successful splice may discard samples from either or both extractors.
- *
- * Splice configuration may fail if the next extractor is not yet in a state that allows the
- * splice to be performed. Calling this method is a noop if the splice has already been
- * configured. Hence this method should be called repeatedly during the window within which a
- * splice can be performed.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @param nextExtractor The extractor being spliced to.
- */
- public final void configureSpliceTo(HlsExtractorWrapper nextExtractor) {
- Assertions.checkState(isPrepared());
- if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
- // The splice is already configured, or the next extractor doesn't want to be spliced in, or
- // the next extractor isn't ready to be spliced in.
- return;
- }
- boolean spliceConfigured = true;
- int trackCount = getTrackCount();
- for (int i = 0; i < trackCount; i++) {
- DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i);
- DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i);
- spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
- }
- this.spliceConfigured = spliceConfigured;
- return;
- }
-
- /**
- * Gets the number of available tracks.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @return The number of available tracks.
- */
- public int getTrackCount() {
- Assertions.checkState(isPrepared());
- return sampleQueues.size();
- }
-
- /**
- * Gets the {@link Format} of the samples belonging to a specified track.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @param track The track index.
- * @return The corresponding sample format.
- */
- public Format getSampleFormat(int track) {
- Assertions.checkState(isPrepared());
- return sampleQueues.valueAt(track).getUpstreamFormat();
- }
-
- /**
- * Gets the next sample for the specified track.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @param track The track from which to read.
- * @param buffer A {@link DecoderInputBuffer} to populate with a sample.
- * @return True if a sample was read. False otherwise.
- */
- public boolean getSample(int track, DecoderInputBuffer buffer) {
- Assertions.checkState(isPrepared());
- return sampleQueues.valueAt(track).getSample(buffer);
- }
-
- /**
- * Discards all samples for the specified track.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @param track The track from which samples should be discarded.
- */
- public void discardSamplesForTrack(int track) {
- Assertions.checkState(isPrepared());
- sampleQueues.valueAt(track).skipAllSamples();
- }
-
- /**
- * Whether samples are available for reading from {@link #getSample(int, DecoderInputBuffer)} for
- * the specified track.
- *
- * This method must only be called after the extractor has been prepared.
- *
- * @return True if samples are available for reading from
- * {@link #getSample(int, DecoderInputBuffer)} for the specified track. False otherwise.
- */
- public boolean hasSamples(int track) {
- Assertions.checkState(isPrepared());
- return !sampleQueues.valueAt(track).isEmpty();
- }
-
- /**
- * Reads from the provided {@link ExtractorInput}.
- *
- * @param input The {@link ExtractorInput} from which to read.
- * @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}.
- * @throws IOException If an error occurred reading from the source.
- * @throws InterruptedException If the thread was interrupted.
- */
- public int read(ExtractorInput input) throws IOException, InterruptedException {
- int result = extractor.read(input, null);
- Assertions.checkState(result != Extractor.RESULT_SEEK);
- return result;
- }
-
- // ExtractorOutput implementation.
-
- @Override
- public TrackOutput track(int id) {
- DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
- sampleQueues.put(id, sampleQueue);
- return sampleQueue;
- }
-
- @Override
- public void endTracks() {
- this.tracksBuilt = true;
- }
-
- @Override
- public void seekMap(SeekMap seekMap) {
- // Do nothing.
- }
-
- @Override
- public void drmInitData(DrmInitData drmInit) {
- // Do nothing.
- }
-
-}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java
new file mode 100644
index 0000000000..4d7a9919da
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsOutput.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.hls;
+
+import com.google.android.exoplayer.drm.DrmInitData;
+import com.google.android.exoplayer.extractor.ExtractorOutput;
+import com.google.android.exoplayer.extractor.RollingSampleBuffer;
+import com.google.android.exoplayer.extractor.SeekMap;
+import com.google.android.exoplayer.upstream.Allocator;
+
+import android.util.SparseArray;
+
+/**
+ * An {@link ExtractorOutput} for HLS playbacks.
+ */
+/* package */ final class HlsOutput implements ExtractorOutput {
+
+ private final Allocator allocator;
+ private final SparseArray
- * Calling this method discards extractors without any samples from the front of the queue. The
- * last extractor is retained even if it doesn't have any samples.
- *
- * This method must not be called unless {@link #extractors} is non-empty.
- *
- * @return The current extractor from which samples should be read. Guaranteed to be non-null.
- */
- private HlsExtractorWrapper getCurrentExtractor() {
- HlsExtractorWrapper extractor = extractors.getFirst();
- while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) {
- // We're finished reading from the extractor for all tracks, and so can discard it.
- extractors.removeFirst().clear();
- extractor = extractors.getFirst();
- }
- return extractor;
- }
-
- private void discardSamplesForDisabledTracks(HlsExtractorWrapper extractor) {
- if (!extractor.isPrepared()) {
+ private void discardSamplesForDisabledTracks() {
+ if (!output.prepare()) {
return;
}
for (int i = 0; i < groupEnabledStates.length; i++) {
if (!groupEnabledStates[i]) {
- extractor.discardSamplesForTrack(i);
+ trackOutputs[i].skipAllSamples();
}
}
}
- private boolean haveSamplesForEnabledTracks(HlsExtractorWrapper extractor) {
- if (!extractor.isPrepared()) {
- return false;
- }
- for (int i = 0; i < groupEnabledStates.length; i++) {
- if (groupEnabledStates[i] && extractor.hasSamples(i)) {
- return true;
- }
- }
- return false;
- }
-
private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs;
loadingFinished = false;
@@ -605,10 +539,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
private void clearState() {
- for (int i = 0; i < extractors.size(); i++) {
- extractors.get(i).clear();
- }
- extractors.clear();
+ tsChunks.clear();
+ output.clear();
clearCurrentLoadable();
previousTsLoadable = null;
}
@@ -651,11 +583,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
if (isPendingReset()) {
pendingResetPositionUs = C.UNSET_TIME_US;
}
- HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper;
- if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) {
- extractorWrapper.init(loadControl.getAllocator());
- extractors.addLast(extractorWrapper);
- }
+ tsChunk.init(output);
+ tsChunks.addLast(tsChunk);
eventDispatcher.loadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger,
tsChunk.format, tsChunk.startTimeUs, tsChunk.endTimeUs);
currentTsLoadable = tsChunk;
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
index bfa6101f8e..7784232422 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
@@ -37,11 +37,13 @@ public final class TsChunk extends MediaChunk {
public final int discontinuitySequenceNumber;
/**
- * The wrapped extractor into which this chunk is being consumed.
+ * The extractor into which this chunk is being consumed.
*/
- public final HlsExtractorWrapper extractorWrapper;
+ public final Extractor extractor;
private final boolean isEncrypted;
+ private final boolean extractorNeedsInit;
+ private final boolean shouldSpliceIn;
private int bytesLoaded;
private volatile boolean loadCanceled;
@@ -53,23 +55,45 @@ public final class TsChunk extends MediaChunk {
* @param format The format of the stream to which this chunk belongs.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
- * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param chunkIndex The index of the chunk.
- * @param extractorWrapper A wrapped extractor to parse samples from the data.
+ * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
+ * @param extractor The extractor to parse samples from the data.
+ * @param extractorNeedsInit Whether the extractor needs initializing with the target
+ * {@link HlsOutput}.
+ * @param shouldSpliceIn Whether the samples parsed from this chunk should be spliced into any
+ * samples already queued to the {@link HlsOutput}.
* @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber,
- HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) {
+ Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn,
+ byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
- this.extractorWrapper = extractorWrapper;
+ this.extractor = extractor;
+ this.extractorNeedsInit = extractorNeedsInit;
+ this.shouldSpliceIn = shouldSpliceIn;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
}
+ /**
+ * Initializes the chunk for loading, setting the {@link HlsOutput} that will receive samples as
+ * they are loaded.
+ *
+ * @param output The output that will receive the loaded samples.
+ */
+ public void init(HlsOutput output) {
+ if (shouldSpliceIn) {
+ output.splice();
+ }
+ if (extractorNeedsInit) {
+ extractor.init(output);
+ }
+ }
+
@Override
public long bytesLoaded() {
return bytesLoaded;
@@ -102,7 +126,6 @@ public final class TsChunk extends MediaChunk {
loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
skipLoadedBytes = false;
}
-
try {
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
@@ -112,7 +135,7 @@ public final class TsChunk extends MediaChunk {
try {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
- result = extractorWrapper.read(input);
+ result = extractor.read(input, null);
}
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);