Smoother playback #1.

Propagate elapsedRealtimeUs to the video renderer. This allows
the renderer to calculate and adjust for the elapsed time since
the start of the current rendering loop. Typically this is <2ms,
but there situations where it can go higher (normally when the
video renderer ends up processing more than 1 output buffer in
a single loop).

Also made variable naming more consistent throughout the package.
This commit is contained in:
Oliver Woodman 2014-10-09 17:26:01 +01:00
parent 3b4409ae0b
commit 027d9eefbd
12 changed files with 161 additions and 151 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

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

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

@ -269,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();
}
@ -585,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);

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

@ -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 {
@ -501,7 +501,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
private void updateLoadControl() {
long loadPositionUs;
if (isPendingReset()) {
loadPositionUs = pendingResetTime;
loadPositionUs = pendingResetPositionUs;
} else {
MediaChunk lastMediaChunk = mediaChunks.getLast();
loadPositionUs = lastMediaChunk.nextChunkIndex == -1 ? -1 : lastMediaChunk.endTimeUs;
@ -529,8 +529,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
if (currentLoadableHolder.chunk == null || now - lastPerformedBufferOperation > 1000) {
lastPerformedBufferOperation = now;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetTime, downstreamPositionUs,
currentLoadableHolder);
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
}
if (nextLoader) {
@ -552,8 +552,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 +577,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 +603,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,
@ -674,7 +674,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 +757,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();