mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Changes around renderer readiness and extraction.
- Make MediaCodecTrackRenderer.isReady more permissive. This largely fixes #21 - Bring WebmExtractor closer to FragmentedMp4Extractor. The two will probably be placed under a common interface fairly soon, which will allow significant code deduplication.
This commit is contained in:
parent
41ff1e4071
commit
9a124120ff
@ -67,7 +67,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
extractor = new MediaExtractor();
|
extractor = new MediaExtractor();
|
||||||
extractor.setDataSource(context, uri, headers);
|
extractor.setDataSource(context, uri, headers);
|
||||||
trackStates = new int[extractor.getTrackCount()];
|
trackStates = new int[extractor.getTrackCount()];
|
||||||
pendingDiscontinuities = new boolean[extractor.getTrackCount()];
|
pendingDiscontinuities = new boolean[trackStates.length];
|
||||||
trackInfos = new TrackInfo[trackStates.length];
|
trackInfos = new TrackInfo[trackStates.length];
|
||||||
for (int i = 0; i < trackStates.length; i++) {
|
for (int i = 0; i < trackStates.length; i++) {
|
||||||
android.media.MediaFormat format = extractor.getTrackFormat(i);
|
android.media.MediaFormat format = extractor.getTrackFormat(i);
|
||||||
@ -84,7 +84,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
@Override
|
@Override
|
||||||
public int getTrackCount() {
|
public int getTrackCount() {
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(prepared);
|
||||||
return extractor.getTrackCount();
|
return trackStates.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -97,17 +97,18 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
public void enable(int track, long timeUs) {
|
public void enable(int track, long timeUs) {
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(prepared);
|
||||||
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
||||||
boolean wasSourceEnabled = isEnabled();
|
|
||||||
trackStates[track] = TRACK_STATE_ENABLED;
|
trackStates[track] = TRACK_STATE_ENABLED;
|
||||||
extractor.selectTrack(track);
|
extractor.selectTrack(track);
|
||||||
if (!wasSourceEnabled) {
|
|
||||||
seekToUs(timeUs);
|
seekToUs(timeUs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public boolean continueBuffering(long playbackPositionUs) {
|
||||||
// Do nothing. The MediaExtractor instance is responsible for buffering.
|
// 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
|
@Override
|
||||||
@ -122,8 +123,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
if (onlyReadDiscontinuity) {
|
if (onlyReadDiscontinuity) {
|
||||||
return NOTHING_READ;
|
return NOTHING_READ;
|
||||||
}
|
}
|
||||||
int extractorTrackIndex = extractor.getSampleTrackIndex();
|
|
||||||
if (extractorTrackIndex == track) {
|
|
||||||
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
||||||
formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16(
|
formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16(
|
||||||
extractor.getTrackFormat(track));
|
extractor.getTrackFormat(track));
|
||||||
@ -131,6 +130,8 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
||||||
return FORMAT_READ;
|
return FORMAT_READ;
|
||||||
}
|
}
|
||||||
|
int extractorTrackIndex = extractor.getSampleTrackIndex();
|
||||||
|
if (extractorTrackIndex == track) {
|
||||||
if (sampleHolder.data != null) {
|
if (sampleHolder.data != null) {
|
||||||
int offset = sampleHolder.data.position();
|
int offset = sampleHolder.data.position();
|
||||||
sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isReady() {
|
protected boolean isReady() {
|
||||||
return getPendingFrameCount() > 0;
|
return super.isReady() || getPendingFrameCount() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,6 +128,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||||||
private int codecReconfigurationState;
|
private int codecReconfigurationState;
|
||||||
|
|
||||||
private int trackIndex;
|
private int trackIndex;
|
||||||
|
private boolean sourceIsReady;
|
||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
private boolean outputStreamEnded;
|
private boolean outputStreamEnded;
|
||||||
private boolean waitingForKeys;
|
private boolean waitingForKeys;
|
||||||
@ -196,6 +197,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected void onEnabled(long timeUs, boolean joining) {
|
protected void onEnabled(long timeUs, boolean joining) {
|
||||||
source.enable(trackIndex, timeUs);
|
source.enable(trackIndex, timeUs);
|
||||||
|
sourceIsReady = false;
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
waitingForKeys = false;
|
waitingForKeys = false;
|
||||||
@ -346,6 +348,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||||||
protected void seekTo(long timeUs) throws ExoPlaybackException {
|
protected void seekTo(long timeUs) throws ExoPlaybackException {
|
||||||
currentPositionUs = timeUs;
|
currentPositionUs = timeUs;
|
||||||
source.seekToUs(timeUs);
|
source.seekToUs(timeUs);
|
||||||
|
sourceIsReady = false;
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
waitingForKeys = false;
|
waitingForKeys = false;
|
||||||
@ -364,7 +367,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
|
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
|
||||||
try {
|
try {
|
||||||
source.continueBuffering(timeUs);
|
sourceIsReady = source.continueBuffering(timeUs);
|
||||||
checkForDiscontinuity();
|
checkForDiscontinuity();
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
readFormat();
|
readFormat();
|
||||||
@ -645,10 +648,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean isReady() {
|
protected boolean isReady() {
|
||||||
return format != null && !waitingForKeys
|
return format != null && !waitingForKeys
|
||||||
&& ((codec == null && !shouldInitCodec()) // We don't want the codec
|
&& (sourceIsReady || outputIndex >= 0 || isWithinHotswapPeriod());
|
||||||
|| 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWithinHotswapPeriod() {
|
private boolean isWithinHotswapPeriod() {
|
||||||
|
@ -235,7 +235,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isReady() {
|
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.
|
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
||||||
joiningDeadlineUs = -1;
|
joiningDeadlineUs = -1;
|
||||||
return true;
|
return true;
|
||||||
|
@ -102,8 +102,11 @@ public interface SampleSource {
|
|||||||
* Indicates to the source that it should still be buffering data.
|
* Indicates to the source that it should still be buffering data.
|
||||||
*
|
*
|
||||||
* @param playbackPositionUs The current playback position.
|
* @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.
|
* Attempts to read either a sample, a new format or or a discontinuity from the source.
|
||||||
|
@ -246,11 +246,21 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public boolean continueBuffering(long playbackPositionUs) throws IOException {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
Assertions.checkState(state == STATE_ENABLED);
|
||||||
downstreamPositionUs = playbackPositionUs;
|
downstreamPositionUs = playbackPositionUs;
|
||||||
chunkSource.continueBuffering(playbackPositionUs);
|
chunkSource.continueBuffering(playbackPositionUs);
|
||||||
updateLoadControl();
|
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
|
@Override
|
||||||
|
@ -99,6 +99,14 @@ public abstract class MediaChunk extends Chunk {
|
|||||||
*/
|
*/
|
||||||
public abstract boolean prepare() throws ParserException;
|
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.
|
* Reads the next media sample from the chunk.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -103,12 +103,19 @@ public final class Mp4MediaChunk extends MediaChunk {
|
|||||||
return prepared;
|
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
|
@Override
|
||||||
public boolean read(SampleHolder holder) throws ParserException {
|
public boolean read(SampleHolder holder) throws ParserException {
|
||||||
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
||||||
Assertions.checkState(inputStream != null);
|
Assertions.checkState(inputStream != null);
|
||||||
int result = extractor.read(inputStream, holder);
|
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) {
|
if (sampleRead) {
|
||||||
holder.timeUs -= sampleOffsetUs;
|
holder.timeUs -= sampleOffsetUs;
|
||||||
}
|
}
|
||||||
|
@ -82,11 +82,16 @@ public class SingleSampleMediaChunk extends MediaChunk {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sampleAvailable() {
|
||||||
|
return isLoadFinished() && !isReadFinished();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean read(SampleHolder holder) {
|
public boolean read(SampleHolder holder) {
|
||||||
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
||||||
Assertions.checkState(inputStream != null);
|
Assertions.checkState(inputStream != null);
|
||||||
if (!isLoadFinished()) {
|
if (!sampleAvailable()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int bytesLoaded = (int) bytesLoaded();
|
int bytesLoaded = (int) bytesLoaded();
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer.chunk;
|
package com.google.android.exoplayer.chunk;
|
||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.parser.webm.WebmExtractor;
|
import com.google.android.exoplayer.parser.webm.WebmExtractor;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
@ -69,11 +70,19 @@ public final class WebmMediaChunk extends MediaChunk {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean read(SampleHolder holder) {
|
public boolean read(SampleHolder holder) {
|
||||||
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
NonBlockingInputStream inputStream = getNonBlockingInputStream();
|
||||||
Assertions.checkState(inputStream != null);
|
Assertions.checkState(inputStream != null);
|
||||||
return extractor.read(inputStream, holder);
|
int result = extractor.read(inputStream, holder);
|
||||||
|
return (result & WebmExtractor.RESULT_READ_SAMPLE) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,7 +146,7 @@ public class DashMp4ChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
RangedUri pendingInitializationUri = null;
|
RangedUri pendingInitializationUri = null;
|
||||||
RangedUri pendingIndexUri = null;
|
RangedUri pendingIndexUri = null;
|
||||||
if (extractor.getTrack() == null) {
|
if (extractor.getFormat() == null) {
|
||||||
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||||
}
|
}
|
||||||
if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) {
|
if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) {
|
||||||
@ -199,10 +199,10 @@ public class DashMp4ChunkSource implements ChunkSource {
|
|||||||
if (initializationUri != null) {
|
if (initializationUri != null) {
|
||||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||||
// the two requests together to request both at once.
|
// the two requests together to request both at once.
|
||||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV;
|
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INIT;
|
||||||
requestUri = initializationUri.attemptMerge(indexUri);
|
requestUri = initializationUri.attemptMerge(indexUri);
|
||||||
if (requestUri != null) {
|
if (requestUri != null) {
|
||||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_SIDX;
|
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_INDEX;
|
||||||
indexAnchor = indexUri.start + indexUri.length;
|
indexAnchor = indexUri.start + indexUri.length;
|
||||||
} else {
|
} else {
|
||||||
requestUri = initializationUri;
|
requestUri = initializationUri;
|
||||||
@ -210,7 +210,7 @@ public class DashMp4ChunkSource implements ChunkSource {
|
|||||||
} else {
|
} else {
|
||||||
requestUri = indexUri;
|
requestUri = indexUri;
|
||||||
indexAnchor = indexUri.start + indexUri.length;
|
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,
|
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
|
||||||
representation.getCacheKey());
|
representation.getCacheKey());
|
||||||
@ -256,9 +256,9 @@ public class DashMp4ChunkSource implements ChunkSource {
|
|||||||
throw new ParserException("Invalid extractor result. Expected "
|
throw new ParserException("Invalid extractor result. Expected "
|
||||||
+ expectedExtractorResult + ", got " + result);
|
+ expectedExtractorResult + ", got " + result);
|
||||||
}
|
}
|
||||||
if ((result & FragmentedMp4Extractor.RESULT_READ_SIDX) != 0) {
|
if ((result & FragmentedMp4Extractor.RESULT_READ_INDEX) != 0) {
|
||||||
segmentIndexes.put(format.id,
|
segmentIndexes.put(format.id,
|
||||||
new DashWrappingSegmentIndex(extractor.getSegmentIndex(), uri, indexAnchor));
|
new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,21 +56,26 @@ public class DashWebmChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
private final Format[] formats;
|
private final Format[] formats;
|
||||||
private final HashMap<String, Representation> representations;
|
private final HashMap<String, Representation> representations;
|
||||||
private final HashMap<String, WebmExtractor> extractors;
|
private final HashMap<String, DefaultWebmExtractor> extractors;
|
||||||
private final HashMap<String, DashSegmentIndex> segmentIndexes;
|
private final HashMap<String, DashSegmentIndex> segmentIndexes;
|
||||||
|
|
||||||
private boolean lastChunkWasInitialization;
|
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,
|
public DashWebmChunkSource(DataSource dataSource, FormatEvaluator evaluator,
|
||||||
Representation... representations) {
|
Representation... representations) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.evaluator = evaluator;
|
this.evaluator = evaluator;
|
||||||
this.formats = new Format[representations.length];
|
this.formats = new Format[representations.length];
|
||||||
this.extractors = new HashMap<String, WebmExtractor>();
|
this.extractors = new HashMap<String, DefaultWebmExtractor>();
|
||||||
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
||||||
this.representations = new HashMap<String, Representation>();
|
this.representations = new HashMap<String, Representation>();
|
||||||
this.trackInfo = new TrackInfo(
|
this.trackInfo = new TrackInfo(representations[0].format.mimeType,
|
||||||
representations[0].format.mimeType, representations[0].periodDurationMs * 1000);
|
representations[0].periodDurationMs * 1000);
|
||||||
this.evaluation = new Evaluation();
|
this.evaluation = new Evaluation();
|
||||||
int maxWidth = 0;
|
int maxWidth = 0;
|
||||||
int maxHeight = 0;
|
int maxHeight = 0;
|
||||||
@ -140,13 +145,18 @@ public class DashWebmChunkSource implements ChunkSource {
|
|||||||
Representation selectedRepresentation = representations.get(selectedFormat.id);
|
Representation selectedRepresentation = representations.get(selectedFormat.id);
|
||||||
WebmExtractor extractor = extractors.get(selectedRepresentation.format.id);
|
WebmExtractor extractor = extractors.get(selectedRepresentation.format.id);
|
||||||
|
|
||||||
if (!extractor.isPrepared()) {
|
RangedUri pendingInitializationUri = null;
|
||||||
// TODO: This code forces cues to exist and to immediately follow the initialization
|
RangedUri pendingIndexUri = null;
|
||||||
// data. Webm extractor should be generalized to allow cues to be optional. See [redacted].
|
if (extractor.getFormat() == null) {
|
||||||
RangedUri initializationUri = selectedRepresentation.getInitializationUri().attemptMerge(
|
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||||
selectedRepresentation.getIndexUri());
|
}
|
||||||
Chunk initializationChunk = newInitializationChunk(initializationUri, selectedRepresentation,
|
if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) {
|
||||||
extractor, dataSource, evaluation.trigger);
|
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;
|
lastChunkWasInitialization = true;
|
||||||
out.chunk = initializationChunk;
|
out.chunk = initializationChunk;
|
||||||
return;
|
return;
|
||||||
@ -181,12 +191,29 @@ public class DashWebmChunkSource implements ChunkSource {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chunk newInitializationChunk(RangedUri initializationUri, Representation representation,
|
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
||||||
WebmExtractor extractor, DataSource dataSource, int trigger) {
|
Representation representation, WebmExtractor extractor, DataSource dataSource,
|
||||||
DataSpec dataSpec = new DataSpec(initializationUri.getUri(), initializationUri.start,
|
int trigger) {
|
||||||
initializationUri.length, representation.getCacheKey());
|
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,
|
return new InitializationWebmLoadable(dataSource, dataSpec, trigger, representation.format,
|
||||||
extractor);
|
extractor, expectedExtractorResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
|
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
|
||||||
@ -206,22 +233,27 @@ public class DashWebmChunkSource implements ChunkSource {
|
|||||||
private class InitializationWebmLoadable extends Chunk {
|
private class InitializationWebmLoadable extends Chunk {
|
||||||
|
|
||||||
private final WebmExtractor extractor;
|
private final WebmExtractor extractor;
|
||||||
|
private final int expectedExtractorResult;
|
||||||
private final Uri uri;
|
private final Uri uri;
|
||||||
|
|
||||||
public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger,
|
public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||||
Format format, WebmExtractor extractor) {
|
Format format, WebmExtractor extractor, int expectedExtractorResult) {
|
||||||
super(dataSource, dataSpec, format, trigger);
|
super(dataSource, dataSpec, format, trigger);
|
||||||
this.extractor = extractor;
|
this.extractor = extractor;
|
||||||
|
this.expectedExtractorResult = expectedExtractorResult;
|
||||||
this.uri = dataSpec.uri;
|
this.uri = dataSpec.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
|
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
|
||||||
extractor.read(stream, null);
|
int result = extractor.read(stream, null);
|
||||||
if (!extractor.isPrepared()) {
|
if (result != expectedExtractorResult) {
|
||||||
throw new ParserException("Invalid initialization data");
|
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
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;
|
public static final int RESULT_NEED_MORE_DATA = 1;
|
||||||
/**
|
/**
|
||||||
@ -69,27 +69,23 @@ public final class FragmentedMp4Extractor {
|
|||||||
/**
|
/**
|
||||||
* A media sample was read.
|
* 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()},
|
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
|
||||||
* {@link #getFormat()} and {@link #getPsshInfo}.
|
|
||||||
*/
|
*/
|
||||||
public static final int RESULT_READ_MOOV = 16;
|
public static final int RESULT_READ_INDEX = 16;
|
||||||
/**
|
|
||||||
* A sidx atom was read. The parsed data can be read using {@link #getSegmentIndex()}.
|
|
||||||
*/
|
|
||||||
public static final int RESULT_READ_SIDX = 32;
|
|
||||||
/**
|
/**
|
||||||
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
|
* 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
|
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[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||||
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
|
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};
|
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_HEADER = 0;
|
||||||
private static final int STATE_READING_ATOM_PAYLOAD = 1;
|
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_CENC_AUXILIARY_DATA = 2;
|
||||||
private static final int STATE_READING_SAMPLE_START = 3;
|
private static final int STATE_READING_SAMPLE = 3;
|
||||||
private static final int STATE_READING_SAMPLE_INCREMENTAL = 4;
|
|
||||||
|
|
||||||
// Atom data offsets
|
// Atom data offsets
|
||||||
private static final int ATOM_HEADER_SIZE = 8;
|
private static final int ATOM_HEADER_SIZE = 8;
|
||||||
@ -169,7 +164,6 @@ public final class FragmentedMp4Extractor {
|
|||||||
private ParsableByteArray atomData;
|
private ParsableByteArray atomData;
|
||||||
private ParsableByteArray cencAuxiliaryData;
|
private ParsableByteArray cencAuxiliaryData;
|
||||||
private int cencAuxiliaryBytesRead;
|
private int cencAuxiliaryBytesRead;
|
||||||
private int sampleBytesRead;
|
|
||||||
|
|
||||||
private int pendingSeekTimeMs;
|
private int pendingSeekTimeMs;
|
||||||
private int sampleIndex;
|
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.
|
* @return The segment index, or null if a SIDX atom has yet to be parsed.
|
||||||
*/
|
*/
|
||||||
public SegmentIndex getSegmentIndex() {
|
public SegmentIndex getIndex() {
|
||||||
return segmentIndex;
|
return segmentIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,17 +239,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the track information parsed from the stream.
|
* Sideloads track information into the extractor.
|
||||||
*
|
|
||||||
* @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()}.
|
|
||||||
*
|
*
|
||||||
* @param track The track to sideload.
|
* @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
|
* 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
|
* 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.
|
* both the reason for termination and data that was parsed during the read.
|
||||||
* <p>
|
|
||||||
* 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 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
|
* @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:
|
case STATE_READING_CENC_AUXILIARY_DATA:
|
||||||
cencAuxiliaryBytesRead = 0;
|
cencAuxiliaryBytesRead = 0;
|
||||||
break;
|
break;
|
||||||
case STATE_READING_SAMPLE_START:
|
|
||||||
sampleBytesRead = 0;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
parserState = state;
|
parserState = state;
|
||||||
}
|
}
|
||||||
@ -383,7 +360,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
enterState(STATE_READING_CENC_AUXILIARY_DATA);
|
enterState(STATE_READING_CENC_AUXILIARY_DATA);
|
||||||
} else {
|
} else {
|
||||||
cencAuxiliaryData = null;
|
cencAuxiliaryData = null;
|
||||||
enterState(STATE_READING_SAMPLE_START);
|
enterState(STATE_READING_SAMPLE);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -442,7 +419,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
containerAtoms.peek().add(leaf);
|
containerAtoms.peek().add(leaf);
|
||||||
} else if (leaf.type == Atom.TYPE_sidx) {
|
} else if (leaf.type == Atom.TYPE_sidx) {
|
||||||
segmentIndex = parseSidx(leaf.getData());
|
segmentIndex = parseSidx(leaf.getData());
|
||||||
return RESULT_READ_SIDX;
|
return RESULT_READ_INDEX;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -450,7 +427,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
private int onContainerAtomRead(ContainerAtom container) {
|
private int onContainerAtomRead(ContainerAtom container) {
|
||||||
if (container.type == Atom.TYPE_moov) {
|
if (container.type == Atom.TYPE_moov) {
|
||||||
onMoovContainerAtomRead(container);
|
onMoovContainerAtomRead(container);
|
||||||
return RESULT_READ_MOOV;
|
return RESULT_READ_INIT;
|
||||||
} else if (container.type == Atom.TYPE_moof) {
|
} else if (container.type == Atom.TYPE_moof) {
|
||||||
onMoofContainerAtomRead(container);
|
onMoofContainerAtomRead(container);
|
||||||
} else if (!containerAtoms.isEmpty()) {
|
} else if (!containerAtoms.isEmpty()) {
|
||||||
@ -1078,7 +1055,7 @@ public final class FragmentedMp4Extractor {
|
|||||||
if (cencAuxiliaryBytesRead < length) {
|
if (cencAuxiliaryBytesRead < length) {
|
||||||
return RESULT_NEED_MORE_DATA;
|
return RESULT_NEED_MORE_DATA;
|
||||||
}
|
}
|
||||||
enterState(STATE_READING_SAMPLE_START);
|
enterState(STATE_READING_SAMPLE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1105,14 +1082,17 @@ public final class FragmentedMp4Extractor {
|
|||||||
enterState(STATE_READING_ATOM_HEADER);
|
enterState(STATE_READING_ATOM_HEADER);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (sampleIndex < pendingSeekSyncSampleIndex) {
|
int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
|
||||||
return skipSample(inputStream);
|
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) {
|
private int skipSample(NonBlockingInputStream inputStream, int sampleSize) {
|
||||||
if (parserState == STATE_READING_SAMPLE_START) {
|
|
||||||
ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
|
ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData
|
||||||
: fragmentRun.smoothStreamingSampleEncryptionData;
|
: fragmentRun.smoothStreamingSampleEncryptionData;
|
||||||
if (sampleEncryptionData != null) {
|
if (sampleEncryptionData != null) {
|
||||||
@ -1128,31 +1108,20 @@ public final class FragmentedMp4Extractor {
|
|||||||
sampleEncryptionData.skip((2 + 4) * subsampleCount);
|
sampleEncryptionData.skip((2 + 4) * subsampleCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
|
inputStream.skip(sampleSize);
|
||||||
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;
|
|
||||||
}
|
|
||||||
sampleIndex++;
|
sampleIndex++;
|
||||||
enterState(STATE_READING_SAMPLE_START);
|
enterState(STATE_READING_SAMPLE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private int readSample(NonBlockingInputStream inputStream, SampleHolder out) {
|
private int readSample(NonBlockingInputStream inputStream, int sampleSize, SampleHolder out) {
|
||||||
if (out == null) {
|
if (out == null) {
|
||||||
return RESULT_NEED_SAMPLE_HOLDER;
|
return RESULT_NEED_SAMPLE_HOLDER;
|
||||||
}
|
}
|
||||||
int sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
|
|
||||||
ByteBuffer outputData = out.data;
|
ByteBuffer outputData = out.data;
|
||||||
if (parserState == STATE_READING_SAMPLE_START) {
|
|
||||||
out.timeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
|
out.timeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||||
out.flags = 0;
|
out.flags = 0;
|
||||||
if (fragmentRun.sampleIsSyncFrameTable[sampleIndex]) {
|
if (fragmentRun.sampleIsSyncFrameTable[sampleIndex]) {
|
||||||
@ -1169,25 +1138,12 @@ public final class FragmentedMp4Extractor {
|
|||||||
if (sampleEncryptionData != null) {
|
if (sampleEncryptionData != null) {
|
||||||
readSampleEncryptionData(sampleEncryptionData, out);
|
readSampleEncryptionData(sampleEncryptionData, out);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int bytesRead;
|
|
||||||
if (outputData == null) {
|
if (outputData == null) {
|
||||||
bytesRead = inputStream.skip(sampleSize - sampleBytesRead);
|
inputStream.skip(sampleSize);
|
||||||
|
out.size = 0;
|
||||||
} else {
|
} else {
|
||||||
bytesRead = inputStream.read(outputData, sampleSize - sampleBytesRead);
|
inputStream.read(outputData, sampleSize);
|
||||||
}
|
|
||||||
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) {
|
|
||||||
if (track.type == Track.TYPE_VIDEO) {
|
if (track.type == Track.TYPE_VIDEO) {
|
||||||
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
|
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
|
||||||
// delimited content. Replace length prefixes with start codes.
|
// delimited content. Replace length prefixes with start codes.
|
||||||
@ -1203,13 +1159,11 @@ public final class FragmentedMp4Extractor {
|
|||||||
outputData.position(sampleOffset + sampleSize);
|
outputData.position(sampleOffset + sampleSize);
|
||||||
}
|
}
|
||||||
out.size = sampleSize;
|
out.size = sampleSize;
|
||||||
} else {
|
|
||||||
out.size = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleIndex++;
|
sampleIndex++;
|
||||||
enterState(STATE_READING_SAMPLE_START);
|
enterState(STATE_READING_SAMPLE);
|
||||||
return RESULT_READ_SAMPLE_FULL;
|
return RESULT_READ_SAMPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
|
@ -138,10 +138,9 @@ import java.util.Stack;
|
|||||||
while (true) {
|
while (true) {
|
||||||
while (!masterElementsStack.isEmpty()
|
while (!masterElementsStack.isEmpty()
|
||||||
&& bytesRead >= masterElementsStack.peek().elementEndOffsetBytes) {
|
&& bytesRead >= masterElementsStack.peek().elementEndOffsetBytes) {
|
||||||
if (!eventHandler.onMasterElementEnd(masterElementsStack.pop().elementId)) {
|
eventHandler.onMasterElementEnd(masterElementsStack.pop().elementId);
|
||||||
return READ_RESULT_CONTINUE;
|
return READ_RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (state == STATE_BEGIN_READING) {
|
if (state == STATE_BEGIN_READING) {
|
||||||
int idResult = readElementId(inputStream);
|
int idResult = readElementId(inputStream);
|
||||||
@ -161,12 +160,10 @@ import java.util.Stack;
|
|||||||
case TYPE_MASTER:
|
case TYPE_MASTER:
|
||||||
int masterHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
|
int masterHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
|
||||||
masterElementsStack.add(new MasterElement(elementId, bytesRead + elementContentSize));
|
masterElementsStack.add(new MasterElement(elementId, bytesRead + elementContentSize));
|
||||||
if (!eventHandler.onMasterElementStart(
|
eventHandler.onMasterElementStart(elementId, elementOffset, masterHeaderSize,
|
||||||
elementId, elementOffset, masterHeaderSize, elementContentSize)) {
|
elementContentSize);
|
||||||
prepareForNextElement();
|
prepareForNextElement();
|
||||||
return READ_RESULT_CONTINUE;
|
return READ_RESULT_CONTINUE;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TYPE_UNSIGNED_INT:
|
case TYPE_UNSIGNED_INT:
|
||||||
if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) {
|
if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) {
|
||||||
throw new IllegalStateException("Invalid integer size " + elementContentSize);
|
throw new IllegalStateException("Invalid integer size " + elementContentSize);
|
||||||
@ -177,11 +174,9 @@ import java.util.Stack;
|
|||||||
return intResult;
|
return intResult;
|
||||||
}
|
}
|
||||||
long intValue = getTempByteArrayValue((int) elementContentSize, false);
|
long intValue = getTempByteArrayValue((int) elementContentSize, false);
|
||||||
if (!eventHandler.onIntegerElement(elementId, intValue)) {
|
eventHandler.onIntegerElement(elementId, intValue);
|
||||||
prepareForNextElement();
|
prepareForNextElement();
|
||||||
return READ_RESULT_CONTINUE;
|
return READ_RESULT_CONTINUE;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TYPE_FLOAT:
|
case TYPE_FLOAT:
|
||||||
if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES
|
if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES
|
||||||
&& elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) {
|
&& elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) {
|
||||||
@ -199,11 +194,9 @@ import java.util.Stack;
|
|||||||
} else {
|
} else {
|
||||||
floatValue = Double.longBitsToDouble(valueBits);
|
floatValue = Double.longBitsToDouble(valueBits);
|
||||||
}
|
}
|
||||||
if (!eventHandler.onFloatElement(elementId, floatValue)) {
|
eventHandler.onFloatElement(elementId, floatValue);
|
||||||
prepareForNextElement();
|
prepareForNextElement();
|
||||||
return READ_RESULT_CONTINUE;
|
return READ_RESULT_CONTINUE;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TYPE_STRING:
|
case TYPE_STRING:
|
||||||
if (elementContentSize > Integer.MAX_VALUE) {
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
@ -219,11 +212,9 @@ import java.util.Stack;
|
|||||||
}
|
}
|
||||||
String stringValue = new String(stringBytes, Charset.forName("UTF-8"));
|
String stringValue = new String(stringBytes, Charset.forName("UTF-8"));
|
||||||
stringBytes = null;
|
stringBytes = null;
|
||||||
if (!eventHandler.onStringElement(elementId, stringValue)) {
|
eventHandler.onStringElement(elementId, stringValue);
|
||||||
prepareForNextElement();
|
prepareForNextElement();
|
||||||
return READ_RESULT_CONTINUE;
|
return READ_RESULT_CONTINUE;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TYPE_BINARY:
|
case TYPE_BINARY:
|
||||||
if (elementContentSize > Integer.MAX_VALUE) {
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
@ -233,18 +224,17 @@ import java.util.Stack;
|
|||||||
return READ_RESULT_NEED_MORE_DATA;
|
return READ_RESULT_NEED_MORE_DATA;
|
||||||
}
|
}
|
||||||
int binaryHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
|
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);
|
elementId, elementOffset, binaryHeaderSize, (int) elementContentSize, inputStream);
|
||||||
|
if (consumed) {
|
||||||
long expectedBytesRead = elementOffset + binaryHeaderSize + elementContentSize;
|
long expectedBytesRead = elementOffset + binaryHeaderSize + elementContentSize;
|
||||||
if (expectedBytesRead != bytesRead) {
|
if (expectedBytesRead != bytesRead) {
|
||||||
throw new IllegalStateException("Incorrect total bytes read. Expected "
|
throw new IllegalStateException("Incorrect total bytes read. Expected "
|
||||||
+ expectedBytesRead + " but actually " + bytesRead);
|
+ expectedBytesRead + " but actually " + bytesRead);
|
||||||
}
|
}
|
||||||
if (!keepGoing) {
|
|
||||||
prepareForNextElement();
|
prepareForNextElement();
|
||||||
return READ_RESULT_CONTINUE;
|
|
||||||
}
|
}
|
||||||
break;
|
return READ_RESULT_CONTINUE;
|
||||||
case TYPE_UNKNOWN:
|
case TYPE_UNKNOWN:
|
||||||
if (elementContentSize > Integer.MAX_VALUE) {
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
@ -254,11 +244,11 @@ import java.util.Stack;
|
|||||||
if (skipResult != READ_RESULT_CONTINUE) {
|
if (skipResult != READ_RESULT_CONTINUE) {
|
||||||
return skipResult;
|
return skipResult;
|
||||||
}
|
}
|
||||||
|
prepareForNextElement();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Invalid element type " + type);
|
throw new IllegalStateException("Invalid element type " + type);
|
||||||
}
|
}
|
||||||
prepareForNextElement();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,7 +498,7 @@ import java.util.Stack;
|
|||||||
*/
|
*/
|
||||||
private int updateBytesState(int additionalBytesRead, int totalBytes) {
|
private int updateBytesState(int additionalBytesRead, int totalBytes) {
|
||||||
if (additionalBytesRead == -1) {
|
if (additionalBytesRead == -1) {
|
||||||
return READ_RESULT_END_OF_FILE;
|
return READ_RESULT_END_OF_STREAM;
|
||||||
}
|
}
|
||||||
bytesState += additionalBytesRead;
|
bytesState += additionalBytesRead;
|
||||||
bytesRead += additionalBytesRead;
|
bytesRead += additionalBytesRead;
|
||||||
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer.util.MimeTypes;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.TimeUnit;
|
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_FIXED = 2;
|
||||||
private static final int LACING_EBML = 3;
|
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 EbmlReader reader;
|
||||||
private final byte[] simpleBlockTimecodeAndFlags = new byte[3];
|
private final byte[] simpleBlockTimecodeAndFlags = new byte[3];
|
||||||
|
|
||||||
private SampleHolder tempSampleHolder;
|
private SampleHolder sampleHolder;
|
||||||
private boolean sampleRead;
|
private int readResults;
|
||||||
|
|
||||||
private long segmentStartOffsetBytes = UNKNOWN;
|
private long segmentStartOffsetBytes = UNKNOWN;
|
||||||
private long segmentEndOffsetBytes = UNKNOWN;
|
private long segmentEndOffsetBytes = UNKNOWN;
|
||||||
@ -107,17 +111,19 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPrepared() {
|
public int read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
|
||||||
return format != null && cues != null;
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
this.sampleHolder = null;
|
||||||
public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
|
return readResults;
|
||||||
tempSampleHolder = sampleHolder;
|
|
||||||
sampleRead = false;
|
|
||||||
reader.read(inputStream);
|
|
||||||
tempSampleHolder = null;
|
|
||||||
return sampleRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -139,7 +145,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SegmentIndex getCues() {
|
public SegmentIndex getIndex() {
|
||||||
return cues;
|
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
|
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
|
||||||
// for info about how data is organized in a SimpleBlock element.
|
// 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.
|
// Value of trackNumber is not used but needs to be read.
|
||||||
reader.readVarint(inputStream);
|
reader.readVarint(inputStream);
|
||||||
|
|
||||||
@ -309,10 +321,10 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
case LACING_NONE:
|
case LACING_NONE:
|
||||||
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
||||||
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
||||||
tempSampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
sampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
||||||
tempSampleHolder.decodeOnly = invisible;
|
sampleHolder.decodeOnly = invisible;
|
||||||
tempSampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
sampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
||||||
tempSampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
|
sampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
|
||||||
break;
|
break;
|
||||||
case LACING_EBML:
|
case LACING_EBML:
|
||||||
case LACING_FIXED:
|
case LACING_FIXED:
|
||||||
@ -321,14 +333,22 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
throw new IllegalStateException("Lacing mode " + lacing + " not supported");
|
throw new IllegalStateException("Lacing mode " + lacing + " not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read video data into sample holder.
|
ByteBuffer outputData = sampleHolder.data;
|
||||||
reader.readBytes(inputStream, tempSampleHolder.data, tempSampleHolder.size);
|
if (sampleHolder.allowDataBufferReplacement
|
||||||
sampleRead = true;
|
&& (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size)) {
|
||||||
return false;
|
outputData = ByteBuffer.allocate(sampleHolder.size);
|
||||||
} else {
|
sampleHolder.data = outputData;
|
||||||
reader.skipBytes(inputStream, contentsSizeBytes);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private long scaleTimecodeToUs(long unscaledTimecode) {
|
||||||
@ -347,6 +367,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
&& (format == null || format.width != pixelWidth || format.height != pixelHeight)) {
|
&& (format == null || format.width != pixelWidth || format.height != pixelHeight)) {
|
||||||
format = MediaFormat.createVideoFormat(
|
format = MediaFormat.createVideoFormat(
|
||||||
MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null);
|
MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null);
|
||||||
|
readResults |= RESULT_READ_INIT;
|
||||||
} else if (format == null) {
|
} else if (format == null) {
|
||||||
throw new IllegalStateException("Unable to build format");
|
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);
|
cues = new SegmentIndex((int) cuesSizeBytes, sizes, offsets, durationsUs, timesUs);
|
||||||
cueTimesUs = null;
|
cueTimesUs = null;
|
||||||
cueClusterPositions = null;
|
cueClusterPositions = null;
|
||||||
|
readResults |= RESULT_READ_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -401,30 +423,30 @@ public final class DefaultWebmExtractor implements WebmExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMasterElementStart(
|
public void onMasterElementStart(
|
||||||
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) {
|
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) {
|
||||||
return DefaultWebmExtractor.this.onMasterElementStart(
|
DefaultWebmExtractor.this.onMasterElementStart(
|
||||||
id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes);
|
id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMasterElementEnd(int id) {
|
public void onMasterElementEnd(int id) {
|
||||||
return DefaultWebmExtractor.this.onMasterElementEnd(id);
|
DefaultWebmExtractor.this.onMasterElementEnd(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onIntegerElement(int id, long value) {
|
public void onIntegerElement(int id, long value) {
|
||||||
return DefaultWebmExtractor.this.onIntegerElement(id, value);
|
DefaultWebmExtractor.this.onIntegerElement(id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onFloatElement(int id, double value) {
|
public void onFloatElement(int id, double value) {
|
||||||
return DefaultWebmExtractor.this.onFloatElement(id, value);
|
DefaultWebmExtractor.this.onFloatElement(id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onStringElement(int id, String value) {
|
public void onStringElement(int id, String value) {
|
||||||
return DefaultWebmExtractor.this.onStringElement(id, value);
|
DefaultWebmExtractor.this.onStringElement(id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,9 +46,8 @@ import java.nio.ByteBuffer;
|
|||||||
* @param elementOffsetBytes The byte offset where this element starts
|
* @param elementOffsetBytes The byte offset where this element starts
|
||||||
* @param headerSizeBytes The byte length of this element's ID and size header
|
* @param headerSizeBytes The byte length of this element's ID and size header
|
||||||
* @param contentsSizeBytes The byte length of this element's children
|
* @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);
|
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,44 +55,42 @@ import java.nio.ByteBuffer;
|
|||||||
* {@link NonBlockingInputStream}.
|
* {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* @param id The integer ID of this element
|
* @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}.
|
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* @param id The integer ID of this element
|
* @param id The integer ID of this element
|
||||||
* @param value The integer value this element contains
|
* @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}.
|
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* @param id The integer ID of this element
|
* @param id The integer ID of this element
|
||||||
* @param value The float value this element contains
|
* @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}.
|
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* @param id The integer ID of this element
|
* @param id The integer ID of this element
|
||||||
* @param value The string value this element contains
|
* @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}.
|
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* <p>The element header (containing element ID and content size) will already have been read.
|
* <p>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
|
* Subclasses must either read nothing and return {@code false}, or exactly read the entire
|
||||||
* {@code contentsSizeBytes} in length. It's guaranteed that the full element contents will be
|
* contents of the element, which is {@code contentsSizeBytes} in length, and return {@code true}.
|
||||||
* immediately available from {@code inputStream}.
|
*
|
||||||
|
* <p>It's guaranteed that the full element contents will be immediately available from
|
||||||
|
* {@code inputStream}.
|
||||||
*
|
*
|
||||||
* <p>Several methods in {@link EbmlReader} are available for reading the contents of a
|
* <p>Several methods in {@link EbmlReader} are available for reading the contents of a
|
||||||
* binary element:
|
* binary element:
|
||||||
@ -111,7 +108,7 @@ import java.nio.ByteBuffer;
|
|||||||
* @param contentsSizeBytes The byte length of this element's contents
|
* @param contentsSizeBytes The byte length of this element's contents
|
||||||
* @param inputStream The {@link NonBlockingInputStream} from which this
|
* @param inputStream The {@link NonBlockingInputStream} from which this
|
||||||
* element's contents should be read
|
* 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(
|
public boolean onBinaryElement(
|
||||||
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
|
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
|
||||||
|
@ -44,12 +44,12 @@ import java.nio.ByteBuffer;
|
|||||||
// Return values for reading methods.
|
// Return values for reading methods.
|
||||||
public static final int READ_RESULT_CONTINUE = 0;
|
public static final int READ_RESULT_CONTINUE = 0;
|
||||||
public static final int READ_RESULT_NEED_MORE_DATA = 1;
|
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);
|
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
|
* @param inputStream The input stream from which data should be read
|
||||||
* @return One of the {@code RESULT_*} flags defined in this interface
|
* @return One of the {@code RESULT_*} flags defined in this interface
|
||||||
|
@ -30,24 +30,38 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
|||||||
public interface WebmExtractor {
|
public interface WebmExtractor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the has parsed the cues and sample format from the stream.
|
* An attempt to read from the input stream returned insufficient data.
|
||||||
*
|
|
||||||
* @return True if the extractor is prepared. False otherwise
|
|
||||||
*/
|
*/
|
||||||
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}.
|
* Consumes data from a {@link NonBlockingInputStream}.
|
||||||
*
|
*
|
||||||
* <p>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 inputStream The input stream from which data should be read
|
||||||
* @param sampleHolder A {@link SampleHolder} into which the sample 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.
|
* 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
|
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
|
||||||
* prepared
|
* prepared
|
||||||
*/
|
*/
|
||||||
public SegmentIndex getCues();
|
public SegmentIndex getIndex();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the format of the samples contained within the media stream.
|
* Returns the format of the samples contained within the media stream.
|
||||||
|
@ -144,7 +144,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
|
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
|
||||||
|
try {
|
||||||
source.continueBuffering(timeUs);
|
source.continueBuffering(timeUs);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ExoPlaybackException(e);
|
||||||
|
}
|
||||||
|
|
||||||
currentPositionUs = timeUs;
|
currentPositionUs = timeUs;
|
||||||
|
|
||||||
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we advance
|
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we advance
|
||||||
|
Loading…
x
Reference in New Issue
Block a user