Commit ce2e2797 by tonihei Committed by Oliver Woodman

Improve DefaultMediaClock behaviour.

DefaultMediaClock has currently two non-ideal behaviours:
1. One part of checking if it should use the renderer clock is checking whether
   the associated renderer finished reading its stream. This only makes sense
   if the renderer isn't already reading ahead into the next period. This can
   be solved by forwarding if we are reading ahead to the sync command.
2. When switching from stand-alone to renderer clock we assume they are
   exactly at the same position. This is true in theory, but in practise there
   may be small differences due to the different natures of these clocks. To
   prevent jumping backwards in time, we can temporarily stop the stand-alone
   clock and only switch once the renderer clock reached the same position.

PiperOrigin-RevId: 260690468
parent 39d5867c
...@@ -40,11 +40,13 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -40,11 +40,13 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters); void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters);
} }
private final StandaloneMediaClock standaloneMediaClock; private final StandaloneMediaClock standaloneClock;
private final PlaybackParameterListener listener; private final PlaybackParameterListener listener;
@Nullable private Renderer rendererClockSource; @Nullable private Renderer rendererClockSource;
@Nullable private MediaClock rendererClock; @Nullable private MediaClock rendererClock;
private boolean isUsingStandaloneClock;
private boolean standaloneClockIsStarted;
/** /**
* Creates a new instance with listener for playback parameter changes and a {@link Clock} to use * Creates a new instance with listener for playback parameter changes and a {@link Clock} to use
...@@ -56,21 +58,24 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -56,21 +58,24 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
*/ */
public DefaultMediaClock(PlaybackParameterListener listener, Clock clock) { public DefaultMediaClock(PlaybackParameterListener listener, Clock clock) {
this.listener = listener; this.listener = listener;
this.standaloneMediaClock = new StandaloneMediaClock(clock); this.standaloneClock = new StandaloneMediaClock(clock);
isUsingStandaloneClock = true;
} }
/** /**
* Starts the standalone fallback clock. * Starts the standalone fallback clock.
*/ */
public void start() { public void start() {
standaloneMediaClock.start(); standaloneClockIsStarted = true;
standaloneClock.start();
} }
/** /**
* Stops the standalone fallback clock. * Stops the standalone fallback clock.
*/ */
public void stop() { public void stop() {
standaloneMediaClock.stop(); standaloneClockIsStarted = false;
standaloneClock.stop();
} }
/** /**
...@@ -79,7 +84,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -79,7 +84,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
* @param positionUs The position to set in microseconds. * @param positionUs The position to set in microseconds.
*/ */
public void resetPosition(long positionUs) { public void resetPosition(long positionUs) {
standaloneMediaClock.resetPosition(positionUs); standaloneClock.resetPosition(positionUs);
} }
/** /**
...@@ -99,8 +104,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -99,8 +104,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
} }
this.rendererClock = rendererMediaClock; this.rendererClock = rendererMediaClock;
this.rendererClockSource = renderer; this.rendererClockSource = renderer;
rendererClock.setPlaybackParameters(standaloneMediaClock.getPlaybackParameters()); rendererClock.setPlaybackParameters(standaloneClock.getPlaybackParameters());
ensureSynced();
} }
} }
...@@ -114,30 +118,25 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -114,30 +118,25 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
if (renderer == rendererClockSource) { if (renderer == rendererClockSource) {
this.rendererClock = null; this.rendererClock = null;
this.rendererClockSource = null; this.rendererClockSource = null;
isUsingStandaloneClock = true;
} }
} }
/** /**
* Syncs internal clock if needed and returns current clock position in microseconds. * Syncs internal clock if needed and returns current clock position in microseconds.
*
* @param isReadingAhead Whether the renderers are reading ahead.
*/ */
public long syncAndGetPositionUs() { public long syncAndGetPositionUs(boolean isReadingAhead) {
if (isUsingRendererClock()) { syncClocks(isReadingAhead);
ensureSynced(); return getPositionUs();
return rendererClock.getPositionUs();
} else {
return standaloneMediaClock.getPositionUs();
}
} }
// MediaClock implementation. // MediaClock implementation.
@Override @Override
public long getPositionUs() { public long getPositionUs() {
if (isUsingRendererClock()) { return isUsingStandaloneClock ? standaloneClock.getPositionUs() : rendererClock.getPositionUs();
return rendererClock.getPositionUs();
} else {
return standaloneMediaClock.getPositionUs();
}
} }
@Override @Override
...@@ -146,32 +145,53 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock; ...@@ -146,32 +145,53 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
rendererClock.setPlaybackParameters(playbackParameters); rendererClock.setPlaybackParameters(playbackParameters);
playbackParameters = rendererClock.getPlaybackParameters(); playbackParameters = rendererClock.getPlaybackParameters();
} }
standaloneMediaClock.setPlaybackParameters(playbackParameters); standaloneClock.setPlaybackParameters(playbackParameters);
} }
@Override @Override
public PlaybackParameters getPlaybackParameters() { public PlaybackParameters getPlaybackParameters() {
return rendererClock != null ? rendererClock.getPlaybackParameters() return rendererClock != null
: standaloneMediaClock.getPlaybackParameters(); ? rendererClock.getPlaybackParameters()
: standaloneClock.getPlaybackParameters();
} }
private void ensureSynced() { private void syncClocks(boolean isReadingAhead) {
if (shouldUseStandaloneClock(isReadingAhead)) {
isUsingStandaloneClock = true;
if (standaloneClockIsStarted) {
standaloneClock.start();
}
return;
}
long rendererClockPositionUs = rendererClock.getPositionUs(); long rendererClockPositionUs = rendererClock.getPositionUs();
standaloneMediaClock.resetPosition(rendererClockPositionUs); if (isUsingStandaloneClock) {
// Ensure enabling the renderer clock doesn't jump backwards in time.
if (rendererClockPositionUs < standaloneClock.getPositionUs()) {
standaloneClock.stop();
return;
}
isUsingStandaloneClock = false;
if (standaloneClockIsStarted) {
standaloneClock.start();
}
}
// Continuously sync stand-alone clock to renderer clock so that it can take over if needed.
standaloneClock.resetPosition(rendererClockPositionUs);
PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters(); PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters();
if (!playbackParameters.equals(standaloneMediaClock.getPlaybackParameters())) { if (!playbackParameters.equals(standaloneClock.getPlaybackParameters())) {
standaloneMediaClock.setPlaybackParameters(playbackParameters); standaloneClock.setPlaybackParameters(playbackParameters);
listener.onPlaybackParametersChanged(playbackParameters); listener.onPlaybackParametersChanged(playbackParameters);
} }
} }
private boolean isUsingRendererClock() { private boolean shouldUseStandaloneClock(boolean isReadingAhead) {
// Use the renderer clock if the providing renderer has not ended or needs the next sample // Use the standalone clock if the clock providing renderer is not set or has ended. Also use
// stream to reenter the ready state. The latter case uses the standalone clock to avoid getting // the standalone clock if the renderer is not ready and we have finished reading the stream or
// stuck if tracks in the current period have uneven durations. // are reading ahead to avoid getting stuck if tracks in the current period have uneven
// See: https://github.com/google/ExoPlayer/issues/1874. // durations. See: https://github.com/google/ExoPlayer/issues/1874.
return rendererClockSource != null && !rendererClockSource.isEnded() return rendererClockSource == null
&& (rendererClockSource.isReady() || !rendererClockSource.hasReadStreamToEnd()); || rendererClockSource.isEnded()
|| (!rendererClockSource.isReady()
&& (isReadingAhead || rendererClockSource.hasReadStreamToEnd()));
} }
} }
...@@ -535,7 +535,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -535,7 +535,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
} }
} else { } else {
rendererPositionUs = mediaClock.syncAndGetPositionUs(); rendererPositionUs =
mediaClock.syncAndGetPositionUs(
/* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod());
periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs); maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs);
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment