diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java index 5f342bc722..ed57cec70c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java @@ -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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index b647e541bc..821671e34e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -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); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index afb6428fa5..4e97a47924 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -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()); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index f3d0e1794b..c5fdf38bfa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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 { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index e2d0ed1422..d4346a65e1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -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() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java index 25f9c9bb38..b9f3a750d7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java @@ -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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java index 3c0ec2a854..b1f53416fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java @@ -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. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandler.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandler.java index b9fe771053..e99c626057 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandler.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandler.java @@ -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 diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java index c78a8c03e0..83ecbacdde 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java @@ -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 { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java index a8ba3b3420..591b94a9cd 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java @@ -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; }