diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index 653dc2d628..bc1ee49a95 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -67,7 +67,7 @@ public final class FrameworkSampleSource implements SampleSource { extractor = new MediaExtractor(); extractor.setDataSource(context, uri, headers); trackStates = new int[extractor.getTrackCount()]; - pendingDiscontinuities = new boolean[extractor.getTrackCount()]; + pendingDiscontinuities = new boolean[trackStates.length]; trackInfos = new TrackInfo[trackStates.length]; for (int i = 0; i < trackStates.length; i++) { android.media.MediaFormat format = extractor.getTrackFormat(i); @@ -84,7 +84,7 @@ public final class FrameworkSampleSource implements SampleSource { @Override public int getTrackCount() { Assertions.checkState(prepared); - return extractor.getTrackCount(); + return trackStates.length; } @Override @@ -97,17 +97,18 @@ public final class FrameworkSampleSource implements SampleSource { public void enable(int track, long timeUs) { Assertions.checkState(prepared); Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED); - boolean wasSourceEnabled = isEnabled(); trackStates[track] = TRACK_STATE_ENABLED; extractor.selectTrack(track); - if (!wasSourceEnabled) { - seekToUs(timeUs); - } + seekToUs(timeUs); } @Override - public void continueBuffering(long playbackPositionUs) { - // Do nothing. The MediaExtractor instance is responsible for buffering. + public boolean continueBuffering(long playbackPositionUs) { + // MediaExtractor takes care of buffering and blocks until it has samples, so we can always + // return true here. Although note that the blocking behavior is itself as bug, as per the + // TODO further up this file. This method will need to return something else as part of fixing + // the TODO. + return true; } @Override @@ -122,15 +123,15 @@ public final class FrameworkSampleSource implements SampleSource { if (onlyReadDiscontinuity) { return NOTHING_READ; } + if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { + formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16( + extractor.getTrackFormat(track)); + formatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null; + trackStates[track] = TRACK_STATE_FORMAT_SENT; + return FORMAT_READ; + } int extractorTrackIndex = extractor.getSampleTrackIndex(); if (extractorTrackIndex == track) { - if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { - formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16( - extractor.getTrackFormat(track)); - formatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null; - trackStates[track] = TRACK_STATE_FORMAT_SENT; - return FORMAT_READ; - } if (sampleHolder.data != null) { int offset = sampleHolder.data.position(); sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset); @@ -202,13 +203,4 @@ public final class FrameworkSampleSource implements SampleSource { } } - private boolean isEnabled() { - for (int i = 0; i < trackStates.length; i++) { - if (trackStates[i] != TRACK_STATE_DISABLED) { - return true; - } - } - return false; - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index 2ffe4a53f6..c02e8a9135 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -417,7 +417,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { @Override protected boolean isReady() { - return getPendingFrameCount() > 0; + return super.isReady() || getPendingFrameCount() > 0; } /** diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index ca9f0f6a80..9073fa93b5 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -128,6 +128,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private int codecReconfigurationState; private int trackIndex; + private boolean sourceIsReady; private boolean inputStreamEnded; private boolean outputStreamEnded; private boolean waitingForKeys; @@ -196,6 +197,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { @Override protected void onEnabled(long timeUs, boolean joining) { source.enable(trackIndex, timeUs); + sourceIsReady = false; inputStreamEnded = false; outputStreamEnded = false; waitingForKeys = false; @@ -346,6 +348,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { protected void seekTo(long timeUs) throws ExoPlaybackException { currentPositionUs = timeUs; source.seekToUs(timeUs); + sourceIsReady = false; inputStreamEnded = false; outputStreamEnded = false; waitingForKeys = false; @@ -364,7 +367,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { @Override protected void doSomeWork(long timeUs) throws ExoPlaybackException { try { - source.continueBuffering(timeUs); + sourceIsReady = source.continueBuffering(timeUs); checkForDiscontinuity(); if (format == null) { readFormat(); @@ -645,10 +648,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { @Override protected boolean isReady() { return format != null && !waitingForKeys - && ((codec == null && !shouldInitCodec()) // We don't want the codec - || outputIndex >= 0 // Or we have an output buffer ready to release - || inputIndex < 0 // Or we don't have any input buffers to write to - || isWithinHotswapPeriod()); // Or the codec is being hotswapped + && (sourceIsReady || outputIndex >= 0 || isWithinHotswapPeriod()); } private boolean isWithinHotswapPeriod() { diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 98ec611d2a..b941767955 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -235,7 +235,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { @Override protected boolean isReady() { - if (super.isReady() && (renderedFirstFrame || !codecInitialized())) { + if (super.isReady()) { // Ready. If we were joining then we've now joined, so clear the joining deadline. joiningDeadlineUs = -1; return true; diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 9c5d6aa303..56b20bf8a7 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -102,8 +102,11 @@ public interface SampleSource { * Indicates to the source that it should still be buffering data. * * @param playbackPositionUs The current playback position. + * @return True if the source has available samples, or if the end of the stream has been reached. + * False if more data needs to be buffered for samples to become available. + * @throws IOException If an error occurred reading from the source. */ - public void continueBuffering(long playbackPositionUs); + public boolean continueBuffering(long playbackPositionUs) throws IOException; /** * Attempts to read either a sample, a new format or or a discontinuity from the source. 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 61ad0c93ee..40e1544bcc 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 @@ -246,11 +246,21 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener { } @Override - public void continueBuffering(long playbackPositionUs) { + public boolean continueBuffering(long playbackPositionUs) throws IOException { Assertions.checkState(state == STATE_ENABLED); downstreamPositionUs = playbackPositionUs; chunkSource.continueBuffering(playbackPositionUs); updateLoadControl(); + if (isPendingReset() || mediaChunks.isEmpty()) { + return false; + } else if (mediaChunks.getFirst().sampleAvailable()) { + // There's a sample available to be read from the current chunk. + return true; + } else { + // It may be the case that the current chunk has been fully read but not yet discarded and + // that the next chunk has an available sample. Return true if so, otherwise false. + return mediaChunks.size() > 1 && mediaChunks.get(1).sampleAvailable(); + } } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java index 51ebac65c2..e03a529d8c 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java @@ -99,6 +99,14 @@ public abstract class MediaChunk extends Chunk { */ public abstract boolean prepare() throws ParserException; + /** + * Returns whether the next sample is available. + * + * @return True if the next sample is available for reading. False otherwise. + * @throws ParserException + */ + public abstract boolean sampleAvailable() throws ParserException; + /** * Reads the next media sample from the chunk. *
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
index 8aaee879e4..01033e73f6 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
@@ -103,12 +103,19 @@ public final class Mp4MediaChunk extends MediaChunk {
return prepared;
}
+ @Override
+ public boolean sampleAvailable() throws ParserException {
+ NonBlockingInputStream inputStream = getNonBlockingInputStream();
+ int result = extractor.read(inputStream, null);
+ return (result & FragmentedMp4Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0;
+ }
+
@Override
public boolean read(SampleHolder holder) throws ParserException {
NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null);
int result = extractor.read(inputStream, holder);
- boolean sampleRead = (result & FragmentedMp4Extractor.RESULT_READ_SAMPLE_FULL) != 0;
+ boolean sampleRead = (result & FragmentedMp4Extractor.RESULT_READ_SAMPLE) != 0;
if (sampleRead) {
holder.timeUs -= sampleOffsetUs;
}
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 6fa2f08962..dfe0d71584 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
@@ -82,11 +82,16 @@ public class SingleSampleMediaChunk extends MediaChunk {
return true;
}
+ @Override
+ public boolean sampleAvailable() {
+ return isLoadFinished() && !isReadFinished();
+ }
+
@Override
public boolean read(SampleHolder holder) {
NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null);
- if (!isLoadFinished()) {
+ if (!sampleAvailable()) {
return false;
}
int bytesLoaded = (int) bytesLoaded();
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
index f7ca26244a..4769da9772 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.parser.webm.WebmExtractor;
import com.google.android.exoplayer.upstream.DataSource;
@@ -69,11 +70,19 @@ public final class WebmMediaChunk extends MediaChunk {
return true;
}
+ @Override
+ public boolean sampleAvailable() throws ParserException {
+ NonBlockingInputStream inputStream = getNonBlockingInputStream();
+ int result = extractor.read(inputStream, null);
+ return (result & WebmExtractor.RESULT_NEED_SAMPLE_HOLDER) != 0;
+ }
+
@Override
public boolean read(SampleHolder holder) {
NonBlockingInputStream inputStream = getNonBlockingInputStream();
Assertions.checkState(inputStream != null);
- return extractor.read(inputStream, holder);
+ int result = extractor.read(inputStream, holder);
+ return (result & WebmExtractor.RESULT_READ_SAMPLE) != 0;
}
@Override
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
index e76152859f..0013df15a4 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
@@ -146,7 +146,7 @@ public class DashMp4ChunkSource implements ChunkSource {
RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null;
- if (extractor.getTrack() == null) {
+ if (extractor.getFormat() == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri();
}
if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) {
@@ -199,10 +199,10 @@ public class DashMp4ChunkSource implements ChunkSource {
if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
- expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV;
+ expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INIT;
requestUri = initializationUri.attemptMerge(indexUri);
if (requestUri != null) {
- expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_SIDX;
+ expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INDEX;
indexAnchor = indexUri.start + indexUri.length;
} else {
requestUri = initializationUri;
@@ -210,7 +210,7 @@ public class DashMp4ChunkSource implements ChunkSource {
} else {
requestUri = indexUri;
indexAnchor = indexUri.start + indexUri.length;
- expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_SIDX;
+ expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INDEX;
}
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
representation.getCacheKey());
@@ -256,9 +256,9 @@ public class DashMp4ChunkSource implements ChunkSource {
throw new ParserException("Invalid extractor result. Expected "
+ expectedExtractorResult + ", got " + result);
}
- if ((result & FragmentedMp4Extractor.RESULT_READ_SIDX) != 0) {
+ if ((result & FragmentedMp4Extractor.RESULT_READ_INDEX) != 0) {
segmentIndexes.put(format.id,
- new DashWrappingSegmentIndex(extractor.getSegmentIndex(), uri, indexAnchor));
+ new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor));
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
index 133b4879ac..2f01a38120 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
@@ -56,21 +56,26 @@ public class DashWebmChunkSource implements ChunkSource {
private final Format[] formats;
private final HashMap
- * If the returned flags include {@link #RESULT_READ_SAMPLE_PARTIAL} then the sample has been
- * partially read into {@code out}. Hence the same {@link SampleHolder} instance must be passed
- * in subsequent calls until the whole sample has been read.
*
* @param inputStream The input stream from which data should be read.
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
@@ -353,9 +333,6 @@ public final class FragmentedMp4Extractor {
case STATE_READING_CENC_AUXILIARY_DATA:
cencAuxiliaryBytesRead = 0;
break;
- case STATE_READING_SAMPLE_START:
- sampleBytesRead = 0;
- break;
}
parserState = state;
}
@@ -383,7 +360,7 @@ public final class FragmentedMp4Extractor {
enterState(STATE_READING_CENC_AUXILIARY_DATA);
} else {
cencAuxiliaryData = null;
- enterState(STATE_READING_SAMPLE_START);
+ enterState(STATE_READING_SAMPLE);
}
return 0;
}
@@ -442,7 +419,7 @@ public final class FragmentedMp4Extractor {
containerAtoms.peek().add(leaf);
} else if (leaf.type == Atom.TYPE_sidx) {
segmentIndex = parseSidx(leaf.getData());
- return RESULT_READ_SIDX;
+ return RESULT_READ_INDEX;
}
return 0;
}
@@ -450,7 +427,7 @@ public final class FragmentedMp4Extractor {
private int onContainerAtomRead(ContainerAtom container) {
if (container.type == Atom.TYPE_moov) {
onMoovContainerAtomRead(container);
- return RESULT_READ_MOOV;
+ return RESULT_READ_INIT;
} else if (container.type == Atom.TYPE_moof) {
onMoofContainerAtomRead(container);
} else if (!containerAtoms.isEmpty()) {
@@ -1078,7 +1055,7 @@ public final class FragmentedMp4Extractor {
if (cencAuxiliaryBytesRead < length) {
return RESULT_NEED_MORE_DATA;
}
- enterState(STATE_READING_SAMPLE_START);
+ enterState(STATE_READING_SAMPLE);
return 0;
}
@@ -1105,89 +1082,68 @@ public final class FragmentedMp4Extractor {
enterState(STATE_READING_ATOM_HEADER);
return 0;
}
- if (sampleIndex < pendingSeekSyncSampleIndex) {
- return skipSample(inputStream);
+ int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
+ if (inputStream.getAvailableByteCount() < sampleSize) {
+ return RESULT_NEED_MORE_DATA;
}
- return readSample(inputStream, out);
+ if (sampleIndex < pendingSeekSyncSampleIndex) {
+ return skipSample(inputStream, sampleSize);
+ }
+ return readSample(inputStream, sampleSize, out);
}
- private int skipSample(NonBlockingInputStream inputStream) {
- if (parserState == STATE_READING_SAMPLE_START) {
- ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
- : fragmentRun.smoothStreamingSampleEncryptionData;
- if (sampleEncryptionData != null) {
- TrackEncryptionBox encryptionBox =
- track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
- int vectorSize = encryptionBox.initializationVectorSize;
- boolean subsampleEncryption = cencAuxiliaryData != null
- ? fragmentRun.auxiliarySampleInfoSizeTable[sampleIndex] > vectorSize
- : fragmentRun.smoothStreamingUsesSubsampleEncryption;
- sampleEncryptionData.skip(vectorSize);
- int subsampleCount = subsampleEncryption ? sampleEncryptionData.readUnsignedShort() : 1;
- if (subsampleEncryption) {
- sampleEncryptionData.skip((2 + 4) * subsampleCount);
- }
+ private int skipSample(NonBlockingInputStream inputStream, int sampleSize) {
+ ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
+ : fragmentRun.smoothStreamingSampleEncryptionData;
+ if (sampleEncryptionData != null) {
+ TrackEncryptionBox encryptionBox =
+ track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
+ int vectorSize = encryptionBox.initializationVectorSize;
+ boolean subsampleEncryption = cencAuxiliaryData != null
+ ? fragmentRun.auxiliarySampleInfoSizeTable[sampleIndex] > vectorSize
+ : fragmentRun.smoothStreamingUsesSubsampleEncryption;
+ sampleEncryptionData.skip(vectorSize);
+ int subsampleCount = subsampleEncryption ? sampleEncryptionData.readUnsignedShort() : 1;
+ if (subsampleEncryption) {
+ sampleEncryptionData.skip((2 + 4) * subsampleCount);
}
}
- int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
- int bytesRead = inputStream.skip(sampleSize - sampleBytesRead);
- if (bytesRead == -1) {
- return RESULT_END_OF_STREAM;
- }
- sampleBytesRead += bytesRead;
- if (sampleSize != sampleBytesRead) {
- enterState(STATE_READING_SAMPLE_INCREMENTAL);
- return RESULT_NEED_MORE_DATA;
- }
+ inputStream.skip(sampleSize);
+
sampleIndex++;
- enterState(STATE_READING_SAMPLE_START);
+ enterState(STATE_READING_SAMPLE);
return 0;
}
@SuppressLint("InlinedApi")
- private int readSample(NonBlockingInputStream inputStream, SampleHolder out) {
+ private int readSample(NonBlockingInputStream inputStream, int sampleSize, SampleHolder out) {
if (out == null) {
return RESULT_NEED_SAMPLE_HOLDER;
}
- int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
ByteBuffer outputData = out.data;
- if (parserState == STATE_READING_SAMPLE_START) {
- out.timeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
- out.flags = 0;
- if (fragmentRun.sampleIsSyncFrameTable[sampleIndex]) {
- out.flags |= MediaExtractor.SAMPLE_FLAG_SYNC;
- lastSyncSampleIndex = sampleIndex;
- }
- if (out.allowDataBufferReplacement
- && (out.data == null || out.data.capacity() < sampleSize)) {
- outputData = ByteBuffer.allocate(sampleSize);
- out.data = outputData;
- }
- ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
- : fragmentRun.smoothStreamingSampleEncryptionData;
- if (sampleEncryptionData != null) {
- readSampleEncryptionData(sampleEncryptionData, out);
- }
+ out.timeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
+ out.flags = 0;
+ if (fragmentRun.sampleIsSyncFrameTable[sampleIndex]) {
+ out.flags |= MediaExtractor.SAMPLE_FLAG_SYNC;
+ lastSyncSampleIndex = sampleIndex;
+ }
+ if (out.allowDataBufferReplacement
+ && (out.data == null || out.data.capacity() < sampleSize)) {
+ outputData = ByteBuffer.allocate(sampleSize);
+ out.data = outputData;
+ }
+ ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
+ : fragmentRun.smoothStreamingSampleEncryptionData;
+ if (sampleEncryptionData != null) {
+ readSampleEncryptionData(sampleEncryptionData, out);
}
- int bytesRead;
if (outputData == null) {
- bytesRead = inputStream.skip(sampleSize - sampleBytesRead);
+ inputStream.skip(sampleSize);
+ out.size = 0;
} else {
- bytesRead = inputStream.read(outputData, sampleSize - sampleBytesRead);
- }
- if (bytesRead == -1) {
- return RESULT_END_OF_STREAM;
- }
- sampleBytesRead += bytesRead;
-
- if (sampleSize != sampleBytesRead) {
- enterState(STATE_READING_SAMPLE_INCREMENTAL);
- return RESULT_NEED_MORE_DATA | RESULT_READ_SAMPLE_PARTIAL;
- }
-
- if (outputData != null) {
+ inputStream.read(outputData, sampleSize);
if (track.type == Track.TYPE_VIDEO) {
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
// delimited content. Replace length prefixes with start codes.
@@ -1203,13 +1159,11 @@ public final class FragmentedMp4Extractor {
outputData.position(sampleOffset + sampleSize);
}
out.size = sampleSize;
- } else {
- out.size = 0;
}
sampleIndex++;
- enterState(STATE_READING_SAMPLE_START);
- return RESULT_READ_SAMPLE_FULL;
+ enterState(STATE_READING_SAMPLE);
+ return RESULT_READ_SAMPLE;
}
@SuppressLint("InlinedApi")
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
index f66b83293f..55eca63de6 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
@@ -138,9 +138,8 @@ import java.util.Stack;
while (true) {
while (!masterElementsStack.isEmpty()
&& bytesRead >= masterElementsStack.peek().elementEndOffsetBytes) {
- if (!eventHandler.onMasterElementEnd(masterElementsStack.pop().elementId)) {
- return READ_RESULT_CONTINUE;
- }
+ eventHandler.onMasterElementEnd(masterElementsStack.pop().elementId);
+ return READ_RESULT_CONTINUE;
}
if (state == STATE_BEGIN_READING) {
@@ -161,12 +160,10 @@ import java.util.Stack;
case TYPE_MASTER:
int masterHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
masterElementsStack.add(new MasterElement(elementId, bytesRead + elementContentSize));
- if (!eventHandler.onMasterElementStart(
- elementId, elementOffset, masterHeaderSize, elementContentSize)) {
- prepareForNextElement();
- return READ_RESULT_CONTINUE;
- }
- break;
+ eventHandler.onMasterElementStart(elementId, elementOffset, masterHeaderSize,
+ elementContentSize);
+ prepareForNextElement();
+ return READ_RESULT_CONTINUE;
case TYPE_UNSIGNED_INT:
if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) {
throw new IllegalStateException("Invalid integer size " + elementContentSize);
@@ -177,11 +174,9 @@ import java.util.Stack;
return intResult;
}
long intValue = getTempByteArrayValue((int) elementContentSize, false);
- if (!eventHandler.onIntegerElement(elementId, intValue)) {
- prepareForNextElement();
- return READ_RESULT_CONTINUE;
- }
- break;
+ eventHandler.onIntegerElement(elementId, intValue);
+ prepareForNextElement();
+ return READ_RESULT_CONTINUE;
case TYPE_FLOAT:
if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES
&& elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) {
@@ -199,11 +194,9 @@ import java.util.Stack;
} else {
floatValue = Double.longBitsToDouble(valueBits);
}
- if (!eventHandler.onFloatElement(elementId, floatValue)) {
- prepareForNextElement();
- return READ_RESULT_CONTINUE;
- }
- break;
+ eventHandler.onFloatElement(elementId, floatValue);
+ prepareForNextElement();
+ return READ_RESULT_CONTINUE;
case TYPE_STRING:
if (elementContentSize > Integer.MAX_VALUE) {
throw new IllegalStateException(
@@ -219,11 +212,9 @@ import java.util.Stack;
}
String stringValue = new String(stringBytes, Charset.forName("UTF-8"));
stringBytes = null;
- if (!eventHandler.onStringElement(elementId, stringValue)) {
- prepareForNextElement();
- return READ_RESULT_CONTINUE;
- }
- break;
+ eventHandler.onStringElement(elementId, stringValue);
+ prepareForNextElement();
+ return READ_RESULT_CONTINUE;
case TYPE_BINARY:
if (elementContentSize > Integer.MAX_VALUE) {
throw new IllegalStateException(
@@ -233,18 +224,17 @@ import java.util.Stack;
return READ_RESULT_NEED_MORE_DATA;
}
int binaryHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
- boolean keepGoing = eventHandler.onBinaryElement(
+ boolean consumed = eventHandler.onBinaryElement(
elementId, elementOffset, binaryHeaderSize, (int) elementContentSize, inputStream);
- long expectedBytesRead = elementOffset + binaryHeaderSize + elementContentSize;
- if (expectedBytesRead != bytesRead) {
- throw new IllegalStateException("Incorrect total bytes read. Expected "
- + expectedBytesRead + " but actually " + bytesRead);
- }
- if (!keepGoing) {
+ if (consumed) {
+ long expectedBytesRead = elementOffset + binaryHeaderSize + elementContentSize;
+ if (expectedBytesRead != bytesRead) {
+ throw new IllegalStateException("Incorrect total bytes read. Expected "
+ + expectedBytesRead + " but actually " + bytesRead);
+ }
prepareForNextElement();
- return READ_RESULT_CONTINUE;
}
- break;
+ return READ_RESULT_CONTINUE;
case TYPE_UNKNOWN:
if (elementContentSize > Integer.MAX_VALUE) {
throw new IllegalStateException(
@@ -254,11 +244,11 @@ import java.util.Stack;
if (skipResult != READ_RESULT_CONTINUE) {
return skipResult;
}
+ prepareForNextElement();
break;
default:
throw new IllegalStateException("Invalid element type " + type);
}
- prepareForNextElement();
}
}
@@ -508,7 +498,7 @@ import java.util.Stack;
*/
private int updateBytesState(int additionalBytesRead, int totalBytes) {
if (additionalBytesRead == -1) {
- return READ_RESULT_END_OF_FILE;
+ return READ_RESULT_END_OF_STREAM;
}
bytesState += additionalBytesRead;
bytesRead += additionalBytesRead;
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
index 8203fffd13..00f24bbad7 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
@@ -25,6 +25,7 @@ import com.google.android.exoplayer.util.MimeTypes;
import android.annotation.TargetApi;
import android.media.MediaExtractor;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -77,11 +78,14 @@ public final class DefaultWebmExtractor implements WebmExtractor {
private static final int LACING_FIXED = 2;
private static final int LACING_EBML = 3;
+ private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM
+ | RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER;
+
private final EbmlReader reader;
private final byte[] simpleBlockTimecodeAndFlags = new byte[3];
- private SampleHolder tempSampleHolder;
- private boolean sampleRead;
+ private SampleHolder sampleHolder;
+ private int readResults;
private long segmentStartOffsetBytes = UNKNOWN;
private long segmentEndOffsetBytes = UNKNOWN;
@@ -107,17 +111,19 @@ public final class DefaultWebmExtractor implements WebmExtractor {
}
@Override
- public boolean isPrepared() {
- return format != null && cues != null;
- }
-
- @Override
- public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
- tempSampleHolder = sampleHolder;
- sampleRead = false;
- reader.read(inputStream);
- tempSampleHolder = null;
- return sampleRead;
+ public int read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
+ this.sampleHolder = sampleHolder;
+ this.readResults = 0;
+ while ((readResults & READ_TERMINATING_RESULTS) == 0) {
+ int ebmlReadResult = reader.read(inputStream);
+ if (ebmlReadResult == EbmlReader.READ_RESULT_NEED_MORE_DATA) {
+ readResults |= WebmExtractor.RESULT_NEED_MORE_DATA;
+ } else if (ebmlReadResult == EbmlReader.READ_RESULT_END_OF_STREAM) {
+ readResults |= WebmExtractor.RESULT_END_OF_STREAM;
+ }
+ }
+ this.sampleHolder = null;
+ return readResults;
}
@Override
@@ -139,7 +145,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
}
@Override
- public SegmentIndex getCues() {
+ public SegmentIndex getIndex() {
return cues;
}
@@ -288,6 +294,12 @@ public final class DefaultWebmExtractor implements WebmExtractor {
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
+ // If we don't have a sample holder then don't consume the data.
+ if (sampleHolder == null) {
+ readResults |= RESULT_NEED_SAMPLE_HOLDER;
+ return false;
+ }
+
// Value of trackNumber is not used but needs to be read.
reader.readVarint(inputStream);
@@ -309,10 +321,10 @@ public final class DefaultWebmExtractor implements WebmExtractor {
case LACING_NONE:
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
- tempSampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
- tempSampleHolder.decodeOnly = invisible;
- tempSampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
- tempSampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
+ sampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
+ sampleHolder.decodeOnly = invisible;
+ sampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
+ sampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
break;
case LACING_EBML:
case LACING_FIXED:
@@ -321,14 +333,22 @@ public final class DefaultWebmExtractor implements WebmExtractor {
throw new IllegalStateException("Lacing mode " + lacing + " not supported");
}
- // Read video data into sample holder.
- reader.readBytes(inputStream, tempSampleHolder.data, tempSampleHolder.size);
- sampleRead = true;
- return false;
- } else {
- reader.skipBytes(inputStream, contentsSizeBytes);
- return true;
+ ByteBuffer outputData = sampleHolder.data;
+ if (sampleHolder.allowDataBufferReplacement
+ && (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size)) {
+ outputData = ByteBuffer.allocate(sampleHolder.size);
+ sampleHolder.data = outputData;
+ }
+
+ if (outputData == null) {
+ reader.skipBytes(inputStream, sampleHolder.size);
+ sampleHolder.size = 0;
+ } else {
+ reader.readBytes(inputStream, outputData, sampleHolder.size);
+ }
+ readResults |= RESULT_READ_SAMPLE;
}
+ return true;
}
private long scaleTimecodeToUs(long unscaledTimecode) {
@@ -347,6 +367,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
&& (format == null || format.width != pixelWidth || format.height != pixelHeight)) {
format = MediaFormat.createVideoFormat(
MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null);
+ readResults |= RESULT_READ_INIT;
} else if (format == null) {
throw new IllegalStateException("Unable to build format");
}
@@ -387,6 +408,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
cues = new SegmentIndex((int) cuesSizeBytes, sizes, offsets, durationsUs, timesUs);
cueTimesUs = null;
cueClusterPositions = null;
+ readResults |= RESULT_READ_INDEX;
}
/**
@@ -401,30 +423,30 @@ public final class DefaultWebmExtractor implements WebmExtractor {
}
@Override
- public boolean onMasterElementStart(
+ public void onMasterElementStart(
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) {
- return DefaultWebmExtractor.this.onMasterElementStart(
+ DefaultWebmExtractor.this.onMasterElementStart(
id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes);
}
@Override
- public boolean onMasterElementEnd(int id) {
- return DefaultWebmExtractor.this.onMasterElementEnd(id);
+ public void onMasterElementEnd(int id) {
+ DefaultWebmExtractor.this.onMasterElementEnd(id);
}
@Override
- public boolean onIntegerElement(int id, long value) {
- return DefaultWebmExtractor.this.onIntegerElement(id, value);
+ public void onIntegerElement(int id, long value) {
+ DefaultWebmExtractor.this.onIntegerElement(id, value);
}
@Override
- public boolean onFloatElement(int id, double value) {
- return DefaultWebmExtractor.this.onFloatElement(id, value);
+ public void onFloatElement(int id, double value) {
+ DefaultWebmExtractor.this.onFloatElement(id, value);
}
@Override
- public boolean onStringElement(int id, String value) {
- return DefaultWebmExtractor.this.onStringElement(id, value);
+ public void onStringElement(int id, String value) {
+ DefaultWebmExtractor.this.onStringElement(id, value);
}
@Override
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
index 42e26d4531..dcedf9a898 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
@@ -46,9 +46,8 @@ import java.nio.ByteBuffer;
* @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's children
- * @return {@code false} if parsing should stop right away
*/
- public boolean onMasterElementStart(
+ public void onMasterElementStart(
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes);
/**
@@ -56,44 +55,42 @@ import java.nio.ByteBuffer;
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
- * @return {@code false} if parsing should stop right away
*/
- public boolean onMasterElementEnd(int id);
+ public void onMasterElementEnd(int id);
/**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The integer value this element contains
- * @return {@code false} if parsing should stop right away
*/
- public boolean onIntegerElement(int id, long value);
+ public void onIntegerElement(int id, long value);
/**
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The float value this element contains
- * @return {@code false} if parsing should stop right away
*/
- public boolean onFloatElement(int id, double value);
+ public void onFloatElement(int id, double value);
/**
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The string value this element contains
- * @return {@code false} if parsing should stop right away
*/
- public boolean onStringElement(int id, String value);
+ public void onStringElement(int id, String value);
/**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
*
* The element header (containing element ID and content size) will already have been read.
- * Subclasses must exactly read the entire contents of the element, which is
- * {@code contentsSizeBytes} in length. It's guaranteed that the full element contents will be
- * immediately available from {@code inputStream}.
+ * Subclasses must either read nothing and return {@code false}, or exactly read the entire
+ * contents of the element, which is {@code contentsSizeBytes} in length, and return {@code true}.
+ *
+ * It's guaranteed that the full element contents will be immediately available from
+ * {@code inputStream}.
*
* Several methods in {@link EbmlReader} are available for reading the contents of a
* binary element:
@@ -111,7 +108,7 @@ import java.nio.ByteBuffer;
* @param contentsSizeBytes The byte length of this element's contents
* @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read
- * @return {@code false} if parsing should stop right away
+ * @return True if the element was read. False otherwise.
*/
public boolean onBinaryElement(
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
index a9bf11f699..dd1c43fce3 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
@@ -44,12 +44,12 @@ import java.nio.ByteBuffer;
// Return values for reading methods.
public static final int READ_RESULT_CONTINUE = 0;
public static final int READ_RESULT_NEED_MORE_DATA = 1;
- public static final int READ_RESULT_END_OF_FILE = 2;
+ public static final int READ_RESULT_END_OF_STREAM = 2;
public void setEventHandler(EbmlEventHandler eventHandler);
/**
- * Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
+ * Reads from a {@link NonBlockingInputStream}, invoking an event callback if possible.
*
* @param inputStream The input stream from which data should be read
* @return One of the {@code RESULT_*} flags defined in this interface
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
index 4ecefe7906..e824887476 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
@@ -30,24 +30,38 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
public interface WebmExtractor {
/**
- * Whether the has parsed the cues and sample format from the stream.
- *
- * @return True if the extractor is prepared. False otherwise
+ * An attempt to read from the input stream returned insufficient data.
*/
- public boolean isPrepared();
+ public static final int RESULT_NEED_MORE_DATA = 1;
+ /**
+ * The end of the input stream was reached.
+ */
+ public static final int RESULT_END_OF_STREAM = 2;
+ /**
+ * A media sample was read.
+ */
+ public static final int RESULT_READ_SAMPLE = 4;
+ /**
+ * Initialization data was read. The parsed data can be read using {@link #getFormat()}.
+ */
+ public static final int RESULT_READ_INIT = 8;
+ /**
+ * A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
+ */
+ public static final int RESULT_READ_INDEX = 16;
+ /**
+ * The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
+ */
+ public static final int RESULT_NEED_SAMPLE_HOLDER = 32;
/**
* Consumes data from a {@link NonBlockingInputStream}.
*
- * If the return value is {@code false}, then a sample may have been partially read into
- * {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
- * in subsequent calls until the whole sample has been read.
- *
* @param inputStream The input stream from which data should be read
* @param sampleHolder A {@link SampleHolder} into which the sample should be read
- * @return {@code true} if a sample has been read into the sample holder
+ * @return One or more of the {@code RESULT_*} flags defined in this class.
*/
- public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder);
+ public int read(NonBlockingInputStream inputStream, SampleHolder sampleHolder);
/**
* Seeks to a position before or equal to the requested time.
@@ -66,7 +80,7 @@ public interface WebmExtractor {
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
* prepared
*/
- public SegmentIndex getCues();
+ public SegmentIndex getIndex();
/**
* Returns the format of the samples contained within the media stream.
diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
index 3e671d2302..6f1e6f96ca 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
@@ -144,7 +144,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
- source.continueBuffering(timeUs);
+ try {
+ source.continueBuffering(timeUs);
+ } catch (IOException e) {
+ throw new ExoPlaybackException(e);
+ }
+
currentPositionUs = timeUs;
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we advance