Pass playback speed to LoadControl and TrackSelection

This allows implementations of those classes to take into account the playback
speed for adaptive track selection and controlling when to resume the player.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176484361
This commit is contained in:
andrewlewis 2017-11-21 02:48:44 -08:00 committed by Oliver Woodman
parent a8d867be37
commit 31e2cfce9e
12 changed files with 89 additions and 29 deletions

View File

@ -174,13 +174,14 @@ public class DefaultLoadControl implements LoadControl {
} }
@Override @Override
public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) { public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed,
boolean rebuffering) {
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs;
} }
@Override @Override
public boolean shouldContinueLoading(long bufferedDurationUs) { public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
int bufferTimeState = getBufferTimeState(bufferedDurationUs); int bufferTimeState = getBufferTimeState(bufferedDurationUs);
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean wasBuffering = isBuffering; boolean wasBuffering = isBuffering;

View File

@ -284,10 +284,8 @@ import java.io.IOException;
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { 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(); eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget();
updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
} }
// Handler.Callback implementation. // Handler.Callback implementation.
@ -573,9 +571,10 @@ import java.io.IOException;
setState(Player.STATE_ENDED); setState(Player.STATE_ENDED);
stopRenderers(); stopRenderers();
} else if (state == Player.STATE_BUFFERING) { } else if (state == Player.STATE_BUFFERING) {
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
boolean isNewlyReady = enabledRenderers.length > 0 boolean isNewlyReady = enabledRenderers.length > 0
? (allRenderersReadyOrEnded ? (allRenderersReadyOrEnded && loadingPeriodHolder.haveSufficientBuffer(
&& loadingPeriodHolder.haveSufficientBuffer(rebuffering, rendererPositionUs)) rendererPositionUs, playbackSpeed, rebuffering))
: isTimelineReady(playingPeriodDurationUs); : isTimelineReady(playingPeriodDurationUs);
if (isNewlyReady) { if (isNewlyReady) {
setState(Player.STATE_READY); setState(Player.STATE_READY);
@ -853,6 +852,7 @@ import java.io.IOException;
// We don't have tracks yet, so we don't care. // We don't have tracks yet, so we don't care.
return; return;
} }
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
// Reselect tracks on each period in turn, until the selection changes. // Reselect tracks on each period in turn, until the selection changes.
MediaPeriodHolder periodHolder = playingPeriodHolder; MediaPeriodHolder periodHolder = playingPeriodHolder;
boolean selectionsChangedForReadPeriod = true; boolean selectionsChangedForReadPeriod = true;
@ -861,7 +861,7 @@ import java.io.IOException;
// The reselection did not change any prepared periods. // The reselection did not change any prepared periods.
return; return;
} }
if (periodHolder.selectTracks()) { if (periodHolder.selectTracks(playbackSpeed)) {
// Selected tracks have changed for this period. // Selected tracks have changed for this period.
break; break;
} }
@ -935,6 +935,18 @@ import java.io.IOException;
} }
} }
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder =
playingPeriodHolder != null ? playingPeriodHolder : loadingPeriodHolder;
while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.trackSelectorResult.selections.getAll();
for (TrackSelection trackSelection : trackSelections) {
trackSelection.onPlaybackSpeed(playbackSpeed);
}
periodHolder = periodHolder.next;
}
}
private boolean isTimelineReady(long playingPeriodDurationUs) { private boolean isTimelineReady(long playingPeriodDurationUs) {
return playingPeriodDurationUs == C.TIME_UNSET return playingPeriodDurationUs == C.TIME_UNSET
|| playbackInfo.positionUs < playingPeriodDurationUs || playbackInfo.positionUs < playingPeriodDurationUs
@ -1391,7 +1403,7 @@ import java.io.IOException;
// Stale event. // Stale event.
return; return;
} }
loadingPeriodHolder.handlePrepared(); loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed);
if (playingPeriodHolder == null) { if (playingPeriodHolder == null) {
// This is the first prepared period, so start playing it. // This is the first prepared period, so start playing it.
readingPeriodHolder = loadingPeriodHolder; readingPeriodHolder = loadingPeriodHolder;
@ -1410,7 +1422,8 @@ import java.io.IOException;
} }
private void maybeContinueLoading() { private void maybeContinueLoading() {
boolean continueLoading = loadingPeriodHolder.shouldContinueLoading(rendererPositionUs); boolean continueLoading = loadingPeriodHolder.shouldContinueLoading(
rendererPositionUs, mediaClock.getPlaybackParameters().speed);
setIsLoading(continueLoading); setIsLoading(continueLoading);
if (continueLoading) { if (continueLoading) {
loadingPeriodHolder.continueLoading(rendererPositionUs); loadingPeriodHolder.continueLoading(rendererPositionUs);
@ -1572,7 +1585,8 @@ import java.io.IOException;
&& (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
} }
public boolean haveSufficientBuffer(boolean rebuffering, long rendererPositionUs) { public boolean haveSufficientBuffer(long rendererPositionUs, float playbackSpeed,
boolean rebuffering) {
long bufferedPositionUs = !prepared ? info.startPositionUs long bufferedPositionUs = !prepared ? info.startPositionUs
: mediaPeriod.getBufferedPositionUs(); : mediaPeriod.getBufferedPositionUs();
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) { if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
@ -1582,24 +1596,24 @@ import java.io.IOException;
bufferedPositionUs = info.durationUs; bufferedPositionUs = info.durationUs;
} }
return loadControl.shouldStartPlayback(bufferedPositionUs - toPeriodTime(rendererPositionUs), return loadControl.shouldStartPlayback(bufferedPositionUs - toPeriodTime(rendererPositionUs),
rebuffering); playbackSpeed, rebuffering);
} }
public void handlePrepared() throws ExoPlaybackException { public void handlePrepared(float playbackSpeed) throws ExoPlaybackException {
prepared = true; prepared = true;
selectTracks(); selectTracks(playbackSpeed);
long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false); long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false);
info = info.copyWithStartPositionUs(newStartPositionUs); info = info.copyWithStartPositionUs(newStartPositionUs);
} }
public boolean shouldContinueLoading(long rendererPositionUs) { public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs(); long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
return false; return false;
} else { } else {
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
return loadControl.shouldContinueLoading(bufferedDurationUs); return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
} }
} }
@ -1608,13 +1622,18 @@ import java.io.IOException;
mediaPeriod.continueLoading(loadingPeriodPositionUs); mediaPeriod.continueLoading(loadingPeriodPositionUs);
} }
public boolean selectTracks() throws ExoPlaybackException { public boolean selectTracks(float playbackSpeed) throws ExoPlaybackException {
TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities, TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
mediaPeriod.getTrackGroups()); mediaPeriod.getTrackGroups());
if (selectorResult.isEquivalent(periodTrackSelectorResult)) { if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
return false; return false;
} }
trackSelectorResult = selectorResult; trackSelectorResult = selectorResult;
for (TrackSelection trackSelection : trackSelectorResult.selections.getAll()) {
if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed);
}
}
return true; return true;
} }

View File

@ -92,19 +92,21 @@ public interface LoadControl {
* started or resumed. * started or resumed.
* *
* @param bufferedDurationUs The duration of media that's currently buffered. * @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed.
* @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by
* buffer depletion rather than a user action. Hence this parameter is false during initial * buffer depletion rather than a user action. Hence this parameter is false during initial
* buffering and when buffering as a result of a seek operation. * buffering and when buffering as a result of a seek operation.
* @return Whether playback should be allowed to start or resume. * @return Whether playback should be allowed to start or resume.
*/ */
boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering); boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering);
/** /**
* Called by the player to determine whether it should continue to load the source. * Called by the player to determine whether it should continue to load the source.
* *
* @param bufferedDurationUs The duration of media that's currently buffered. * @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed.
* @return Whether the loading should continue. * @return Whether the loading should continue.
*/ */
boolean shouldContinueLoading(long bufferedDurationUs); boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed);
} }

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import com.google.android.exoplayer2.util.Assertions;
/** /**
* The parameters that apply to playback. * The parameters that apply to playback.
*/ */
@ -40,23 +42,25 @@ public final class PlaybackParameters {
/** /**
* Creates new playback parameters. * Creates new playback parameters.
* *
* @param speed The factor by which playback will be sped up. * @param speed The factor by which playback will be sped up. Must be greater than zero.
* @param pitch The factor by which the audio pitch will be scaled. * @param pitch The factor by which the audio pitch will be scaled. Must be greater than zero.
*/ */
public PlaybackParameters(float speed, float pitch) { public PlaybackParameters(float speed, float pitch) {
Assertions.checkArgument(speed > 0);
Assertions.checkArgument(pitch > 0);
this.speed = speed; this.speed = speed;
this.pitch = pitch; this.pitch = pitch;
scaledUsPerMs = Math.round(speed * 1000f); scaledUsPerMs = Math.round(speed * 1000f);
} }
/** /**
* Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in * Returns the media time in microseconds that will elapse in {@code timeMs} milliseconds of
* microseconds. * wallclock time.
* *
* @param timeMs The time to scale, in milliseconds. * @param timeMs The time to scale, in milliseconds.
* @return The scaled time, in microseconds. * @return The scaled time, in microseconds.
*/ */
public long getSpeedAdjustedDurationUs(long timeMs) { public long getMediaTimeUsForPlayoutTimeMs(long timeMs) {
return timeMs * scaledUsPerMs; return timeMs * scaledUsPerMs;
} }

View File

@ -1012,7 +1012,8 @@ public final class DefaultAudioSink implements AudioSink {
} }
// We are playing data at a previous playback speed, so fall back to multiplying by the speed. // We are playing data at a previous playback speed, so fall back to multiplying by the speed.
return playbackParametersOffsetUs return playbackParametersOffsetUs
+ (long) ((double) playbackParameters.speed * (positionUs - playbackParametersPositionUs)); + Util.getMediaDurationForPlayoutDuration(
positionUs - playbackParametersPositionUs, playbackParameters.speed);
} }
/** /**

View File

@ -138,6 +138,11 @@ public abstract class BaseTrackSelection implements TrackSelection {
return tracks[getSelectedIndex()]; return tracks[getSelectedIndex()];
} }
@Override
public void onPlaybackSpeed(float playbackSpeed) {
// Do nothing.
}
@Override @Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) { public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
return queue.size(); return queue.size();

View File

@ -136,6 +136,14 @@ public interface TrackSelection {
// Adaptation. // Adaptation.
/**
* Called to notify the selection of the current playback speed. The playback speed may affect
* adaptive track selection.
*
* @param speed The playback speed.
*/
void onPlaybackSpeed(float speed);
/** /**
* Updates the selected track. * Updates the selected track.
* <p> * <p>

View File

@ -88,7 +88,7 @@ public final class StandaloneMediaClock implements MediaClock {
if (playbackParameters.speed == 1f) { if (playbackParameters.speed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs); positionUs += C.msToUs(elapsedSinceBaseMs);
} else { } else {
positionUs += playbackParameters.getSpeedAdjustedDurationUs(elapsedSinceBaseMs); positionUs += playbackParameters.getMediaTimeUsForPlayoutTimeMs(elapsedSinceBaseMs);
} }
} }
return positionUs; return positionUs;

View File

@ -672,6 +672,20 @@ public final class Util {
} }
} }
/**
* Returns the duration of media that will elapse in {@code playoutDuration}.
*
* @param playoutDuration The duration to scale.
* @param speed The playback speed.
* @return The scaled duration, in the same units as {@code playoutDuration}.
*/
public static long getMediaDurationForPlayoutDuration(long playoutDuration, float speed) {
if (speed == 1f) {
return playoutDuration;
}
return Math.round((double) playoutDuration * speed);
}
/** /**
* Converts a list of integers to a primitive array. * Converts a list of integers to a primitive array.
* *

View File

@ -368,7 +368,7 @@ public class DefaultMediaClockTest {
long clockStartUs = mediaClock.syncAndGetPositionUs(); long clockStartUs = mediaClock.syncAndGetPositionUs();
fakeClock.advanceTime(SLEEP_TIME_MS); fakeClock.advanceTime(SLEEP_TIME_MS);
assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(clockStartUs assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(clockStartUs
+ mediaClock.getPlaybackParameters().getSpeedAdjustedDurationUs(SLEEP_TIME_MS)); + mediaClock.getPlaybackParameters().getMediaTimeUsForPlayoutTimeMs(SLEEP_TIME_MS));
} }
private void assertClockIsStopped() { private void assertClockIsStopped() {

View File

@ -465,7 +465,7 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs(); long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
long bufferedDurationUs = nextLoadPositionUs - rendererPositionUs; long bufferedDurationUs = nextLoadPositionUs - rendererPositionUs;
if (loadControl.shouldContinueLoading(bufferedDurationUs)) { if (loadControl.shouldContinueLoading(bufferedDurationUs, 1f)) {
newIsLoading = true; newIsLoading = true;
mediaPeriod.continueLoading(rendererPositionUs); mediaPeriod.continueLoading(rendererPositionUs);
} }
@ -488,7 +488,8 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer {
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) { if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
return true; return true;
} }
return loadControl.shouldStartPlayback(bufferedPositionUs - rendererPositionUs, rebuffering); return
loadControl.shouldStartPlayback(bufferedPositionUs - rendererPositionUs, 1f, rebuffering);
} }
private void handlePlayerError(final ExoPlaybackException e) { private void handlePlayerError(final ExoPlaybackException e) {

View File

@ -111,6 +111,11 @@ public final class FakeTrackSelection implements TrackSelection {
return null; return null;
} }
@Override
public void onPlaybackSpeed(float speed) {
// Do nothing.
}
@Override @Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) { long availableDurationUs) {