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
public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) {
public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed,
boolean rebuffering) {
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs;
}
@Override
public boolean shouldContinueLoading(long bufferedDurationUs) {
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
int bufferTimeState = getBufferTimeState(bufferedDurationUs);
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean wasBuffering = isBuffering;

View File

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

View File

@ -92,19 +92,21 @@ public interface LoadControl {
* started or resumed.
*
* @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
* 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.
* @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.
*
* @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed.
* @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;
import com.google.android.exoplayer2.util.Assertions;
/**
* The parameters that apply to playback.
*/
@ -40,23 +42,25 @@ public final class PlaybackParameters {
/**
* Creates new playback parameters.
*
* @param speed The factor by which playback will be sped up.
* @param pitch The factor by which the audio pitch will be scaled.
* @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. Must be greater than zero.
*/
public PlaybackParameters(float speed, float pitch) {
Assertions.checkArgument(speed > 0);
Assertions.checkArgument(pitch > 0);
this.speed = speed;
this.pitch = pitch;
scaledUsPerMs = Math.round(speed * 1000f);
}
/**
* Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in
* microseconds.
* Returns the media time in microseconds that will elapse in {@code timeMs} milliseconds of
* wallclock time.
*
* @param timeMs The time to scale, in milliseconds.
* @return The scaled time, in microseconds.
*/
public long getSpeedAdjustedDurationUs(long timeMs) {
public long getMediaTimeUsForPlayoutTimeMs(long timeMs) {
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.
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()];
}
@Override
public void onPlaybackSpeed(float playbackSpeed) {
// Do nothing.
}
@Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
return queue.size();

View File

@ -136,6 +136,14 @@ public interface TrackSelection {
// 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.
* <p>

View File

@ -88,7 +88,7 @@ public final class StandaloneMediaClock implements MediaClock {
if (playbackParameters.speed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs);
} else {
positionUs += playbackParameters.getSpeedAdjustedDurationUs(elapsedSinceBaseMs);
positionUs += playbackParameters.getMediaTimeUsForPlayoutTimeMs(elapsedSinceBaseMs);
}
}
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.
*

View File

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

View File

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

View File

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