From 31e2cfce9e21c0d594d6b10cf726d498b07a7492 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 21 Nov 2017 02:48:44 -0800 Subject: [PATCH] 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 --- .../exoplayer2/DefaultLoadControl.java | 5 +- .../exoplayer2/ExoPlayerImplInternal.java | 49 +++++++++++++------ .../android/exoplayer2/LoadControl.java | 6 ++- .../exoplayer2/PlaybackParameters.java | 14 ++++-- .../exoplayer2/audio/DefaultAudioSink.java | 3 +- .../trackselection/BaseTrackSelection.java | 5 ++ .../trackselection/TrackSelection.java | 8 +++ .../exoplayer2/util/StandaloneMediaClock.java | 2 +- .../google/android/exoplayer2/util/Util.java | 14 ++++++ .../exoplayer2/DefaultMediaClockTest.java | 2 +- .../testutil/FakeSimpleExoPlayer.java | 5 +- .../testutil/FakeTrackSelection.java | 5 ++ 12 files changed, 89 insertions(+), 29 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index 4cbcc00886..56bc633c9b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 668d52425e..71da7043be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java index 44b16b0cf6..ee4775d048 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java @@ -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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java index 90aded7660..47d5bc88b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java @@ -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; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index ba62ac126e..3b14b69916 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -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); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java index 6bc6afb88b..9a58ac07aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java @@ -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 queue) { return queue.size(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java index 027b2abde9..55e6050622 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java @@ -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. *

diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java index fad3a00f10..3c0ec2a854 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 3b402ec59d..4582ab7c86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -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. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java index 9db4d57a65..9edb84eaa9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java @@ -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() { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java index 6dc9cf7fd8..094aaa5273 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java @@ -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) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java index 20346a0355..717dcda7b1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java @@ -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) {