Commit 75acfc79 by tonihei Committed by Oliver Woodman

Move media clock handling to its own class.

This class implements MediaClock itself and handles the switching between
renderer and standalone media clock.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176340615
parent 3f6b4d18
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.StandaloneMediaClock;
/**
* Default {@link MediaClock} which uses a renderer media clock and falls back to a
* {@link StandaloneMediaClock} if necessary.
*/
/* package */ final class DefaultMediaClock implements MediaClock {
/**
* Listener interface to be notified of changes to the active playback parameters.
*/
public interface PlaybackParameterListener {
/**
* Called when the active playback parameters changed.
*
* @param newPlaybackParameters The newly active {@link PlaybackParameters}.
*/
void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters);
}
private final StandaloneMediaClock standaloneMediaClock;
private final PlaybackParameterListener listener;
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.
*
* @param listener A {@link PlaybackParameterListener} to listen for playback parameter
* changes.
* @param clock A {@link Clock}.
*/
public DefaultMediaClock(PlaybackParameterListener listener, Clock clock) {
this.listener = listener;
this.standaloneMediaClock = new StandaloneMediaClock(clock);
}
/**
* Starts the standalone fallback clock.
*/
public void start() {
standaloneMediaClock.start();
}
/**
* Stops the standalone fallback clock.
*/
public void stop() {
standaloneMediaClock.stop();
}
/**
* Resets the position of the standalone fallback clock.
*
* @param positionUs The position to set in microseconds.
*/
public void resetPosition(long positionUs) {
standaloneMediaClock.resetPosition(positionUs);
}
/**
* Notifies the media clock that a renderer has been enabled. Starts using the media clock of the
* provided renderer if available.
*
* @param renderer The renderer which has been enabled.
* @throws ExoPlaybackException If the renderer provides a media clock and another renderer media
* clock is already provided.
*/
public void onRendererEnabled(Renderer renderer) throws ExoPlaybackException {
MediaClock rendererMediaClock = renderer.getMediaClock();
if (rendererMediaClock != null && rendererMediaClock != rendererClock) {
if (rendererClock != null) {
throw ExoPlaybackException.createForUnexpected(
new IllegalStateException("Multiple renderer media clocks enabled."));
}
this.rendererClock = rendererMediaClock;
this.rendererClockSource = renderer;
rendererClock.setPlaybackParameters(standaloneMediaClock.getPlaybackParameters());
ensureSynced();
}
}
/**
* Notifies the media clock that a renderer has been disabled. Stops using the media clock of this
* renderer if used.
*
* @param renderer The renderer which has been disabled.
*/
public void onRendererDisabled(Renderer renderer) {
if (renderer == rendererClockSource) {
this.rendererClock = null;
this.rendererClockSource = null;
}
}
/**
* Syncs internal clock if needed and returns current clock position in microseconds.
*/
public long syncAndGetPositionUs() {
if (isUsingRendererClock()) {
ensureSynced();
return rendererClock.getPositionUs();
} else {
return standaloneMediaClock.getPositionUs();
}
}
// MediaClock implementation.
@Override
public long getPositionUs() {
if (isUsingRendererClock()) {
return rendererClock.getPositionUs();
} else {
return standaloneMediaClock.getPositionUs();
}
}
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
if (rendererClock != null) {
playbackParameters = rendererClock.setPlaybackParameters(playbackParameters);
}
standaloneMediaClock.setPlaybackParameters(playbackParameters);
listener.onPlaybackParametersChanged(playbackParameters);
return playbackParameters;
}
@Override
public PlaybackParameters getPlaybackParameters() {
return rendererClock != null ? rendererClock.getPlaybackParameters()
: standaloneMediaClock.getPlaybackParameters();
}
private void ensureSynced() {
long rendererClockPositionUs = rendererClock.getPositionUs();
standaloneMediaClock.resetPosition(rendererClockPositionUs);
PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters();
if (!playbackParameters.equals(standaloneMediaClock.getPlaybackParameters())) {
standaloneMediaClock.setPlaybackParameters(playbackParameters);
listener.onPlaybackParametersChanged(playbackParameters);
}
}
private boolean isUsingRendererClock() {
// Use the renderer clock if the providing renderer has not ended or needs the next sample
// stream to reenter the ready state. The latter case uses the standalone clock to avoid getting
// stuck if tracks in the current period have uneven durations.
// See: https://github.com/google/ExoPlayer/issues/1874.
return rendererClockSource != null && !rendererClockSource.isEnded()
&& (rendererClockSource.isReady() || !rendererClockSource.hasReadStreamToEnd());
}
}
...@@ -24,6 +24,7 @@ import android.os.SystemClock; ...@@ -24,6 +24,7 @@ import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo; import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo;
import com.google.android.exoplayer2.source.ClippingMediaPeriod; import com.google.android.exoplayer2.source.ClippingMediaPeriod;
...@@ -37,8 +38,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; ...@@ -37,8 +38,6 @@ 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.MediaClock;
import com.google.android.exoplayer2.util.StandaloneMediaClock;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import java.io.IOException; import java.io.IOException;
...@@ -46,7 +45,8 @@ import java.io.IOException; ...@@ -46,7 +45,8 @@ import java.io.IOException;
* Implements the internal behavior of {@link ExoPlayerImpl}. * Implements the internal behavior of {@link ExoPlayerImpl}.
*/ */
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, /* package */ final class ExoPlayerImplInternal implements Handler.Callback,
MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener { MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener,
PlaybackParameterListener {
private static final String TAG = "ExoPlayerImplInternal"; private static final String TAG = "ExoPlayerImplInternal";
...@@ -99,7 +99,6 @@ import java.io.IOException; ...@@ -99,7 +99,6 @@ import java.io.IOException;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final LoadControl loadControl; private final LoadControl loadControl;
private final StandaloneMediaClock standaloneMediaClock;
private final Handler handler; private final Handler handler;
private final HandlerThread internalPlaybackThread; private final HandlerThread internalPlaybackThread;
private final Handler eventHandler; private final Handler eventHandler;
...@@ -109,11 +108,9 @@ import java.io.IOException; ...@@ -109,11 +108,9 @@ import java.io.IOException;
private final MediaPeriodInfoSequence mediaPeriodInfoSequence; private final MediaPeriodInfoSequence mediaPeriodInfoSequence;
private final long backBufferDurationUs; private final long backBufferDurationUs;
private final boolean retainBackBufferFromKeyframe; private final boolean retainBackBufferFromKeyframe;
private final DefaultMediaClock mediaClock;
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
private PlaybackParameters playbackParameters;
private Renderer rendererMediaClockSource;
private MediaClock rendererMediaClock;
private MediaSource mediaSource; private MediaSource mediaSource;
private Renderer[] enabledRenderers; private Renderer[] enabledRenderers;
private boolean released; private boolean released;
...@@ -158,13 +155,12 @@ import java.io.IOException; ...@@ -158,13 +155,12 @@ import java.io.IOException;
renderers[i].setIndex(i); renderers[i].setIndex(i);
rendererCapabilities[i] = renderers[i].getCapabilities(); rendererCapabilities[i] = renderers[i].getCapabilities();
} }
standaloneMediaClock = new StandaloneMediaClock(); mediaClock = new DefaultMediaClock(this);
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
window = new Timeline.Window(); window = new Timeline.Window();
period = new Timeline.Period(); period = new Timeline.Period();
mediaPeriodInfoSequence = new MediaPeriodInfoSequence(); mediaPeriodInfoSequence = new MediaPeriodInfoSequence();
trackSelector.init(this); trackSelector.init(this);
playbackParameters = PlaybackParameters.DEFAULT;
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect. // not normally change to this priority" is incorrect.
...@@ -284,6 +280,16 @@ import java.io.IOException; ...@@ -284,6 +280,16 @@ import java.io.IOException;
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED); handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
} }
// DefaultMediaClock.PlaybackParameterListener implementation.
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// TODO(b/37237846): Make LoadControl, period transition position projection, adaptive track
// selection and potentially any time-related code in renderers take into account the playback
// speed.
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget();
}
// Handler.Callback implementation. // Handler.Callback implementation.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -486,14 +492,14 @@ import java.io.IOException; ...@@ -486,14 +492,14 @@ import java.io.IOException;
private void startRenderers() throws ExoPlaybackException { private void startRenderers() throws ExoPlaybackException {
rebuffering = false; rebuffering = false;
standaloneMediaClock.start(); mediaClock.start();
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.start(); renderer.start();
} }
} }
private void stopRenderers() throws ExoPlaybackException { private void stopRenderers() throws ExoPlaybackException {
standaloneMediaClock.stop(); mediaClock.stop();
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
ensureStopped(renderer); ensureStopped(renderer);
} }
...@@ -509,18 +515,7 @@ import java.io.IOException; ...@@ -509,18 +515,7 @@ import java.io.IOException;
if (periodPositionUs != C.TIME_UNSET) { if (periodPositionUs != C.TIME_UNSET) {
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
} else { } else {
// Use the standalone clock if there's no renderer clock, or if the providing renderer has rendererPositionUs = mediaClock.syncAndGetPositionUs();
// ended or needs the next sample stream to reenter the ready state. The latter case uses the
// standalone clock to avoid getting stuck if tracks in the current period have uneven
// durations. See: https://github.com/google/ExoPlayer/issues/1874.
if (rendererMediaClockSource == null || rendererMediaClockSource.isEnded()
|| (!rendererMediaClockSource.isReady()
&& rendererMediaClockSource.hasReadStreamToEnd())) {
rendererPositionUs = standaloneMediaClock.getPositionUs();
} else {
rendererPositionUs = rendererMediaClock.getPositionUs();
standaloneMediaClock.setPositionUs(rendererPositionUs);
}
periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
} }
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
...@@ -573,19 +568,6 @@ import java.io.IOException; ...@@ -573,19 +568,6 @@ import java.io.IOException;
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
} }
// The standalone media clock never changes playback parameters, so just check the renderer.
if (rendererMediaClock != null) {
PlaybackParameters playbackParameters = rendererMediaClock.getPlaybackParameters();
if (!playbackParameters.equals(this.playbackParameters)) {
// TODO: Make LoadControl, period transition position projection, adaptive track selection
// and potentially any time-related code in renderers take into account the playback speed.
this.playbackParameters = playbackParameters;
standaloneMediaClock.setPlaybackParameters(playbackParameters);
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters)
.sendToTarget();
}
}
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
if (allRenderersEnded if (allRenderersEnded
&& (playingPeriodDurationUs == C.TIME_UNSET && (playingPeriodDurationUs == C.TIME_UNSET
...@@ -771,19 +753,14 @@ import java.io.IOException; ...@@ -771,19 +753,14 @@ import java.io.IOException;
rendererPositionUs = playingPeriodHolder == null rendererPositionUs = playingPeriodHolder == null
? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US ? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US
: playingPeriodHolder.toRendererTime(periodPositionUs); : playingPeriodHolder.toRendererTime(periodPositionUs);
standaloneMediaClock.setPositionUs(rendererPositionUs); mediaClock.resetPosition(rendererPositionUs);
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.resetPosition(rendererPositionUs); renderer.resetPosition(rendererPositionUs);
} }
} }
private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) { private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
if (rendererMediaClock != null) { mediaClock.setPlaybackParameters(playbackParameters);
playbackParameters = rendererMediaClock.setPlaybackParameters(playbackParameters);
}
standaloneMediaClock.setPlaybackParameters(playbackParameters);
this.playbackParameters = playbackParameters;
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget();
} }
private void stopInternal() { private void stopInternal() {
...@@ -806,7 +783,7 @@ import java.io.IOException; ...@@ -806,7 +783,7 @@ import java.io.IOException;
private void resetInternal(boolean releaseMediaSource) { private void resetInternal(boolean releaseMediaSource) {
handler.removeMessages(MSG_DO_SOME_WORK); handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false; rebuffering = false;
standaloneMediaClock.stop(); mediaClock.stop();
rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US; rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
try { try {
...@@ -857,10 +834,7 @@ import java.io.IOException; ...@@ -857,10 +834,7 @@ import java.io.IOException;
} }
private void disableRenderer(Renderer renderer) throws ExoPlaybackException { private void disableRenderer(Renderer renderer) throws ExoPlaybackException {
if (renderer == rendererMediaClockSource) { mediaClock.onRendererDisabled(renderer);
rendererMediaClock = null;
rendererMediaClockSource = null;
}
ensureStopped(renderer); ensureStopped(renderer);
renderer.disable(); renderer.disable();
} }
...@@ -1498,16 +1472,7 @@ import java.io.IOException; ...@@ -1498,16 +1472,7 @@ import java.io.IOException;
renderer.enable(rendererConfiguration, formats, renderer.enable(rendererConfiguration, formats,
playingPeriodHolder.sampleStreams[rendererIndex], rendererPositionUs, playingPeriodHolder.sampleStreams[rendererIndex], rendererPositionUs,
joining, playingPeriodHolder.getRendererOffset()); joining, playingPeriodHolder.getRendererOffset());
MediaClock mediaClock = renderer.getMediaClock(); mediaClock.onRendererEnabled(renderer);
if (mediaClock != null) {
if (rendererMediaClock != null) {
throw ExoPlaybackException.createForUnexpected(
new IllegalStateException("Multiple renderer media clocks enabled."));
}
rendererMediaClock = mediaClock;
rendererMediaClockSource = renderer;
rendererMediaClock.setPlaybackParameters(playbackParameters);
}
// Start the renderer if playing. // Start the renderer if playing.
if (playing) { if (playing) {
renderer.start(); renderer.start();
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import android.os.SystemClock;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
...@@ -25,6 +24,8 @@ import com.google.android.exoplayer2.PlaybackParameters; ...@@ -25,6 +24,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
*/ */
public final class StandaloneMediaClock implements MediaClock { public final class StandaloneMediaClock implements MediaClock {
private final Clock clock;
private boolean started; private boolean started;
private long baseUs; private long baseUs;
private long baseElapsedMs; private long baseElapsedMs;
...@@ -34,7 +35,17 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -34,7 +35,17 @@ public final class StandaloneMediaClock implements MediaClock {
* Creates a new standalone media clock. * Creates a new standalone media clock.
*/ */
public StandaloneMediaClock() { public StandaloneMediaClock() {
playbackParameters = PlaybackParameters.DEFAULT; this(Clock.DEFAULT);
}
/**
* Creates a new standalone media clock using the given {@link Clock} implementation.
*
* @param clock A {@link Clock}.
*/
public StandaloneMediaClock(Clock clock) {
this.clock = clock;
this.playbackParameters = PlaybackParameters.DEFAULT;
} }
/** /**
...@@ -42,7 +53,7 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -42,7 +53,7 @@ public final class StandaloneMediaClock implements MediaClock {
*/ */
public void start() { public void start() {
if (!started) { if (!started) {
baseElapsedMs = SystemClock.elapsedRealtime(); baseElapsedMs = clock.elapsedRealtime();
started = true; started = true;
} }
} }
...@@ -52,20 +63,20 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -52,20 +63,20 @@ public final class StandaloneMediaClock implements MediaClock {
*/ */
public void stop() { public void stop() {
if (started) { if (started) {
setPositionUs(getPositionUs()); resetPosition(getPositionUs());
started = false; started = false;
} }
} }
/** /**
* Sets the clock's position. * Resets the clock's position.
* *
* @param positionUs The position to set in microseconds. * @param positionUs The position to set in microseconds.
*/ */
public void setPositionUs(long positionUs) { public void resetPosition(long positionUs) {
baseUs = positionUs; baseUs = positionUs;
if (started) { if (started) {
baseElapsedMs = SystemClock.elapsedRealtime(); baseElapsedMs = clock.elapsedRealtime();
} }
} }
...@@ -73,7 +84,7 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -73,7 +84,7 @@ public final class StandaloneMediaClock implements MediaClock {
public long getPositionUs() { public long getPositionUs() {
long positionUs = baseUs; long positionUs = baseUs;
if (started) { if (started) {
long elapsedSinceBaseMs = SystemClock.elapsedRealtime() - baseElapsedMs; long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs;
if (playbackParameters.speed == 1f) { if (playbackParameters.speed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs); positionUs += C.msToUs(elapsedSinceBaseMs);
} else { } else {
...@@ -87,7 +98,7 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -87,7 +98,7 @@ public final class StandaloneMediaClock implements MediaClock {
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
// Store the current position as the new base, in case the playback speed has changed. // Store the current position as the new base, in case the playback speed has changed.
if (started) { if (started) {
setPositionUs(getPositionUs()); resetPosition(getPositionUs());
} }
this.playbackParameters = playbackParameters; this.playbackParameters = playbackParameters;
return playbackParameters; return playbackParameters;
......
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/**
* Unit test for {@link DefaultMediaClock}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public class DefaultMediaClockTest {
private static final long TEST_POSITION_US = 123456789012345678L;
private static final long SLEEP_TIME_MS = 1_000;
private static final PlaybackParameters TEST_PLAYBACK_PARAMETERS =
new PlaybackParameters(2.0f, 1.0f);
@Mock private PlaybackParameterListener listener;
private FakeClock fakeClock;
private DefaultMediaClock mediaClock;
@Before
public void initMediaClockWithFakeClock() {
initMocks(this);
fakeClock = new FakeClock(0);
mediaClock = new DefaultMediaClock(listener, fakeClock);
}
@Test
public void standaloneResetPosition_getPositionShouldReturnSameValue() throws Exception {
mediaClock.resetPosition(TEST_POSITION_US);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
}
@Test
public void standaloneGetAndResetPosition_shouldNotTriggerCallback() throws Exception {
mediaClock.resetPosition(TEST_POSITION_US);
mediaClock.syncAndGetPositionUs();
verifyNoMoreInteractions(listener);
}
@Test
public void standaloneClock_shouldNotAutoStart() throws Exception {
assertClockIsStopped();
}
@Test
public void standaloneResetPosition_shouldNotStartClock() throws Exception {
mediaClock.resetPosition(TEST_POSITION_US);
assertClockIsStopped();
}
@Test
public void standaloneStart_shouldStartClock() throws Exception {
mediaClock.start();
assertClockIsRunning();
}
@Test
public void standaloneStop_shouldKeepClockStopped() throws Exception {
mediaClock.stop();
assertClockIsStopped();
}
@Test
public void standaloneStartAndStop_shouldStopClock() throws Exception {
mediaClock.start();
mediaClock.stop();
assertClockIsStopped();
}
@Test
public void standaloneStartStopStart_shouldRestartClock() throws Exception {
mediaClock.start();
mediaClock.stop();
mediaClock.start();
assertClockIsRunning();
}
@Test
public void standaloneStartAndStop_shouldNotTriggerCallback() throws Exception {
mediaClock.start();
mediaClock.stop();
verifyNoMoreInteractions(listener);
}
@Test
public void standaloneGetPlaybackParameters_initializedWithDefaultPlaybackParameters() {
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
public void standaloneSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
PlaybackParameters parameters = mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
assertThat(parameters).isEqualTo(TEST_PLAYBACK_PARAMETERS);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void standaloneSetPlaybackParameters_shouldTriggerCallback() {
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void standaloneSetPlaybackParameters_shouldApplyNewPlaybackSpeed() {
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
mediaClock.start();
// Asserts that clock is running with speed declared in getPlaybackParameters().
assertClockIsRunning();
}
@Test
public void standaloneSetOtherPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
PlaybackParameters parameters = mediaClock.setPlaybackParameters(PlaybackParameters.DEFAULT);
assertThat(parameters).isEqualTo(PlaybackParameters.DEFAULT);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
public void standaloneSetOtherPlaybackParameters_shouldTriggerCallbackAgain() {
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
mediaClock.setPlaybackParameters(PlaybackParameters.DEFAULT);
verify(listener).onPlaybackParametersChanged(PlaybackParameters.DEFAULT);
}
@Test
public void standaloneSetSamePlaybackParametersAgain_shouldTriggerCallbackAgain() {
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verify(listener, times(2)).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void enableRendererMediaClock_shouldOverwriteRendererPlaybackParametersIfPossible()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
verifyNoMoreInteractions(listener);
}
@Test
public void enableRendererMediaClockWithFixedParameters_usesRendererPlaybackParameters()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void enableRendererMediaClockWithFixedParameters_shouldTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void enableRendererMediaClockWithFixedButSamePlaybackParameters_shouldNotTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
verifyNoMoreInteractions(listener);
}
@Test
public void disableRendererMediaClock_shouldKeepPlaybackParameters()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.onRendererDisabled(mediaClockRenderer);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void rendererClockSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
PlaybackParameters parameters = mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
assertThat(parameters).isEqualTo(TEST_PLAYBACK_PARAMETERS);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void rendererClockSetPlaybackParameters_shouldTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void rendererClockSetPlaybackParametersOverwrite_getParametersShouldReturnSameValue()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
PlaybackParameters parameters = mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
assertThat(parameters).isEqualTo(PlaybackParameters.DEFAULT);
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
public void rendererClockSetPlaybackParametersOverwrite_shouldTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verify(listener).onPlaybackParametersChanged(PlaybackParameters.DEFAULT);
}
@Test
public void enableRendererMediaClock_usesRendererClockPosition() throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer();
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClockRenderer.positionUs = TEST_POSITION_US;
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
// We're not advancing the renderer media clock. Thus, the clock should appear to be stopped.
assertClockIsStopped();
}
@Test
public void resetPositionWhileUsingRendererMediaClock_shouldHaveNoEffect()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer();
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClockRenderer.positionUs = TEST_POSITION_US;
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
mediaClock.resetPosition(0);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
}
@Test
public void disableRendererMediaClock_standaloneShouldBeSynced() throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer();
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClockRenderer.positionUs = TEST_POSITION_US;
mediaClock.syncAndGetPositionUs();
mediaClock.onRendererDisabled(mediaClockRenderer);
fakeClock.advanceTime(SLEEP_TIME_MS);
assertThat(mediaClock.syncAndGetPositionUs())
.isEqualTo(TEST_POSITION_US + C.msToUs(SLEEP_TIME_MS));
assertClockIsRunning();
}
@Test
public void getPositionWithPlaybackParameterChange_shouldTriggerCallback()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
/* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
// Silently change playback parameters of renderer clock.
mediaClockRenderer.playbackParameters = TEST_PLAYBACK_PARAMETERS;
mediaClock.syncAndGetPositionUs();
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void rendererNotReady_shouldStillUseRendererClock() throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer(/* isReady= */ false,
/* isEnded= */ false, /* hasReadStreamToEnd= */ false);
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
// We're not advancing the renderer media clock. Thus, the clock should appear to be stopped.
assertClockIsStopped();
}
@Test
public void rendererNotReadyAndReadStreamToEnd_shouldFallbackToStandaloneClock()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer(/* isReady= */ false,
/* isEnded= */ false, /* hasReadStreamToEnd= */ true);
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
assertClockIsRunning();
}
@Test
public void rendererEnded_shouldFallbackToStandaloneClock()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer(/* isReady= */ true,
/* isEnded= */ true, /* hasReadStreamToEnd= */ true);
mediaClock.start();
mediaClock.onRendererEnabled(mediaClockRenderer);
assertClockIsRunning();
}
@Test
public void staleDisableRendererClock_shouldNotThrow()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer();
mediaClockRenderer.positionUs = TEST_POSITION_US;
mediaClock.onRendererDisabled(mediaClockRenderer);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(C.msToUs(fakeClock.elapsedRealtime()));
}
@Test
public void enableSameRendererClockTwice_shouldNotThrow()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer = new MediaClockRenderer();
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClockRenderer.positionUs = TEST_POSITION_US;
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
}
@Test
public void enableOtherRendererClock_shouldThrow()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer1 = new MediaClockRenderer();
MediaClockRenderer mediaClockRenderer2 = new MediaClockRenderer();
mediaClockRenderer1.positionUs = TEST_POSITION_US;
mediaClock.onRendererEnabled(mediaClockRenderer1);
try {
mediaClock.onRendererEnabled(mediaClockRenderer2);
fail();
} catch (ExoPlaybackException e) {
// Expected.
}
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(TEST_POSITION_US);
}
private void assertClockIsRunning() {
long clockStartUs = mediaClock.syncAndGetPositionUs();
fakeClock.advanceTime(SLEEP_TIME_MS);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(clockStartUs
+ mediaClock.getPlaybackParameters().getSpeedAdjustedDurationUs(SLEEP_TIME_MS));
}
private void assertClockIsStopped() {
long positionAtStartUs = mediaClock.syncAndGetPositionUs();
fakeClock.advanceTime(SLEEP_TIME_MS);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(positionAtStartUs);
}
private static class MediaClockRenderer extends FakeMediaClockRenderer {
public long positionUs;
public PlaybackParameters playbackParameters;
private final boolean playbackParametersAreMutable;
public MediaClockRenderer() throws ExoPlaybackException {
this(PlaybackParameters.DEFAULT, false, true, false, false);
}
public MediaClockRenderer(PlaybackParameters playbackParameters,
boolean playbackParametersAreMutable)
throws ExoPlaybackException {
this(playbackParameters, playbackParametersAreMutable, true, false, false);
}
public MediaClockRenderer(boolean isReady, boolean isEnded, boolean hasReadStreamToEnd)
throws ExoPlaybackException {
this(PlaybackParameters.DEFAULT, false, isReady, isEnded, hasReadStreamToEnd);
}
private MediaClockRenderer(PlaybackParameters playbackParameters,
boolean playbackParametersAreMutable, boolean isReady, boolean isEnded,
boolean hasReadStreamToEnd)
throws ExoPlaybackException {
this.positionUs = TEST_POSITION_US;
this.playbackParameters = playbackParameters;
this.playbackParametersAreMutable = playbackParametersAreMutable;
this.isReady = isReady;
this.isEnded = isEnded;
if (!hasReadStreamToEnd) {
resetPosition(0);
}
}
@Override
public long getPositionUs() {
return positionUs;
}
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
if (playbackParametersAreMutable) {
this.playbackParameters = playbackParameters;
}
return this.playbackParameters;
}
@Override
public PlaybackParameters getPlaybackParameters() {
return playbackParameters;
}
@Override
public boolean isReady() {
return isReady;
}
}
}
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