Commit 31e2cfce by andrewlewis Committed by Oliver Woodman

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
parent a8d867be
...@@ -174,13 +174,14 @@ public class DefaultLoadControl implements LoadControl { ...@@ -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;
......
...@@ -284,10 +284,8 @@ import java.io.IOException; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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;
} }
......
...@@ -92,19 +92,21 @@ public interface LoadControl { ...@@ -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);
} }
...@@ -15,6 +15,8 @@ ...@@ -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 { ...@@ -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;
} }
......
...@@ -1012,7 +1012,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -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);
} }
/** /**
......
...@@ -139,6 +139,11 @@ public abstract class BaseTrackSelection implements TrackSelection { ...@@ -139,6 +139,11 @@ public abstract class BaseTrackSelection implements TrackSelection {
} }
@Override @Override
public void onPlaybackSpeed(float playbackSpeed) {
// Do nothing.
}
@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();
} }
......
...@@ -137,6 +137,14 @@ public interface TrackSelection { ...@@ -137,6 +137,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>
* This method may only be called when the selection is enabled. * This method may only be called when the selection is enabled.
......
...@@ -88,7 +88,7 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -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;
......
...@@ -673,6 +673,20 @@ public final class Util { ...@@ -673,6 +673,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.
* *
* @param list A list of integers. * @param list A list of integers.
......
...@@ -368,7 +368,7 @@ public class DefaultMediaClockTest { ...@@ -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() {
......
...@@ -465,7 +465,7 @@ public class FakeSimpleExoPlayer extends SimpleExoPlayer { ...@@ -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 { ...@@ -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) {
......
...@@ -112,6 +112,11 @@ public final class FakeTrackSelection implements TrackSelection { ...@@ -112,6 +112,11 @@ public final class FakeTrackSelection implements TrackSelection {
} }
@Override @Override
public void onPlaybackSpeed(float speed) {
// Do nothing.
}
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) { long availableDurationUs) {
Assert.assertTrue(isEnabled); Assert.assertTrue(isEnabled);
......
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