From 027d9eefbdfdfbc74208376cc1834e54a8cd1d93 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 9 Oct 2014 17:26:01 +0100 Subject: [PATCH] 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. --- .../demo/full/player/DebugTrackRenderer.java | 6 +- .../android/exoplayer/DummyTrackRenderer.java | 4 +- .../exoplayer/ExoPlayerImplInternal.java | 10 +-- .../exoplayer/FrameworkSampleSource.java | 20 +++--- .../google/android/exoplayer/MediaClock.java | 24 +++---- .../MediaCodecAudioTrackRenderer.java | 16 ++--- .../exoplayer/MediaCodecTrackRenderer.java | 39 +++++----- .../MediaCodecVideoTrackRenderer.java | 15 ++-- .../android/exoplayer/SampleSource.java | 18 ++--- .../android/exoplayer/TrackRenderer.java | 42 ++++++----- .../exoplayer/chunk/ChunkSampleSource.java | 72 +++++++++---------- .../exoplayer/text/TextTrackRenderer.java | 46 ++++++------ 12 files changed, 161 insertions(+), 151 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java index 8093bad814..d848dd3908 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java @@ -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); } } diff --git a/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java index 4bafdd07b8..4dd5ef4a42 100644 --- a/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java @@ -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(); } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index 0184ea9956..2dfac29519 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index 63afbdf0f5..0fc39b0e1a 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -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 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; diff --git a/library/src/main/java/com/google/android/exoplayer/MediaClock.java b/library/src/main/java/com/google/android/exoplayer/MediaClock.java index 9abd3c1f03..c2696e3b74 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaClock.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaClock.java @@ -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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index b59744d893..5027cb7830 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 4124a22c27..5e28f36d1b 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 565ab41723..a19be59df1 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 2f26d30e9a..9a3d40819b 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -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 { *

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

* 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. */ diff --git a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java index f27433d06e..66e20291f7 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java @@ -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. *

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

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

* 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 { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index f2f0ce031a..a436077d5f 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -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 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)); } }); } diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index 405b778209..f7f38d986a 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -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();