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 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
* 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.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
/**
* A factory for {@link ExoPlayer} instances.
@ -160,7 +161,7 @@ public final class ExoPlayerFactory {
*/
public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector,
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.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
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 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.
*/
@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)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0);
@ -116,7 +119,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
repeatMode,
shuffleModeEnabled,
eventHandler,
this);
this,
clock);
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.TrackSelectorResult;
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.Util;
import java.io.IOException;
@ -102,7 +104,7 @@ import java.util.Collections;
private final TrackSelector trackSelector;
private final TrackSelectorResult emptyTrackSelectorResult;
private final LoadControl loadControl;
private final Handler handler;
private final HandlerWrapper handler;
private final HandlerThread internalPlaybackThread;
private final Handler eventHandler;
private final ExoPlayer player;
@ -126,7 +128,6 @@ import java.util.Collections;
private boolean rebuffering;
private @Player.RepeatMode int repeatMode;
private boolean shuffleModeEnabled;
private long elapsedRealtimeUs;
private int pendingPrepareCount;
private SeekPosition pendingInitialSeekPosition;
@ -146,7 +147,8 @@ import java.util.Collections;
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled,
Handler eventHandler,
ExoPlayer player) {
ExoPlayer player,
Clock clock) {
this.renderers = renderers;
this.trackSelector = trackSelector;
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
@ -170,7 +172,7 @@ import java.util.Collections;
renderers[i].setIndex(i);
rendererCapabilities[i] = renderers[i].getCapabilities();
}
mediaClock = new DefaultMediaClock(this);
mediaClock = new DefaultMediaClock(this, clock);
customMessageInfos = new ArrayList<>();
enabledRenderers = new Renderer[0];
window = new Timeline.Window();
@ -183,7 +185,7 @@ import java.util.Collections;
internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler",
Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
handler = new Handler(internalPlaybackThread.getLooper(), this);
handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
}
public void prepare(MediaSource mediaSource, boolean resetPosition) {
@ -527,7 +529,6 @@ import java.util.Collections;
maybeTriggerCustomMessages(playbackInfo.positionUs, periodPositionUs);
playbackInfo.positionUs = periodPositionUs;
}
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
// Update the buffered position.
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
@ -549,16 +550,19 @@ import java.util.Collections;
TraceUtil.beginSection("doSomeWork");
updatePlaybackPositions();
long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs,
retainBackBufferFromKeyframe);
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
for (Renderer renderer : enabledRenderers) {
// 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
// invocation of this method.
renderer.render(rendererPositionUs, elapsedRealtimeUs);
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
// 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
@ -625,13 +629,7 @@ import java.util.Collections;
private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
handler.removeMessages(MSG_DO_SOME_WORK);
long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs;
long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime();
if (nextOperationDelayMs <= 0) {
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} else {
handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, nextOperationDelayMs);
}
handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, intervalMs, thisOperationStartTimeMs);
}
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.trackselection.TrackSelectionArray;
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.video.VideoRendererEventListener;
import java.util.List;
@ -109,8 +110,28 @@ public class SimpleExoPlayer implements ExoPlayer {
private AudioAttributes audioAttributes;
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();
videoListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>();
@ -129,7 +150,7 @@ public class SimpleExoPlayer implements ExoPlayer {
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
// 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 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 this instance.
* @return A new {@link ExoPlayer} instance.
*/
protected ExoPlayer createExoPlayerImpl(
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl);
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock);
}
private void removeSurfaceCallbacks() {

View File

@ -59,8 +59,18 @@ public interface HandlerWrapper {
/** @see Handler#sendEmptyMessage(int). */
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). */
void removeMessages(int what);

View File

@ -31,13 +31,6 @@ public final class StandaloneMediaClock implements MediaClock {
private long baseElapsedMs;
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.
*

View File

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

View File

@ -177,7 +177,8 @@ public final class FakeClock implements Clock {
}
@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) {
return handler.sendEmptyMessage(what);
} 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.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArraySet;
@ -58,13 +59,13 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
public FakeSimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
LoadControl loadControl, FakeClock clock) {
super (renderersFactory, trackSelector, loadControl);
super(renderersFactory, trackSelector, loadControl, clock);
player.setFakeClock(clock);
}
@Override
protected ExoPlayer createExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl) {
protected ExoPlayer createExoPlayerImpl(
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
this.player = new FakeExoPlayer(renderers, trackSelector, loadControl);
return player;
}