Clean up enabled renderers + processing of resets.

- Made enabledRenderers an array to avoid loads of method calls.
- Made if so that enabled renderers are always called in a consistent
  order, rather than their order changing if they're enabled/disabled
  over time. This is likely to make performance more predictable.
- Split out reading of resets into a separate method. This method is
  now called directly after seeking on the source, so as to ensure
  instant propagation of the new position from source->renderers in
  the common case.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117225639
This commit is contained in:
olly 2016-03-15 04:27:59 -07:00 committed by Oliver Woodman
parent 9b467b7c1b
commit 9a40a4c77d
10 changed files with 118 additions and 100 deletions

View File

@ -36,7 +36,12 @@ public final class DummyTrackRenderer extends TrackRenderer {
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) {
protected void checkForReset() throws ExoPlaybackException {
throw new IllegalStateException();
}
@Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
throw new IllegalStateException();
}

View File

@ -32,9 +32,7 @@ import android.util.Log;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -73,7 +71,6 @@ import java.util.concurrent.atomic.AtomicInteger;
private final StandaloneMediaClock standaloneMediaClock;
private final long minBufferUs;
private final long minRebufferUs;
private final List<TrackRenderer> enabledRenderers;
private final TrackSelection[] trackSelections;
private final Handler handler;
private final HandlerThread internalPlaybackThread;
@ -81,6 +78,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private final AtomicInteger pendingSeekCount;
private SampleSource source;
private TrackRenderer[] enabledRenderers;
private boolean released;
private boolean playWhenReady;
private boolean rebuffering;
@ -123,8 +121,8 @@ import java.util.concurrent.atomic.AtomicInteger;
standaloneMediaClock = new StandaloneMediaClock();
pendingSeekCount = new AtomicInteger();
enabledRenderers = new ArrayList<>(renderers.length);
trackSelections = new TrackSelection[renderers.length];
enabledRenderers = new TrackRenderer[0];
trackSelector.init(this);
@ -361,21 +359,21 @@ import java.util.concurrent.atomic.AtomicInteger;
private void startRenderers() throws ExoPlaybackException {
rebuffering = false;
standaloneMediaClock.start();
for (int i = 0; i < enabledRenderers.size(); i++) {
enabledRenderers.get(i).start();
for (TrackRenderer renderer : enabledRenderers) {
renderer.start();
}
}
private void stopRenderers() throws ExoPlaybackException {
standaloneMediaClock.stop();
for (int i = 0; i < enabledRenderers.size(); i++) {
ensureStopped(enabledRenderers.get(i));
for (TrackRenderer renderer : enabledRenderers) {
ensureStopped(renderer);
}
}
private void updatePositionUs() {
if (rendererMediaClock != null && enabledRenderers.contains(rendererMediaClockSource)
&& !rendererMediaClockSource.isEnded()) {
if (rendererMediaClock != null
&& rendererMediaClockSource.getState() != TrackRenderer.STATE_DISABLED) {
positionUs = rendererMediaClock.getPositionUs();
standaloneMediaClock.setPositionUs(positionUs);
} else {
@ -387,18 +385,23 @@ import java.util.concurrent.atomic.AtomicInteger;
private void doSomeWork() throws ExoPlaybackException, IOException {
TraceUtil.beginSection("doSomeWork");
long operationStartTimeMs = SystemClock.elapsedRealtime();
// Process resets.
for (TrackRenderer renderer : enabledRenderers) {
renderer.checkForReset();
}
updatePositionUs();
bufferedPositionUs = source.getBufferedPositionUs();
source.continueBuffering(positionUs);
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
for (int i = 0; i < enabledRenderers.size(); i++) {
TrackRenderer renderer = enabledRenderers.get(i);
for (TrackRenderer renderer : enabledRenderers) {
// 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, elapsedRealtimeUs);
renderer.render(positionUs, elapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
// Determine whether the renderer is ready (or ended). If it's not, throw an error that's
// preventing the renderer from making progress, if such an error exists.
@ -427,7 +430,7 @@ import java.util.concurrent.atomic.AtomicInteger;
handler.removeMessages(MSG_DO_SOME_WORK);
if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS);
} else if (!enabledRenderers.isEmpty()) {
} else if (enabledRenderers.length != 0) {
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS);
}
@ -459,12 +462,16 @@ import java.util.concurrent.atomic.AtomicInteger;
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
return;
}
for (int i = 0; i < enabledRenderers.size(); i++) {
TrackRenderer renderer = enabledRenderers.get(i);
for (TrackRenderer renderer : enabledRenderers) {
ensureStopped(renderer);
}
setState(ExoPlayer.STATE_BUFFERING);
source.seekToUs(positionUs);
for (TrackRenderer renderer : enabledRenderers) {
renderer.checkForReset();
}
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} finally {
pendingSeekCount.decrementAndGet();
@ -493,10 +500,10 @@ import java.util.concurrent.atomic.AtomicInteger;
if (renderers == null) {
return;
}
for (int i = 0; i < renderers.length; i++) {
resetRendererInternal(renderers[i]);
for (TrackRenderer renderer : renderers) {
resetRendererInternal(renderer);
}
enabledRenderers.clear();
enabledRenderers = new TrackRenderer[0];
Arrays.fill(trackSelections, null);
source = null;
}
@ -547,16 +554,19 @@ import java.util.concurrent.atomic.AtomicInteger;
// Disable renderers whose track selections have changed.
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
int enabledRendererCount = 0;
for (int i = 0; i < renderers.length; i++) {
TrackRenderer renderer = renderers[i];
TrackSelection previousTrackSelection = trackSelections[i];
trackSelections[i] = newTrackSelections.get(i);
if (trackSelections[i] != null) {
enabledRendererCount++;
}
rendererWasEnabledFlags[i] = renderer.getState() != TrackRenderer.STATE_DISABLED;
if (!Util.areEqual(previousTrackSelection, trackSelections[i])) {
// The track selection has changed for this renderer.
int rendererState = renderer.getState();
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|| rendererState == TrackRenderer.STATE_STARTED;
if (isEnabled) {
if (rendererWasEnabledFlags[i]) {
// We need to disable the renderer so that we can enable it with its new selection.
if (trackSelections[i] == null && renderer == rendererMediaClockSource) {
// We've been using rendererMediaClockSource to advance the current position, but it's
// being disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take
@ -564,37 +574,37 @@ import java.util.concurrent.atomic.AtomicInteger;
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
}
ensureStopped(renderer);
enabledRenderers.remove(renderer);
renderer.disable();
}
rendererWasEnabledFlags[i] = isEnabled;
}
}
enabledRenderers = new TrackRenderer[enabledRendererCount];
enabledRendererCount = 0;
// Enable renderers with their new track selections.
for (int i = 0; i < renderers.length; i++) {
TrackRenderer renderer = renderers[i];
TrackSelection trackSelection = trackSelections[i];
int rendererState = renderer.getState();
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|| rendererState == TrackRenderer.STATE_STARTED;
if (!isEnabled && trackSelection != null) {
// The renderer needs enabling with its new track selection.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining only if the renderer was previously disabled.
boolean joining = !rendererWasEnabledFlags[i] && playing;
// Enable the source and obtain the stream for the renderer to consume.
TrackStream trackStream = source.enable(trackSelection, positionUs);
// Build an array of formats contained by the new selection.
Format[] formats = new Format[trackSelection.length];
for (int j = 0; j < formats.length; j++) {
formats[j] = groups.get(trackSelections[i].group).getFormat(trackSelection.getTrack(j));
}
// Enable the renderer.
renderer.enable(formats, trackStream, positionUs, joining);
enabledRenderers.add(renderer);
if (playing) {
renderer.start();
if (trackSelection != null) {
enabledRenderers[enabledRendererCount++] = renderer;
if (renderer.getState() == TrackRenderer.STATE_DISABLED) {
// The renderer needs enabling with its new track selection.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining only if the renderer was previously disabled.
boolean joining = !rendererWasEnabledFlags[i] && playing;
// Enable the source and obtain the stream for the renderer to consume.
TrackStream trackStream = source.enable(trackSelection, positionUs);
// Build an array of formats contained by the new selection.
Format[] formats = new Format[trackSelection.length];
for (int j = 0; j < formats.length; j++) {
formats[j] = groups.get(trackSelections[i].group).getFormat(trackSelection.getTrack(j));
}
// Enable the renderer.
renderer.enable(formats, trackStream, positionUs, joining);
if (playing) {
renderer.start();
}
}
}
}

View File

@ -319,8 +319,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected void onReset(long positionUs) throws ExoPlaybackException {
super.onReset(positionUs);
protected void reset(long positionUs) throws ExoPlaybackException {
super.reset(positionUs);
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;

View File

@ -461,7 +461,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
}
@Override
protected void onReset(long positionUs) throws ExoPlaybackException {
protected void reset(long positionUs) throws ExoPlaybackException {
sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false;
outputStreamEnded = false;
@ -481,7 +481,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
protected void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException {
sourceState = sourceIsReady
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
@ -537,7 +537,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
/**
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long, long)}. False otherwise.
* {@link #render(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/

View File

@ -284,8 +284,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void onReset(long positionUs) throws ExoPlaybackException {
super.onReset(positionUs);
protected void reset(long positionUs) throws ExoPlaybackException {
super.reset(positionUs);
renderedFirstFrame = false;
consecutiveDroppedFrameCount = 0;
joiningDeadlineUs = -1;
@ -293,8 +293,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected boolean isReady() {
if (super.isReady() && (renderedFirstFrame || !codecInitialized()
|| getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL)) {
if (super.isReady()
&& (renderedFirstFrame || getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL)) {
// Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineUs = -1;
return true;

View File

@ -31,23 +31,26 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
this.trackStream = Assertions.checkNotNull(trackStream);
onReset(positionUs);
reset(positionUs);
}
@Override
protected final void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
// TODO[REFACTOR]: Consider splitting reading of resets into a separate method?
protected final void checkForReset() throws ExoPlaybackException {
long resetPositionUs = trackStream.readReset();
if (resetPositionUs != TrackStream.NO_RESET) {
onReset(resetPositionUs);
reset(resetPositionUs);
return;
}
doSomeWork(positionUs, elapsedRealtimeUs, trackStream.isReady());
}
@Override
protected void maybeThrowError() throws IOException {
protected final void render(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
render(positionUs, elapsedRealtimeUs, trackStream.isReady());
}
@Override
protected final void maybeThrowError() throws IOException {
trackStream.maybeThrowError();
}
@ -77,15 +80,15 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
// Abstract methods.
/**
* Invoked when a reset is encountered. Also invoked when the renderer is enabled.
* Invoked when a reset is encountered, and also when the renderer is enabled.
*
* @param positionUs The playback position in microseconds.
* @throws ExoPlaybackException If an error occurs handling the reset.
*/
protected abstract void onReset(long positionUs) throws ExoPlaybackException;
protected abstract void reset(long positionUs) throws ExoPlaybackException;
/**
* Called by {@link #doSomeWork(long, long)}.
* Called by {@link #render(long, long)}.
*
* @param positionUs The current media time in microseconds, measured at the start of the
* current iteration of the rendering loop.
@ -93,9 +96,8 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
* measured at the start of the current iteration of the rendering loop.
* @param sourceIsReady The result of the most recent call to {@link TrackStream#isReady()}.
* @throws ExoPlaybackException If an error occurs.
* @throws ExoPlaybackException
*/
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
protected abstract void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException;
}

View File

@ -92,19 +92,16 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
public static final int ADAPTIVE_NOT_SUPPORTED = 0b0000;
/**
* The renderer is idle.
* The renderer is disabled.
*/
protected static final int STATE_IDLE = 0;
protected static final int STATE_DISABLED = 0;
/**
* The renderer is enabled. It should either be ready to be started, or be actively working
* towards this state (e.g. a renderer in this state will typically hold any resources that it
* requires, such as media decoders, and will have buffered or be buffering any media data that
* is required to start playback).
* The renderer is enabled but not started. A renderer in this state will typically hold any
* resources that it requires for rendering (e.g. media decoders).
*/
protected static final int STATE_ENABLED = 1;
/**
* The renderer is started. Calls to {@link #doSomeWork(long, long)} should cause the media to be
* rendered.
* The renderer is started. Calls to {@link #render(long, long)} will cause media to be rendered.
*/
protected static final int STATE_STARTED = 2;
@ -195,7 +192,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*/
/* package */ final void enable(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == STATE_IDLE);
Assertions.checkState(state == STATE_DISABLED);
state = STATE_ENABLED;
onEnabled(formats, trackStream, positionUs, joining);
}
@ -217,8 +214,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
}
/**
* Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the
* track to be rendered.
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be
* rendered.
*
* @throws ExoPlaybackException If an error occurs.
*/
@ -268,7 +265,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*/
/* package */ final void disable() throws ExoPlaybackException {
Assertions.checkState(state == STATE_ENABLED);
state = STATE_IDLE;
state = STATE_DISABLED;
onDisabled();
}
@ -289,7 +286,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* returned by all of its {@link TrackRenderer}s.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @return Whether the renderer is ready for the player to transition to the ended state.
*/
@ -306,26 +303,30 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* renderer is ready for playback to be started. Returning false indicates that it is not.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @return True if the renderer is ready to render media. False otherwise.
*/
protected abstract boolean isReady();
/**
* Invoked to make progress when the renderer is in the {@link #STATE_ENABLED} or
* {@link #STATE_STARTED} states.
* <p>
* If the renderer's state is {@link #STATE_STARTED}, then repeated calls to this method should
* cause the media track to be rendered. If the state is {@link #STATE_ENABLED}, then repeated
* calls should make progress towards getting the renderer into a position where it is ready to
* render the track.
* <p>
* This method should return quickly, and should not block if the renderer is currently unable to
* make any useful progress.
* Attempts to read and process a pending reset from the {@link TrackStream}.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void checkForReset() throws ExoPlaybackException;
/**
* Incrementally renders the {@link TrackStream}.
* <p>
* This method should return quickly, and should not block if the renderer is unable to make
* useful progress.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @param positionUs The current media time in microseconds, measured at the start of the
* current iteration of the rendering loop.
@ -333,7 +334,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* measured at the start of the current iteration of the rendering loop.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs)
protected abstract void render(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException;
/**
@ -341,7 +342,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* this point in time.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}
* {@link #STATE_ENABLED}.
*
* @throws IOException An error that's preventing the renderer from making progress or buffering
* more data.

View File

@ -92,13 +92,13 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
}
@Override
protected void onReset(long positionUs) {
protected void reset(long positionUs) {
pendingMetadata = null;
inputStreamEnded = false;
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
protected void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException {
if (!inputStreamEnded && pendingMetadata == null) {
sampleHolder.clearData();

View File

@ -173,7 +173,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
}
@Override
protected void onReset(long positionUs) {
protected void reset(long positionUs) {
inputStreamEnded = false;
subtitle = null;
nextSubtitle = null;
@ -184,7 +184,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
protected void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException {
if (nextSubtitle == null) {
try {

View File

@ -93,7 +93,7 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
}
@Override
protected void onReset(long positionUs) {
protected void reset(long positionUs) {
inputStreamEnded = false;
repeatableControl = null;
pendingCaptionLists.clear();
@ -104,7 +104,7 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
protected void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException {
if (isSamplePending()) {
maybeParsePendingSample(positionUs);