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;
......
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