Merge pull request #75 from google/dev

Merge dev -> dev-hls
This commit is contained in:
ojw28 2014-10-09 17:31:36 +01:00
commit d2e480f01b
16 changed files with 279 additions and 201 deletions

View File

@ -68,10 +68,10 @@ import android.widget.TextView;
}
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
maybeFail();
if (timeUs < currentPositionUs || timeUs > currentPositionUs + 1000000) {
currentPositionUs = timeUs;
if (positionUs < currentPositionUs || positionUs > currentPositionUs + 1000000) {
currentPositionUs = positionUs;
textView.post(this);
}
}

View File

@ -166,9 +166,9 @@ public class DefaultLoadControl implements LoadControl {
// Update the loader state.
int loaderBufferState = getLoaderBufferState(playbackPositionUs, nextLoadPositionUs);
LoaderState loaderState = loaderStates.get(loader);
boolean loaderStateChanged = loaderState.bufferState != loaderBufferState ||
loaderState.nextLoadPositionUs != nextLoadPositionUs || loaderState.loading != loading ||
loaderState.failed != failed;
boolean loaderStateChanged = loaderState.bufferState != loaderBufferState
|| loaderState.nextLoadPositionUs != nextLoadPositionUs || loaderState.loading != loading
|| loaderState.failed != failed;
if (loaderStateChanged) {
loaderState.bufferState = loaderBufferState;
loaderState.nextLoadPositionUs = nextLoadPositionUs;
@ -214,17 +214,17 @@ public class DefaultLoadControl implements LoadControl {
private void updateControlState() {
boolean loading = false;
boolean failed = false;
boolean finished = true;
boolean haveNextLoadPosition = false;
int highestState = bufferPoolState;
for (int i = 0; i < loaders.size(); i++) {
LoaderState loaderState = loaderStates.get(loaders.get(i));
loading |= loaderState.loading;
failed |= loaderState.failed;
finished &= loaderState.nextLoadPositionUs == -1;
haveNextLoadPosition |= loaderState.nextLoadPositionUs != -1;
highestState = Math.max(highestState, loaderState.bufferState);
}
fillingBuffers = !loaders.isEmpty() && !finished && !failed
fillingBuffers = !loaders.isEmpty() && !failed && (loading || haveNextLoadPosition)
&& (highestState == BELOW_LOW_WATERMARK
|| (highestState == BETWEEN_WATERMARKS && fillingBuffers));
if (fillingBuffers && !streamingPrioritySet) {

View File

@ -40,12 +40,12 @@ public class DummyTrackRenderer extends TrackRenderer {
}
@Override
protected void seekTo(long timeUs) {
protected void seekTo(long positionUs) {
throw new IllegalStateException();
}
@Override
protected void doSomeWork(long timeUs) {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) {
throw new IllegalStateException();
}

View File

@ -77,6 +77,7 @@ import java.util.List;
private int state;
private int customMessagesSent = 0;
private int customMessagesProcessed = 0;
private long elapsedRealtimeUs;
private volatile long durationUs;
private volatile long positionUs;
@ -383,7 +384,8 @@ import java.util.List;
positionUs = timeSourceTrackRenderer != null &&
enabledRenderers.contains(timeSourceTrackRenderer) ?
timeSourceTrackRenderer.getCurrentPositionUs() :
mediaClock.getTimeUs();
mediaClock.getPositionUs();
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
}
private void doSomeWork() throws ExoPlaybackException {
@ -399,7 +401,7 @@ import java.util.List;
// TODO: Each renderer should return the maximum delay before which it wishes to be
// invoked again. The minimum of these values should then be used as the delay before the next
// invocation of this method.
renderer.doSomeWork(positionUs);
renderer.doSomeWork(positionUs, elapsedRealtimeUs);
isEnded = isEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);
@ -462,7 +464,7 @@ import java.util.List;
rebuffering = false;
positionUs = positionMs * 1000L;
mediaClock.stop();
mediaClock.setTimeUs(positionUs);
mediaClock.setPositionUs(positionUs);
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
return;
}
@ -582,7 +584,7 @@ import java.util.List;
if (renderer == timeSourceTrackRenderer) {
// We've been using timeSourceTrackRenderer to advance the current position, but it's
// being disabled. Sync mediaClock so that it can take over timing responsibilities.
mediaClock.setTimeUs(renderer.getCurrentPositionUs());
mediaClock.setPositionUs(renderer.getCurrentPositionUs());
}
ensureStopped(renderer);
enabledRenderers.remove(renderer);

View File

@ -50,7 +50,7 @@ public final class FrameworkSampleSource implements SampleSource {
private int[] trackStates;
private boolean[] pendingDiscontinuities;
private long seekTimeUs;
private long seekPositionUs;
public FrameworkSampleSource(Context context, Uri uri, Map<String, String> headers,
int downstreamRendererCount) {
@ -94,16 +94,16 @@ public final class FrameworkSampleSource implements SampleSource {
}
@Override
public void enable(int track, long timeUs) {
public void enable(int track, long positionUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
trackStates[track] = TRACK_STATE_ENABLED;
extractor.selectTrack(track);
seekToUs(timeUs);
seekToUs(positionUs);
}
@Override
public boolean continueBuffering(long playbackPositionUs) {
public boolean continueBuffering(long positionUs) {
// 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
@ -112,7 +112,7 @@ public final class FrameworkSampleSource implements SampleSource {
}
@Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
@ -144,7 +144,7 @@ public final class FrameworkSampleSource implements SampleSource {
if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
}
seekTimeUs = -1;
seekPositionUs = -1;
extractor.advance();
return SAMPLE_READ;
} else {
@ -168,13 +168,13 @@ public final class FrameworkSampleSource implements SampleSource {
}
@Override
public void seekToUs(long timeUs) {
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
if (seekTimeUs != timeUs) {
if (seekPositionUs != positionUs) {
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
// have been no interleaving calls to advance.
seekTimeUs = timeUs;
extractor.seekTo(timeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
seekPositionUs = positionUs;
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
for (int i = 0; i < trackStates.length; ++i) {
if (trackStates[i] != TRACK_STATE_DISABLED) {
pendingDiscontinuities[i] = true;

View File

@ -65,9 +65,10 @@ public interface LoadControl {
*
* @param loader The loader invoking the update.
* @param playbackPositionUs The loader's playback position.
* @param nextLoadPositionUs The loader's next load position, or -1 if finished.
* @param nextLoadPositionUs The loader's next load position. -1 if finished, failed, or if the
* next load position is not yet known.
* @param loading Whether the loader is currently loading data.
* @param failed Whether the loader has failed, meaning it does not wish to load more data.
* @param failed Whether the loader has failed.
* @return True if the loader is allowed to start its next load. False otherwise.
*/
boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs,

View File

@ -29,10 +29,10 @@ import android.os.SystemClock;
/**
* The media time when the clock was last set or stopped.
*/
private long timeUs;
private long positionUs;
/**
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #timeUs}
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #positionUs}
* when the clock was last set or started.
*/
private long deltaUs;
@ -43,7 +43,7 @@ import android.os.SystemClock;
public void start() {
if (!started) {
started = true;
deltaUs = elapsedRealtimeMinus(timeUs);
deltaUs = elapsedRealtimeMinus(positionUs);
}
}
@ -52,28 +52,28 @@ import android.os.SystemClock;
*/
public void stop() {
if (started) {
timeUs = elapsedRealtimeMinus(deltaUs);
positionUs = elapsedRealtimeMinus(deltaUs);
started = false;
}
}
/**
* @param timeUs The time to set in microseconds.
* @param timeUs The position to set in microseconds.
*/
public void setTimeUs(long timeUs) {
this.timeUs = timeUs;
public void setPositionUs(long timeUs) {
this.positionUs = timeUs;
deltaUs = elapsedRealtimeMinus(timeUs);
}
/**
* @return The current time in microseconds.
* @return The current position in microseconds.
*/
public long getTimeUs() {
return started ? elapsedRealtimeMinus(deltaUs) : timeUs;
public long getPositionUs() {
return started ? elapsedRealtimeMinus(deltaUs) : positionUs;
}
private long elapsedRealtimeMinus(long microSeconds) {
return SystemClock.elapsedRealtime() * 1000 - microSeconds;
private long elapsedRealtimeMinus(long toSubtractUs) {
return SystemClock.elapsedRealtime() * 1000 - toSubtractUs;
}
}

View File

@ -94,10 +94,18 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
/**
* AudioTrack timestamps are deemed spurious if they are offset from the system clock by more
* than this amount. This is a fail safe that should not be required on correctly functioning
* devices.
* than this amount.
* <p>
* This is a fail safe that should not be required on correctly functioning devices.
*/
private static final long MAX_AUDIO_TIMSTAMP_OFFSET_US = 10 * MICROS_PER_SECOND;
private static final long MAX_AUDIO_TIMESTAMP_OFFSET_US = 10 * MICROS_PER_SECOND;
/**
* AudioTrack latencies are deemed impossibly large if they are greater than this amount.
* <p>
* This is a fail safe that should not be required on correctly functioning devices.
*/
private static final long MAX_AUDIO_TRACK_LATENCY_US = 10 * MICROS_PER_SECOND;
private static final int MAX_PLAYHEAD_OFFSET_COUNT = 10;
private static final int MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US = 30000;
@ -261,14 +269,14 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void onEnabled(long timeUs, boolean joining) {
super.onEnabled(timeUs, joining);
protected void onEnabled(long positionUs, boolean joining) {
super.onEnabled(positionUs, joining);
lastReportedCurrentPositionUs = Long.MIN_VALUE;
}
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
super.doSomeWork(timeUs);
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
super.doSomeWork(positionUs, elapsedRealtimeUs);
maybeSampleSyncParams();
}
@ -515,7 +523,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
if (audioTimestampUs < audioTrackResumeSystemTimeUs) {
// The timestamp corresponds to a time before the track was most recently resumed.
audioTimestampSet = false;
} else if (Math.abs(audioTimestampUs - systemClockUs) > MAX_AUDIO_TIMSTAMP_OFFSET_US) {
} else if (Math.abs(audioTimestampUs - systemClockUs) > MAX_AUDIO_TIMESTAMP_OFFSET_US) {
// The timestamp time base is probably wrong.
audioTimestampSet = false;
Log.w(TAG, "Spurious audio timestamp: " + audioTimestampCompat.getFramePosition() + ", "
@ -531,6 +539,11 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
framesToDurationUs(bufferSize / frameSize);
// Sanity check that the latency is non-negative.
audioTrackLatencyUs = Math.max(audioTrackLatencyUs, 0);
// Sanity check that the latency isn't too large.
if (audioTrackLatencyUs > MAX_AUDIO_TRACK_LATENCY_US) {
Log.w(TAG, "Ignoring impossibly large audio latency: " + audioTrackLatencyUs);
audioTrackLatencyUs = 0;
}
} catch (Exception e) {
// The method existed, but doesn't work. Don't try again.
audioTrackGetLatencyMethod = null;
@ -572,16 +585,16 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
super.seekTo(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
// TODO: Try and re-use the same AudioTrack instance once [redacted] is fixed.
releaseAudioTrack();
lastReportedCurrentPositionUs = Long.MIN_VALUE;
}
@Override
protected boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
throws ExoPlaybackException {
if (shouldSkip) {
codec.releaseOutputBuffer(bufferIndex, false);
@ -616,6 +629,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
// time and the number of bytes submitted. Also reset lastReportedCurrentPositionUs to
// allow time to jump backwards if it really wants to.
audioTrackStartMediaTimeUs += (bufferStartTime - expectedBufferStartTime);
audioTrackStartMediaTimeState = START_IN_SYNC;
lastReportedCurrentPositionUs = Long.MIN_VALUE;
}
}

View File

@ -217,13 +217,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected void onEnabled(long timeUs, boolean joining) {
source.enable(trackIndex, timeUs);
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false;
outputStreamEnded = false;
waitingForKeys = false;
currentPositionUs = timeUs;
currentPositionUs = positionUs;
}
/**
@ -367,9 +367,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
currentPositionUs = timeUs;
source.seekToUs(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
currentPositionUs = positionUs;
source.seekToUs(positionUs);
sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false;
outputStreamEnded = false;
@ -387,22 +387,22 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
sourceState = source.continueBuffering(timeUs)
sourceState = source.continueBuffering(positionUs)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY;
checkForDiscontinuity();
if (format == null) {
readFormat();
} else if (codec == null && !shouldInitCodec() && getState() == TrackRenderer.STATE_STARTED) {
discardSamples(timeUs);
discardSamples(positionUs);
} else {
if (codec == null && shouldInitCodec()) {
maybeInitCodec();
}
if (codec != null) {
while (drainOutputBuffer(timeUs)) {}
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (feedInputBuffer(true)) {
while (feedInputBuffer(false)) {}
}
@ -421,10 +421,10 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
private void discardSamples(long timeUs) throws IOException, ExoPlaybackException {
private void discardSamples(long positionUs) throws IOException, ExoPlaybackException {
sampleHolder.data = null;
int result = SampleSource.SAMPLE_READ;
while (result == SampleSource.SAMPLE_READ && currentPositionUs <= timeUs) {
while (result == SampleSource.SAMPLE_READ && currentPositionUs <= positionUs) {
result = source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
if (!sampleHolder.decodeOnly) {
@ -469,7 +469,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
/**
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long)}. False otherwise.
* {@link #doSomeWork(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws IOException If an error occurs reading data from the upstream source.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
@ -694,7 +694,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @return True if it may be possible to drain more output data. False otherwise.
* @throws ExoPlaybackException If an error occurs draining the output buffer.
*/
private boolean drainOutputBuffer(long timeUs) throws ExoPlaybackException {
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
if (outputStreamEnded) {
return false;
}
@ -722,8 +723,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
boolean decodeOnly = decodeOnlyPresentationTimestamps.contains(
outputBufferInfo.presentationTimeUs);
if (processOutputBuffer(timeUs, codec, outputBuffers[outputIndex], outputBufferInfo,
outputIndex, decodeOnly)) {
if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex],
outputBufferInfo, outputIndex, decodeOnly)) {
if (decodeOnly) {
decodeOnlyPresentationTimestamps.remove(outputBufferInfo.presentationTimeUs);
} else {
@ -743,9 +744,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* longer required. False otherwise.
* @throws ExoPlaybackException If an error occurs processing the output buffer.
*/
protected abstract boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
throws ExoPlaybackException;
protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs,
MediaCodec codec, ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex,
boolean shouldSkip) throws ExoPlaybackException;
/**
* Returns the name of the secure variant of a given decoder.

View File

@ -225,8 +225,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void onEnabled(long startTimeUs, boolean joining) {
super.onEnabled(startTimeUs, joining);
protected void onEnabled(long positionUs, boolean joining) {
super.onEnabled(positionUs, joining);
renderedFirstFrame = false;
if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
@ -234,8 +234,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
super.seekTo(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
renderedFirstFrame = false;
joiningDeadlineUs = -1;
}
@ -354,14 +354,15 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
if (shouldSkip) {
skipOutputBuffer(codec, bufferIndex);
return true;
}
long earlyUs = bufferInfo.presentationTimeUs - timeUs;
long elapsedSinceStartOfLoop = SystemClock.elapsedRealtime() * 1000 - elapsedRealtimeUs;
long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoop;
if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex);

View File

@ -26,8 +26,12 @@ public class ParserException extends IOException {
super(message);
}
public ParserException(Exception cause) {
public ParserException(Throwable cause) {
super(cause);
}
public ParserException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -85,9 +85,9 @@ public interface SampleSource {
* This method should not be called until after the source has been successfully prepared.
*
* @param track The track to enable.
* @param timeUs The player's current playback position.
* @param positionUs The player's current playback position.
*/
public void enable(int track, long timeUs);
public void enable(int track, long positionUs);
/**
* Disable the specified track.
@ -101,12 +101,12 @@ public interface SampleSource {
/**
* Indicates to the source that it should still be buffering data.
*
* @param playbackPositionUs The current playback position.
* @param positionUs 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 boolean continueBuffering(long playbackPositionUs) throws IOException;
public boolean continueBuffering(long positionUs) throws IOException;
/**
* Attempts to read either a sample, a new format or or a discontinuity from the source.
@ -118,7 +118,7 @@ public interface SampleSource {
* than the one for which data was requested.
*
* @param track The track from which to read.
* @param playbackPositionUs The current playback position.
* @param positionUs The current playback position.
* @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. If
* the caller requires the sample data then it must ensure that {@link SampleHolder#data}
@ -129,7 +129,7 @@ public interface SampleSource {
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
* @throws IOException If an error occurred reading from the source.
*/
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException;
/**
@ -137,16 +137,16 @@ public interface SampleSource {
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @param timeUs The seek position in microseconds.
* @param positionUs The seek position in microseconds.
*/
public void seekToUs(long timeUs);
public void seekToUs(long positionUs);
/**
* Returns an estimate of the position up to which data is buffered.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* @return An estimate of the absolute position in microseconds up to which data is buffered,
* or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or
* {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
*/

View File

@ -18,6 +18,8 @@ package com.google.android.exoplayer;
import com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent;
import com.google.android.exoplayer.util.Assertions;
import android.os.SystemClock;
/**
* Renders a single component of media.
*
@ -59,7 +61,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*/
protected static final int STATE_ENABLED = 2;
/**
* The renderer is started. Calls to {@link #doSomeWork(long)} should cause the media to be
* The renderer is started. Calls to {@link #doSomeWork(long, long)} should cause the media to be
* rendered.
*/
protected static final int STATE_STARTED = 3;
@ -83,9 +85,9 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* A time source renderer is a renderer that, when started, advances its own playback position.
* This means that {@link #getCurrentPositionUs()} will return increasing positions independently
* to increasing values being passed to {@link #doSomeWork(long)}. A player may have at most one
* time source renderer. If provided, the player will use such a renderer as its source of time
* during playback.
* to increasing values being passed to {@link #doSomeWork(long, long)}. A player may have at most
* one time source renderer. If provided, the player will use such a renderer as its source of
* time during playback.
* <p>
* This method may be called when the renderer is in any state.
*
@ -136,15 +138,15 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Enable the renderer.
*
* @param timeUs The player's current position.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback. If true
* then {@link #start} must be called immediately after this method returns (unless a
* {@link ExoPlaybackException} is thrown).
*/
/* package */ final void enable(long timeUs, boolean joining) throws ExoPlaybackException {
/* package */ final void enable(long positionUs, boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_PREPARED);
state = TrackRenderer.STATE_ENABLED;
onEnabled(timeUs, joining);
onEnabled(positionUs, joining);
}
/**
@ -152,18 +154,18 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* <p>
* The default implementation is a no-op.
*
* @param timeUs The player's current position.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback. If true
* then {@link #onStarted} is guaranteed to be called immediately after this method returns
* (unless a {@link ExoPlaybackException} is thrown).
* @throws ExoPlaybackException If an error occurs.
*/
protected void onEnabled(long timeUs, boolean joining) throws ExoPlaybackException {
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
// Do nothing.
}
/**
* Starts the renderer, meaning that calls to {@link #doSomeWork(long)} will cause the
* Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the
* track to be rendered.
*/
/* package */ final void start() throws ExoPlaybackException {
@ -289,10 +291,14 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @param timeUs The current playback time.
* @param positionUs The current media time in microseconds, measured at the start of the
* current iteration of the rendering loop.
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at
* the start of the current iteration of the rendering loop.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void doSomeWork(long timeUs) throws ExoPlaybackException;
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException;
/**
* Returns the duration of the media being rendered.
@ -300,7 +306,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return The duration of the track in micro-seconds, or {@link #MATCH_LONGEST_US} if
* @return The duration of the track in microseconds, or {@link #MATCH_LONGEST_US} if
* the track's duration should match that of the longest track whose duration is known, or
* or {@link #UNKNOWN_TIME_US} if the duration is not known.
*/
@ -312,17 +318,17 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return The current playback position in micro-seconds.
* @return The current playback position in microseconds.
*/
protected abstract long getCurrentPositionUs();
/**
* Returns an estimate of the absolute position in micro-seconds up to which data is buffered.
* Returns an estimate of the absolute position in microseconds up to which data is buffered.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* @return An estimate of the absolute position in microseconds up to which data is buffered,
* or {@link #END_OF_TRACK_US} if the track is fully buffered, or {@link #UNKNOWN_TIME_US} if
* no estimate is available.
*/
@ -334,10 +340,10 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}
*
* @param timeUs The desired time in micro-seconds.
* @param positionUs The desired playback position in microseconds.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void seekTo(long timeUs) throws ExoPlaybackException;
protected abstract void seekTo(long positionUs) throws ExoPlaybackException;
@Override
public void handleMessage(int what, Object object) throws ExoPlaybackException {

View File

@ -154,7 +154,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
private int state;
private long downstreamPositionUs;
private long lastSeekPositionUs;
private long pendingResetTime;
private long pendingResetPositionUs;
private long lastPerformedBufferOperation;
private boolean pendingDiscontinuity;
@ -219,7 +219,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
@Override
public void enable(int track, long timeUs) {
public void enable(int track, long positionUs) {
Assertions.checkState(state == STATE_PREPARED);
Assertions.checkState(track == 0);
state = STATE_ENABLED;
@ -227,9 +227,9 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
loadControl.register(this, bufferSizeContribution);
downstreamFormat = null;
downstreamMediaFormat = null;
downstreamPositionUs = timeUs;
lastSeekPositionUs = timeUs;
restartFrom(timeUs);
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
restartFrom(positionUs);
}
@Override
@ -253,10 +253,10 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
@Override
public boolean continueBuffering(long playbackPositionUs) throws IOException {
public boolean continueBuffering(long positionUs) throws IOException {
Assertions.checkState(state == STATE_ENABLED);
downstreamPositionUs = playbackPositionUs;
chunkSource.continueBuffering(playbackPositionUs);
downstreamPositionUs = positionUs;
chunkSource.continueBuffering(positionUs);
updateLoadControl();
if (isPendingReset() || mediaChunks.isEmpty()) {
return false;
@ -271,7 +271,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
@Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException {
Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0);
@ -285,7 +285,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
return NOTHING_READ;
}
downstreamPositionUs = playbackPositionUs;
downstreamPositionUs = positionUs;
if (isPendingReset()) {
if (currentLoadableException != null) {
throw currentLoadableException;
@ -304,7 +304,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
discardDownstreamMediaChunk();
mediaChunk = mediaChunks.getFirst();
mediaChunk.seekToStart();
return readData(track, playbackPositionUs, formatHolder, sampleHolder, false);
return readData(track, positionUs, formatHolder, sampleHolder, false);
} else if (mediaChunk.isLastChunk()) {
return END_OF_STREAM;
}
@ -350,32 +350,32 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
@Override
public void seekToUs(long timeUs) {
public void seekToUs(long positionUs) {
Assertions.checkState(state == STATE_ENABLED);
downstreamPositionUs = timeUs;
lastSeekPositionUs = timeUs;
if (pendingResetTime == timeUs) {
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
if (pendingResetPositionUs == positionUs) {
return;
}
MediaChunk mediaChunk = getMediaChunk(timeUs);
MediaChunk mediaChunk = getMediaChunk(positionUs);
if (mediaChunk == null) {
restartFrom(timeUs);
restartFrom(positionUs);
pendingDiscontinuity = true;
} else {
pendingDiscontinuity |= mediaChunk.seekTo(timeUs, mediaChunk == mediaChunks.getFirst());
pendingDiscontinuity |= mediaChunk.seekTo(positionUs, mediaChunk == mediaChunks.getFirst());
discardDownstreamMediaChunks(mediaChunk);
updateLoadControl();
}
}
private MediaChunk getMediaChunk(long timeUs) {
private MediaChunk getMediaChunk(long positionUs) {
Iterator<MediaChunk> mediaChunkIterator = mediaChunks.iterator();
while (mediaChunkIterator.hasNext()) {
MediaChunk mediaChunk = mediaChunkIterator.next();
if (timeUs < mediaChunk.startTimeUs) {
if (positionUs < mediaChunk.startTimeUs) {
return null;
} else if (mediaChunk.isLastChunk() || timeUs < mediaChunk.endTimeUs) {
} else if (mediaChunk.isLastChunk() || positionUs < mediaChunk.endTimeUs) {
return mediaChunk;
}
}
@ -386,7 +386,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
public long getBufferedPositionUs() {
Assertions.checkState(state == STATE_ENABLED);
if (isPendingReset()) {
return pendingResetTime;
return pendingResetPositionUs;
}
MediaChunk mediaChunk = mediaChunks.getLast();
Chunk currentLoadable = currentLoadableHolder.chunk;
@ -448,7 +448,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
clearCurrentLoadable();
if (state == STATE_ENABLED) {
restartFrom(pendingResetTime);
restartFrom(pendingResetPositionUs);
} else {
clearMediaChunks();
loadControl.trimAllocator();
@ -476,8 +476,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
// no-op
}
private void restartFrom(long timeUs) {
pendingResetTime = timeUs;
private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
@ -499,23 +499,40 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
private void updateLoadControl() {
long loadPositionUs;
if (isPendingReset()) {
loadPositionUs = pendingResetTime;
} else {
MediaChunk lastMediaChunk = mediaChunks.getLast();
loadPositionUs = lastMediaChunk.nextChunkIndex == -1 ? -1 : lastMediaChunk.endTimeUs;
}
boolean isBackedOff = currentLoadableException != null && !currentLoadableExceptionFatal;
boolean nextLoader = loadControl.update(this, downstreamPositionUs, loadPositionUs,
isBackedOff || loader.isLoading(), currentLoadableExceptionFatal);
if (currentLoadableExceptionFatal) {
// We've failed, but we still need to update the control with our current state.
loadControl.update(this, downstreamPositionUs, -1, false, true);
return;
}
long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null;
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
// If we're not loading or backed off, evaluate the operation if (a) we don't have the next
// chunk yet and we're not finished, or (b) if the last evaluation was over 2000ms ago.
if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|| (now - lastPerformedBufferOperation > 2000))) {
// Perform the evaluation.
lastPerformedBufferOperation = now;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
// Update the next load position as appropriate.
if (currentLoadableHolder.chunk == null) {
// Set loadPosition to -1 to indicate that we don't have anything to load.
nextLoadPositionUs = -1;
} else if (chunksDiscarded) {
// Chunks were discarded, so we need to re-evaluate the load position.
nextLoadPositionUs = getNextLoadPositionUs();
}
}
// Update the control with our current state, and determine whether we're the next loader.
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
loadingOrBackedOff, false);
if (isBackedOff) {
long elapsedMillis = now - currentLoadableExceptionTimestamp;
@ -525,17 +542,21 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
return;
}
if (!loader.isLoading()) {
if (currentLoadableHolder.chunk == null || now - lastPerformedBufferOperation > 1000) {
lastPerformedBufferOperation = now;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetTime, downstreamPositionUs,
currentLoadableHolder);
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
}
if (nextLoader) {
maybeStartLoading();
}
if (!loader.isLoading() && nextLoader) {
maybeStartLoading();
}
}
/**
* Gets the next load time, assuming that the next load starts where the previous chunk ended (or
* from the pending reset time, if there is one).
*/
private long getNextLoadPositionUs() {
if (isPendingReset()) {
return pendingResetPositionUs;
} else {
MediaChunk lastMediaChunk = mediaChunks.getLast();
return lastMediaChunk.nextChunkIndex == -1 ? -1 : lastMediaChunk.endTimeUs;
}
}
@ -552,8 +573,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
Chunk backedOffChunk = currentLoadableHolder.chunk;
if (!isMediaChunk(backedOffChunk)) {
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetTime, downstreamPositionUs,
currentLoadableHolder);
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading.
@ -577,7 +598,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
MediaChunk removedChunk = mediaChunks.removeLast();
Assertions.checkState(backedOffChunk == removedChunk);
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetTime, downstreamPositionUs,
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs, downstreamPositionUs,
currentLoadableHolder);
mediaChunks.add(removedChunk);
@ -603,8 +624,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
if (isMediaChunk(currentLoadable)) {
MediaChunk mediaChunk = (MediaChunk) currentLoadable;
if (isPendingReset()) {
mediaChunk.seekTo(pendingResetTime, false);
pendingResetTime = NO_RESET_PENDING;
mediaChunk.seekTo(pendingResetPositionUs, false);
pendingResetPositionUs = NO_RESET_PENDING;
}
mediaChunks.add(mediaChunk);
notifyLoadStarted(mediaChunk.format.id, mediaChunk.trigger, false,
@ -652,10 +673,11 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
* Discard upstream media chunks until the queue length is equal to the length specified.
*
* @param queueLength The desired length of the queue.
* @return True if chunks were discarded. False otherwise.
*/
private void discardUpstreamMediaChunks(int queueLength) {
private boolean discardUpstreamMediaChunks(int queueLength) {
if (mediaChunks.size() <= queueLength) {
return;
return false;
}
long totalBytes = 0;
long startTimeUs = 0;
@ -667,6 +689,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
removed.release();
}
notifyUpstreamDiscarded(startTimeUs, endTimeUs, totalBytes);
return true;
}
private boolean isMediaChunk(Chunk chunk) {
@ -674,7 +697,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
private boolean isPendingReset() {
return pendingResetTime != NO_RESET_PENDING;
return pendingResetPositionUs != NO_RESET_PENDING;
}
private long getRetryDelayMillis(long errorCount) {
@ -757,13 +780,13 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
}
private void notifyDownstreamFormatChanged(final String formatId, final int trigger,
final long mediaTimeUs) {
final long positionUs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDownstreamFormatChanged(eventSourceId, formatId, trigger,
usToMs(mediaTimeUs));
usToMs(positionUs));
}
});
}

View File

@ -115,43 +115,43 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected void onEnabled(long timeUs, boolean joining) {
source.enable(trackIndex, timeUs);
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
parserThread = new HandlerThread("textParser");
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParser);
seekToInternal(timeUs);
seekToInternal(positionUs);
}
@Override
protected void seekTo(long timeUs) {
source.seekToUs(timeUs);
seekToInternal(timeUs);
protected void seekTo(long positionUs) {
source.seekToUs(positionUs);
seekToInternal(positionUs);
}
private void seekToInternal(long timeUs) {
private void seekToInternal(long positionUs) {
inputStreamEnded = false;
currentPositionUs = timeUs;
source.seekToUs(timeUs);
if (subtitle != null && (timeUs < subtitle.getStartTime()
|| subtitle.getLastEventTime() <= timeUs)) {
currentPositionUs = positionUs;
source.seekToUs(positionUs);
if (subtitle != null && (positionUs < subtitle.getStartTime()
|| subtitle.getLastEventTime() <= positionUs)) {
subtitle = null;
}
parserHelper.flush();
clearTextRenderer();
syncNextEventIndex(timeUs);
syncNextEventIndex(positionUs);
textRendererNeedsUpdate = subtitle != null;
}
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
source.continueBuffering(timeUs);
source.continueBuffering(positionUs);
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
currentPositionUs = timeUs;
currentPositionUs = positionUs;
if (parserHelper.isParsing()) {
return;
@ -169,13 +169,13 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if (subtitle == null && dequeuedSubtitle != null) {
// We've dequeued a new subtitle. Sync the event index and update the subtitle.
subtitle = dequeuedSubtitle;
syncNextEventIndex(timeUs);
syncNextEventIndex(positionUs);
textRendererNeedsUpdate = true;
} else if (subtitle != null) {
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
// advance to the next event.
long nextEventTimeUs = getNextEventTime();
while (nextEventTimeUs <= timeUs) {
while (nextEventTimeUs <= positionUs) {
nextSubtitleEventIndex++;
nextEventTimeUs = getNextEventTime();
textRendererNeedsUpdate = true;
@ -191,7 +191,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if (subtitle == null) {
try {
SampleHolder sampleHolder = parserHelper.getSampleHolder();
int result = source.readData(trackIndex, timeUs, formatHolder, sampleHolder, false);
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
parserHelper.startParseOperation();
} else if (result == SampleSource.END_OF_STREAM) {
@ -208,7 +208,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if (subtitle == null) {
clearTextRenderer();
} else {
updateTextRenderer(timeUs);
updateTextRenderer(positionUs);
}
}
}
@ -256,8 +256,8 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
return true;
}
private void syncNextEventIndex(long timeUs) {
nextSubtitleEventIndex = subtitle == null ? -1 : subtitle.getNextEventTimeIndex(timeUs);
private void syncNextEventIndex(long positionUs) {
nextSubtitleEventIndex = subtitle == null ? -1 : subtitle.getNextEventTimeIndex(positionUs);
}
private long getNextEventTime() {
@ -266,8 +266,8 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
: (subtitle.getEventTime(nextSubtitleEventIndex));
}
private void updateTextRenderer(long timeUs) {
String text = subtitle.getText(timeUs);
private void updateTextRenderer(long positionUs) {
String text = subtitle.getText(positionUs);
log("updateTextRenderer; text=: " + text);
if (textRendererHandler != null) {
textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, text).sendToTarget();

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.text.SubtitleParser;
import com.google.android.exoplayer.util.MimeTypes;
@ -72,8 +73,23 @@ public class TtmlParser implements SubtitleParser {
private static final int DEFAULT_TICKRATE = 1;
private final XmlPullParserFactory xmlParserFactory;
private final boolean strictParsing;
/**
* Equivalent to {@code TtmlParser(true)}.
*/
public TtmlParser() {
this(true);
}
/**
* @param strictParsing If true, {@link #parse(InputStream, String, long)} will throw a
* {@link ParserException} if the stream contains invalid ttml. If false, the parser will
* make a best effort to ignore minor errors in the stream. Note however that a
* {@link ParserException} will still be thrown when this is not possible.
*/
public TtmlParser(boolean strictParsing) {
this.strictParsing = strictParsing;
try {
xmlParserFactory = XmlPullParserFactory.newInstance();
} catch (XmlPullParserException e) {
@ -89,21 +105,31 @@ public class TtmlParser implements SubtitleParser {
xmlParser.setInput(inputStream, inputEncoding);
TtmlSubtitle ttmlSubtitle = null;
LinkedList<TtmlNode> nodeStack = new LinkedList<TtmlNode>();
int unsupportedTagDepth = 0;
int unsupportedNodeDepth = 0;
int eventType = xmlParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
TtmlNode parent = nodeStack.peekLast();
if (unsupportedTagDepth == 0) {
if (unsupportedNodeDepth == 0) {
String name = xmlParser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (!isSupportedTag(name)) {
Log.w(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
unsupportedTagDepth++;
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
unsupportedNodeDepth++;
} else {
TtmlNode node = parseNode(xmlParser, parent);
nodeStack.addLast(node);
if (parent != null) {
parent.addChild(node);
try {
TtmlNode node = parseNode(xmlParser, parent);
nodeStack.addLast(node);
if (parent != null) {
parent.addChild(node);
}
} catch (ParserException e) {
if (strictParsing) {
throw e;
} else {
Log.e(TAG, "Suppressing parser error", e);
// Treat the node (and by extension, all of its children) as unsupported.
unsupportedNodeDepth++;
}
}
}
} else if (eventType == XmlPullParser.TEXT) {
@ -116,9 +142,9 @@ public class TtmlParser implements SubtitleParser {
}
} else {
if (eventType == XmlPullParser.START_TAG) {
unsupportedTagDepth++;
unsupportedNodeDepth++;
} else if (eventType == XmlPullParser.END_TAG) {
unsupportedTagDepth--;
unsupportedNodeDepth--;
}
}
xmlParser.next();
@ -126,7 +152,7 @@ public class TtmlParser implements SubtitleParser {
}
return ttmlSubtitle;
} catch (XmlPullParserException xppe) {
throw new IOException("Unable to parse source", xppe);
throw new ParserException("Unable to parse source", xppe);
}
}
@ -135,7 +161,7 @@ public class TtmlParser implements SubtitleParser {
return MimeTypes.APPLICATION_TTML.equals(mimeType);
}
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent) {
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent) throws ParserException {
long duration = 0;
long startTime = TtmlNode.UNDEFINED_TIME;
long endTime = TtmlNode.UNDEFINED_TIME;
@ -209,10 +235,10 @@ public class TtmlParser implements SubtitleParser {
* @param subframeRate The sub-framerate of the stream
* @param tickRate The tick rate of the stream.
* @return The parsed timestamp in microseconds.
* @throws NumberFormatException If the given string does not contain a valid time expression.
* @throws ParserException If the given string does not contain a valid time expression.
*/
private static long parseTimeExpression(String time, int frameRate, int subframeRate,
int tickRate) {
int tickRate) throws ParserException {
Matcher matcher = CLOCK_TIME.matcher(time);
if (matcher.matches()) {
String hours = matcher.group(1);
@ -250,7 +276,7 @@ public class TtmlParser implements SubtitleParser {
}
return (long) (offsetSeconds * 1000000);
}
throw new NumberFormatException("Malformed time expression: " + time);
throw new ParserException("Malformed time expression: " + time);
}
}