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 representations; - private final HashMap extractors; + private final HashMap extractors; private final HashMap segmentIndexes; private boolean lastChunkWasInitialization; + /** + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param evaluator Selects from the available formats. + * @param representations The representations to be considered by the source. + */ public DashWebmChunkSource(DataSource dataSource, FormatEvaluator evaluator, Representation... representations) { this.dataSource = dataSource; this.evaluator = evaluator; this.formats = new Format[representations.length]; - this.extractors = new HashMap(); + this.extractors = new HashMap(); this.segmentIndexes = new HashMap(); this.representations = new HashMap(); - this.trackInfo = new TrackInfo( - representations[0].format.mimeType, representations[0].periodDurationMs * 1000); + this.trackInfo = new TrackInfo(representations[0].format.mimeType, + representations[0].periodDurationMs * 1000); this.evaluation = new Evaluation(); int maxWidth = 0; int maxHeight = 0; @@ -109,7 +114,7 @@ public class DashWebmChunkSource implements ChunkSource { @Override public void disable(List queue) { - evaluator.disable(); + evaluator.disable(); } @Override @@ -140,13 +145,18 @@ public class DashWebmChunkSource implements ChunkSource { Representation selectedRepresentation = representations.get(selectedFormat.id); WebmExtractor extractor = extractors.get(selectedRepresentation.format.id); - if (!extractor.isPrepared()) { - // TODO: This code forces cues to exist and to immediately follow the initialization - // data. Webm extractor should be generalized to allow cues to be optional. See [redacted]. - RangedUri initializationUri = selectedRepresentation.getInitializationUri().attemptMerge( - selectedRepresentation.getIndexUri()); - Chunk initializationChunk = newInitializationChunk(initializationUri, selectedRepresentation, - extractor, dataSource, evaluation.trigger); + RangedUri pendingInitializationUri = null; + RangedUri pendingIndexUri = null; + if (extractor.getFormat() == null) { + pendingInitializationUri = selectedRepresentation.getInitializationUri(); + } + if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) { + pendingIndexUri = selectedRepresentation.getIndexUri(); + } + if (pendingInitializationUri != null || pendingIndexUri != null) { + // We have initialization and/or index requests to make. + Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri, + selectedRepresentation, extractor, dataSource, evaluation.trigger); lastChunkWasInitialization = true; out.chunk = initializationChunk; return; @@ -181,12 +191,29 @@ public class DashWebmChunkSource implements ChunkSource { // Do nothing. } - private Chunk newInitializationChunk(RangedUri initializationUri, Representation representation, - WebmExtractor extractor, DataSource dataSource, int trigger) { - DataSpec dataSpec = new DataSpec(initializationUri.getUri(), initializationUri.start, - initializationUri.length, representation.getCacheKey()); + private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, + Representation representation, WebmExtractor extractor, DataSource dataSource, + int trigger) { + int expectedExtractorResult = WebmExtractor.RESULT_END_OF_STREAM; + RangedUri requestUri; + 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 |= WebmExtractor.RESULT_READ_INIT; + requestUri = initializationUri.attemptMerge(indexUri); + if (requestUri != null) { + expectedExtractorResult |= WebmExtractor.RESULT_READ_INDEX; + } else { + requestUri = initializationUri; + } + } else { + requestUri = indexUri; + expectedExtractorResult |= WebmExtractor.RESULT_READ_INDEX; + } + DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, + representation.getCacheKey()); return new InitializationWebmLoadable(dataSource, dataSpec, trigger, representation.format, - extractor); + extractor, expectedExtractorResult); } private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex, @@ -206,22 +233,27 @@ public class DashWebmChunkSource implements ChunkSource { private class InitializationWebmLoadable extends Chunk { private final WebmExtractor extractor; + private final int expectedExtractorResult; private final Uri uri; public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger, - Format format, WebmExtractor extractor) { + Format format, WebmExtractor extractor, int expectedExtractorResult) { super(dataSource, dataSpec, format, trigger); this.extractor = extractor; + this.expectedExtractorResult = expectedExtractorResult; this.uri = dataSpec.uri; } @Override protected void consumeStream(NonBlockingInputStream stream) throws IOException { - extractor.read(stream, null); - if (!extractor.isPrepared()) { - throw new ParserException("Invalid initialization data"); + int result = extractor.read(stream, null); + if (result != expectedExtractorResult) { + throw new ParserException("Invalid extractor result. Expected " + + expectedExtractorResult + ", got " + result); + } + if ((result & WebmExtractor.RESULT_READ_INDEX) != 0) { + segmentIndexes.put(format.id, new DashWrappingSegmentIndex(extractor.getIndex(), uri, 0)); } - segmentIndexes.put(format.id, new DashWrappingSegmentIndex(extractor.getCues(), uri, 0)); } } diff --git a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java index 16e1788943..7a990ee2a7 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java @@ -59,7 +59,7 @@ public final class FragmentedMp4Extractor { public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; /** - * An attempt to read from the input stream returned 0 bytes of data. + * An attempt to read from the input stream returned insufficient data. */ public static final int RESULT_NEED_MORE_DATA = 1; /** @@ -69,27 +69,23 @@ public final class FragmentedMp4Extractor { /** * A media sample was read. */ - public static final int RESULT_READ_SAMPLE_FULL = 4; + public static final int RESULT_READ_SAMPLE = 4; /** - * A media sample was partially read. + * A moov atom was read. The parsed data can be read using {@link #getFormat()} and + * {@link #getPsshInfo}. */ - public static final int RESULT_READ_SAMPLE_PARTIAL = 8; + public static final int RESULT_READ_INIT = 8; /** - * A moov atom was read. The parsed data can be read using {@link #getTrack()}, - * {@link #getFormat()} and {@link #getPsshInfo}. + * A sidx atom was read. The parsed data can be read using {@link #getIndex()}. */ - public static final int RESULT_READ_MOOV = 16; - /** - * A sidx atom was read. The parsed data can be read using {@link #getSegmentIndex()}. - */ - public static final int RESULT_READ_SIDX = 32; + 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 = 64; + public static final int RESULT_NEED_SAMPLE_HOLDER = 32; private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM - | RESULT_READ_SAMPLE_FULL | RESULT_NEED_SAMPLE_HOLDER; + | RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER; private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; @@ -98,8 +94,7 @@ public final class FragmentedMp4Extractor { private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_CENC_AUXILIARY_DATA = 2; - private static final int STATE_READING_SAMPLE_START = 3; - private static final int STATE_READING_SAMPLE_INCREMENTAL = 4; + private static final int STATE_READING_SAMPLE = 3; // Atom data offsets private static final int ATOM_HEADER_SIZE = 8; @@ -169,7 +164,6 @@ public final class FragmentedMp4Extractor { private ParsableByteArray atomData; private ParsableByteArray cencAuxiliaryData; private int cencAuxiliaryBytesRead; - private int sampleBytesRead; private int pendingSeekTimeMs; private int sampleIndex; @@ -207,7 +201,7 @@ public final class FragmentedMp4Extractor { * * @return The segment index, or null if a SIDX atom has yet to be parsed. */ - public SegmentIndex getSegmentIndex() { + public SegmentIndex getIndex() { return segmentIndex; } @@ -245,17 +239,7 @@ public final class FragmentedMp4Extractor { } /** - * Returns the track information parsed from the stream. - * - * @return The track, or null if a MOOV atom has yet to be parsed. - */ - public Track getTrack() { - return track; - } - - /** - * Sideloads track information into the extractor, so that it can be read through - * {@link #getTrack()}. + * Sideloads track information into the extractor. * * @param track The track to sideload. */ @@ -270,10 +254,6 @@ public final class FragmentedMp4Extractor { * The read terminates if the end of the input stream is reached, if an attempt to read from the * input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate * both the reason for termination and data that was parsed during the read. - *

- * 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