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:
parent
c6529344db
commit
61b9e846a8
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user