Allow setting a Clock for the main playback thread.

This allows to inject a FakeClock for tests. Other playback components
(e.g. some media sources) still use SystemClock but they can be amended
in the future if needed.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=179921889
This commit is contained in:
tonihei 2017-12-22 06:36:46 -08:00 committed by Oliver Woodman
parent c6529344db
commit 61b9e846a8
10 changed files with 74 additions and 47 deletions

View File

@ -46,16 +46,6 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
private @Nullable Renderer rendererClockSource; private @Nullable Renderer rendererClockSource;
private @Nullable MediaClock rendererClock; private @Nullable MediaClock rendererClock;
/**
* Creates a new instance with listener for playback parameter changes.
*
* @param listener A {@link PlaybackParameterListener} to listen for playback parameter
* changes.
*/
public DefaultMediaClock(PlaybackParameterListener listener) {
this(listener, Clock.DEFAULT);
}
/** /**
* 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
* for the standalone clock implementation. * for the standalone clock implementation.

View File

@ -20,6 +20,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
/** /**
* A factory for {@link ExoPlayer} instances. * A factory for {@link ExoPlayer} instances.
@ -160,7 +161,7 @@ public final class ExoPlayerFactory {
*/ */
public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl) { LoadControl loadControl) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl); return new ExoPlayerImpl(renderers, trackSelector, loadControl, Clock.DEFAULT);
} }
} }

View File

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -75,9 +76,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
* @param renderers The {@link Renderer}s that will be used by the instance. * @param renderers The {@link Renderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} that will be used by the instance.
*/ */
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { public ExoPlayerImpl(
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " [" Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]"); + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0); Assertions.checkState(renderers.length > 0);
@ -116,7 +119,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
repeatMode, repeatMode,
shuffleModeEnabled, shuffleModeEnabled,
eventHandler, eventHandler,
this); this,
clock);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
} }

View File

@ -39,6 +39,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
@ -102,7 +104,7 @@ import java.util.Collections;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final TrackSelectorResult emptyTrackSelectorResult; private final TrackSelectorResult emptyTrackSelectorResult;
private final LoadControl loadControl; private final LoadControl loadControl;
private final Handler handler; private final HandlerWrapper handler;
private final HandlerThread internalPlaybackThread; private final HandlerThread internalPlaybackThread;
private final Handler eventHandler; private final Handler eventHandler;
private final ExoPlayer player; private final ExoPlayer player;
@ -126,7 +128,6 @@ import java.util.Collections;
private boolean rebuffering; private boolean rebuffering;
private @Player.RepeatMode int repeatMode; private @Player.RepeatMode int repeatMode;
private boolean shuffleModeEnabled; private boolean shuffleModeEnabled;
private long elapsedRealtimeUs;
private int pendingPrepareCount; private int pendingPrepareCount;
private SeekPosition pendingInitialSeekPosition; private SeekPosition pendingInitialSeekPosition;
@ -146,7 +147,8 @@ import java.util.Collections;
@Player.RepeatMode int repeatMode, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled, boolean shuffleModeEnabled,
Handler eventHandler, Handler eventHandler,
ExoPlayer player) { ExoPlayer player,
Clock clock) {
this.renderers = renderers; this.renderers = renderers;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.emptyTrackSelectorResult = emptyTrackSelectorResult; this.emptyTrackSelectorResult = emptyTrackSelectorResult;
@ -170,7 +172,7 @@ import java.util.Collections;
renderers[i].setIndex(i); renderers[i].setIndex(i);
rendererCapabilities[i] = renderers[i].getCapabilities(); rendererCapabilities[i] = renderers[i].getCapabilities();
} }
mediaClock = new DefaultMediaClock(this); mediaClock = new DefaultMediaClock(this, clock);
customMessageInfos = new ArrayList<>(); customMessageInfos = new ArrayList<>();
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
window = new Timeline.Window(); window = new Timeline.Window();
@ -183,7 +185,7 @@ import java.util.Collections;
internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler", internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler",
Process.THREAD_PRIORITY_AUDIO); Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start(); internalPlaybackThread.start();
handler = new Handler(internalPlaybackThread.getLooper(), this); handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
} }
public void prepare(MediaSource mediaSource, boolean resetPosition) { public void prepare(MediaSource mediaSource, boolean resetPosition) {
@ -527,7 +529,6 @@ import java.util.Collections;
maybeTriggerCustomMessages(playbackInfo.positionUs, periodPositionUs); maybeTriggerCustomMessages(playbackInfo.positionUs, periodPositionUs);
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
} }
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
// Update the buffered position. // Update the buffered position.
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
@ -549,16 +550,19 @@ import java.util.Collections;
TraceUtil.beginSection("doSomeWork"); TraceUtil.beginSection("doSomeWork");
updatePlaybackPositions(); updatePlaybackPositions();
long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs, playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs,
retainBackBufferFromKeyframe); retainBackBufferFromKeyframe);
boolean allRenderersEnded = true; boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true; boolean allRenderersReadyOrEnded = true;
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
// TODO: Each renderer should return the maximum delay before which it wishes to be called // TODO: Each renderer should return the maximum delay before which it wishes to be called
// again. The minimum of these values should then be used as the delay before the next // again. The minimum of these values should then be used as the delay before the next
// invocation of this method. // invocation of this method.
renderer.render(rendererPositionUs, elapsedRealtimeUs); renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded(); allRenderersEnded = allRenderersEnded && renderer.isEnded();
// Determine whether the renderer is ready (or ended). We override to assume the renderer is // Determine whether the renderer is ready (or ended). We override to assume the renderer is
// ready if it needs the next sample stream. This is necessary to avoid getting stuck if // ready if it needs the next sample stream. This is necessary to avoid getting stuck if
@ -625,13 +629,7 @@ import java.util.Collections;
private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) { private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
handler.removeMessages(MSG_DO_SOME_WORK); handler.removeMessages(MSG_DO_SOME_WORK);
long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs; handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, intervalMs, thisOperationStartTimeMs);
long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime();
if (nextOperationDelayMs <= 0) {
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} else {
handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, nextOperationDelayMs);
}
} }
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {

View File

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.List; import java.util.List;
@ -109,8 +110,28 @@ public class SimpleExoPlayer implements ExoPlayer {
private AudioAttributes audioAttributes; private AudioAttributes audioAttributes;
private float audioVolume; private float audioVolume;
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, /**
LoadControl loadControl) { * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory, TrackSelector trackSelector, LoadControl loadControl) {
this(renderersFactory, trackSelector, loadControl, Clock.DEFAULT);
}
/**
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} that will be used by the instance. Should always be {@link
* Clock#DEFAULT}, unless the player is being used from a test.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
Clock clock) {
componentListener = new ComponentListener(); componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>(); videoListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>(); textOutputs = new CopyOnWriteArraySet<>();
@ -129,7 +150,7 @@ public class SimpleExoPlayer implements ExoPlayer {
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT; videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
// Build the player and associated objects. // Build the player and associated objects.
player = createExoPlayerImpl(renderers, trackSelector, loadControl); player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock);
} }
/** /**
@ -864,11 +885,12 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param renderers The {@link Renderer}s that will be used by the instance. * @param renderers The {@link Renderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} that will be used by this instance.
* @return A new {@link ExoPlayer} instance. * @return A new {@link ExoPlayer} instance.
*/ */
protected ExoPlayer createExoPlayerImpl( protected ExoPlayer createExoPlayerImpl(
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl); return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock);
} }
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {

View File

@ -59,8 +59,18 @@ public interface HandlerWrapper {
/** @see Handler#sendEmptyMessage(int). */ /** @see Handler#sendEmptyMessage(int). */
boolean sendEmptyMessage(int what); boolean sendEmptyMessage(int what);
/** @see Handler#sendEmptyMessageDelayed(int, long). */ /**
boolean sendEmptyMessageDelayed(int what, long delayMs); * Variant of {@code Handler#sendEmptyMessageDelayed(int, long)} which also takes a reference time
* measured by {@code android.os.SystemClock#elapsedRealtime()} to which the delay is added.
*
* @param what The message identifier.
* @param delayMs The delay in milliseconds to send the message. This delay is added to the {@code
* referenceTimeMs}.
* @param referenceTimeMs The time which the delay is added to. Always measured with {@code
* android.os.SystemClock#elapsedRealtime()}.
* @return Whether the message was successfully enqueued on the Handler thread.
*/
boolean sendEmptyMessageDelayed(int what, long delayMs, long referenceTimeMs);
/** @see Handler#removeMessages(int). */ /** @see Handler#removeMessages(int). */
void removeMessages(int what); void removeMessages(int what);

View File

@ -31,13 +31,6 @@ public final class StandaloneMediaClock implements MediaClock {
private long baseElapsedMs; private long baseElapsedMs;
private PlaybackParameters playbackParameters; private PlaybackParameters playbackParameters;
/**
* Creates a new standalone media clock.
*/
public StandaloneMediaClock() {
this(Clock.DEFAULT);
}
/** /**
* Creates a new standalone media clock using the given {@link Clock} implementation. * Creates a new standalone media clock using the given {@link Clock} implementation.
* *

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import android.os.Handler.Callback; import android.os.Handler.Callback;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.SystemClock;
/** The standard implementation of {@link HandlerWrapper}. */ /** The standard implementation of {@link HandlerWrapper}. */
/* package */ final class SystemHandler implements HandlerWrapper { /* package */ final class SystemHandler implements HandlerWrapper {
@ -67,8 +68,14 @@ import android.os.Message;
} }
@Override @Override
public boolean sendEmptyMessageDelayed(int what, long delayMs) { public boolean sendEmptyMessageDelayed(int what, long delayMs, long referenceTimeMs) {
return handler.sendEmptyMessageDelayed(what, delayMs); long targetMessageTime = referenceTimeMs + delayMs;
long remainingDelayMs = targetMessageTime - SystemClock.elapsedRealtime();
if (remainingDelayMs <= 0) {
return handler.sendEmptyMessage(what);
} else {
return handler.sendEmptyMessageDelayed(what, remainingDelayMs);
}
} }
@Override @Override

View File

@ -177,7 +177,8 @@ public final class FakeClock implements Clock {
} }
@Override @Override
public boolean sendEmptyMessageDelayed(int what, long delayMs) { public boolean sendEmptyMessageDelayed(int what, long delayMs, long referenceTimeMs) {
// Ignore referenceTimeMs measured by SystemClock and just send with requested delay.
if (delayMs <= 0) { if (delayMs <= 0) {
return handler.sendEmptyMessage(what); return handler.sendEmptyMessage(what);
} else { } else {

View File

@ -44,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -58,13 +59,13 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
public FakeSimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, public FakeSimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
LoadControl loadControl, FakeClock clock) { LoadControl loadControl, FakeClock clock) {
super (renderersFactory, trackSelector, loadControl); super(renderersFactory, trackSelector, loadControl, clock);
player.setFakeClock(clock); player.setFakeClock(clock);
} }
@Override @Override
protected ExoPlayer createExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, protected ExoPlayer createExoPlayerImpl(
LoadControl loadControl) { Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
this.player = new FakeExoPlayer(renderers, trackSelector, loadControl); this.player = new FakeExoPlayer(renderers, trackSelector, loadControl);
return player; return player;
} }